Fixing bug 1437
[osm/RO.git] / RO-VIM-openstack / osm_rovim_openstack / vimconn_openstack.py
index 289c827..bd3955a 100644 (file)
@@ -152,7 +152,7 @@ class vimconnector(vimconn.VimConnector):
         logging.getLogger('urllib3').setLevel(logging.WARNING)
         logging.getLogger('keystoneauth').setLevel(logging.WARNING)
         logging.getLogger('novaclient').setLevel(logging.WARNING)
-        self.logger = logging.getLogger('openmano.vim.openstack')
+        self.logger = logging.getLogger('ro.vim.openstack')
 
         # allow security_groups to be a list or a single string
         if isinstance(self.config.get('security_groups'), str):
@@ -161,7 +161,7 @@ class vimconnector(vimconn.VimConnector):
 
         # ###### VIO Specific Changes #########
         if self.vim_type == "VIO":
-            self.logger = logging.getLogger('openmano.vim.vio')
+            self.logger = logging.getLogger('ro.vim.vio')
 
         if log_level:
             self.logger.setLevel(getattr(logging, log_level))
@@ -410,13 +410,16 @@ class vimconnector(vimconn.VimConnector):
         """Transform a keystone, nova, neutron  exception into a vimconn exception discovering the cause"""
 
         message_error = str(exception)
+        tip = ""
 
         if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound,
                                   gl1Exceptions.HTTPNotFound)):
             raise vimconn.VimConnNotFoundException(type(exception).__name__ + ": " + message_error)
         elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
                                     ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
-            raise vimconn.VimConnConnectionException(type(exception).__name__ + ": " + message_error)
+            if type(exception).__name__ == "SSLError":
+                tip = " (maybe option 'insecure' must be added to the VIM)"
+            raise vimconn.VimConnConnectionException("Invalid URL or credentials{}: {}".format(tip, message_error))
         elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
             raise vimconn.VimConnException(type(exception).__name__ + ": " + message_error)
         elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
@@ -566,9 +569,10 @@ class vimconnector(vimconn.VimConnector):
                         provider_physical_network = provider_physical_network[0]
 
                 if not provider_physical_network:
-                    raise vimconn.VimConnConflictException("You must provide a 'dataplane_physical_net' at VIM_config "
-                                                           "for creating underlay networks. or use the NS instantiation"
-                                                           " parameter provider-network:physical-network for the VLD")
+                    raise vimconn.VimConnConflictException(
+                        "missing information needed for underlay networks. Provide 'dataplane_physical_net' "
+                        "configuration at VIM or use the NS instantiation parameter 'provider-network.physical-network'"
+                        " for the VLD")
 
                 if not self.config.get('multisegment_support'):
                     network_dict["provider:physical_network"] = provider_physical_network
@@ -1221,20 +1225,21 @@ class vimconnector(vimconn.VimConnector):
                 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
                 vim_id: filled/added by this function
                 floating_ip: True/False (or it can be None)
+                port_security: True/False
             'cloud_config': (optional) dictionary with:
-            'key-pairs': (optional) list of strings with the public key to be inserted to the default user
-            'users': (optional) list of users to be inserted, each item is a dict with:
-                'name': (mandatory) user name,
-                'key-pairs': (optional) list of strings with the public key to be inserted to the user
-            'user-data': (optional) string is a text script to be passed directly to cloud-init
-            'config-files': (optional). List of files to be transferred. Each item is a dict with:
-                'dest': (mandatory) string with the destination absolute path
-                'encoding': (optional, by default text). Can be one of:
-                    'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
-                'content' (mandatory): string with the content of the file
-                'permissions': (optional) string with file permissions, typically octal notation '0644'
-                'owner': (optional) file owner, string with the format 'owner:group'
-            'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
+                'key-pairs': (optional) list of strings with the public key to be inserted to the default user
+                'users': (optional) list of users to be inserted, each item is a dict with:
+                    'name': (mandatory) user name,
+                    'key-pairs': (optional) list of strings with the public key to be inserted to the user
+                'user-data': (optional) string is a text script to be passed directly to cloud-init
+                'config-files': (optional). List of files to be transferred. Each item is a dict with:
+                    'dest': (mandatory) string with the destination absolute path
+                    'encoding': (optional, by default text). Can be one of:
+                        'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
+                    'content' (mandatory): string with the content of the file
+                    'permissions': (optional) string with file permissions, typically octal notation '0644'
+                    'owner': (optional) file owner, string with the format 'owner:group'
+                'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
             'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
                 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
                 'size': (mandatory) string with the size of the disk in GB
@@ -1334,7 +1339,7 @@ class vimconnector(vimconn.VimConnector):
                 # is dropped.
                 # As a workaround we wait until the VM is active and then disable the port-security
                 if net.get("port_security") is False and not self.config.get("no_port_security_extension"):
-                    no_secured_ports.append(new_port["port"]["id"])
+                    no_secured_ports.append((new_port["port"]["id"], net.get("port_security_disable_strategy")))
 
             # if metadata_vpci:
             #     metadata = {"pci_assignement": json.dumps(metadata_vpci)}
@@ -1408,30 +1413,53 @@ class vimconnector(vimconn.VimConnector):
             if no_secured_ports:
                 self.__wait_for_vm(server.id, 'ACTIVE')
 
-            for port_id in no_secured_ports:
+            for port in no_secured_ports:
+                port_update = {
+                    "port": {
+                        "port_security_enabled": False,
+                        "security_groups": None
+                    }
+                }
+
+                if port[1] == "allow-address-pairs":
+                    port_update = {
+                        "port": {
+                            "allowed_address_pairs": [
+                                {
+                                    "ip_address": "0.0.0.0/0"
+                                }
+                            ]
+                        }
+                    }
+
                 try:
