############################################################################### # Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) # # and others # # # # All rights reserved. This program and the accompanying materials # # are made available under the terms of the Apache License, Version 2.0 # # which accompanies this distribution, and is available at # # http://www.apache.org/licenses/LICENSE-2.0 # ############################################################################### import re from discover.fetchers.aci.aci_access import AciAccess, aci_config_required from utils.inventory_mgr import InventoryMgr from utils.util import decode_aci_dn, encode_aci_dn, get_object_path_part # Fetches and adds to database: # 1. ACI Switches with role "spine" # # Returns: # 1. ACI Switch uplink pnics that belong to the "leaf" switch # 2. ACI Switch downlink pnics that belong to "spine" switches mentioned above # and are connected to the "leaf" switch class AciFetchLeafToSpinePnics(AciAccess): def __init__(self): super().__init__() self.inv = InventoryMgr() def fetch_switches_by_role(self, role_name): query_filter = {"query-target-filter": "eq(fabricNode.role, \"{}\")".format(role_name)} switches = self.fetch_objects_by_class("fabricNode", query_filter) return [switch["attributes"] for switch in switches] def fetch_adjacent_connections(self, device_id): dn = "/".join((device_id, "sys")) response = self.fetch_mo_data(dn, {"query-target": "subtree", "target-subtree-class": "lldpAdjEp"}) connections = self.get_objects_by_field_names(response, "lldpAdjEp", "attributes") return connections # Returns: # List of: # 1. Switches with role "spine" # 2. Downlink pnic id for spine switch # 3. Uplink pnic id for leaf switch def fetch_spines_and_pnics_by_leaf_id(self, leaf_id): spine_switches = self.fetch_switches_by_role("spine") adjacent_devices = self.fetch_adjacent_connections(leaf_id) spines = [] for spine in spine_switches: # Check if spine switch is connected to current leaf switch connection = next((d for d in adjacent_devices if spine["name"] == d["sysName"]), None) if connection: try: # Extract pnics from adjacency data uplink_pnic = re.match(".*\[(.+?)\].*", connection["dn"]).group(1) downlink_pnic = re.match(".*\[(.+?)\].*", connection["portDesc"]).group(1) spines.append({ "device": spine, "downlink_pnic": downlink_pnic, "uplink_pnic": uplink_pnic }) except AttributeError: continue # TODO: probably raise an exception return spines @aci_config_required(default=[]) def get(self, db_leaf_pnic_id): environment = self.get_env() leaf_pnic = self.inv.get_by_id(environment=environment, item_id=db_leaf_pnic_id) leaf_switch_id = leaf_pnic['switch'] # Decode aci leaf switch id from db format aci_leaf_pnic_id = decode_aci_dn(db_leaf_pnic_id) aci_leaf_id = re.match("switch-(.+?)-leaf", aci_leaf_pnic_id).group(1) # Fetch all leaf-to-spine connectivity data spines_with_pnics = self.fetch_spines_and_pnics_by_leaf_id(aci_leaf_id) pnics = [] for spine_with_pnic in spines_with_pnics: spine = spine_with_pnic["device"] downlink_pnic_id = spine_with_pnic["downlink_pnic"] uplink_pnic_id = spine_with_pnic["uplink_pnic"] # Add spine switch to db if it's not there yet spine_id_match = re.match("topology/(.+)", spine["dn"]) if not spine_id_match: raise ValueError("Failed to fetch spine switch id " "from switch dn: {}".format(spine["dn"])) aci_spine_id = spine_id_match.group(1) db_spine_id = "-".join(("switch", encode_aci_dn(aci_spine_id), spine["role"])) if not self.inv.get_by_id(environment, db_spine_id): spine_json = { "id": db_spine_id, "type": "switch", "switch": db_spine_id, "aci_document": spine } # Region name is the same as region id region_id = get_object_path_part(leaf_pnic["name_path"], "Regions") region = self.inv.get_by_id(environment, region_id) self.inv.save_inventory_object(o=spine_json, parent=region, environment=environment) # Add downlink and uplink pnics to results list, # including their mutual connection data # (see "connected_to" field). db_downlink_pnic_id = "-".join((db_spine_id, encode_aci_dn(downlink_pnic_id))) db_uplink_pnic_id = "-".join((leaf_pnic["switch"], encode_aci_dn(uplink_pnic_id))) downlink_pnic_json = { "id": db_downlink_pnic_id, "object_name": downlink_pnic_id, "type": "switch_pnic", "role": "downlink", "connected_to": db_uplink_pnic_id, "switch": db_spine_id, "parent_id": db_spine_id, "parent_type": "switch", "aci_document": {} # TODO: what can we add here? } uplink_pnic_json = { "id": db_uplink_pnic_id, "object_name": uplink_pnic_id, "type": "switch_pnic", "role": "uplink", "connected_to": db_downlink_pnic_id, "switch": leaf_switch_id, "parent_id": leaf_switch_id, "parent_type": "switch", "aci_document": {} # TODO: what can we add here? } pnics.extend([downlink_pnic_json, uplink_pnic_json]) return pnics