bug 331 Ensure neutron port is deleted upon vm creation failed
[osm/RO.git] / osm_ro / vim_thread.py
index 1fba7a6..2c30fb9 100644 (file)
 """"
 This is thread that interacts with a VIM. It processes TASKs sequentially against a single VIM.
 The tasks are stored at database in table vim_actions
-The task content is:
+The task content are (M: stored at memory, D: stored at database):
     MD  instance_action_id:  reference a global action over an instance-scenario: database instance_actions
     MD  task_index:     index number of the task. This with the previous are a key
     MD  datacenter_vim_id:  should contain the uuid of the VIM managed by this thread
     MD  vim_id:     id of the vm,net,etc at VIM
-    MD  action:     CREATE, DELETE, LOOK, TODO: LOOK_CREATE
+    MD  action:     CREATE, DELETE, FIND
     MD  item:       database table name, can be instance_vms, instance_nets, TODO: datacenter_flavors, datacenter_images
     MD  item_id:    uuid of the referenced entry in the preious table
     MD  status:     SCHEDULED,BUILD,DONE,FAILED,SUPERSEDED
     MD  extra:      text with yaml format at database, dict at memory with:
-            params:     list with the params to be sent to the VIM for CREATE or LOOK. For DELETE the vim_id is taken from other related tasks
+            params:     list with the params to be sent to the VIM for CREATE or FIND. For DELETE the vim_id is taken from other related tasks
+            find:       (only for CREATE tasks) if present it should FIND before creating and use if existing. Contains the FIND params
             depends_on: list with the 'task_index'es of tasks that must be completed before. e.g. a vm depends on a net
             sdn_net_id: used for net.
             tries:
@@ -42,6 +43,7 @@ The task content is:
                 iface_id: uuid of intance_interfaces
                 sdn_port_id:
                 sdn_net_id:
+            created_items: dictionary with extra elements created that need to be deleted. e.g. ports, volumes,...
             created:    False if the VIM element is not created by other actions, and it should not be deleted
             vim_status: VIM status of the element. Stored also at database in the instance_XXX
     M   depends:    dict with task_index(from depends_on) to task class
@@ -78,6 +80,10 @@ class VimThreadException(Exception):
     pass
 
 
+class VimThreadExceptionNotFound(VimThreadException):
+    pass
+
+
 class vim_thread(threading.Thread):
     REFRESH_BUILD = 5      # 5 seconds
     REFRESH_ACTIVE = 60    # 1 minute
@@ -520,13 +526,18 @@ class vim_thread(threading.Thread):
                 for to_supersede in self.grouped_tasks.get(action_key, ()):
                     if to_supersede["action"] == "FIND" and to_supersede.get("vim_id"):
                         task["vim_id"] = to_supersede["vim_id"]
-                    if to_supersede["action"] == "CREATE" and to_supersede.get("vim_id"):
+                    if to_supersede["action"] == "CREATE" and to_supersede.get("vim_id") and \
+                            to_supersede["extra"].get("created", True):
                         need_delete_action = True
                         task["vim_id"] = to_supersede["vim_id"]
                         if to_supersede["extra"].get("sdn_vim_id"):
                             task["extra"]["sdn_vim_id"] = to_supersede["extra"]["sdn_vim_id"]
                         if to_supersede["extra"].get("interfaces"):
                             task["extra"]["interfaces"] = to_supersede["extra"]["interfaces"]
+                        if to_supersede["extra"].get("created_items"):
+                            if not task["extra"].get("created_items"):
+                                task["extra"]["created_items"] = {}
+                            task["extra"]["created_items"].update(to_supersede["extra"]["created_items"])
                     # Mark task as SUPERSEDED.
                     #   If task is in self.pending_tasks, it will be removed and database will be update
                     #   If task is in self.refresh_tasks, it will be removed
@@ -621,48 +632,6 @@ class vim_thread(threading.Thread):
             return error_text[:max_length//2-3] + " ... " + error_text[-max_length//2+3:]
         return error_text
 
-    def new_net(self, task):
-        try:
-            task_id = task["instance_action_id"] + "." + str(task["task_index"])
-            params = task["params"]
-            vim_net_id = self.vim.new_network(*params)
-
-            net_name = params[0]
-            net_type = params[1]
-
-            network = None
-            sdn_net_id = None
-            sdn_controller = self.vim.config.get('sdn-controller')
-            if sdn_controller and (net_type == "data" or net_type == "ptp"):
-                network = {"name": net_name, "type": net_type, "region": self.vim["config"]["datacenter_id"]}
-
-                vim_net = self.vim.get_network(vim_net_id)
-                if vim_net.get('encapsulation') != 'vlan':
-                    raise vimconn.vimconnException(
-                        "net '{}' defined as type '{}' has not vlan encapsulation '{}'".format(
-                            net_name, net_type, vim_net['encapsulation']))
-                network["vlan"] = vim_net.get('segmentation_id')
-                try:
-                    with self.db_lock:
-                        sdn_net_id = self.ovim.new_network(network)
-                except (ovimException, Exception) as e:
-                    self.logger.error("task=%s cannot create SDN network vim_net_id=%s input='%s' ovimException='%s'",
-                                      str(task_id), vim_net_id, str(network), str(e))
-            task["status"] = "DONE"
-            task["extra"]["vim_info"] = {}
-            task["extra"]["sdn_net_id"] = sdn_net_id
-            task["error_msg"] = None
-            task["vim_id"] = vim_net_id
-            instance_element_update = {"vim_net_id": vim_net_id, "sdn_net_id": sdn_net_id, "status": "BUILD", "error_msg": None}
-            return True, instance_element_update
-        except vimconn.vimconnException as e:
-            self.logger.error("Error creating NET, task=%s: %s", str(task_id), str(e))
-            task["status"] = "FAILED"
-            task["vim_id"] = None
-            task["error_msg"] = self._format_vim_error_msg(str(e))
-            instance_element_update = {"vim_net_id": None, "sdn_net_id": None, "status": "VIM_ERROR", "error_msg": task["error_msg"]}
-            return False, instance_element_update
-
     def new_vm(self, task):
         try:
             params = task["params"]
@@ -685,7 +654,7 @@ class vim_thread(threading.Thread):
                             "Cannot create VM because depends on a network not created or found: " +
                             str(task_net["error_msg"]))
                     net["net_id"] = network_id
-            vim_vm_id = self.vim.new_vminstance(*params)
+            vim_vm_id, created_items = self.vim.new_vminstance(*params)
 
             # fill task_interfaces. Look for snd_net_id at database for each interface
             task_interfaces = {}
@@ -704,6 +673,8 @@ class vim_thread(threading.Thread):
             task["vim_info"] = {}
             task["vim_interfaces"] = {}
             task["extra"]["interfaces"] = task_interfaces
+            task["extra"]["created"] = True
+            task["extra"]["created_items"] = created_items
             task["error_msg"] = None
             task["status"] = "DONE"
             task["vim_id"] = vim_vm_id
@@ -733,7 +704,7 @@ class vim_thread(threading.Thread):
                             iface["sdn_port_id"], vm_vim_id) + str(e), exc_info=True)
                         # TODO Set error_msg at instance_nets
 
-            self.vim.delete_vminstance(vm_vim_id)
+            self.vim.delete_vminstance(vm_vim_id, task["extra"].get("created_items"))
             task["status"] = "DONE"
             task["error_msg"] = None
             return True, None
@@ -747,6 +718,112 @@ class vim_thread(threading.Thread):
             task["status"] = "FAILED"
             return False, None
 
+    def _get_net_internal(self, task, filter_param):
+        """
+        Common code for get_net and new_net. It looks for a network on VIM with the filter_params
+        :param task: task for this find or find-or-create action
+        :param filter_param: parameters to send to the vimconnector
+        :return: a dict with the content to update the instance_nets database table. Raises an exception on error, or
+            when network is not found or found more than one
+        """
+        vim_nets = self.vim.get_network_list(filter_param)
+        if not vim_nets:
+            raise VimThreadExceptionNotFound("Network not found with this criteria: '{}'".format(filter))
+        elif len(vim_nets) > 1:
+            raise VimThreadException("More than one network found with this criteria: '{}'".format(filter))
+        vim_net_id = vim_nets[0]["id"]
+
+        # Discover if this network is managed by a sdn controller
+        sdn_net_id = None
+        with self.db_lock:
+            result = self.db.get_rows(SELECT=('sdn_net_id',), FROM='instance_nets',
+                                      WHERE={'vim_net_id': vim_net_id, 'instance_scenario_id': None,
+                                             'datacenter_tenant_id': self.datacenter_tenant_id})
+        if result:
+            sdn_net_id = result[0]['sdn_net_id']
+
+        task["status"] = "DONE"
+        task["extra"]["vim_info"] = {}
+        task["extra"]["created"] = False
+        task["extra"]["sdn_net_id"] = sdn_net_id
+        task["error_msg"] = None
+        task["vim_id"] = vim_net_id
+        instance_element_update = {"vim_net_id": vim_net_id, "created": False, "status": "BUILD",
+                                   "error_msg": None}
+        return instance_element_update
+
+    def get_net(self, task):
+        try:
+            task_id = task["instance_action_id"] + "." + str(task["task_index"])
+            params = task["params"]
+            filter_param = params[0]
+            instance_element_update = self._get_net_internal(task, filter_param)
+            return True, instance_element_update
+
+        except (vimconn.vimconnException, VimThreadException) as e:
+            self.logger.error("Error looking for  NET, task=%s: %s", str(task_id), str(e))
+            task["status"] = "FAILED"
+            task["vim_id"] = None
+            task["error_msg"] = self._format_vim_error_msg(str(e))
+            instance_element_update = {"vim_net_id": None, "status": "VIM_ERROR",
+                                       "error_msg": task["error_msg"]}
+            return False, instance_element_update
+
+    def new_net(self, task):
+        try:
+            task_id = task["instance_action_id"] + "." + str(task["task_index"])
+            # FIND
+            if task["extra"].get("find"):
+                action_text = "finding"
+                filter_param = task["extra"]["find"][0]
+                try:
+                    instance_element_update = self._get_net_internal(task, filter_param)
+                    return True, instance_element_update
+                except VimThreadExceptionNotFound:
+                    pass
+            # CREATE
+            params = task["params"]
+            action_text = "creating"
+            vim_net_id = self.vim.new_network(*params)
+
+            net_name = params[0]
+            net_type = params[1]
+
+            sdn_net_id = None
+            sdn_controller = self.vim.config.get('sdn-controller')
+            if sdn_controller and (net_type == "data" or net_type == "ptp"):
+                network = {"name": net_name, "type": net_type, "region": self.vim["config"]["datacenter_id"]}
+
+                vim_net = self.vim.get_network(vim_net_id)
+                if vim_net.get('encapsulation') != 'vlan':
+                    raise vimconn.vimconnException(
+                        "net '{}' defined as type '{}' has not vlan encapsulation '{}'".format(
+                            net_name, net_type, vim_net['encapsulation']))
+                network["vlan"] = vim_net.get('segmentation_id')
+                try:
+                    with self.db_lock:
+                        sdn_net_id = self.ovim.new_network(network)
+                except (ovimException, Exception) as e:
+                    self.logger.error("task=%s cannot create SDN network vim_net_id=%s input='%s' ovimException='%s'",
+                                      str(task_id), vim_net_id, str(network), str(e))
+            task["status"] = "DONE"
+            task["extra"]["vim_info"] = {}
+            task["extra"]["sdn_net_id"] = sdn_net_id
+            task["extra"]["created"] = True
+            task["error_msg"] = None
+            task["vim_id"] = vim_net_id
+            instance_element_update = {"vim_net_id": vim_net_id, "sdn_net_id": sdn_net_id, "status": "BUILD",
+                                       "created": True, "error_msg": None}
+            return True, instance_element_update
+        except vimconn.vimconnException as e:
+            self.logger.error("Error {} NET, task=%s: %s", action_text, str(task_id), str(e))
+            task["status"] = "FAILED"
+            task["vim_id"] = None
+            task["error_msg"] = self._format_vim_error_msg(str(e))
+            instance_element_update = {"vim_net_id": None, "sdn_net_id": None, "status": "VIM_ERROR",
+                                       "error_msg": task["error_msg"]}
+            return False, instance_element_update
+
     def del_net(self, task):
         net_vim_id = task["vim_id"]
         sdn_net_id = task["extra"].get("sdn_net_id")
@@ -775,42 +852,3 @@ class vim_thread(threading.Thread):
                 return True, None
         task["status"] = "FAILED"
         return False, None
-
-    def get_net(self, task):
-        try:
-            task_id = task["instance_action_id"] + "." + str(task["task_index"])
-            params = task["params"]
-            filter = params[0]
-            vim_nets = self.vim.get_network_list(filter)
-            if not vim_nets:
-                raise VimThreadException("Network not found with this criteria: '{}'".format(filter))
-            elif len(vim_nets) > 1:
-                raise VimThreadException("More than one network found with this criteria: '{}'".format(filter))
-            vim_net_id = vim_nets[0]["id"]
-
-            # Discover if this network is managed by a sdn controller
-            sdn_net_id = None
-            with self.db_lock:
-                result = self.db.get_rows(SELECT=('sdn_net_id',), FROM='instance_nets',
-                    WHERE={'vim_net_id': vim_net_id, 'instance_scenario_id': None,
-                           'datacenter_tenant_id': self.datacenter_tenant_id})
-            if result:
-                sdn_net_id = result[0]['sdn_net_id']
-
-            task["status"] = "DONE"
-            task["extra"]["vim_info"] = {}
-            task["extra"]["created"] = False
-            task["extra"]["sdn_net_id"] = sdn_net_id
-            task["error_msg"] = None
-            task["vim_id"] = vim_net_id
-            instance_element_update = {"vim_net_id": vim_net_id, "created": False, "status": "BUILD",
-                                       "error_msg": None}
-            return True, instance_element_update
-        except (vimconn.vimconnException, VimThreadException) as e:
-            self.logger.error("Error looking for  NET, task=%s: %s", str(task_id), str(e))
-            task["status"] = "FAILED"
-            task["vim_id"] = None
-            task["error_msg"] = self._format_vim_error_msg(str(e))
-            instance_element_update = {"vim_net_id": None, "status": "VIM_ERROR",
-                                       "error_msg": task["error_msg"]}
-            return False, instance_element_update