diff --git a/README.md b/README.md index 98d04d9c0b589f04b2f69943a0e586e94449120b..774e59d40a7f1d8830855cb1547a71fdfa01a6a3 100644 --- a/README.md +++ b/README.md @@ -354,20 +354,7 @@ Any non-empty responses are JSON formatted messages. from the external inventory system, status information is retrieved from the alarms database -* /classifier/infinera-dna-addresses, /classifier/juniper-server-addresses - Both of these resources return lists of source addresses - of known senders of snmp traps. Responses will be - formatted as follows: - - ```json - { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": {"type": "string"} - } - ``` - * /classifier/*`type`*/*`source-equipment`*/*`source-interface`* The source-equipment is the equipment that causes the trap, not the NMS that @@ -426,11 +413,30 @@ Any non-empty responses are JSON formatted messages. ``` +### Testing utilities + +The following routes are only available if the server +was started with the `ENABLE_TESTING_ROUTES` flag. + + * `/testing/flushdb` This method erases all data in the backend redis - data bases and is only available if the server was - started with the `ENABLE_TESTING_ROUTES` flag. + database. + +* /testing/infinera-dna-addresses, /testing/coriant-tnmp-addresses, /testing/juniper-server-addresses` + + All of these resources return lists of source addresses + of known senders of snmp traps. Responses will be + formatted as follows: + + ```json + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": {"type": "string"} + } + ``` ## Backend (Redis) Storage Schema diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py index 3307321ecbd8f748dc0d5ea584c6ff479b00cdbe..024fac841eb30cde1e859612856db2bc2b1e1293 100644 --- a/inventory_provider/__init__.py +++ b/inventory_provider/__init__.py @@ -30,9 +30,6 @@ def create_app(): from inventory_provider.routes import jobs app.register_blueprint(jobs.routes, url_prefix='/jobs') - from inventory_provider.routes import opsdb - app.register_blueprint(opsdb.routes, url_prefix='/opsdb') - from inventory_provider.routes import classifier app.register_blueprint(classifier.routes, url_prefix='/classifier') diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py index 43c8b35a4fd931aa48b6bdedb73aded5c601bf0e..0a9602b8a92c737005567918e1bb9983f09b166b 100644 --- a/inventory_provider/routes/classifier.py +++ b/inventory_provider/routes/classifier.py @@ -1,42 +1,12 @@ import json -from flask import Blueprint, Response, current_app, jsonify +from flask import Blueprint, Response from inventory_provider.routes import common routes = Blueprint("inventory-data-classifier-support-routes", __name__) -@routes.route("/infinera-dna-addresses", methods=['GET', 'POST']) -@common.require_accepts_json -def infinera_addresses(): - infinera_config = current_app.config[ - "INVENTORY_PROVIDER_CONFIG"]["infinera-dna"] - return jsonify([dna['address'] for dna in infinera_config]) - - -@routes.route("/coriant-tnms-addresses", methods=['GET', 'POST']) -@common.require_accepts_json -def coriant_addresses(): - coriant_config = current_app.config[ - "INVENTORY_PROVIDER_CONFIG"]["coriant-tnms"] - return jsonify([tnms['address'] for tnms in coriant_config]) - - -@routes.route("/juniper-server-addresses", methods=['GET', 'POST']) -@common.require_accepts_json -def juniper_addresses(): - # TODO: this route (and corant, infinera routes) can be removed - r = common.get_redis() - routers = [] - for k in r.keys('junosspace:*'): - info = r.get(k.decode('utf-8')) - assert info # sanity: value shouldn't be empty - info = json.loads(info.decode('utf-8')) - routers.append(info['address']) - return jsonify(routers) - - @routes.route("/trap-metadata/<source_equipment>/<path:interface>", methods=['GET', 'POST']) @common.require_accepts_json diff --git a/inventory_provider/routes/opsdb.py b/inventory_provider/routes/opsdb.py deleted file mode 100644 index 4b7fba38a3ea89d7b03933ded6f5b145dab978c9..0000000000000000000000000000000000000000 --- a/inventory_provider/routes/opsdb.py +++ /dev/null @@ -1,88 +0,0 @@ -import collections -import json -import re - -from flask import Blueprint, jsonify -from inventory_provider.routes import common - -routes = Blueprint("inventory-opsdb-query-routes", __name__) - -interfaces_key = "interface_services" -equipment_locations_key = "equipment_locations" -service_child_to_parents_key = "child_to_parent_circuit_relations" -service_parent_to_children_key = "parent_to_children_circuit_relations" -interface_status_key = "interface_statuses" - - -# def _decode_utf8_dict(d): -# return {k.decode('utf8'): json.loads(v) for k, v in d.items()} -# -# -@routes.route("/interfaces") -def get_all_interface_details(): - r = common.get_redis() - result = collections.defaultdict(list) - for k in r.keys('opsdb:interface_services:*'): - m = re.match( - r'^opsdb:interface_services:([^:]+):(.*)$', - k.decode('utf-8')) - assert m - result[m.group(1)].append(m.group(2)) - return jsonify(result) - - -@routes.route("/interfaces/<equipment_name>") -def get_interface_details_for_equipment(equipment_name): - r = common.get_redis() - result = [] - for k in r.keys('opsdb:interface_services:%s:*' % equipment_name): - m = re.match( - r'^opsdb:interface_services:%s:(.*)$' % equipment_name, - k.decode('utf-8')) - assert m - result.append(m.group(1)) - return jsonify(result) - - -@routes.route("/interfaces/<equipment_name>/<path:interface>") -def get_interface_details(equipment_name, interface): - r = common.get_redis() - key = 'opsdb:interface_services:%s:%s' % (equipment_name, interface) - # TODO: handle None (return 404) - return jsonify(json.loads(r.get(key).decode('utf-8'))) - - -@routes.route("/equipment-location") -def get_all_equipment_locations(): - r = common.get_redis() - result = {} - for k in r.keys('opsdb:location:*'): - k = k.decode('utf-8') - m = re.match(r'^opsdb:location:(.*)$', k) - assert m - result[m.group(1)] = json.loads(r.get(k).decode('utf-8')) - return jsonify(result) - - -@routes.route("/equipment-location/<path:equipment_name>") -def get_equipment_location(equipment_name): - r = common.get_redis() - result = r.get('opsdb:location:' + equipment_name) - # TODO: handle None (return 404) - return jsonify(json.loads(result.decode('utf-8'))) - - -@routes.route("/circuit-hierarchy/children/<int:parent_id>") -def get_children(parent_id): - r = common.get_redis() - result = r.get('opsdb:services:children:%d' % parent_id) - # TODO: handle None (return 404) - return jsonify(json.loads(result.decode('utf-8'))) - - -@routes.route("/circuit-hierarchy/parents/<int:child_id>") -def get_parents(child_id): - r = common.get_redis() - result = r.get('opsdb:services:parents:%d' % child_id) - # TODO: handle None (return 404) - return jsonify(json.loads(result.decode('utf-8'))) diff --git a/inventory_provider/routes/testing.py b/inventory_provider/routes/testing.py index 16f1d670265c555f20938d5e2099fe5eb660f0c8..8d66aa5016c124ed9238a957116ac2a58c79cc4f 100644 --- a/inventory_provider/routes/testing.py +++ b/inventory_provider/routes/testing.py @@ -1,10 +1,114 @@ -from flask import Blueprint, Response +import collections +import json +import re + +from flask import Blueprint, Response, current_app, jsonify from inventory_provider.routes import common routes = Blueprint("inventory-data-testing-support-routes", __name__) -@routes.route("/flushdb", methods=['GET', 'POST']) +@routes.route("flushdb", methods=['GET', 'POST']) def flushdb(): common.get_redis().flushdb() return Response('OK') + + +@routes.route("infinera-dna-addresses", methods=['GET', 'POST']) +@common.require_accepts_json +def infinera_addresses(): + infinera_config = current_app.config[ + "INVENTORY_PROVIDER_CONFIG"]["infinera-dna"] + return jsonify([dna['address'] for dna in infinera_config]) + + +@routes.route("coriant-tnms-addresses", methods=['GET', 'POST']) +@common.require_accepts_json +def coriant_addresses(): + coriant_config = current_app.config[ + "INVENTORY_PROVIDER_CONFIG"]["coriant-tnms"] + return jsonify([tnms['address'] for tnms in coriant_config]) + + +@routes.route("juniper-server-addresses", methods=['GET', 'POST']) +@common.require_accepts_json +def juniper_addresses(): + # TODO: this route (and corant, infinera routes) can be removed + r = common.get_redis() + routers = [] + for k in r.keys('junosspace:*'): + info = r.get(k.decode('utf-8')) + assert info # sanity: value shouldn't be empty + info = json.loads(info.decode('utf-8')) + routers.append(info['address']) + return jsonify(routers) + + +@routes.route("opsdb/interfaces") +def get_all_interface_details(): + r = common.get_redis() + result = collections.defaultdict(list) + for k in r.keys('opsdb:interface_services:*'): + m = re.match( + r'^opsdb:interface_services:([^:]+):(.*)$', + k.decode('utf-8')) + assert m + result[m.group(1)].append(m.group(2)) + return jsonify(result) + + +@routes.route("opsdb/interfaces/<equipment_name>") +def get_interface_details_for_equipment(equipment_name): + r = common.get_redis() + result = [] + for k in r.keys('opsdb:interface_services:%s:*' % equipment_name): + m = re.match( + r'^opsdb:interface_services:%s:(.*)$' % equipment_name, + k.decode('utf-8')) + assert m + result.append(m.group(1)) + return jsonify(result) + + +@routes.route("opsdb/interfaces/<equipment_name>/<path:interface>") +def get_interface_details(equipment_name, interface): + r = common.get_redis() + key = 'opsdb:interface_services:%s:%s' % (equipment_name, interface) + # TODO: handle None (return 404) + return jsonify(json.loads(r.get(key).decode('utf-8'))) + + +@routes.route("opsdb/equipment-location") +def get_all_equipment_locations(): + r = common.get_redis() + result = {} + for k in r.keys('opsdb:location:*'): + k = k.decode('utf-8') + m = re.match(r'^opsdb:location:(.*)$', k) + assert m + result[m.group(1)] = json.loads(r.get(k).decode('utf-8')) + return jsonify(result) + + +@routes.route("opsdb/equipment-location/<path:equipment_name>") +def get_equipment_location(equipment_name): + r = common.get_redis() + result = r.get('opsdb:location:' + equipment_name) + # TODO: handle None (return 404) + return jsonify(json.loads(result.decode('utf-8'))) + + +@routes.route("opsdb/circuit-hierarchy/children/<int:parent_id>") +def get_children(parent_id): + r = common.get_redis() + result = r.get('opsdb:services:children:%d' % parent_id) + # TODO: handle None (return 404) + return jsonify(json.loads(result.decode('utf-8'))) + + +@routes.route("opsdb/circuit-hierarchy/parents/<int:child_id>") +def get_parents(child_id): + r = common.get_redis() + result = r.get('opsdb:services:parents:%d' % child_id) + # TODO: handle None (return 404) + return jsonify(json.loads(result.decode('utf-8'))) diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py index 7ea6f6c810f7ac638334f239423f6972123caba5..6b68221d42e1805c373990ea12cbdc8950bb57db 100644 --- a/test/test_classifier_routes.py +++ b/test/test_classifier_routes.py @@ -7,55 +7,6 @@ DEFAULT_REQUEST_HEADERS = { } -def test_infinera_addresses(client): - response_schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": {"type": "string"} - } - - rv = client.post( - "/classifier/infinera-dna-addresses", - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - jsonschema.validate( - json.loads(rv.data.decode("utf-8")), - response_schema) - - -def test_coriant_addresses(client): - response_schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": {"type": "string"} - } - - rv = client.post( - "/classifier/coriant-tnms-addresses", - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - jsonschema.validate( - json.loads(rv.data.decode("utf-8")), - response_schema) - - -def test_juniper_addresses(mocker, client): - - response_schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": {"type": "string"} - } - - rv = client.post( - "/classifier/juniper-server-addresses", - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - response_data = json.loads(rv.data.decode('utf-8')) - jsonschema.validate(response_data, response_schema) - assert len(response_data) > 0 # test data is not empty - - def test_trap_metadata(client_with_mocked_data): response_schema = { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/test/test_external_inventory_routes.py b/test/test_external_inventory_routes.py deleted file mode 100644 index 94687251b8fc35075d8a8763a47d9cd69bc4dc87..0000000000000000000000000000000000000000 --- a/test/test_external_inventory_routes.py +++ /dev/null @@ -1,60 +0,0 @@ - -DEFAULT_REQUEST_HEADERS = { - "Content-type": "application/json", - "Accept": ["application/json"] -} - - -def test_get_equipment_location(client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/equipment-location', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema - - -def test_get_interface_info(client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/interfaces', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema - - -def test_get_interface_info_for_equipment(client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/interfaces/mx1.ams.nl.geant.net', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema - - -def test_get_interface_info_for_equipment_and_interface( - client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/interfaces/mx1.ams.nl.geant.net/ae0.0', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema - - -def test_get_children(client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/circuit-hierarchy/children/12363', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema - - -def test_get_parents(client_with_mocked_data): - rv = client_with_mocked_data.get( - '/opsdb/circuit-hierarchy/parents/11725', - headers=DEFAULT_REQUEST_HEADERS) - assert rv.status_code == 200 - assert rv.is_json - # TODO: validate against schema diff --git a/test/test_testing_routes.py b/test/test_testing_routes.py index 44ae19a006225b9386cebb8eba3d0b71f62a9d72..2ab4bd1930514a772f5d7b5f97b265dbe4c3f7ee 100644 --- a/test/test_testing_routes.py +++ b/test/test_testing_routes.py @@ -1,4 +1,103 @@ +import json +import jsonschema + +DEFAULT_REQUEST_HEADERS = { + "Content-type": "application/json", + "Accept": ["application/json"] +} + +ROUTER_LIST_SCHEMA = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": {"type": "string"} +} + def test_flushdb(client): rv = client.post("/testing/flushdb") assert rv.status_code == 200 + + +def test_infinera_addresses(client): + rv = client.post( + "/testing/infinera-dna-addresses", + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + jsonschema.validate( + json.loads(rv.data.decode("utf-8")), + ROUTER_LIST_SCHEMA) + + +def test_coriant_addresses(client): + rv = client.post( + "/testing/coriant-tnms-addresses", + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + jsonschema.validate( + json.loads(rv.data.decode("utf-8")), + ROUTER_LIST_SCHEMA) + + +def test_juniper_addresses(client): + rv = client.post( + "/testing/juniper-server-addresses", + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + response_data = json.loads(rv.data.decode('utf-8')) + jsonschema.validate(response_data, ROUTER_LIST_SCHEMA) + assert len(response_data) > 0 # test data is not empty + + +def test_get_equipment_location(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/equipment-location', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema + + +def test_get_interface_info(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/interfaces', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema + + +def test_get_interface_info_for_equipment(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/interfaces/mx1.ams.nl.geant.net', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema + + +def test_get_interface_info_for_equipment_and_interface( + client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/interfaces/mx1.ams.nl.geant.net/ae0.0', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema + + +def test_get_children(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/circuit-hierarchy/children/12363', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema + + +def test_get_parents(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/testing/opsdb/circuit-hierarchy/parents/11725', + headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 200 + assert rv.is_json + # TODO: validate against schema