-                    self.neutron.update_port(port_id,
-                                             {"port": {"port_security_enabled": False, "security_groups": None}})
+                    self.neutron.update_port(port[0], port_update)
                 except Exception:
-                    raise vimconn.VimConnException("It was not possible to disable port security for port {}".format(
-                        port_id))
+                    raise vimconn.VimConnException(
+                        "It was not possible to disable port security for port {}"
+                        .format(port[0])
+                    )
+
             # print "DONE :-)", server
 
             # pool_id = None
-            if external_network:
-                floating_ips = self.neutron.list_floatingips().get("floatingips", ())
             for floating_network in external_network:
                 try:
                     assigned = False
+                    floating_ip_retries = 3
+                    # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
+                    # several times
                     while not assigned:
-                        if floating_ips:
-                            ip = floating_ips.pop(0)
-                            if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
+                        floating_ips = self.neutron.list_floatingips().get("floatingips", ())
+                        random.shuffle(floating_ips)   # randomize
+                        for fip in floating_ips:
+                            if fip.get("port_id") or fip.get('tenant_id') != server.tenant_id:
                                 continue
                             if isinstance(floating_network['floating_ip'], str):
-                                if ip.get("floating_network_id") != floating_network['floating_ip']:
+                                if fip.get("floating_network_id") != floating_network['floating_ip']:
                                     continue
-                            free_floating_ip = ip["id"]
+                            free_floating_ip = fip["id"]
+                            break
                         else:
                             if isinstance(floating_network['floating_ip'], str) and \
                                     floating_network['floating_ip'].lower() != "true":
@@ -1458,31 +1486,44 @@ class vimconnector(vimconn.VimConnector):
                                 # self.logger.debug("Creating floating IP")
                                 new_floating_ip = self.neutron.create_floatingip(param)
                                 free_floating_ip = new_floating_ip['floatingip']['id']
+                                created_items["floating_ip:" + str(free_floating_ip)] = True
                             except Exception as e:
                                 raise vimconn.VimConnException(type(e).__name__ + ": Cannot create new floating_ip " +
                                                                str(e), http_code=vimconn.HTTP_Conflict)
 
-                        while not assigned:
-                            try:
-                                # the vim_id key contains the neutron.port_id
-                                self.neutron.update_floatingip(free_floating_ip,
-                                                               {"floatingip": {"port_id": floating_network["vim_id"]}})
-                                # Using nove is deprecated on nova client 10.0
-                                assigned = True
-                            except Exception as e:
-                                # openstack need some time after VM creation to asign an IP. So retry if fails
-                                vm_status = self.nova.servers.get(server.id).status
-                                if vm_status != 'ACTIVE' and vm_status != 'ERROR':
-                                    if time.time() - vm_start_time < server_timeout:
-                                        time.sleep(5)
-                                        continue
-                                raise vimconn.VimConnException(
-                                    "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
-                                    http_code=vimconn.HTTP_Conflict)
+                        try:
+                            # for race condition ensure not already assigned
+                            fip = self.neutron.show_floatingip(free_floating_ip)
+                            if fip['floatingip']['port_id']:
+                                continue
+                            # the vim_id key contains the neutron.port_id
+                            self.neutron.update_floatingip(free_floating_ip,
+                                                           {"floatingip": {"port_id": floating_network["vim_id"]}})
+                            # for race condition ensure not re-assigned to other VM after 5 seconds
+                            time.sleep(5)
+                            fip = self.neutron.show_floatingip(free_floating_ip)
+                            if fip['floatingip']['port_id'] != floating_network["vim_id"]:
+                                self.logger.error("floating_ip {} re-assigned to other port".format(free_floating_ip))
+                                continue
+                            self.logger.debug("Assigned floating_ip {} to VM {}".format(free_floating_ip, server.id))
+                            assigned = True
+                        except Exception as e:
+                            # openstack need some time after VM creation to assign an IP. So retry if fails
+                            vm_status = self.nova.servers.get(server.id).status
+                            if vm_status not in ('ACTIVE', 'ERROR'):
+                                if time.time() - vm_start_time < server_timeout:
+                                    time.sleep(5)
+                                    continue
+                            elif floating_ip_retries > 0:
+                                floating_ip_retries -= 1
+                                continue
+                            raise vimconn.VimConnException(
+                                "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
+                                http_code=vimconn.HTTP_Conflict)
 
                 except Exception as e:
                     if not floating_network['exit_on_floating_ip_error']:
-                        self.logger.warning("Cannot create floating_ip. %s", str(e))
+                        self.logger.error("Cannot create floating_ip. %s", str(e))
                         continue
                     raise
 
@@ -1614,8 +1655,13 @@ class vimconnector(vimconn.VimConnector):
                                 keep_waiting = True
                             else:
                                 self.cinder.volumes.delete(k_id)
+                                created_items[k] = None
+                        elif k_item == "floating_ip":  # floating ip
+                            self.neutron.delete_floatingip(k_id)
+                            created_items[k] = None
+
                     except Exception as e:
-                        self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
+                        self.logger.error("Error deleting {}: {}".format(k, e))
                 if keep_waiting:
                     time.sleep(1)
                     elapsed_time += 1
@@ -1661,7 +1707,8 @@ class vimconnector(vimconn.VimConnector):
                 else:
                     vm['status'] = "OTHER"
                     vm['error_msg'] = "VIM status reported " + vm_vim['status']
-
+                vm_vim.pop("OS-EXT-SRV-ATTR:user_data", None)
+                vm_vim.pop("user_data", None)
                 vm['vim_info'] = self.serialize(vm_vim)
 
                 vm["interfaces"] = []