bug 1079 capture SDN plugin unhandled exceptions
[osm/RO.git] / RO / osm_ro / vim_thread.py
index 06e2592..3039bab 100644 (file)
@@ -126,7 +126,7 @@ class vim_thread(threading.Thread):
         self.error_status = None
         self.wim_account_id = wim_account_id
         self.datacenter_tenant_id = datacenter_tenant_id
-        self.port_mapping = None
+        self.port_mappings = None
         if self.wim_account_id:
             self.target_k = "wim_account_id"
             self.target_v = self.wim_account_id
@@ -293,9 +293,6 @@ class vim_thread(threading.Thread):
                                                        })
                         continue
 
-                    # task of creation must be the first in the list of related_task
-                    assert(related_tasks[0]["action"] in ("CREATE", "FIND"))
-
                     task["params"] = None
                     if task["extra"]:
                         extra = yaml.load(task["extra"], Loader=yaml.Loader)
@@ -323,6 +320,8 @@ class vim_thread(threading.Thread):
                 copy_to["sdn_net_id"] = copy_from["sdn_net_id"]
             if copy_from.get("interfaces"):
                 copy_to["interfaces"] = copy_from["interfaces"]
+            if copy_from.get("sdn-ports"):
+                copy_to["sdn-ports"] = copy_from["sdn-ports"]
             if copy_from.get("created_items"):
                 if not copy_to.get("created_items"):
                     copy_to["created_items"] = {}
@@ -330,11 +329,13 @@ class vim_thread(threading.Thread):
 
         task_create = None
         dependency_task = None
-        deletion_needed = False
+        deletion_needed = task["extra"].get("created", False)
         if task["status"] == "FAILED":
             return   # TODO need to be retry??
         try:
-            # get all related tasks
+            # get all related tasks. task of creation must be the first in the list of related_task,
+            # unless the deletion fails and it is pendingit fails
+            # TODO this should be removed, passing related_tasks
             related_tasks = self.db.get_rows(FROM="vim_wim_actions",
                                              WHERE={self.target_k: self.target_v,
                                                     "status": ['SCHEDULED', 'BUILD', 'DONE', 'FAILED'],
@@ -358,13 +359,14 @@ class vim_thread(threading.Thread):
                     break
 
             # mark task_create as FINISHED
-            self.db.update_rows("vim_wim_actions", UPDATE={"status": "FINISHED"},
-                                WHERE={self.target_k: self.target_v,
-                                       "instance_action_id": task_create["instance_action_id"],
-                                       "task_index": task_create["task_index"]
-                                       })
+            if task_create:
+                self.db.update_rows("vim_wim_actions", UPDATE={"status": "FINISHED"},
+                                    WHERE={self.target_k: self.target_v,
+                                           "instance_action_id": task_create["instance_action_id"],
+                                           "task_index": task_create["task_index"]
+                                           })
             if not deletion_needed:
-                return
+                return False
             elif dependency_task:
                 # move create information  from task_create to relate_task
                 extra_new_created = yaml.load(dependency_task["extra"], Loader=yaml.Loader) or {}
@@ -380,10 +382,19 @@ class vim_thread(threading.Thread):
                                            "task_index": dependency_task["task_index"]
                                            })
                 return False
-            else:
+            elif task_create:
                 task["vim_id"] = task_create["vim_id"]
                 copy_extra_created(copy_to=task["extra"], copy_from=task_create["extra"])
+                # Ensure this task extra information is stored at database
+                self.db.update_rows("vim_wim_actions",
+                                    UPDATE={"extra":  yaml.safe_dump(task["extra"], default_flow_style=True,
+                                                                     width=256)},
+                                    WHERE={self.target_k: self.target_v,
+                                           "instance_action_id": task["instance_action_id"],
+                                           "task_index": task["task_index"],
+                                           })
                 return True
+            return deletion_needed
 
         except Exception as e:
             self.logger.critical("Unexpected exception at _delete_task: " + str(e), exc_info=True)
