Reformatting RO
[osm/RO.git] / RO-plugin / osm_ro_plugin / openflow_conn.py
index f46c6cf..17351b0 100644 (file)
@@ -31,6 +31,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 +39,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 +90,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 +155,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 +166,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 +202,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 +227,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 +273,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 +288,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 +327,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 +348,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:
@@ -311,13 +371,16 @@ class SdnConnectorOpenFlow(SdnConnectorBase):
                     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 +397,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 +468,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 +517,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