diff --git a/Changelog.md b/Changelog.md index 7e3a39738561186d4e619e5ca6e679450d9bc26d..055c1ebd34e5bec0ad59c4ba2805e0a9be024d18 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [0.62] - 2021-03-24 +- POL1-392: added latch timestamp to version response + ## [0.61] - 2021-03-05 - POL1-380: added /poller/speeds route diff --git a/inventory_provider/routes/default.py b/inventory_provider/routes/default.py index d50792f9486b4d059dadba53cc4856373e8d07d0..2fb187189f54a04fb8f979dee7756a51de825508 100644 --- a/inventory_provider/routes/default.py +++ b/inventory_provider/routes/default.py @@ -4,42 +4,43 @@ from flask import Blueprint, jsonify, current_app from inventory_provider.routes import common from inventory_provider.tasks.common import get_current_redis, get_latch -routes = Blueprint("inventory-data-default-routes", __name__) +routes = Blueprint('inventory-data-default-routes', __name__) API_VERSION = '0.1' VERSION_SCHEMA = { - "$schema": "http://json-schema.org/draft-07/schema#", + '$schema': 'http://json-schema.org/draft-07/schema#', - "definitions": { - "latch": { - "type": "object", - "properties": { - "current": {"type": "integer"}, - "next": {"type": "integer"}, - "this": {"type": "integer"}, - "failure": {"type": "boolean"}, - "pending": {"type": "boolean"}, + 'definitions': { + 'latch': { + 'type': 'object', + 'properties': { + 'current': {'type': 'integer'}, + 'next': {'type': 'integer'}, + 'this': {'type': 'integer'}, + 'failure': {'type': 'boolean'}, + 'pending': {'type': 'boolean'}, + 'timestamp': {'type': 'number'} }, - "required": ["current", "next", "this", "pending", "failure"], - "additionalProperties": False + 'required': ['current', 'next', 'this', 'pending', 'failure'], + 'additionalProperties': False } }, - "type": "object", - "properties": { - "api": { - "type": "string", - "pattern": r'\d+\.\d+' + 'type': 'object', + 'properties': { + 'api': { + 'type': 'string', + 'pattern': r'\d+\.\d+' }, - "module": { - "type": "string", - "pattern": r'\d+\.\d+' + 'module': { + 'type': 'string', + 'pattern': r'\d+\.\d+' }, - "latch": {"$ref": "#/definitions/latch"} + 'latch': {'$ref': '#/definitions/latch'} }, - "required": ["api", "module"], - "additionalProperties": False + 'required': ['api', 'module'], + 'additionalProperties': False } @@ -48,7 +49,7 @@ def after_request(resp): return common.after_request(resp) -@routes.route("/version", methods=['GET', 'POST']) +@routes.route('/version', methods=['GET', 'POST']) @common.require_accepts_json def version(): """ diff --git a/inventory_provider/tasks/common.py b/inventory_provider/tasks/common.py index ac85afb7de7094517c95cf2ed5f2929b211f6afc..427d51d8a18e4128f3dd3b174274e65fa17f5f8f 100644 --- a/inventory_provider/tasks/common.py +++ b/inventory_provider/tasks/common.py @@ -1,5 +1,6 @@ import json import logging +import time import jsonschema import redis @@ -11,17 +12,18 @@ DEFAULT_REDIS_SENTINEL_TIMEOUT = 0.1 DEFAULT_SENTINEL_SOCKET_TIMEOUT = 0.1 DB_LATCH_SCHEMA = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "current": {"type": "integer"}, - "next": {"type": "integer"}, - "this": {"type": "integer"}, - "pending": {"type": "boolean"}, - "failure": {"type": "boolean"} + '$schema': 'http://json-schema.org/draft-07/schema#', + 'type': 'object', + 'properties': { + 'current': {'type': 'integer'}, + 'next': {'type': 'integer'}, + 'this': {'type': 'integer'}, + 'pending': {'type': 'boolean'}, + 'failure': {'type': 'boolean'}, + 'timestamp': {'type': 'number'} }, - "required": ["current", "next", "this"], - "additionalProperties": False + 'required': ['current', 'next', 'this'], + 'additionalProperties': False } TASK_LOG_SCHEMA = { @@ -86,17 +88,24 @@ def update_latch_status(config, pending=False, failure=False): logger.debug('updating latch status: pending={}, failure={}'.format( pending, failure)) + now = time.time() for db in config['redis-databases']: r = _get_redis(config, dbid=db) latch = get_latch(r) if not latch: continue + if not pending and not failure: + if not latch['pending'] and not latch['failure']: + logger.error( + 'updating latch for db {db} with pending=failure=True, ' + f'but latch is already {latch}') + latch['timestamp'] = now latch['pending'] = pending latch['failure'] = failure r.set('db:latch', json.dumps(latch)) -def set_latch(config, new_current, new_next): +def set_latch(config, new_current, new_next, timestamp): logger.debug('setting latch: new current={}, new next={}'.format( new_current, new_next)) @@ -107,7 +116,8 @@ def set_latch(config, new_current, new_next): 'next': new_next, 'this': db, 'pending': False, - 'failure': False + 'failure': False, + 'timestamp': timestamp } r = _get_redis(config, dbid=db) @@ -129,7 +139,11 @@ def latch_db(config): next_idx = db_ids.index(latch['next']) next_idx = (next_idx + 1) % len(db_ids) - set_latch(config, new_current=latch['next'], new_next=db_ids[next_idx]) + set_latch( + config, + new_current=latch['next'], + new_next=db_ids[next_idx], + timestamp=time.time()) def _get_redis(config, dbid=None): diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 64021861dd877eb6725c8dbee724ddc7c23b7093..550690ee64f4607415c55fd0b9099231fb7fb191 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -389,7 +389,8 @@ def _erase_next_db(config): set_latch( config, new_current=saved_latch['current'], - new_next=saved_latch['next']) + new_next=saved_latch['next'], + timestamp=saved_latch.get('timestamp', 0)) @log_task_entry_and_exit diff --git a/test/test_celery_worker_global.py b/test/test_celery_worker_global.py index 90ef484f8f711b931cf304036b440322ff001a8e..3391c83307c3c18e1e3bc238f2a8287c6774af73 100644 --- a/test/test_celery_worker_global.py +++ b/test/test_celery_worker_global.py @@ -32,7 +32,7 @@ def test_next_redis(data_config, mocked_redis): :param mocked_redis: :return: """ - common.set_latch(data_config, 10, 20) + common.set_latch(data_config, 10, 20, 100) r = common.get_next_redis(data_config) assert r @@ -40,6 +40,7 @@ def test_next_redis(data_config, mocked_redis): latch = common.get_latch(r) assert latch['current'] == 10 assert latch['next'] == 20 + assert latch['timestamp'] == 100 def test_next_redis_with_none(data_config, mocked_redis): diff --git a/test/test_general_routes.py b/test/test_general_routes.py index 254d9db122b4d944bdd66df67f8a41e5ca67712c..8bc82db71c96426f68d502f8fe4dac4e2414cd40 100644 --- a/test/test_general_routes.py +++ b/test/test_general_routes.py @@ -5,59 +5,59 @@ from inventory_provider.routes import common from inventory_provider.routes.default import VERSION_SCHEMA DEFAULT_REQUEST_HEADERS = { - "Content-type": "application/json", - "Accept": ["application/json"] + 'Content-type': 'application/json', + 'Accept': ['application/json'] } def test_version_request(client, mocked_redis): rv = client.post( - "version", + 'version', headers=DEFAULT_REQUEST_HEADERS) assert rv.status_code == 200 jsonschema.validate( - json.loads(rv.data.decode("utf-8")), + json.loads(rv.data.decode('utf-8')), VERSION_SCHEMA) def test_load_json_docs(data_config, mocked_redis): INTERFACE_SCHEMA = { - "$schema": "http://json-schema.org/draft-07/schema#", + '$schema': 'http://json-schema.org/draft-07/schema#', - "definitions": { - "interface": { - "type": "object", - "properties": { - "logical-system": {"type": "string"}, - "name": {"type": "string"}, - "description": {"type": "string"}, - "bundle": { - "type": "array", - "items": {"type": "string"} + 'definitions': { + 'interface': { + 'type': 'object', + 'properties': { + 'logical-system': {'type': 'string'}, + 'name': {'type': 'string'}, + 'description': {'type': 'string'}, + 'bundle': { + 'type': 'array', + 'items': {'type': 'string'} }, - "ipv4": { - "type": "array", - "items": {"type": "string"} + 'ipv4': { + 'type': 'array', + 'items': {'type': 'string'} }, - "ipv6": { - "type": "array", - "items": {"type": "string"} + 'ipv6': { + 'type': 'array', + 'items': {'type': 'string'} } }, - "required": ["name", "description", "ipv4", "ipv6"], - "additionalProperties": False + 'required': ['name', 'description', 'ipv4', 'ipv6'], + 'additionalProperties': False } }, - "type": "object", - "properties": { - "key": {"type": "string"}, - "value": {"$ref": "#/definitions/interface"} + 'type': 'object', + 'properties': { + 'key': {'type': 'string'}, + 'value': {'$ref': '#/definitions/interface'} }, - "required": ["key", "value"], - "additionalProperties": False + 'required': ['key', 'value'], + 'additionalProperties': False } for ifc in common.load_json_docs(