X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=RO-plugin%2Fosm_ro_plugin%2Fopenflow_conn.py;h=afc0b8210ca0d960c42f736c708c206fc5f8d2e7;hb=7b521f73dd996a279f23b2f512cd89a42c1c79f6;hp=f46c6cfc7eabc449110b8c94e950894ecaa3d98c;hpb=e493e9b91720e5116e00b4c06cf66c767bccce2f;p=osm%2FRO.git diff --git a/RO-plugin/osm_ro_plugin/openflow_conn.py b/RO-plugin/osm_ro_plugin/openflow_conn.py index f46c6cfc..afc0b821 100644 --- a/RO-plugin/osm_ro_plugin/openflow_conn.py +++ b/RO-plugin/osm_ro_plugin/openflow_conn.py @@ -15,11 +15,14 @@ # under the License. # ## -import logging + from http import HTTPStatus -from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError +import logging from uuid import uuid4 +from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError + + """ Implement an Abstract class 'OpenflowConn' and an engine 'SdnConnectorOpenFlow' used for base class for SDN plugings that implements a pro-active opeflow rules. @@ -31,6 +34,7 @@ __date__ = "2019-11-11" class OpenflowConnException(Exception): """Common and base class Exception for all vimconnector exceptions""" + def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value): Exception.__init__(self, message) self.http_code = http_code @@ -38,42 +42,49 @@ class OpenflowConnException(Exception): class OpenflowConnConnectionException(OpenflowConnException): """Connectivity error with the VIM""" + def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnUnexpectedResponse(OpenflowConnException): """Get an wrong response from VIM""" + def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnAuthException(OpenflowConnException): """Invalid credentials or authorization to perform this action over the VIM""" + def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnNotFoundException(OpenflowConnException): """The item is not found at VIM""" + def __init__(self, message, http_code=HTTPStatus.NOT_FOUND.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnConflictException(OpenflowConnException): """There is a conflict, e.g. more item found than one""" + def __init__(self, message, http_code=HTTPStatus.CONFLICT.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnNotSupportedException(OpenflowConnException): """The request is not supported by connector""" + def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value): OpenflowConnException.__init__(self, message, http_code) class OpenflowConnNotImplemented(OpenflowConnException): """The method is not implemented by the connected""" + def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED.value): OpenflowConnException.__init__(self, message, http_code) @@ -82,14 +93,15 @@ class OpenflowConn: """ Openflow controller connector abstract implementeation. """ + def __init__(self, params): self.name = "openflow_conector" self.pp2ofi = {} # From Physical Port to OpenFlow Index self.ofi2pp = {} # From OpenFlow Index to Physical Port - self.logger = logging.getLogger('ro.sdn.openflow_conn') + self.logger = logging.getLogger("ro.sdn.openflow_conn") def get_of_switches(self): - """" + """ Obtain a a list of switches or DPID detected by this controller :return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails """ @@ -146,7 +158,7 @@ class OpenflowConn: raise OpenflowConnNotImplemented("Should have implemented this") def clear_all_flows(self): - """" + """ Delete all existing rules :return: None if ok, text_error if fails """ @@ -157,13 +169,24 @@ class SdnConnectorOpenFlow(SdnConnectorBase): """ This class is the base engine of SDN plugins base on openflow rules """ - flow_fields = ('priority', 'vlan', 'ingress_port', 'actions', 'dst_mac', 'src_mac', 'net_id') + + flow_fields = ( + "priority", + "vlan", + "ingress_port", + "actions", + "dst_mac", + "src_mac", + "net_id", + ) def __init__(self, wim, wim_account, config=None, logger=None, of_connector=None): - self.logger = logger or logging.getLogger('ro.sdn.openflow_conn') + self.logger = logger or logging.getLogger("ro.sdn.openflow_conn") self.of_connector = of_connector config = config or {} - self.of_controller_nets_with_same_vlan = config.get("of_controller_nets_with_same_vlan", False) + self.of_controller_nets_with_same_vlan = config.get( + "of_controller_nets_with_same_vlan", False + ) def check_credentials(self): try: @@ -182,16 +205,23 @@ class SdnConnectorOpenFlow(SdnConnectorBase): def create_connectivity_service(self, service_type, connection_points, **kwargs): net_id = str(uuid4()) ports = [] + for cp in connection_points: port = { "uuid": cp["service_endpoint_id"], "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"), "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"), - "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"), + "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get( + "switch_port" + ), } ports.append(port) + try: - created_items = self._set_openflow_rules(service_type, net_id, ports, created_items=None) + created_items = self._set_openflow_rules( + service_type, net_id, ports, created_items=None + ) + return net_id, created_items except (SdnConnectorError, OpenflowConnException) as e: raise SdnConnectorError(e, http_code=e.http_code) @@ -200,24 +230,36 @@ class SdnConnectorOpenFlow(SdnConnectorBase): try: service_type = "ELAN" ports = [] - self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info) + self._set_openflow_rules( + service_type, service_uuid, ports, created_items=conn_info + ) + return None except (SdnConnectorError, OpenflowConnException) as e: raise SdnConnectorError(e, http_code=e.http_code) - def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs): + def edit_connectivity_service( + self, service_uuid, conn_info=None, connection_points=None, **kwargs + ): ports = [] for cp in connection_points: port = { "uuid": cp["service_endpoint_id"], "vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"), "mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"), - "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"), + "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get( + "switch_port" + ), } ports.append(port) + service_type = "ELAN" # TODO. Store at conn_info for later use + try: - created_items = self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info) + created_items = self._set_openflow_rules( + service_type, service_uuid, ports, created_items=conn_info + ) + return created_items except (SdnConnectorError, OpenflowConnException) as e: raise SdnConnectorError(e, http_code=e.http_code) @@ -234,8 +276,13 @@ class SdnConnectorOpenFlow(SdnConnectorBase): def _set_openflow_rules(self, net_type, net_id, ports, created_items=None): ifaces_nb = len(ports) + if not created_items: - created_items = {"status": None, "error_msg": None, "installed_rules_ids": []} + created_items = { + "status": None, + "error_msg": None, + "installed_rules_ids": [], + } rules_to_delete = created_items.get("installed_rules_ids") or [] new_installed_rules_ids = [] error_list = [] @@ -244,22 +291,31 @@ class SdnConnectorOpenFlow(SdnConnectorBase): step = "Checking ports and network type compatibility" if ifaces_nb < 2: pass - elif net_type == 'ELINE': + elif net_type == "ELINE": if ifaces_nb > 2: - raise SdnConnectorError("'ELINE' type network cannot connect {} interfaces, only 2".format( - ifaces_nb)) - elif net_type == 'ELAN': + raise SdnConnectorError( + "'ELINE' type network cannot connect {} interfaces, only 2".format( + ifaces_nb + ) + ) + elif net_type == "ELAN": if ifaces_nb > 2 and self.of_controller_nets_with_same_vlan: # check all ports are VLAN (tagged) or none vlan_tags = [] + for port in ports: if port["vlan"] not in vlan_tags: vlan_tags.append(port["vlan"]) + if len(vlan_tags) > 1: - raise SdnConnectorError("This pluging cannot connect ports with diferent VLAN tags when flag " - "'of_controller_nets_with_same_vlan' is active") + raise SdnConnectorError( + "This pluging cannot connect ports with diferent VLAN tags when flag " + "'of_controller_nets_with_same_vlan' is active" + ) else: - raise SdnConnectorError('Only ELINE or ELAN network types are supported for openflow') + raise SdnConnectorError( + "Only ELINE or ELAN network types are supported for openflow" + ) # Get the existing flows at openflow controller step = "Getting installed openflow rules" @@ -274,16 +330,20 @@ class SdnConnectorOpenFlow(SdnConnectorBase): for flow in new_flows: # 1 check if an equal flow is already present index = self._check_flow_already_present(flow, existing_flows) + if index >= 0: flow_id = existing_flows[index]["name"] self.logger.debug("Skipping already present flow %s", str(flow)) else: # 2 look for a non used name flow_name = flow["net_id"] + "." + str(name_index) + while flow_name in existing_flows_ids: name_index += 1 flow_name = flow["net_id"] + "." + str(name_index) - flow['name'] = flow_name + + flow["name"] = flow_name + # 3 insert at openflow try: self.of_connector.new_flow(flow) @@ -291,8 +351,11 @@ class SdnConnectorOpenFlow(SdnConnectorBase): existing_flows_ids.append(flow_id) except OpenflowConnException as e: flow_id = None - error_list.append("Cannot create rule for ingress_port={}, dst_mac={}: {}" - .format(flow["ingress_port"], flow["dst_mac"], e)) + error_list.append( + "Cannot create rule for ingress_port={}, dst_mac={}: {}".format( + flow["ingress_port"], flow["dst_mac"], e + ) + ) # 4 insert at database if flow_id: @@ -306,18 +369,21 @@ class SdnConnectorOpenFlow(SdnConnectorBase): try: self.of_connector.del_flow(flow_id) except OpenflowConnNotFoundException: - pass + self.logger.exception("OpenflowConnNotFoundException occured.") except OpenflowConnException as e: error_text = "Cannot remove rule '{}': {}".format(flow_id, e) error_list.append(error_text) self.logger.error(error_text) + created_items["installed_rules_ids"] = new_installed_rules_ids + if error_list: created_items["error_msg"] = ";".join(error_list)[:1000] created_items["error_msg"] = "ERROR" else: created_items["error_msg"] = None created_items["status"] = "ACTIVE" + return created_items except (SdnConnectorError, OpenflowConnException) as e: raise SdnConnectorError("Error while {}: {}".format(step, e)) from e @@ -334,54 +400,69 @@ class SdnConnectorOpenFlow(SdnConnectorBase): # Check switch_port information is right for port in ports: nb_ports += 1 - if str(port['switch_port']) not in self.of_connector.pp2ofi: - raise SdnConnectorError("switch port name '{}' is not valid for the openflow controller". - format(port['switch_port'])) + + if str(port["switch_port"]) not in self.of_connector.pp2ofi: + raise SdnConnectorError( + "switch port name '{}' is not valid for the openflow controller".format( + port["switch_port"] + ) + ) + priority = 1000 # 1100 for src_port in ports: # if src_port.get("groups") - vlan_in = src_port['vlan'] + vlan_in = src_port["vlan"] # BROADCAST: - broadcast_key = src_port['uuid'] + "." + str(vlan_in) + broadcast_key = src_port["uuid"] + "." + str(vlan_in) if broadcast_key in new_broadcast_flows: flow_broadcast = new_broadcast_flows[broadcast_key] else: - flow_broadcast = {'priority': priority, - 'net_id': net_id, - 'dst_mac': 'ff:ff:ff:ff:ff:ff', - "ingress_port": str(src_port['switch_port']), - 'vlan_id': vlan_in, - 'actions': [] - } + flow_broadcast = { + "priority": priority, + "net_id": net_id, + "dst_mac": "ff:ff:ff:ff:ff:ff", + "ingress_port": str(src_port["switch_port"]), + "vlan_id": vlan_in, + "actions": [], + } new_broadcast_flows[broadcast_key] = flow_broadcast + if vlan_in is not None: - flow_broadcast['vlan_id'] = str(vlan_in) + flow_broadcast["vlan_id"] = str(vlan_in) for dst_port in ports: - vlan_out = dst_port['vlan'] - if src_port['switch_port'] == dst_port['switch_port'] and vlan_in == vlan_out: + vlan_out = dst_port["vlan"] + + if ( + src_port["switch_port"] == dst_port["switch_port"] + and vlan_in == vlan_out + ): continue + flow = { "priority": priority, - 'net_id': net_id, - "ingress_port": str(src_port['switch_port']), - 'vlan_id': vlan_in, - 'actions': [] + "net_id": net_id, + "ingress_port": str(src_port["switch_port"]), + "vlan_id": vlan_in, + "actions": [], } + # allow that one port have no mac - if dst_port['mac'] is None or nb_ports == 2: # point to point or nets with 2 elements - flow['priority'] = priority - 5 # less priority + # point to point or nets with 2 elements + if dst_port["mac"] is None or nb_ports == 2: + flow["priority"] = priority - 5 # less priority else: - flow['dst_mac'] = str(dst_port['mac']) + flow["dst_mac"] = str(dst_port["mac"]) if vlan_out is None: if vlan_in: - flow['actions'].append(('vlan', None)) + flow["actions"].append(("vlan", None)) else: - flow['actions'].append(('vlan', vlan_out)) - flow['actions'].append(('out', str(dst_port['switch_port']))) + flow["actions"].append(("vlan", vlan_out)) + + flow["actions"].append(("out", str(dst_port["switch_port"]))) if self._check_flow_already_present(flow, new_flows) >= 0: self.logger.debug("Skipping repeated flow '%s'", str(flow)) @@ -390,33 +471,45 @@ class SdnConnectorOpenFlow(SdnConnectorBase): new_flows.append(flow) # BROADCAST: - if nb_ports <= 2: # point to multipoint or nets with more than 2 elements + # point to multipoint or nets with more than 2 elements + if nb_ports <= 2: continue - out = (vlan_out, str(dst_port['switch_port'])) - if out not in flow_broadcast['actions']: - flow_broadcast['actions'].append(out) + + out = (vlan_out, str(dst_port["switch_port"])) + + if out not in flow_broadcast["actions"]: + flow_broadcast["actions"].append(out) # BROADCAST for flow_broadcast in new_broadcast_flows.values(): - if len(flow_broadcast['actions']) == 0: + if len(flow_broadcast["actions"]) == 0: continue # nothing to do, skip - flow_broadcast['actions'].sort() - if 'vlan_id' in flow_broadcast: - previous_vlan = 0 # indicates that a packet contains a vlan, and the vlan + + flow_broadcast["actions"].sort() + + if "vlan_id" in flow_broadcast: + # indicates that a packet contains a vlan, and the vlan + previous_vlan = 0 else: previous_vlan = None + final_actions = [] action_number = 0 - for action in flow_broadcast['actions']: + + for action in flow_broadcast["actions"]: if action[0] != previous_vlan: - final_actions.append(('vlan', action[0])) + final_actions.append(("vlan", action[0])) previous_vlan = action[0] + if self.of_controller_nets_with_same_vlan and action_number: - raise SdnConnectorError("Cannot interconnect different vlan tags in a network when flag " - "'of_controller_nets_with_same_vlan' is True.") + raise SdnConnectorError( + "Cannot interconnect different vlan tags in a network when flag " + "'of_controller_nets_with_same_vlan' is True." + ) + action_number += 1 - final_actions.append(('out', action[1])) - flow_broadcast['actions'] = final_actions + final_actions.append(("out", action[1])) + flow_broadcast["actions"] = final_actions if self._check_flow_already_present(flow_broadcast, new_flows) >= 0: self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast)) @@ -427,39 +520,51 @@ class SdnConnectorOpenFlow(SdnConnectorBase): # UNIFY openflow rules with the same input port and vlan and the same output actions # These flows differ at the dst_mac; and they are unified by not filtering by dst_mac # this can happen if there is only two ports. It is converted to a point to point connection - flow_dict = {} # use as key vlan_id+ingress_port and as value the list of flows matching these values + # use as key vlan_id+ingress_port and as value the list of flows matching these values + flow_dict = {} for flow in new_flows: key = str(flow.get("vlan_id")) + ":" + flow["ingress_port"] + if key in flow_dict: flow_dict[key].append(flow) else: flow_dict[key] = [flow] + new_flows2 = [] + for flow_list in flow_dict.values(): convert2ptp = False + if len(flow_list) >= 2: convert2ptp = True + for f in flow_list: - if f['actions'] != flow_list[0]['actions']: + if f["actions"] != flow_list[0]["actions"]: convert2ptp = False break + if convert2ptp: # add only one unified rule without dst_mac - self.logger.debug("Convert flow rules to NON mac dst_address " + str(flow_list)) - flow_list[0].pop('dst_mac') + self.logger.debug( + "Convert flow rules to NON mac dst_address " + str(flow_list) + ) + flow_list[0].pop("dst_mac") flow_list[0]["priority"] -= 5 new_flows2.append(flow_list[0]) else: # add all the rules new_flows2 += flow_list + return new_flows2 def _check_flow_already_present(self, new_flow, flow_list): - '''check if the same flow is already present in the flow list + """check if the same flow is already present in the flow list The flow is repeated if all the fields, apart from name, are equal - Return the index of matching flow, -1 if not match''' + Return the index of matching flow, -1 if not match + """ for index, flow in enumerate(flow_list): for f in self.flow_fields: if flow.get(f) != new_flow.get(f): break else: return index + return -1