diff --git a/README.md b/README.md index 142aacf6f32c6ee28f219f19618b3e77bdb31972..7f849d0d42dfde718dcb559a9aea4a0f9190de87 100644 --- a/README.md +++ b/README.md @@ -256,3 +256,17 @@ Any non-empty responses are JSON formatted messages. This resource updates data that should only be refreshed in case of system restart. + +* /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"} + } + ``` \ No newline at end of file diff --git a/changelog b/changelog index 73a599d671ad205bf199e5b7a6cb2257f87fe409..c2891b4f8c49c97607dea1713160f957295e5f44 100644 --- a/changelog +++ b/changelog @@ -17,3 +17,5 @@ 0.14: added sample route for startup-only tasks added method for caching selected alarmsdb tables added caching of last known interface status from alarmsdb +0.15: added some routes for initial classifier support + added basic replacement for findAffectedCircuits \ No newline at end of file diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py index a8e55e8edb0d75ffbbbcae07669b9d63450a064f..a8adb17fc82ed6aca7c697d723990e18c98cb710 100644 --- a/inventory_provider/__init__.py +++ b/inventory_provider/__init__.py @@ -26,8 +26,8 @@ def create_app(): from inventory_provider.routes import opsdb app.register_blueprint(opsdb.routes, url_prefix='/opsdb') - from inventory_provider.routes import alarmsdb - app.register_blueprint(alarmsdb.routes, url_prefix='/alarmsdb') + from inventory_provider.routes import classifier + app.register_blueprint(classifier.routes, url_prefix='/classifier') if "SETTINGS_FILENAME" not in os.environ: assert False, \ diff --git a/inventory_provider/config.py b/inventory_provider/config.py index 5dd0019a9ac2770e785be3e6b8d2671ba3d7d579..0f6bc1b320d17da8e4b328cd0957c9f23424eb45 100644 --- a/inventory_provider/config.py +++ b/inventory_provider/config.py @@ -46,6 +46,30 @@ CONFIG_SCHEMA = { }, "required": ["hostname", "port"], "additionalProperties": False + }, + "infinera-dna": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "address": {"type": "string"} + }, + "required": ["name", "address"], + "additionalProperties": False + } + }, + "coriant-tnms": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "address": {"type": "string"} + }, + "required": ["name", "address"], + "additionalProperties": False + } } }, "required": [ @@ -54,7 +78,9 @@ CONFIG_SCHEMA = { "oid_list.conf", "routers_community.conf", "ssh", - "redis"], + "redis", + "infinera-dna", + "coriant-tnms"], "additionalProperties": False } diff --git a/inventory_provider/opsdb.py b/inventory_provider/opsdb.py index c0a6417abbc10b803ba4ee39053d8e1498f64533..7848f09de3407fd5c77a56f18b3ae0833e1bd6c1 100644 --- a/inventory_provider/opsdb.py +++ b/inventory_provider/opsdb.py @@ -18,7 +18,14 @@ equipment_location_query = """SELECT ON g.absid = p.PTR_geocoding WHERE e.status != 'terminated' - AND e.status != 'disposed'""" + AND e.status != 'disposed' + ORDER BY + FIELD(e.status, + 'spare', + 'planned', + 'ordered', + 'installed', + 'operational')""" circuit_hierarchy_query = """SELECT @@ -159,6 +166,7 @@ retrieve_services_query = """SELECT * c.status != 'terminated' AND is_circuit = 1) AS inner_query + WHERE circuit_type IN ('path', 'service', 'l2circuit') ORDER BY FIELD(status, 'spare', @@ -204,6 +212,14 @@ def _juniper_field_update(record): return record +def _coriant_field_update(record): + record["interface_name"] = record["card_id"] + if record["port"] is not None and record["port"] != "": + separator = "/" if "-" in record["interface_name"] else "" + record["interface_name"] += separator + str(record["port"]) + return record + + def _update_fields(r): func = globals().get("_" + r["manufacturer"] + "_field_update") return func(r) if func else r diff --git a/inventory_provider/routes/alarmsdb.py b/inventory_provider/routes/alarmsdb.py deleted file mode 100644 index ee1fda69bdeed171f78e2bdea561903562c7dfc0..0000000000000000000000000000000000000000 --- a/inventory_provider/routes/alarmsdb.py +++ /dev/null @@ -1,43 +0,0 @@ -import functools -import json - -from flask import Blueprint, request, Response, current_app -from inventory_provider import alarmsdb, db - -routes = Blueprint("inventory-alarmsdb-query-routes", __name__) - - -def require_accepts_json(f): - """ - used as a route handler decorator to return an error - unless the request allows responses with type "application/json" - :param f: the function to be decorated - :return: the decorated function - """ - @functools.wraps(f) - def decorated_function(*args, **kwargs): - # TODO: use best_match to disallow */* ...? - if not request.accept_mimetypes.accept_json: - return Response( - response="response will be json", - status=406, - mimetype="text/html") - return f(*args, **kwargs) - return decorated_function - - -@routes.route("/interface-status", methods=['GET', 'POST']) -@require_accepts_json -def get_interface_status(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - equipment = request.args.get("equipment") - interface = request.args.get("interface") - - with db.connection(config['alarms-db']) as connection: - result = {"status": alarmsdb.get_last_known_interface_status( - connection, equipment, interface)} - - return Response( - json.dumps(result), - mimetype="application/json") diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py new file mode 100644 index 0000000000000000000000000000000000000000..85dcbd779d3d419b12625f402b9b17c16ca8a55f --- /dev/null +++ b/inventory_provider/routes/classifier.py @@ -0,0 +1,71 @@ +import functools + +from flask import Blueprint, request, Response, current_app, jsonify +import json +import jsonschema + +from inventory_provider import db + +routes = Blueprint("inventory-data-classifier-support-routes", __name__) + + +def require_accepts_json(f): + """ + used as a route handler decorator to return an error + unless the request allows responses with type "application/json" + :param f: the function to be decorated + :return: the decorated function + """ + @functools.wraps(f) + def decorated_function(*args, **kwargs): + # TODO: use best_match to disallow */* ...? + if not request.accept_mimetypes.accept_json: + return Response( + response="response will be json", + status=406, + mimetype="text/html") + return f(*args, **kwargs) + return decorated_function + + +@routes.route("/infinera-dna-addresses", methods=['GET', 'POST']) +@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']) +@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']) +@require_accepts_json +def juniper_addresses(): + backend_data_schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "ip_address": {"type": "string"} + }, + "required": ["ip_address"] + } + } + + servers = db.get_redis().get('alarmsdb:juniper_servers') + if not servers: + return Response( + response="no juniper server data found", + status=404, + mimetype="text/html") + + servers = json.loads(servers.decode('utf-8')) + jsonschema.validate(servers, backend_data_schema) + return jsonify([s['ip_address'] for s in servers]) diff --git a/inventory_provider/routes/data.py b/inventory_provider/routes/data.py index 30810e45b074fbf599e5d26ace622675e7e7ca53..7b819151faf1a9ca1527cbe140f56072b8fd2ab8 100644 --- a/inventory_provider/routes/data.py +++ b/inventory_provider/routes/data.py @@ -7,7 +7,6 @@ from lxml import etree import redis from inventory_provider import db, juniper -from inventory_provider.storage import external_inventory routes = Blueprint("inventory-data-query-routes", __name__) @@ -170,7 +169,7 @@ def bgp_configs(hostname): @require_accepts_json def interface_statuses(hostname, interface): r = db.get_redis() - result = r.hget(external_inventory.interface_status_key, + result = r.hget("interface_statuses", "{}::{}".format(hostname, interface)) if not result: return Response( @@ -178,3 +177,17 @@ def interface_statuses(hostname, interface): status=404, mimetype="text/html") return jsonify({"status": result.decode('utf-8')}) + + +@routes.route("/services/<hostname>/<path:interface>", + methods=['GET', 'POST']) +def services_for_interface(hostname, interface): + r = db.get_redis() + result = r.hget("interface_services", + "{}::{}".format(hostname, interface)) + if not result: + return Response( + response="no available info for {} {}".format(hostname, interface), + status=404, + mimetype="text/html") + return jsonify(json.loads(result.decode('utf-8'))) diff --git a/inventory_provider/routes/jobs.py b/inventory_provider/routes/jobs.py index 315c3e8d7c960228304a6a5020497cde0bbe1f42..25954f2b93df1eef33bb70e53c72649f81ec400b 100644 --- a/inventory_provider/routes/jobs.py +++ b/inventory_provider/routes/jobs.py @@ -1,8 +1,7 @@ import logging + from flask import Blueprint, Response, current_app -import inventory_provider.storage.external_inventory as external_inventory -from inventory_provider import db, opsdb from inventory_provider.tasks.app import app from inventory_provider.constants import TASK_LOGGER_NAME @@ -49,56 +48,38 @@ def startup_update(): return Response("OK") -@routes.route("update-services", methods=['GET']) -def update_service(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - with db.connection(config['ops-db']) as connection: - result = opsdb.get_circuits(connection) - external_inventory.update_services_to_monitor(result) - return Response("OK") - - -@routes.route("update-interfaces", methods=['GET']) -def update_interfaces(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - with db.connection(config['ops-db']) as connection: - result = opsdb.get_circuits(connection) - external_inventory.update_interfaces_to_services(result) +@routes.route("update-interfaces-to-services", methods=['GET']) +def update_interfaces_to_services(): + app.send_task( + 'inventory_provider.tasks.worker.update_interfaces_to_services') return Response("OK") @routes.route("update-service-hierarchy", methods=['GET']) def update_service_hierarchy(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - with db.connection(config['ops-db']) as connection: - result = opsdb.get_circuit_hierarchy(connection) - external_inventory.update_service_hierarchy(result) + app.send_task('inventory_provider.tasks.worker.update_circuit_hierarchy') return Response("OK") @routes.route("update-equipment-locations", methods=['GET']) def update_equipment_locations(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - with db.connection(config['ops-db']) as connection: - result = opsdb.get_equipment_location_data(connection) - external_inventory.update_equipment_locations(result) + app.send_task('inventory_provider.tasks.worker.update_equipment_locations') return Response("OK") @routes.route("update-from-inventory-system", methods=['GET']) def update_from_inventory_system(): - config = current_app.config['INVENTORY_PROVIDER_CONFIG'] - - with db.connection(config['ops-db']) as connection: - circuits = opsdb.get_circuits(connection) - hierarchy = opsdb.get_circuit_hierarchy(connection) - equipment_locations = opsdb.get_equipment_location_data(connection) - external_inventory.update_services_to_monitor(circuits) - external_inventory.update_interfaces_to_services(circuits) - external_inventory.update_service_hierarchy(hierarchy) - external_inventory.update_equipment_locations(equipment_locations) + app.send_task( + 'inventory_provider.tasks.worker.update_service_to_monitor') + app.send_task( + 'inventory_provider.tasks.worker.update_interfaces_to_services') + app.send_task('inventory_provider.tasks.worker.update_circuit_hierarchy') + app.send_task('inventory_provider.tasks.worker.update_equipment_locations') + return Response("OK") + + +@routes.route("update-interface-statuses") +def update_interface_statuses(): + app.send_task( + 'inventory_provider.tasks.worker.update_interface_statuses') return Response("OK") diff --git a/inventory_provider/routes/opsdb.py b/inventory_provider/routes/opsdb.py index 9d176e6825704b1ddfe7d645a23bab021606f79e..c49fe1f269e04944aca61cb69e5cb158648185a7 100644 --- a/inventory_provider/routes/opsdb.py +++ b/inventory_provider/routes/opsdb.py @@ -3,13 +3,14 @@ import json from flask import Blueprint, request, Response from inventory_provider import db -from inventory_provider.storage import external_inventory routes = Blueprint("inventory-opsdb-query-routes", __name__) -services_key = "inv_services" -interfaces_key = "inv_interfaces" -equipment_locations_key = "inv_eq_locations" +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 require_accepts_json(f): @@ -93,7 +94,7 @@ def get_children(parent_id): r = db.get_redis() return Response( r.hget( - external_inventory.service_parent_to_children_key, + service_parent_to_children_key, parent_id), mimetype="application/json") @@ -103,6 +104,6 @@ def get_parents(child_id): r = db.get_redis() return Response( r.hget( - external_inventory.service_child_to_parents_key, + service_child_to_parents_key, child_id), mimetype="application/json") diff --git a/inventory_provider/storage/__init__.py b/inventory_provider/storage/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/inventory_provider/storage/external_inventory.py b/inventory_provider/storage/external_inventory.py deleted file mode 100644 index e721a348d52a83ca82121dd299f674f630d38392..0000000000000000000000000000000000000000 --- a/inventory_provider/storage/external_inventory.py +++ /dev/null @@ -1,72 +0,0 @@ -import json -from collections import defaultdict -from flask import current_app -from inventory_provider import alarmsdb, db - - -services_key = "inv_services" -interfaces_key = "inv_interfaces" -equipment_locations_key = "inv_eq_locations" -service_child_to_parents_key = "inv_service_child_to_parents" -service_parent_to_children_key = "inv_service_parent_to_children" -interface_status_key = "service_status" - - -def update_services_to_monitor(services): - r = db.get_redis() - relevant_types = ("path", "service", "l2circuit") - r.delete(services_key) - for service in services: - if service["circuit_type"].lower() in relevant_types: - r.hset(services_key, service["id"], json.dumps(service)) - - -def update_interfaces_to_services(services): - r = db.get_redis() - config = current_app.config["INVENTORY_PROVIDER_CONFIG"] - with db.connection(config["alarms-db"]) as cnx: - with db.cursor(cnx) as csr: - mapped_interfaces = defaultdict(list) - r.delete(interfaces_key) - for service in services: - key = "{}::{}".format( - service["equipment"], - service["interface_name"] - ) - mapped_interfaces[key].append(service) - if not r.hexists(interface_status_key, key): - r.hset(interface_status_key, key, - alarmsdb.get_last_known_interface_status( - csr, - service["equipment"], - service["interface_name"] - )) - - for key, value in mapped_interfaces.items(): - r.hset(interfaces_key, key, json.dumps(value)) - - -def update_service_hierarchy(records): - r = db.get_redis() - children_to_parents = defaultdict(list) - parents_to_children = defaultdict(list) - for relation in records: - parent_id = relation["parent_circuit_id"] - child_id = relation["child_circuit_id"] - parents_to_children[parent_id].append(relation) - children_to_parents[child_id].append(relation) - - r.delete(service_child_to_parents_key) - for child, parents in children_to_parents.items(): - r.hset(service_child_to_parents_key, child, json.dumps(parents)) - - r.delete(service_parent_to_children_key) - for parent, children in parents_to_children.items(): - r.hset(service_parent_to_children_key, parent, json.dumps(children)) - - -def update_equipment_locations(equipment_location_data): - r = db.get_redis() - r.delete(equipment_locations_key) - for ld in equipment_location_data: - r.hset(equipment_locations_key, ld["equipment_name"], json.dumps(ld)) diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 8f2efb84f0badfac81a87851d541f9297c6a1bd0..2f48358a17bc2b5480ace087e8ab60391a4ea6af 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -2,6 +2,7 @@ import json import logging from celery import bootsteps, Task +from collections import defaultdict import redis from lxml import etree @@ -11,6 +12,7 @@ from inventory_provider import config from inventory_provider import constants from inventory_provider import db from inventory_provider import environment +from inventory_provider import opsdb from inventory_provider import snmp from inventory_provider import juniper @@ -81,6 +83,13 @@ class WorkerArgs(bootsteps.Step): InventoryTask.logger = logging.getLogger(constants.TASK_LOGGER_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 worker_args(parser): parser.add_argument( "--config_filename", @@ -135,3 +144,98 @@ def update_alarmsdb_cache(self): InventoryTask.save_value_json('alarmsdb:%s' % table_name, data) logger.debug('FINISHED: update_alarmsdb_cache') + + +@app.task() +def update_interfaces_to_services(): + # todo - factor this connection stuff out + r = redis.StrictRedis( + host=InventoryTask.config["redis"]["hostname"], + port=InventoryTask.config["redis"]["port"]) + with db.connection(InventoryTask.config["ops-db"]) as cx: + services = opsdb.get_circuits(cx) + + mapped_interfaces = defaultdict(list) + for service in services: + key = "{}::{}".format( + service["equipment"], + service["interface_name"] + ) + mapped_interfaces[key].append(service) + # Puts lu services under the parent ae as well as their own interface + # eg. services on ae15.12 would be found under ae15 as well as ae15.12 + if "." in service["interface_name"]: + key = "{}::{}".format( + service["equipment"], + service["interface_name"].split(".")[0] + ) + mapped_interfaces[key].append(service) + + r.delete(interfaces_key) + for key, value in mapped_interfaces.items(): + r.hset(interfaces_key, key, json.dumps(value)) + + +@app.task() +def update_equipment_locations(): + # todo - factor this connection stuff out + r = redis.StrictRedis( + host=InventoryTask.config["redis"]["hostname"], + port=InventoryTask.config["redis"]["port"]) + r.delete(equipment_locations_key) + + with db.connection(InventoryTask.config["ops-db"]) as cx: + for ld in opsdb.get_equipment_location_data(cx): + r.hset( + equipment_locations_key, ld["equipment_name"], json.dumps(ld)) + + +@app.task() +def update_circuit_hierarchy(): + # todo - factor this connection stuff out + r = redis.StrictRedis( + host=InventoryTask.config["redis"]["hostname"], + port=InventoryTask.config["redis"]["port"]) + children_to_parents = defaultdict(list) + parents_to_children = defaultdict(list) + with db.connection(InventoryTask.config["ops-db"]) as cx: + records = opsdb.get_circuit_hierarchy(cx) + for relation in records: + parent_id = relation["parent_circuit_id"] + child_id = relation["child_circuit_id"] + parents_to_children[parent_id].append(relation) + children_to_parents[child_id].append(relation) + + r.delete(service_child_to_parents_key) + for child, parents in children_to_parents.items(): + r.hset(service_child_to_parents_key, child, json.dumps(parents)) + + r.delete(service_parent_to_children_key) + for parent, children in parents_to_children.items(): + r.hset( + service_parent_to_children_key, parent, json.dumps(children)) + + +@app.task() +def update_interface_statuses(): + # todo - factor this connection stuff out + r = redis.StrictRedis( + host=InventoryTask.config["redis"]["hostname"], + port=InventoryTask.config["redis"]["port"]) + with db.connection(InventoryTask.config["ops-db"]) as cx: + services = opsdb.get_circuits(cx) + with db.connection(InventoryTask.config["alarms-db"]) as cx: + with db.cursor(cx) as csr: + for service in services: + key = "{}::{}".format( + service["equipment"], + service["interface_name"] + ) + if not r.hexists(interface_status_key, key): + r.hset(interface_status_key, + key, + alarmsdb.get_last_known_interface_status( + csr, + service["equipment"], + service["interface_name"] + )) diff --git a/setup.py b/setup.py index d48ac903b5b8d269c01cbe3d2f8b600d35383380..fd7d16bf8508369e6fc9cce9803c83911e6a5b51 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='inventory-provider', - version="0.14", + version="0.15", author='GEANT', author_email='swd@geant.org', description='Dashboard inventory provider', diff --git a/test/conftest.py b/test/conftest.py index 1b8bf386dfaf2bf9055449ed04710a531bc161a7..26186461066c26a4c6654a6b021f975831544cf7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -42,7 +42,17 @@ def data_config_filename(tmp_dir_name): "redis": { "hostname": "xxxxxx", "port": 6379 - } + }, + "infinera-dna": [ + {"name": "name1", "address": "123.456.789.0"}, + {"name": "name2", "address": "012.345.678.9"}, + {"name": "name3", "address": "111.222.333.000"} + ], + "coriant-tnms": [ + {"name": "name1", "address": "123.456.789.0"}, + {"name": "name2", "address": "012.345.678.9"}, + {"name": "name3", "address": "111.222.333.000"} + ] } shutil.copyfile( diff --git a/test/storage/test_external_inventory.py b/test/storage/test_external_inventory.py deleted file mode 100644 index b0caf9684824c9a701e67a05f25ceca5468723ea..0000000000000000000000000000000000000000 --- a/test/storage/test_external_inventory.py +++ /dev/null @@ -1,156 +0,0 @@ -from inventory_provider.storage import external_inventory - - -def test_update_services_to_monitor(mocker): - mocked_redis = mocker.patch( - "inventory_provider.storage.external_inventory.db.get_redis") - mocked_hset = mocked_redis.return_value.hset - - services = [ - {"circuit_type": "path", "id": "test_id_0", "other": "stuff"}, - {"circuit_type": "service", "id": "test_id_1", "other": "stuff"}, - {"circuit_type": "l2circuit", "id": "test_id_2", "other": "stuff"}, - {"circuit_type": "spam", "id": "test_id_4", "other": "stuff"} - ] - external_inventory.update_services_to_monitor(services) - mocked_hset.assert_any_call( - external_inventory.services_key, "test_id_0", - "{\"circuit_type\": \"path\"," - " \"id\": \"test_id_0\"," - " \"other\": \"stuff\"}") - mocked_hset.assert_any_call( - external_inventory.services_key, "test_id_1", - "{\"circuit_type\": \"service\"," - " \"id\": \"test_id_1\"," - " \"other\": \"stuff\"}") - mocked_hset.assert_any_call( - external_inventory.services_key, "test_id_2", - "{\"circuit_type\": \"l2circuit\"," - " \"id\": \"test_id_2\"," - " \"other\": \"stuff\"}") - assert mocked_hset.call_count == 3 - - -def test_update_interfaces_to_services(mocker): - mocked_redis = mocker.patch( - "inventory_provider.storage.external_inventory.db.get_redis") - mocker.patch( - "inventory_provider.storage.external_inventory.current_app") - mocker.patch( - "inventory_provider.storage.external_inventory.db.connection") - mocker.patch( - "inventory_provider.storage.external_inventory.db.cursor") - mocked_hset = mocked_redis.return_value.hset - services = [ - {"equipment": "eq_0", "interface_name": "if_0"}, - {"equipment": "eq_1", "interface_name": "if_1"}, - {"equipment": "eq_2", "interface_name": "if_2"}, - {"equipment": "eq_3", "interface_name": "if_3"}, - {"equipment": "eq_3", "interface_name": "if_3", "extra": "stuff"}, - {"equipment": "eq_4", "interface_name": "if_4"} - ] - unique_keys = set(s["equipment"] + "::" + s["interface_name"] - for s in services) - - external_inventory.update_interfaces_to_services(services) - assert mocked_hset.call_count == len(unique_keys) - mocked_hset.assert_any_call(external_inventory.interfaces_key, - "eq_2::if_2", - "[{\"equipment\": \"eq_2\"," - " \"interface_name\": \"if_2\"}]") - mocked_hset.assert_any_call(external_inventory.interfaces_key, - "eq_3::if_3", - "[{\"equipment\": \"eq_3\"," - " \"interface_name\": \"if_3\"}," - " {\"equipment\": \"eq_3\"," - " \"interface_name\": \"if_3\"," - " \"extra\": \"stuff\"}]") - - -def test_update_service_hierarchy(mocker): - mocked_redis = mocker.patch( - "inventory_provider.storage.external_inventory.db.get_redis") - mocked_hset = mocked_redis.return_value.hset - data = [ - {"parent_circuit": "PC_1", - "parent_circuit_id": "1001", - "parent_circuit_status": "operational", - "child_circuit": "CC_1", - "child_circuit_id": "2001", - "segment_group": 1}, - {"parent_circuit": "PC_2", - "parent_circuit_id": "1002", - "parent_circuit_status": "operational", - "child_circuit": "CC_1", - "child_circuit_id": "2001", - "segment_group": 1}, - {"parent_circuit": "PC_3", - "parent_circuit_id": "1003", - "parent_circuit_status": "operational", - "child_circuit": "CC_2", - "child_circuit_id": "2002", - "segment_group": 1}, - {"parent_circuit": "PC_3", - "parent_circuit_id": "1003", - "parent_circuit_status": "operational", - "child_circuit": "CC_3", - "child_circuit_id": "2003", - "segment_group": 1} - ] - external_inventory.update_service_hierarchy(data) - u_parent_keys = set([d["parent_circuit_id"] for d in data]) - u_child_keys = set([d["child_circuit_id"] for d in data]) - assert mocked_hset.call_count == len(u_parent_keys) + len(u_child_keys) - mocked_hset.assert_any_call( - external_inventory.service_child_to_parents_key, - "2001", - "[{\"parent_circuit\": \"PC_1\"," - " \"parent_circuit_id\": \"1001\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_1\"," - " \"child_circuit_id\": \"2001\"," - " \"segment_group\": 1}," - " {\"parent_circuit\": \"PC_2\"," - " \"parent_circuit_id\": \"1002\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_1\"," - " \"child_circuit_id\": \"2001\"," - " \"segment_group\": 1}]" - ) - mocked_hset.assert_any_call( - external_inventory.service_child_to_parents_key, - "2002", - "[{\"parent_circuit\": \"PC_3\"," - " \"parent_circuit_id\": \"1003\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_2\"," - " \"child_circuit_id\": \"2002\"," - " \"segment_group\": 1}]" - ) - - mocked_hset.assert_any_call( - external_inventory.service_parent_to_children_key, - "1003", - "[{\"parent_circuit\": \"PC_3\"," - " \"parent_circuit_id\": \"1003\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_2\"," - " \"child_circuit_id\": \"2002\"," - " \"segment_group\": 1}," - " {\"parent_circuit\": \"PC_3\"," - " \"parent_circuit_id\": \"1003\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_3\"," - " \"child_circuit_id\": \"2003\"," - " \"segment_group\": 1}]" - ) - mocked_hset.assert_any_call( - external_inventory.service_parent_to_children_key, - "1002", - "[{\"parent_circuit\": \"PC_2\"," - " \"parent_circuit_id\": \"1002\"," - " \"parent_circuit_status\": \"operational\"," - " \"child_circuit\": \"CC_1\"," - " \"child_circuit_id\": \"2001\"," - " \"segment_group\": 1}]" - ) diff --git a/test/test_alarmdb_routes.py b/test/test_alarmdb_routes.py deleted file mode 100644 index 82522455868b48bdda22a82131c606dcdd8f0d72..0000000000000000000000000000000000000000 --- a/test/test_alarmdb_routes.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -import jsonschema - -DEFAULT_REQUEST_HEADERS = { - "Content-type": "application/json", - "Accept": ["application/json"] -} - - -def test_get_interface_status(mocker, client): - mocked_conn = mocker.patch('inventory_provider.routes.alarmsdb' - '.db.connection') - mocked_conn.return_value.__enter__.return_value = None - - mocked_inteface_status = mocker.patch( - 'inventory_provider.routes.alarmsdb.' - 'alarmsdb.get_last_known_interface_status') - mocked_inteface_status.return_value = "up" - - rv = client.get( - '/alarmsdb/interface-status?' - 'equipment=mx1.lon.uk.geant.net&interface=xe-1/2/2', - headers=DEFAULT_REQUEST_HEADERS) - - interfaces_list_schema = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "status": { - "type": "string", - } - } - } - - response = json.loads(rv.data.decode("utf-8")) - jsonschema.validate(response, interfaces_list_schema) - assert response == {"status": "up"} diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py new file mode 100644 index 0000000000000000000000000000000000000000..114112d1a6cd3789e38211547a03eb14497cfbe8 --- /dev/null +++ b/test/test_classifier_routes.py @@ -0,0 +1,75 @@ +import json +import jsonschema + +DEFAULT_REQUEST_HEADERS = { + "Content-type": "application/json", + "Accept": ["application/json"] +} + + +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): + + test_data = [ + {"ip_address": "a.b.c.d", "project_name": "AAABBB"}, + {"ip_address": "b.c.d.e", "project_name": "CCCCDDDDD"}, + {"ip_address": "c.d.e.f", "project_name": "EFEFEFEF"}, + {"ip_address": "::1", "project_name": "GGHHGGHH"} + ] + + class MockedRedis(): + def __init__(self): + pass + + def get(self, ignored): + return json.dumps(test_data).encode('utf-8') + + mocker.patch( + 'inventory_provider.routes.classifier.db.get_redis', + return_value=MockedRedis()) + + 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) == len(test_data) + assert set([x['ip_address'] for x in test_data]) == set(response_data) diff --git a/test/test_external_inventory_routes.py b/test/test_external_inventory_routes.py index 4adc7d5890795740626e7a696fe51f6c1a89a6fc..32c72c8a1787bfddfd6530f4b272fed485753aeb 100644 --- a/test/test_external_inventory_routes.py +++ b/test/test_external_inventory_routes.py @@ -1,5 +1,5 @@ import json -from inventory_provider.storage import external_inventory +from inventory_provider.tasks import worker DEFAULT_REQUEST_HEADERS = { "Content-type": "application/json", @@ -31,7 +31,7 @@ def test_get_one_equipment_location(mocker, client): assert dummy_data == json.loads(rv.data.decode("utf-8")) mocked_hget.assert_called_with( - external_inventory.equipment_locations_key, + worker.equipment_locations_key, "dummy-equipment" ) @@ -48,7 +48,7 @@ def test_get_equipment_location(mocker, client): assert rv.is_json mocked_hgetall.assert_called_with( - external_inventory.equipment_locations_key + worker.equipment_locations_key ) @@ -64,7 +64,7 @@ def test_get_interface_info(mocker, client): assert rv.is_json mocked_hgetall.assert_called_with( - external_inventory.interfaces_key + worker.interfaces_key ) @@ -80,7 +80,7 @@ def test_get_interface_info_for_equipment(mocker, client): assert rv.is_json mocked_hscan_iter.assert_called_with( - external_inventory.interfaces_key, "dummy-equipment::*" + worker.interfaces_key, "dummy-equipment::*" ) @@ -96,7 +96,7 @@ def test_get_interface_info_for_equipment_and_interface(mocker, client): assert rv.is_json mocked_hget.assert_called_with( - external_inventory.interfaces_key, "dummy-equipment::xe-2/3/1" + worker.interfaces_key, "dummy-equipment::xe-2/3/1" ) @@ -112,7 +112,7 @@ def test_get_children(mocker, client): assert rv.is_json mocked_hget.assert_called_with( - external_inventory.service_parent_to_children_key, + worker.service_parent_to_children_key, "22987" ) @@ -129,6 +129,6 @@ def test_get_parents(mocker, client): assert rv.is_json mocked_hget.assert_called_with( - external_inventory.service_parent_to_children_key, + worker.service_parent_to_children_key, "22987" ) diff --git a/test/test_opsdb.py b/test/test_opsdb.py index 3507bdc0bcb80e341586cd2d8d966a5591da94fc..7e6c1848378c14f31ea6ede92b04a1bc2a65f2e2 100644 --- a/test/test_opsdb.py +++ b/test/test_opsdb.py @@ -97,6 +97,24 @@ def test_juniper_field_update(): assert r["interface_name"] == "xe-2/0/0.123" +def test_coriant_update_fields(): + i = { + "equipment": "groove-1", + "card_id": "2-3", + "port": None + } + r = inventory_provider.opsdb._coriant_field_update(i) + assert r["interface_name"] == "2-3" + + i = { + "equipment": "groove-1", + "card_id": "2-3", + "port": "4" + } + r = inventory_provider.opsdb._coriant_field_update(i) + assert r["interface_name"] == "2-3/4" + + def test_get_circuits(mocker): mocker.patch("inventory_provider.opsdb.db.cursor") mocked_convert_to_dict = mocker.patch(