X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=blobdiff_plain;f=RO-SDN-arista%2Fosm_rosdn_arista%2Fwimconn_arista.py;h=571add6853d12a80c861e838004b7cf8b8dc1ef8;hp=0b49cb03d7b8934e80b6f4da7ed3b8383fd68bbb;hb=277bf0f8bb1fd4ebf7732360e418b6fc0f97c260;hpb=8e282cfed41bdee78e9f0152fbcc3c2b33bc7c65 diff --git a/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py b/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py index 0b49cb03..571add68 100644 --- a/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py +++ b/RO-SDN-arista/osm_rosdn_arista/wimconn_arista.py @@ -41,9 +41,10 @@ from enum import Enum from requests import RequestException from cvprac.cvp_client import CvpClient +from cvprac.cvp_api import CvpApi from cvprac.cvp_client_errors import CvpLoginError, CvpSessionLogOutError, CvpApiError +from cvprac import __version__ as cvprac_version -from osm_rosdn_arista.aristaSwitch import AristaSwitch from osm_rosdn_arista.aristaConfigLet import AristaSDNConfigLet from osm_rosdn_arista.aristaTask import AristaCVPTask @@ -103,6 +104,7 @@ class AristaSdnConnector(SdnConnectorBase): __supported_service_types = ["ELINE", "ELAN"] __supported_encapsulation_types = ["dot1q"] __WIM_LOGGER = 'openmano.sdnconn.arista' + __SERVICE_ENDPOINT_MAPPING = 'service_endpoint_mapping' __ENCAPSULATION_TYPE_PARAM = "service_endpoint_encapsulation_type" __ENCAPSULATION_INFO_PARAM = "service_endpoint_encapsulation_info" __BACKUP_PARAM = "backup" @@ -118,11 +120,21 @@ class AristaSdnConnector(SdnConnectorBase): __VLAN_PARAM = "vlan" __VNI_PARAM = "vni" __SEPARATOR = '_' + __MANAGED_BY_OSM = '## Managed by OSM ' __OSM_PREFIX = "osm_" __OSM_METADATA = "OSM_metadata" __METADATA_PREFIX = '!## Service' - __EXC_TASK_EXEC_WAIT = 1 - __ROLLB_TASK_EXEC_WAIT = 5 + __EXC_TASK_EXEC_WAIT = 10 + __ROLLB_TASK_EXEC_WAIT = 10 + __API_REQUEST_TOUT = 60 + __SWITCH_TAG_NAME = 'topology_type' + __SWITCH_TAG_VALUE = 'leaf' + __LOOPBACK_INTF = "Loopback0" + _VLAN = "VLAN" + _VXLAN = "VXLAN" + _VLAN_MLAG = "VLAN-MLAG" + _VXLAN_MLAG = "VXLAN-MLAG" + def __init__(self, wim, wim_account, config=None, logger=None): """ @@ -168,116 +180,123 @@ class AristaSdnConnector(SdnConnectorBase): self.__passwd = wim_account.get("password") self.client = None self.cvp_inventory = None - self.logger.debug("Arista SDN assist {}, user:{} and conf:{}". - format(wim, self.__user, config)) + self.cvp_tags = None + self.logger.debug("Arista SDN plugin {}, cvprac version {}, user:{} and config:{}". + format(wim, cvprac_version, self.__user, + self.delete_keys_from_dict(config, ('passwd',)))) self.allDeviceFacts = [] - self.__load_switches() - self.clC = AristaSDNConfigLet() self.taskC = None - self.sw_loopback0 = {} - self.bgp = {} - - for s in self.s_api: - # Each switch has a different loopback address, - # so it's a different configLet - inf = self.__get_switch_interface_ip(s, 'Loopback0') - self.sw_loopback0[s] = inf.split('/')[0] - self.bgp[s] = self.__get_switch_asn(s) + try: + self.__load_topology() + self.__load_switches() + except SdnConnectorError as sc: + raise sc + except Exception as e: + raise SdnConnectorError(message="Unable to load switches from CVP", + http_code=500) from e + self.logger.debug("Using topology {} in Arista Leaf switches: {}".format( + self.topology, + self.delete_keys_from_dict(self.switches, ('passwd',)))) + self.clC = AristaSDNConfigLet(self.topology) + + def __load_topology(self): + self.topology = self._VXLAN_MLAG + if self.__config and self.__config.get('topology'): + topology = self.__config.get('topology') + if topology == "VLAN": + self.topology = self._VLAN + elif topology == "VXLAN": + self.topology = self._VXLAN + elif topology == "VLAN-MLAG": + self.topology = self._VLAN_MLAG + elif topology == "VXLAN-MLAG": + self.topology = self._VXLAN_MLAG def __load_switches(self): """ Retrieves the switches to configure in the following order - 1.- from incomming configuration - 2.- Looking in the CloudVision inventory for those switches whose hostname starts with 'leaf' + 1. from incoming configuration: + 1.1 using port mapping + using user and password from WIM + retrieving Lo0 and AS from switch + 1.2 from 'switches' parameter, + if any parameter is not present + Lo0 and AS - it will be requested to the switch + 2. Looking in the CloudVision inventory if not in configuration parameters + 2.1 using the switches with the topology_type tag set to 'leaf' + + All the search methods will be used """ - if not self.__config or not self.__config.get('switches'): - if self.client is None: - self.client = self.__connect() - self.__load_inventory() - self.switches = {} + self.switches = {} + if self.__config and self.__config.get(self.__SERVICE_ENDPOINT_MAPPING): + for port in self.__config.get(self.__SERVICE_ENDPOINT_MAPPING): + switch_dpid = port.get(self.__SW_ID_PARAM) + if switch_dpid and switch_dpid not in self.switches: + self.switches[switch_dpid] = {'passwd': self.__passwd, + 'ip': None, + 'usr': self.__user, + 'lo0': None, + 'AS': None, + 'serialNumber': None, + 'mlagPeerDevice': None} + + if self.__config and self.__config.get('switches'): + # Not directly from json, complete one by one + config_switches = self.__config.get('switches') + for cs, cs_content in config_switches.items(): + if cs not in self.switches: + self.switches[cs] = {'passwd': self.__passwd, + 'ip': None, + 'usr': self.__user, + 'lo0': None, + 'AS': None, + 'serialNumber': None, + 'mlagPeerDevice': None} + if cs_content: + self.switches[cs].update(cs_content) + + # Load the rest of the data + if self.client == None: + self.client = self.__connect() + self.__load_inventory() + if not self.switches: + self.__get_tags(self.__SWITCH_TAG_NAME, self.__SWITCH_TAG_VALUE) for device in self.allDeviceFacts: - if device['hostname'].startswith('Leaf'): - switch_data = {"passwd": self.__passwd, - "ip": device['ipAddress'], - "usr": self.__user} - self.switches[device['hostname']] = switch_data - if len(self.switches) == 0: - self.logger.error("Unable to load Leaf switches from CVP") - return - else: - # directly json - self.switches = self.__config.get['switches'] - # self.s_api are switch objects, one for each switch in self.switches, + # get the switches whose topology_tag is 'leaf' + if device['serialNumber'] in self.cvp_tags: + if not self.switches.get(device['hostname']): + switch_data = {'passwd': self.__passwd, + 'ip': device['ipAddress'], + 'usr': self.__user, + 'lo0': None, + 'AS': None, + 'serialNumber': None, + 'mlagPeerDevice': None} + self.switches[device['hostname']] = switch_data + if len(self.switches) == 0: + self.logger.error("Unable to load Leaf switches from CVP") + return + + # self.switches are switch objects, one for each switch in self.switches, # used to make eAPI calls by using switch.py module - self.s_api = {} for s in self.switches: - self.logger.debug("Using Arista Leaf switch: {} {} {}".format( - s, - self.switches[s]["ip"], - self.switches[s]["usr"])) - if self.is_valid_destination(self.switches[s]["ip"]): - self.s_api[s] = AristaSwitch(host=self.switches[s]["ip"], - user=self.switches[s]["usr"], - passwd=self.switches[s]["passwd"], - logger=self.logger) - - def __lldp_find_neighbor(self, tlv_name=None, tlv_value=None): - """Returns a list of dicts where a mathing LLDP neighbor has been found - Each dict has: - switch -> switch name - interface -> switch interface - """ - r = [] - lldp_info = {} - - # Get LLDP info from each switch - for s in self.s_api: - result = self.s_api[s].run("show lldp neighbors detail") - lldp_info[s] = result[0]["lldpNeighbors"] - # Look LLDP match on each interface - # Note that eAPI returns [] for an interface with no LLDP neighbors - # in the corresponding interface lldpNeighborInfo field - for interface in lldp_info[s]: - if lldp_info[s][interface]["lldpNeighborInfo"]: - lldp_nInf = lldp_info[s][interface]["lldpNeighborInfo"][0] - if tlv_name in lldp_nInf: - if lldp_nInf[tlv_name] == tlv_value: - r.append({"name": s, "interface": interface}) - - return r - - def __get_switch_asn(self, switch): - """Returns switch ASN in default VRF - """ - bgp_info = self.s_api[switch].run("show ip bgp summary")[0] - return(bgp_info["vrfs"]["default"]["asn"]) - - def __get_switch_po(self, switch, interface=None): - """Returns Port-Channels for a given interface - If interface is None returns a list with all PO interfaces - Note that if specified, interface should be exact name - for instance: Ethernet3 and not e3 eth3 and so on - """ - po_inf = self.s_api[switch].run("show port-channel")[0]["portChannels"] - - if interface: - r = [x for x in po_inf if interface in po_inf[x]["activePorts"]] - else: - r = po_inf - - return r - - def __get_switch_interface_ip(self, switch, interface=None): - """Returns interface primary ip - interface should be exact name - for instance: Ethernet3 and not ethernet 3, e3 eth3 and so on - """ - cmd = "show ip interface {}".format(interface) - ip_info = self.s_api[switch].run(cmd)[0]["interfaces"][interface] - - ip = ip_info["interfaceAddress"]["primaryIp"]["address"] - mask = ip_info["interfaceAddress"]["primaryIp"]["maskLen"] + for device in self.allDeviceFacts: + if device['hostname'] == s: + if not self.switches[s].get('ip'): + self.switches[s]['ip'] = device['ipAddress'] + self.switches[s]['serialNumber'] = device['serialNumber'] + break - return "{}/{}".format(ip, mask) + # Each switch has a different loopback address, + # so it's a different configLet + if not self.switches[s].get('lo0'): + inf = self.__get_interface_ip(self.switches[s]['serialNumber'], self.__LOOPBACK_INTF) + self.switches[s]["lo0"] = inf.split('/')[0] + if not self.switches[s].get('AS'): + self.switches[s]["AS"] = self.__get_device_ASN(self.switches[s]['serialNumber']) + if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG): + for s in self.switches: + if not self.switches[s].get('mlagPeerDevice'): + self.switches[s]['mlagPeerDevice'] = self.__get_peer_MLAG(self.switches[s]['serialNumber']) def __check_service(self, service_type, connection_points, check_vlan=True, check_num_cp=True, kwargs=None): @@ -289,10 +308,10 @@ class AristaSdnConnector(SdnConnectorBase): self.__supported_service_types)) if check_num_cp: - if (len(connection_points) < 2): + if len(connection_points) < 2: raise Exception(SdnError.CONNECTION_POINTS_SIZE) - if ((len(connection_points) != self.__ELINE_num_connection_points) and - (service_type == self.__service_types_ELINE)): + if (len(connection_points) != self.__ELINE_num_connection_points and + service_type == self.__service_types_ELINE): raise Exception(SdnError.CONNECTION_POINTS_SIZE) if check_vlan: @@ -311,8 +330,8 @@ class AristaSdnConnector(SdnConnectorBase): raise Exception(SdnError.VLAN_INCONSISTENT) if not vlan_id: raise Exception(SdnError.VLAN_NOT_PROVIDED) - if vlan_id in self.__get_srvVLANs(): - raise Exception('VLAN {} already assigned to a connectivity service'.format(vlan_id)) + if vlan_id in self.__get_srvVLANs(): + raise Exception('VLAN {} already assigned to a connectivity service'.format(vlan_id)) # Commented out for as long as parameter isn't implemented # bandwidth = kwargs.get(self.__BANDWIDTH_PARAM) @@ -329,7 +348,7 @@ class AristaSdnConnector(SdnConnectorBase): for testing the access to CloudVision API """ try: - if self.client is None: + if self.client == None: self.client = self.__connect() result = self.client.api.get_cvp_info() self.logger.debug(result) @@ -381,12 +400,13 @@ class AristaSdnConnector(SdnConnectorBase): new information available for the connectivity service. """ try: + self.logger.debug("invoked get_connectivity_service_status '{}'".format(service_uuid)) if not service_uuid: raise SdnConnectorError(message='No connection service UUID', http_code=500) self.__get_Connection() - if conn_info is None: + if conn_info == None: raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid), http_code=500) @@ -403,10 +423,15 @@ class AristaSdnConnector(SdnConnectorBase): t_isFailed = False t_isPending = False failed_switches = [] - for s in self.s_api: - if (len(cls_perSw[s]) > 0): + for s in self.switches: + if len(cls_perSw[s]) > 0: for cl in cls_perSw[s]: - if len(cls_perSw[s][0]['config']) == 0: + # Fix 1030 SDN-ARISTA Key error note when deploy a NS + # Added protection to check that 'note' exists and additionally + # verify that it is managed by OSM + if (not cls_perSw[s][0]['config'] or + not cl.get('note') or + self.__MANAGED_BY_OSM not in cl['note']): continue note = cl['note'] t_id = note.split(self.__SEPARATOR)[1] @@ -477,7 +502,7 @@ class AristaSdnConnector(SdnConnectorBase): 'service_endpoint_mapping' (see __init__) "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id - "swith_port": ... present if mapping has been found + "switch_port": ... present if mapping has been found for this device_id,device_interface_id "service_mapping_info": present if mapping has been found for this device_id,device_interface_id @@ -527,6 +552,11 @@ class AristaSdnConnector(SdnConnectorBase): http_code=401) from e except SdnConnectorError as sde: raise sde + except ValueError as err: + self.client = None + self.logger.error(str(err), exc_info=True) + raise SdnConnectorError(message=str(err), + http_code=500) from err except Exception as ex: self.client = None self.logger.error(str(ex), exc_info=True) @@ -555,7 +585,7 @@ class AristaSdnConnector(SdnConnectorBase): cls_perSw = {} cls_cp = {} cl_bgp = {} - for s in self.s_api: + for s in self.switches: cls_perSw[s] = [] cls_cp[s] = [] vlan_processed = False @@ -585,28 +615,9 @@ class AristaSdnConnector(SdnConnectorBase): encap_type = cp.get(self.__ENCAPSULATION_TYPE_PARAM) switch_id = encap_info.get(self.__SW_ID_PARAM) - if not switch_id: - point_mac = encap_info.get(self.__MAC_PARAM) - switches = self.__lldp_find_neighbor("chassisId", point_mac) + interface = encap_info.get(self.__SW_PORT_PARAM) + switches = [{'name': switch_id, 'interface': interface}] - if len(switches) == 0: - raise SdnConnectorError(message="Connection point MAC address {} not found in the switches".format(point_mac), - http_code=406) - self.logger.debug("Found connection point for MAC {}: {}". - format(point_mac, switches)) - port_channel = self.__get_switch_po(switch['name'], - switch['interface']) - if len(port_channel) > 0: - interface = port_channel[0] - else: - interface = switch['interface'] - else: - interface = encap_info.get(self.__SW_PORT_PARAM) - switches = [{'name': switch_id, 'interface': interface}] - - if not interface: - raise SdnConnectorError(message="Connection point switch port empty for switch_dpid {}".format(switch_id), - http_code=406) # remove those connections that are equal. This happens when several sriovs are located in the same # compute node interface, that is, in the same switch and interface switches = [x for x in switches if x not in processed_connection_points] @@ -614,6 +625,9 @@ class AristaSdnConnector(SdnConnectorBase): continue processed_connection_points += switches for switch in switches: + if not interface: + raise SdnConnectorError(message="Connection point switch port empty for switch_dpid {}".format(switch_id), + http_code=406) # it should be only one switch where the mac is attached if encap_type == 'dot1q': # SRIOV configLet for Leaf switch mac's attached to @@ -641,28 +655,38 @@ class AristaSdnConnector(SdnConnectorBase): raise SdnConnectorError(message=SdnError.UNSUPPORTED_FEATURE, http_code=406) - for s in self.s_api: + for s in self.switches: # for cl in cp_configLets: cl_name = (self.__OSM_PREFIX + s + self.__SEPARATOR + service_type + str(vlan_id) + self.__SEPARATOR + service_uuid) - # apply VLAN and BGP configLet to all Leaf switches - if service_type == self.__service_types_ELAN: - cl_bgp[s] = self.clC.getElan_bgp(service_uuid, - vlan_id, - vni_id, - self.sw_loopback0[s], - self.bgp[s]) + cl_config = '' + # Apply BGP configuration only for VXLAN topologies + if self.topology in (self._VXLAN_MLAG, self._VXLAN): + if service_type == self.__service_types_ELAN: + cl_bgp[s] = self.clC.getElan_bgp(service_uuid, + vlan_id, + vni_id, + self.switches[s]['lo0'], + self.switches[s]['AS']) + else: + cl_bgp[s] = self.clC.getEline_bgp(service_uuid, + vlan_id, + vni_id, + self.switches[s]['lo0'], + self.switches[s]['AS']) else: - cl_bgp[s] = self.clC.getEline_bgp(service_uuid, - vlan_id, - vni_id, - self.sw_loopback0[s], - self.bgp[s]) + cl_bgp[s] = '' if not cls_cp.get(s): - cl_config = '' + # Apply VLAN configuration to peer MLAG switch, + # only necessary when there are no connection points in the switch + if self.topology in (self._VXLAN_MLAG, self._VLAN_MLAG): + for p in self.switches: + if self.switches[p]['mlagPeerDevice'] == s: + if cls_cp.get(p): + cl_config = str(cl_vlan) else: cl_config = str(cl_vlan) + str(cl_bgp[s]) + str(cls_cp[s]) @@ -704,12 +728,11 @@ class AristaSdnConnector(SdnConnectorBase): allLeafConfigured = {} allLeafModified = {} - for s in self.s_api: + for s in self.switches: allLeafConfigured[s] = False allLeafModified[s] = False - tasks = dict() cl_toDelete = [] - for s in self.s_api: + for s in self.switches: toDelete_in_cvp = False if not (cls_perSw.get(s) and cls_perSw[s][0].get('config')): # when there is no configuration, means that there is no interface @@ -744,36 +767,33 @@ class AristaSdnConnector(SdnConnectorBase): raise Exception(str(res)) self.logger.info("Device {} modify result {}".format(s, res)) for t_id in res[1]['tasks']: - tasks[t_id] = {'workOrderId': t_id} if not toDelete_in_cvp: - note_msg = "## Managed by OSM {}{}{}##".format(self.__SEPARATOR, - t_id, - self.__SEPARATOR) + note_msg = "{}{}{}{}##".format(self.__MANAGED_BY_OSM, + self.__SEPARATOR, + t_id, + self.__SEPARATOR) self.client.api.add_note_to_configlet( cls_perSw[s][0]['key'], note_msg) - cls_perSw[s][0].update([('note', note_msg)]) + cls_perSw[s][0]['note'] = note_msg + tasks = { t_id : {'workOrderId': t_id} } + self.__exec_task(tasks, self.__EXC_TASK_EXEC_WAIT) # with just one configLet assigned to a device, # delete all if there are errors in next loops if not toDelete_in_cvp: allLeafModified[s] = True - if self.taskC is None: - self.__connect() - data = self.taskC.update_all_tasks(tasks).values() - self.taskC.task_action(data, - self.__EXC_TASK_EXEC_WAIT, - 'executed') if len(cl_toDelete) > 0: self.__configlet_modify(cl_toDelete, delete=True) + return allLeafConfigured, allLeafModified except Exception as ex: try: self.__rollbackConnection(cls_perSw, - allLeafConfigured=True, - allLeafModified=True) + allLeafConfigured, + allLeafModified) except Exception as e: - self.logger.info("Exception rolling back in updating connection: {}". - format(e)) + self.logger.error("Exception rolling back in updating connection: {}". + format(e), exc_info=True) raise ex def __rollbackConnection(self, @@ -782,8 +802,7 @@ class AristaSdnConnector(SdnConnectorBase): allLeafModified): """ Removes the given configLet from the devices and then remove the configLets """ - tasks = dict() - for s in self.s_api: + for s in self.switches: if allLeafModified[s]: try: res = self.__device_modify( @@ -792,22 +811,24 @@ class AristaSdnConnector(SdnConnectorBase): delete=True) if "errorMessage" in str(res): raise Exception(str(res)) + tasks = dict() for t_id in res[1]['tasks']: tasks[t_id] = {'workOrderId': t_id} + self.__exec_task(tasks) self.logger.info("Device {} modify result {}".format(s, res)) except Exception as e: - self.logger.info('Error removing configlets from device {}: {}'.format(s, e)) + self.logger.error('Error removing configlets from device {}: {}'.format(s, e)) pass - if self.taskC is None: - self.__connect() - data = self.taskC.update_all_tasks(tasks).values() - self.taskC.task_action(data, - self.__ROLLB_TASK_EXEC_WAIT, - 'executed') - for s in self.s_api: + for s in self.switches: if allLeafConfigured[s]: self.__configlet_modify(cls_perSw[s], delete=True) + def __exec_task(self, tasks, tout=10): + if self.taskC == None: + self.__connect() + data = self.taskC.update_all_tasks(tasks).values() + self.taskC.task_action(data, tout, 'executed') + def __device_modify(self, device_to_update, new_configlets, delete): """ Updates the devices (switches) adding or removing the configLet, the tasks Id's associated to the change are returned @@ -820,7 +841,7 @@ class AristaSdnConnector(SdnConnectorBase): newTasks = [] if (len(new_configlets) == 0 or - device_to_update is None or + device_to_update == None or len(device_to_update) == 0): data = {'updated': updated, 'tasks': newTasks} return [changed, data] @@ -907,10 +928,14 @@ class AristaSdnConnector(SdnConnectorBase): else: changed = True if 'taskIds' in str(dev_action): + # Fix 1030 SDN-ARISTA Key error note when deploy a NS + if not dev_action['data']['taskIds']: + raise SdnConnectorError("No taskIds found: Device {} Configlets couldnot be updated".format( + up_device['hostname'])) for taskId in dev_action['data']['taskIds']: updated.append({up_device['hostname']: - "Configlets-{}".format( - taskId)}) + "Configlets-{}".format( + taskId)}) newTasks.append(taskId) else: updated.append({up_device['hostname']: @@ -988,7 +1013,8 @@ class AristaSdnConnector(SdnConnectorBase): resp = self.client.api.update_configlet( configlet['config'], configlet['data']['key'], - configlet['data']['name']) + configlet['data']['name'], + wait_task_ids=True) elif to_create: operation = 'create' resp = self.client.api.add_configlet( @@ -1041,7 +1067,7 @@ class AristaSdnConnector(SdnConnectorBase): return [changed, data] def __get_configletsDevices(self, configlets): - for s in self.s_api: + for s in self.switches: configlet = configlets[s] # Add applied Devices if len(configlet) > 0: @@ -1053,14 +1079,14 @@ class AristaSdnConnector(SdnConnectorBase): def __get_serviceData(self, service_uuid, service_type, vlan_id, conn_info=None): cls_perSw = {} - for s in self.s_api: + for s in self.switches: cls_perSw[s] = [] if not conn_info: srv_cls = self.__get_serviceConfigLets(service_uuid, service_type, vlan_id) self.__get_configletsDevices(srv_cls) - for s in self.s_api: + for s in self.switches: cl = srv_cls[s] if len(cl) > 0: for dev in cl['devices']: @@ -1087,7 +1113,7 @@ class AristaSdnConnector(SdnConnectorBase): http_code=500) self.__get_Connection() - if conn_info is None: + if conn_info == None: raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid), http_code=500) c_info = None @@ -1097,11 +1123,11 @@ class AristaSdnConnector(SdnConnectorBase): c_info) allLeafConfigured = {} allLeafModified = {} - for s in self.s_api: + for s in self.switches: allLeafConfigured[s] = True allLeafModified[s] = True found_in_cvp = False - for s in self.s_api: + for s in self.switches: if cls_perSw[s]: found_in_cvp = True if found_in_cvp: @@ -1220,7 +1246,7 @@ class AristaSdnConnector(SdnConnectorBase): raise SdnConnectorError(message='Unable to perform operation, missing or empty connection information', http_code=500) - if connection_points is None: + if connection_points == None: return None self.__get_Connection() @@ -1334,7 +1360,7 @@ class AristaSdnConnector(SdnConnectorBase): connectivity service """ srv_cls = {} - for s in self.s_api: + for s in self.switches: srv_cls[s] = [] found_in_cvp = False name = (self.__OSM_PREFIX + @@ -1416,11 +1442,11 @@ class AristaSdnConnector(SdnConnectorBase): invoking the version retrival as test """ try: - if self.client is None: + if self.client == None: self.client = self.__connect() self.client.api.get_cvp_info() except (CvpSessionLogOutError, RequestException) as e: - self.logger.debug("Connection error '{}'. Reconnencting".format(e)) + self.logger.debug("Connection error '{}'. Reconnecting".format(e)) self.client = self.__connect() self.client.api.get_cvp_info() @@ -1438,12 +1464,13 @@ class AristaSdnConnector(SdnConnectorBase): else: port = 443 - client.connect([host], + client.connect([host], self.__user, self.__passwd, protocol=protocol or "https", port=port, connect_timeout=2) + client.api = CvpApi(client, request_timeout=self.__API_REQUEST_TOUT) self.taskC = AristaCVPTask(client.api) return client @@ -1477,6 +1504,79 @@ class AristaSdnConnector(SdnConnectorBase): for device in self.cvp_inventory: self.allDeviceFacts.append(device) + def __get_tags(self, name, value): + if not self.cvp_tags: + self.cvp_tags = [] + url = '/api/v1/rest/analytics/tags/labels/devices/{}/value/{}/elements'.format(name, value) + self.logger.debug('get_tags: URL {}'.format(url)) + data = self.client.get(url, timeout=self.__API_REQUEST_TOUT) + for dev in data['notifications']: + for elem in dev['updates']: + self.cvp_tags.append(elem) + self.logger.debug('Available devices with tag_name {} - value {}: {} '.format(name, value, self.cvp_tags)) + + def __get_interface_ip(self, device_id, interface): + url = '/api/v1/rest/{}/Sysdb/ip/config/ipIntfConfig/{}/'.format(device_id, interface) + self.logger.debug('get_interface_ip: URL {}'.format(url)) + try: + data = self.client.get(url, timeout=self.__API_REQUEST_TOUT) + return data['notifications'][0]['updates']['addrWithMask']['value'].split('/')[0] + except Exception: + raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data)) + + def __get_device_ASN(self, device_id): + url = '/api/v1/rest/{}/Sysdb/routing/bgp/config/'.format(device_id) + self.logger.debug('get_device_ASN: URL {}'.format(url)) + try: + data = self.client.get(url, timeout=self.__API_REQUEST_TOUT) + return data['notifications'][0]['updates']['asNumber']['value']['value']['int'] + except Exception: + raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data)) + + def __get_peer_MLAG(self, device_id): + peer = None + url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(device_id) + self.logger.debug('get_MLAG_status: URL {}'.format(url)) + try: + data = self.client.get(url, timeout=self.__API_REQUEST_TOUT) + if data['notifications']: + found = False + for notification in data['notifications']: + for update in notification['updates']: + if update == 'systemId': + mlagSystemId = notification['updates'][update]['value'] + found = True + break + if found: + break + # search the MLAG System Id + if found: + for s in self.switches: + if self.switches[s]['serialNumber'] == device_id: + continue + url = '/api/v1/rest/{}/Sysdb/mlag/status/'.format(self.switches[s]['serialNumber']) + self.logger.debug('Searching for MLAG system id {} in switch {}'.format(mlagSystemId, s)) + data = self.client.get(url, timeout=self.__API_REQUEST_TOUT) + found = False + for notification in data['notifications']: + for update in notification['updates']: + if update == 'systemId': + if mlagSystemId == notification['updates'][update]['value']: + peer = s + found = True + break + if found: + break + if found: + break + if peer == None: + self.logger.error('No Peer device found for device {} with MLAG address {}'.format(device_id, mlagSystemId)) + else: + self.logger.debug('Peer MLAG for device {} - value {}'.format(device_id, peer)) + return peer + except Exception: + raise SdnConnectorError("Invalid response from url {}: data {}".format(url, data)) + def is_valid_destination(self, url): """ Check that the provided WIM URL is correct """ @@ -1510,3 +1610,12 @@ class AristaSdnConnector(SdnConnectorBase): except socket.error: # not a valid address return False return True + + def delete_keys_from_dict(self, dict_del, lst_keys): + if dict_del == None: + return dict_del + dict_copy = {k: v for k, v in dict_del.items() if k not in lst_keys} + for k, v in dict_copy.items(): + if isinstance(v, dict): + dict_copy[k] = self.delete_keys_from_dict(v, lst_keys) + return dict_copy