X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=RO-SDN-arista_cloudvision%2Fosm_rosdn_arista_cloudvision%2Fwimconn_arista.py;h=e72a082204c50d6d89d38a779e1689c3fdbb44ed;hb=3f8f2f4707c2a5095fd5cb9cc6602e7e9f33dbd8;hp=37f4c5828a89686efe4c4046c41c0afbf71b35a3;hpb=667d158c0d3ee7b4c176ad0b27ac428c81b0ddbc;p=osm%2FRO.git diff --git a/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py b/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py index 37f4c582..e72a0822 100644 --- a/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py +++ b/RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision/wimconn_arista.py @@ -26,7 +26,7 @@ # # This work has been performed in the context of Arista Telefonica OSM PoC. ## -from osm_ro.wim.sdnconn import SdnConnectorBase, SdnConnectorError +from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError import re import socket # Required by compare function @@ -38,11 +38,10 @@ import difflib import logging import uuid from enum import Enum -from requests import RequestException - +from requests import RequestException, ConnectionError, ConnectTimeout, Timeout from cvprac.cvp_client import CvpClient from cvprac.cvp_api import CvpApi -from cvprac.cvp_client_errors import CvpLoginError, CvpSessionLogOutError, CvpApiError +from cvprac.cvp_client_errors import CvpLoginError, CvpSessionLogOutError, CvpApiError from cvprac import __version__ as cvprac_version from osm_rosdn_arista_cloudvision.aristaConfigLet import AristaSDNConfigLet @@ -50,15 +49,16 @@ from osm_rosdn_arista_cloudvision.aristaTask import AristaCVPTask class SdnError(Enum): - UNREACHABLE = 'Unable to reach the WIM.', + UNREACHABLE = 'Unable to reach the WIM url, connect error.', + TIMEOUT = 'Unable to reach the WIM url, timeout.', VLAN_INCONSISTENT = \ 'VLAN value inconsistent between the connection points', VLAN_NOT_PROVIDED = 'VLAN value not provided', CONNECTION_POINTS_SIZE = \ 'Unexpected number of connection points: 2 expected.', ENCAPSULATION_TYPE = \ - 'Unexpected service_endpoint_encapsulation_type. \ - Only "dotq1" is accepted.', + 'Unexpected service_endpoint_encapsulation_type. ' \ + 'Only "dotq1" is accepted.', BANDWIDTH = 'Unable to get the bandwidth.', STATUS = 'Unable to get the status for the service.', DELETE = 'Unable to delete service.', @@ -103,7 +103,7 @@ class AristaSdnConnector(SdnConnectorBase): __ELINE_num_connection_points = 2 __supported_service_types = ["ELINE", "ELAN"] __supported_encapsulation_types = ["dot1q"] - __WIM_LOGGER = 'openmano.sdnconn.arista' + __WIM_LOGGER = 'ro.sdn.arista' __SERVICE_ENDPOINT_MAPPING = 'service_endpoint_mapping' __ENCAPSULATION_TYPE_PARAM = "service_endpoint_encapsulation_type" __ENCAPSULATION_INFO_PARAM = "service_endpoint_encapsulation_info" @@ -135,7 +135,6 @@ class AristaSdnConnector(SdnConnectorBase): _VLAN_MLAG = "VLAN-MLAG" _VXLAN_MLAG = "VXLAN-MLAG" - def __init__(self, wim, wim_account, config=None, logger=None): """ @@ -145,19 +144,19 @@ class AristaSdnConnector(SdnConnectorBase): :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning: 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed. 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is: - KEY meaning for WIM meaning for SDN assist + KEY meaning for WIM meaning for SDN assist -------- -------- -------- - device_id pop_switch_dpid compute_id - device_interface_id pop_switch_port compute_pci_address - service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id - service_mapping_info wan_service_mapping_info SDN_service_mapping_info - contains extra information if needed. Text in Yaml format - switch_dpid wan_switch_dpid SDN_switch_dpid - switch_port wan_switch_port SDN_switch_port + device_id pop_switch_dpid compute_id + device_interface_id pop_switch_port compute_pci_address + service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id + service_mapping_info wan_service_mapping_info SDN_service_mapping_info + contains extra information if needed. Text in Yaml format + switch_dpid wan_switch_dpid SDN_switch_dpid + switch_port wan_switch_port SDN_switch_port datacenter_id vim_account vim_account id: (internal, do not use) wim_id: (internal, do not use) - :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used. + :param logger (logging.Logger): optional logger object. If none is passed 'ro.sdn.sdnconn' is used. """ self.__regex = re.compile( r'^(?:http|ftp)s?://' # http:// or https:// @@ -189,14 +188,20 @@ class AristaSdnConnector(SdnConnectorBase): try: self.__load_topology() self.__load_switches() + except (ConnectTimeout, Timeout) as ct: + raise SdnConnectorError(message=SdnError.TIMEOUT + " " + str(ct), http_code=408) + except ConnectionError as ce: + raise SdnConnectorError(message=SdnError.UNREACHABLE + " " + str(ce), http_code=404) except SdnConnectorError as sc: raise sc + except CvpLoginError as le: + raise SdnConnectorError(message=le.msg, http_code=500) from le except Exception as e: - raise SdnConnectorError(message="Unable to load switches from CVP", + raise SdnConnectorError(message="Unable to load switches from CVP" + " " + str(e), 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.topology, + self.delete_keys_from_dict(self.switches, ('passwd',)))) self.clC = AristaSDNConfigLet(self.topology) def __load_topology(self): @@ -255,7 +260,7 @@ class AristaSdnConnector(SdnConnectorBase): self.switches[cs].update(cs_content) # Load the rest of the data - if self.client == None: + if self.client is None: self.client = self.__connect() self.__load_inventory() if not self.switches: @@ -348,19 +353,19 @@ class AristaSdnConnector(SdnConnectorBase): for testing the access to CloudVision API """ try: - if self.client == None: + if self.client is None: self.client = self.__connect() result = self.client.api.get_cvp_info() self.logger.debug(result) except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except Exception as ex: self.client = None self.logger.error(str(ex)) - raise SdnConnectorError(message=SdnError.INTERNAL_ERROR, + raise SdnConnectorError(message=SdnError.INTERNAL_ERROR + " " + str(ex), http_code=500) from ex def get_connectivity_service_status(self, service_uuid, conn_info=None): @@ -406,7 +411,7 @@ class AristaSdnConnector(SdnConnectorBase): http_code=500) self.__get_Connection() - if conn_info == None: + if conn_info is None: raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid), http_code=500) @@ -464,12 +469,12 @@ class AristaSdnConnector(SdnConnectorBase): except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except Exception as ex: self.client = None self.logger.error(str(ex), exc_info=True) - raise SdnConnectorError(message=str(ex), + raise SdnConnectorError(message=str(ex) + " " + str(ex), http_code=500) from ex def create_connectivity_service(self, service_type, connection_points, @@ -535,20 +540,20 @@ class AristaSdnConnector(SdnConnectorBase): self.logger.info("Service with uuid {} created.". format(service_uuid)) s_uid, s_connInf = self.__processConnection( - service_uuid, - service_type, - connection_points, - kwargs) + service_uuid, + service_type, + connection_points, + kwargs) try: self.__addMetadata(s_uid, service_type, s_connInf['vlan_id']) - except Exception as e: + except Exception: pass return (s_uid, s_connInf) except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except SdnConnectorError as sde: raise sde @@ -626,8 +631,9 @@ class AristaSdnConnector(SdnConnectorBase): 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) + 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 @@ -686,7 +692,10 @@ class AristaSdnConnector(SdnConnectorBase): for p in self.switches: if self.switches[p]['mlagPeerDevice'] == s: if cls_cp.get(p): - cl_config = str(cl_vlan) + if self.topology == self._VXLAN_MLAG: + cl_config = str(cl_vlan) + str(cl_bgp[s]) + else: + cl_config = str(cl_vlan) else: cl_config = str(cl_vlan) + str(cl_bgp[s]) + str(cls_cp[s]) @@ -737,7 +746,7 @@ class AristaSdnConnector(SdnConnectorBase): if not (cls_perSw.get(s) and cls_perSw[s][0].get('config')): # when there is no configuration, means that there is no interface # in the switch to be connected, so the configLet has to be removed from CloudVision - # after removing the ConfigLet fron the switch if it was already there + # after removing the ConfigLet from the switch if it was already there # get config let name and key cl = cls_perSw[s] @@ -760,9 +769,9 @@ class AristaSdnConnector(SdnConnectorBase): continue cl = cls_perSw[s] res = self.__device_modify( - device_to_update=s, - new_configlets=cl, - delete=toDelete_in_cvp) + device_to_update=s, + new_configlets=cl, + delete=toDelete_in_cvp) if "errorMessage" in str(res): raise Exception(str(res)) self.logger.info("Device {} modify result {}".format(s, res)) @@ -773,10 +782,10 @@ class AristaSdnConnector(SdnConnectorBase): t_id, self.__SEPARATOR) self.client.api.add_note_to_configlet( - cls_perSw[s][0]['key'], - note_msg) + cls_perSw[s][0]['key'], + note_msg) cls_perSw[s][0]['note'] = note_msg - tasks = { t_id : {'workOrderId': t_id} } + 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 @@ -793,7 +802,7 @@ class AristaSdnConnector(SdnConnectorBase): allLeafModified) except Exception as e: self.logger.error("Exception rolling back in updating connection: {}". - format(e), exc_info=True) + format(e), exc_info=True) raise ex def __rollbackConnection(self, @@ -824,7 +833,7 @@ class AristaSdnConnector(SdnConnectorBase): self.__configlet_modify(cls_perSw[s], delete=True) def __exec_task(self, tasks, tout=10): - if self.taskC == None: + if self.taskC is None: self.__connect() data = self.taskC.update_all_tasks(tasks).values() self.taskC.task_action(data, tout, 'executed') @@ -833,15 +842,14 @@ class AristaSdnConnector(SdnConnectorBase): """ Updates the devices (switches) adding or removing the configLet, the tasks Id's associated to the change are returned """ - self.logger.info('Enter in __device_modify delete: {}'.format( - delete)) + self.logger.info('Enter in __device_modify delete: {}'.format(delete)) updated = [] changed = False # Task Ids that have been identified during device actions newTasks = [] if (len(new_configlets) == 0 or - device_to_update == None or + device_to_update is None or len(device_to_update) == 0): data = {'updated': updated, 'tasks': newTasks} return [changed, data] @@ -857,14 +865,14 @@ class AristaSdnConnector(SdnConnectorBase): if try_device['hostname'] not in device_to_update: continue dev_cvp_configlets = self.client.api.get_configlets_by_device_id( - try_device['systemMacAddress']) + try_device['systemMacAddress']) # self.logger.debug(dev_cvp_configlets) try_device['deviceSpecificConfiglets'] = [] for cvp_configlet in dev_cvp_configlets: if int(cvp_configlet['containerCount']) == 0: try_device['deviceSpecificConfiglets'].append( - {'name': cvp_configlet['name'], - 'key': cvp_configlet['key']}) + {'name': cvp_configlet['name'], + 'key': cvp_configlet['key']}) # self.logger.debug(device) device = try_device break @@ -900,42 +908,41 @@ class AristaSdnConnector(SdnConnectorBase): try: if delete and len(cl_toDel) > 0: r = self.client.api.remove_configlets_from_device( - 'OSM', - up_device['device'], - cl_toDel, - create_task=True) + 'OSM', + up_device['device'], + cl_toDel, + create_task=True) dev_action = r self.logger.debug("remove_configlets_from_device {} {}".format(dev_action, cl_toDel)) elif len(cl_toAdd) > 0: r = self.client.api.apply_configlets_to_device( - 'OSM', - up_device['device'], - cl_toAdd, - create_task=True) + 'OSM', + up_device['device'], + cl_toAdd, + create_task=True) dev_action = r self.logger.debug("apply_configlets_to_device {} {}".format(dev_action, cl_toAdd)) except Exception as error: errorMessage = str(error) msg = "errorMessage: Device {} Configlets couldnot be updated: {}".format( - up_device['hostname'], errorMessage) + up_device['hostname'], errorMessage) raise SdnConnectorError(msg) from error else: if "errorMessage" in str(dev_action): m = "Device {} Configlets update fail: {}".format( - up_device['name'], dev_action['errorMessage']) + up_device['name'], dev_action['errorMessage']) raise SdnConnectorError(m) 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'])) + raise SdnConnectorError("No taskIds found: Device {} Configlets could not be updated".format( + up_device['hostname'])) for taskId in dev_action['data']['taskIds']: - updated.append({up_device['hostname']: - "Configlets-{}".format( - taskId)}) + updated.append({ + up_device['hostname']: "Configlets-{}".format(taskId)}) newTasks.append(taskId) else: updated.append({up_device['hostname']: @@ -951,7 +958,7 @@ class AristaSdnConnector(SdnConnectorBase): :return: data: dict of module actions and taskIDs ''' self.logger.info('Enter in __configlet_modify delete:{}'.format( - delete)) + delete)) # Compare configlets against cvp_facts-configlets changed = False @@ -1006,27 +1013,27 @@ class AristaSdnConnector(SdnConnectorBase): if to_delete: operation = 'delete' resp = self.client.api.delete_configlet( - configlet['data']['name'], - configlet['data']['key']) + configlet['data']['name'], + configlet['data']['key']) elif to_update: operation = 'update' resp = self.client.api.update_configlet( - configlet['config'], - configlet['data']['key'], - configlet['data']['name'], - wait_task_ids=True) + configlet['config'], + configlet['data']['key'], + configlet['data']['name'], + wait_task_ids=True) elif to_create: operation = 'create' resp = self.client.api.add_configlet( - configlet['name'], - configlet['config']) + configlet['name'], + configlet['config']) else: operation = 'checked' resp = 'checked' except Exception as error: errorMessage = str(error).split(':')[-1] message = "Configlet {} cannot be {}: {}".format( - cl['name'], operation, errorMessage) + cl['name'], operation, errorMessage) if to_delete: deleted.append({configlet['name']: message}) elif to_update: @@ -1039,7 +1046,7 @@ class AristaSdnConnector(SdnConnectorBase): else: if "error" in str(resp).lower(): message = "Configlet {} cannot be deleted: {}".format( - cl['name'], resp['errorMessage']) + cl['name'], resp['errorMessage']) if to_delete: deleted.append({configlet['name']: message}) elif to_update: @@ -1073,7 +1080,7 @@ class AristaSdnConnector(SdnConnectorBase): if len(configlet) > 0: configlet['devices'] = [] applied_devices = self.client.api.get_applied_devices( - configlet['name']) + configlet['name']) for device in applied_devices['data']: configlet['devices'].append(device['hostName']) @@ -1113,7 +1120,7 @@ class AristaSdnConnector(SdnConnectorBase): http_code=500) self.__get_Connection() - if conn_info == None: + if conn_info is None: raise SdnConnectorError(message='No connection information for service UUID {}'.format(service_uuid), http_code=500) c_info = None @@ -1143,7 +1150,7 @@ class AristaSdnConnector(SdnConnectorBase): except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except SdnConnectorError as sde: raise sde @@ -1152,7 +1159,7 @@ class AristaSdnConnector(SdnConnectorBase): self.logger.error(ex) if self.raiseException: raise ex - raise SdnConnectorError(message=SdnError.INTERNAL_ERROR, + raise SdnConnectorError(message=SdnError.INTERNAL_ERROR + " " + str(ex), http_code=500) from ex def __addMetadata(self, service_uuid, service_type, vlan_id): @@ -1246,7 +1253,7 @@ class AristaSdnConnector(SdnConnectorBase): raise SdnConnectorError(message='Unable to perform operation, missing or empty connection information', http_code=500) - if connection_points == None: + if connection_points is None: return None self.__get_Connection() @@ -1261,17 +1268,17 @@ class AristaSdnConnector(SdnConnectorBase): kwargs=kwargs) s_uid, s_connInf = self.__processConnection( - service_uuid, - service_type, - connection_points, - kwargs) + service_uuid, + service_type, + connection_points, + kwargs) self.logger.info("Service with uuid {} configuration updated". format(s_uid)) return s_connInf except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except SdnConnectorError as sde: raise sde @@ -1281,8 +1288,8 @@ class AristaSdnConnector(SdnConnectorBase): # TODO check if there are pending task, and cancel them before restoring self.__updateConnection(cls_currentPerSw) except Exception as e: - self.logger.error("Unable to restore configuration in service {} after an error in the configuration updated: {}". - format(service_uuid, str(e))) + self.logger.error("Unable to restore configuration in service {} after an error in the configuration" + " updated: {}".format(service_uuid, str(e))) if self.raiseException: raise ex raise SdnConnectorError(message=str(ex), @@ -1307,7 +1314,7 @@ class AristaSdnConnector(SdnConnectorBase): except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except SdnConnectorError as sde: raise sde @@ -1316,7 +1323,7 @@ class AristaSdnConnector(SdnConnectorBase): self.logger.error(ex) if self.raiseException: raise ex - raise SdnConnectorError(message=SdnError.INTERNAL_ERROR, + raise SdnConnectorError(message=SdnError.INTERNAL_ERROR + " " + str(ex), http_code=500) from ex def get_all_active_connectivity_services(self): @@ -1342,7 +1349,7 @@ class AristaSdnConnector(SdnConnectorBase): except CvpLoginError as e: self.logger.info(str(e)) self.client = None - raise SdnConnectorError(message=SdnError.UNAUTHORIZED, + raise SdnConnectorError(message=SdnError.UNAUTHORIZED + " " + str(e), http_code=401) from e except SdnConnectorError as sde: raise sde @@ -1442,7 +1449,7 @@ class AristaSdnConnector(SdnConnectorBase): invoking the version retrival as test """ try: - if self.client == None: + if self.client is None: self.client = self.__connect() self.client.api.get_cvp_info() except (CvpSessionLogOutError, RequestException) as e: @@ -1482,11 +1489,11 @@ class AristaSdnConnector(SdnConnectorBase): Score - 1.0 if the sequences are identical, and 0.0 if they have nothing in common. unified diff list - Code Meaning - '- ' line unique to sequence 1 - '+ ' line unique to sequence 2 - ' ' line common to both sequences - '? ' line not present in either input sequence + Code Meaning + '- ' line unique to sequence 1 + '+ ' line unique to sequence 2 + ' ' line common to both sequences + '? ' line not present in either input sequence """ fromlines = fromText.splitlines(1) tolines = toText.splitlines(1) @@ -1518,20 +1525,33 @@ class AristaSdnConnector(SdnConnectorBase): 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)) + data = None 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)) + if data['notifications']: + for notification in data['notifications']: + for update in notification['updates']: + if update == 'addrWithMask': + return notification['updates'][update]['value'] + except Exception as e: + raise SdnConnectorError("Invalid response from url {}: data {} - {}".format(url, data, str(e))) + raise SdnConnectorError("Unable to get ip for interface {} in device {}, data {}". + format(interface, device_id, 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)) + data = None 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)) + if data['notifications']: + for notification in data['notifications']: + for update in notification['updates']: + if update == 'asNumber': + return notification['updates'][update]['value']['value']['int'] + except Exception as e: + raise SdnConnectorError("Invalid response from url {}: data {} - {}".format(url, data, str(e))) + raise SdnConnectorError("Unable to get AS in device {}, data {}".format(device_id, data)) def __get_peer_MLAG(self, device_id): peer = None @@ -1569,8 +1589,9 @@ class AristaSdnConnector(SdnConnectorBase): break if found: break - if peer == None: - self.logger.error('No Peer device found for device {} with MLAG address {}'.format(device_id, mlagSystemId)) + if peer is 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 @@ -1612,7 +1633,7 @@ class AristaSdnConnector(SdnConnectorBase): return True def delete_keys_from_dict(self, dict_del, lst_keys): - if dict_del == None: + if dict_del is 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():