@@ -548,6 +559,7 @@ class vim_thread(threading.Thread):
         old_task_status = task["status"]
         create_or_find = False   # if as result of processing this task something is created or found
         next_refresh = 0
+        task_id = task["instance_action_id"] + "." + str(task["task_index"])
 
         try:
             if task["status"] == "SCHEDULED":
@@ -706,16 +718,17 @@ class vim_thread(threading.Thread):
             else:
                 raise vimconn.vimconnException(self.name + "unknown task item {}".format(task["item"]))
                 # TODO
-        except VimThreadException as e:
+        except Exception as e:
+            if not isinstance(e, VimThreadException):
+                self.logger.error("Error executing task={}: {}".format(task_id, e), exc_info=True)
             task["error_msg"] = str(e)
             task["status"] = "FAILED"
             database_update = {"status": "VIM_ERROR", "error_msg": task["error_msg"]}
-            if task["item"] == 'instance_vms':
-                database_update["vim_vm_id"] = None
-            elif task["item"] == 'instance_nets':
-                database_update["vim_net_id"] = None
+            if task["item"] == 'instance_vms':
+                database_update["vim_vm_id"] = None
+            elif task["item"] == 'instance_nets':
+                database_update["vim_net_id"] = None
 
-        task_id = task["instance_action_id"] + "." + str(task["task_index"])
         self.logger.debug("task={} item={} action={} result={}:'{}' params={}".format(
             task_id, task["item"], task["action"], task["status"],
             task["vim_id"] if task["status"] == "DONE" else task.get("error_msg"), task["params"]))
@@ -1127,19 +1140,21 @@ class vim_thread(threading.Thread):
             # look for ports
             sdn_ports = []
             pending_ports = 0
+            vlan_used = None
 
             ports = self.db.get_rows(FROM='instance_interfaces', WHERE={'instance_wim_net_id': task["item_id"]})
             sdn_need_update = False
             for port in ports:
+                vlan_used = port.get("vlan") or vlan_used
                 # TODO. Do not connect if already done
                 if port.get("compute_node") and port.get("pci"):
-                    for map in self.port_mappings:
-                        if map.get("device_id") == port["compute_node"] and \
-                                map.get("device_interface_id") == port["pci"]:
+                    for pmap in self.port_mappings:
+                        if pmap.get("device_id") == port["compute_node"] and \
+                                pmap.get("device_interface_id") == port["pci"]:
                             break
                     else:
                         if self.sdnconn_config.get("mapping_not_needed"):
-                            map = {
+                            pmap = {
                                 "service_endpoint_id": "{}:{}".format(port["compute_node"], port["pci"]),
                                 "service_endpoint_encapsulation_info": {
                                     "vlan": port["vlan"],
@@ -1149,25 +1164,25 @@ class vim_thread(threading.Thread):
                                 }
                             }
                         else:
-                            map = None
+                            pmap = None
                             error_list.append("Port mapping not found for compute_node={} pci={}".format(
                                 port["compute_node"], port["pci"]))
 
-                    if map:
-                        if port["uuid"] not in connected_ports or port["modified_at"] > last_update:
+                    if pmap:
+                        if port["modified_at"] > last_update:
                             sdn_need_update = True
                         new_connected_ports.append(port["uuid"])
                         sdn_ports.append({
-                            "service_endpoint_id": map["service_endpoint_id"],
+                            "service_endpoint_id": pmap["service_endpoint_id"],
                             "service_endpoint_encapsulation_type": "dot1q" if port["model"] == "SR-IOV" else None,
                             "service_endpoint_encapsulation_info": {
                                 "vlan": port["vlan"],
                                 "mac": port["mac_address"],
-                                "device_id": map.get("device_id"),
-                                "device_interface_id": map.get("device_interface_id"),
-                                "switch_dpid": map.get("switch_dpid"),
-                                "switch_port": map.get("switch_port"),
-                                "service_mapping_info": map.get("service_mapping_info"),
+                                "device_id": pmap.get("device_id"),
+                                "device_interface_id": pmap.get("device_interface_id"),
+                                "switch_dpid": pmap.get("switch_dpid"),
+                                "switch_port": pmap.get("switch_port"),
+                                "service_mapping_info": pmap.get("service_mapping_info"),
                             }
                         })
 
@@ -1176,24 +1191,44 @@ class vim_thread(threading.Thread):
             if pending_ports:
                 error_list.append("Waiting for getting interfaces location from VIM. Obtained '{}' of {}"
                                   .format(len(ports)-pending_ports, len(ports)))
+
+            # connect external ports
+            for index, external_port in enumerate(task["extra"].get("sdn-ports") or ()):
+                external_port_id = external_port.get("service_endpoint_id") or str(index)
+                sdn_ports.append({
+                    "service_endpoint_id": external_port_id,
+                    "service_endpoint_encapsulation_type": external_port.get("service_endpoint_encapsulation_type",
+                                                                             "dot1q"),
+                    "service_endpoint_encapsulation_info": {
+                        "vlan": external_port.get("vlan") or vlan_used,
+                        "mac": external_port.get("mac_address"),
+                        "device_id": external_port.get("device_id"),
+                        "device_interface_id": external_port.get("device_interface_id"),
+                        "switch_dpid": external_port.get("switch_dpid") or external_port.get("switch_id"),
+                        "switch_port": external_port.get("switch_port"),
+                        "service_mapping_info": external_port.get("service_mapping_info"),
+                    }})
+                new_connected_ports.append(external_port_id)
+
             # if there are more ports to connect or they have been modified, call create/update
-            if sdn_need_update and len(sdn_ports) >= 2:
-                if not wimconn_net_id:
-                    if params[0] == "data":
-                        net_type = "ELAN"
-                    elif params[0] == "ptp":
-                        net_type = "ELINE"
-                    else:
-                        net_type = "L3"
+            try:
+                if (set(connected_ports) != set(new_connected_ports) or sdn_need_update) and len(sdn_ports) >= 2:
+                    last_update = time.time()
+                    if not wimconn_net_id:
+                        if params[0] == "data":
+                            net_type = "ELAN"
+                        elif params[0] == "ptp":
+                            net_type = "ELINE"
+                        else:
+                            net_type = "L3"
 
-                    wimconn_net_id, created_items = self.sdnconnector.create_connectivity_service(net_type, sdn_ports)
-                else:
-                    created_items = self.sdnconnector.edit_connectivity_service(wimconn_net_id, conn_info=created_items,
-                                                                                connection_points=sdn_ports)
-                last_update = time.time()
-                connected_ports = new_connected_ports
-            elif wimconn_net_id:
-                try:
+                        wimconn_net_id, created_items = self.sdnconnector.create_connectivity_service(
+                            net_type, sdn_ports)
+                    else:
+                        created_items = self.sdnconnector.edit_connectivity_service(
+                            wimconn_net_id, conn_info=created_items, connection_points=sdn_ports)
+                    connected_ports = new_connected_ports
+                elif wimconn_net_id:
                     wim_status_dict = self.sdnconnector.get_connectivity_service_status(wimconn_net_id,
                                                                                         conn_info=created_items)
                     sdn_status = wim_status_dict["sdn_status"]
@@ -1201,8 +1236,8 @@ class vim_thread(threading.Thread):
                         error_list.append(wim_status_dict.get("error_msg"))
                     if wim_status_dict.get("sdn_info"):
                         sdn_info = str(wim_status_dict.get("sdn_info"))
-                except Exception as e:
-                    self._proccess_sdn_exception(e)
+            except Exception as e:
+                self._proccess_sdn_exception(e)
 
             task["status"] = "DONE"
             task["extra"]["vim_info"] = {}
@@ -1224,6 +1259,7 @@ class vim_thread(threading.Thread):
             # task["extra"]["sdn_net_id"] = sdn_net_id
             instance_element_update = {"wim_internal_id": wimconn_net_id, "status": "WIM_ERROR",
                                        "error_msg": task["error_msg"]}
+
         if sdn_info:
             instance_element_update["wim_info"] = sdn_info
         return instance_element_update