bug 425 fix SR-IOV PCI-PASSTHROUGH interfaces
[osm/RO.git] / osm_ro / vimconn_vmware.py
index 701008f..d1c3977 100644 (file)
@@ -1372,30 +1372,60 @@ class vimconnector(vimconn.vimconnector):
             return None
         return None
 
-    def new_vminstance(self, name=None, description="", start=False, image_id=None, flavor_id=None, net_list={},
-                       cloud_config=None, disk_list=None):
+    def new_vminstance(self, name=None, description="", start=False, image_id=None, flavor_id=None, net_list=[],
+                       cloud_config=None, disk_list=None, availability_zone_index=None, availability_zone_list=None):
         """Adds a VM instance to VIM
         Params:
-            start: indicates if VM must start or boot in pause mode. Ignored
-            image_id,flavor_id: image and flavor uuid
-            net_list: list of interfaces, each one is a dictionary with:
-                name:
-                net_id: network uuid to connect
-                vpci: virtual vcpi to assign
-                model: interface model, virtio, e2000, ...
-                mac_address:
-                use: 'data', 'bridge',  'mgmt'
-                type: 'virtual', 'PF', 'VF', 'VFnotShared'
-                vim_id: filled/added by this function
-                cloud_config: can be a text script to be passed directly to cloud-init,
-                    or an object to inject users and ssh keys with format:
-                        key-pairs: [] list of keys to install to the default user
-                        users: [{ name, key-pairs: []}] list of users to add with their key-pair
-                #TODO ip, security groups
-        Returns >=0, the instance identifier
-                <0, error_text
+            'start': (boolean) indicates if VM must start or created in pause mode.
+            'image_id','flavor_id': image and flavor VIM id to use for the VM
+            'net_list': list of interfaces, each one is a dictionary with:
+                'name': (optional) name for the interface.
+                'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
+                'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities
+                'model': (optional and only have sense for type==virtual) interface model: virtio, e2000, ...
+                'mac_address': (optional) mac address to assign to this interface
+                #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided,
+                    the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF
+                'type': (mandatory) can be one of:
+                    'virtual', in this case always connected to a network of type 'net_type=bridge'
+                     'PCI-PASSTHROUGH' or 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it
+                           can created unconnected
+                     'SR-IOV' or 'VF' (SRIOV with VLAN tag): same as PF for network connectivity.
+                     'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs
+                            are allocated on the same physical NIC
+                'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS
+                'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing
+                                or True, it must apply the default VIM behaviour
+                After execution the method will add the key:
+                'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this
+                        interface. 'net_list' is modified
+            '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) can be a string with the text script to be passed directly to cloud-init,
+                    or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
+                '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
+            availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
+            availability_zone_list: list of availability zones given by user in the VNFD descriptor.  Ignore if
+                availability_zone_index is None
+        Returns a tuple with the instance identifier and created_items or raises an exception on error
+            created_items can be None or a dictionary where this method can include key-values that will be passed to
+            the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
+            Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
+            as not present.
         """
-
         self.logger.info("Creating new instance for entry {}".format(name))
         self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {} disk_list {}".format(
                                     description, start, image_id, flavor_id, net_list, cloud_config, disk_list))
@@ -1469,7 +1499,7 @@ class vimconnector(vimconn.vimconnector):
         network_mode = 'bridged'
         if net_list is not None and len(net_list) > 0:
             for net in net_list:
-                if 'use' in net and net['use'] == 'mgmt':
+                if 'use' in net and net['use'] == 'mgmt' and not primary_net:
                     primary_net = net
             if primary_net is None:
                 primary_net = net_list[0]
@@ -1534,9 +1564,9 @@ class vimconnector(vimconn.vimconnector):
         reserve_memory = False
 
         for net in net_list:
-            if net["type"]=="PF":
+            if net["type"] == "PF" or net["type"] == "PCI-PASSTHROUGH":
                 pci_devices_info.append(net)
-            elif  (net["type"]=="VF" or  net["type"]=="VFnotShared") and 'net_id'in net:
+            elif (net["type"] == "VF" or net["type"] == "SR-IOV" or net["type"] == "VFnotShared") and 'net_id'in net:
                 sriov_net_info.append(net)
 
         #Add PCI
@@ -1613,6 +1643,10 @@ class vimconnector(vimconn.vimconnector):
                 if 'net_id' not in net:
                     continue
 
+                #Using net_id as a vim_id i.e. vim interface id, as do not have saperate vim interface id
+                #Same will be returned in refresh_vms_status() as vim_interface_id
+                net['vim_id'] = net['net_id']  # Provide the same VIM identifier as the VIM network
+
                 interface_net_id = net['net_id']
                 interface_net_name = self.get_network_name_by_id(network_uuid=interface_net_id)
                 interface_network_mode = net['use']
@@ -1636,7 +1670,7 @@ class vimconnector(vimconn.vimconnector):
                             self.vca.block_until_completed(task)
                         # connect network to VM - with all DHCP by default
 
-                        type_list = ['PF','VF','VFnotShared']
+                        type_list = ('PF', 'PCI-PASSTHROUGH', 'VF', 'SR-IOV', 'VFnotShared')
                         if 'type' in net and net['type'] not in type_list:
                             # fetching nic type from vnf
                             if 'model' in net:
@@ -1696,11 +1730,11 @@ class vimconnector(vimconn.vimconnector):
                 task = vm_obj.ReconfigVM_Task(spec=spec)
                 if task:
                     result = self.wait_for_vcenter_task(task, vcenter_conect)
-                    self.logger.info("Reserved memmoery {} MB for "\
-                                     "VM VM status: {}".format(str(memReserve),result))
+                    self.logger.info("Reserved memory {} MB for "
+                                     "VM VM status: {}".format(str(memReserve), result))
                 else:
-                    self.logger.info("Fail to reserved memmoery {} to VM {}".format(
-                                                                str(memReserve),str(vm_obj)))
+                    self.logger.info("Fail to reserved memory {} to VM {}".format(
+                                                                str(memReserve), str(vm_obj)))
 
             self.logger.debug("new_vminstance(): power on vApp {} ".format(name))
 
@@ -1737,7 +1771,7 @@ class vimconnector(vimconn.vimconnector):
             wait_time +=INTERVAL_TIME
 
         if vapp_uuid is not None:
-            return vapp_uuid
+            return vapp_uuid, None
         else:
             raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name))
 
@@ -1795,7 +1829,7 @@ class vimconnector(vimconn.vimconnector):
 
         return vm_dict
 
-    def delete_vminstance(self, vm__vim_uuid):
+    def delete_vminstance(self, vm__vim_uuid, created_items=None):
         """Method poweroff and remove VM instance from vcloud director network.
 
         Args:
@@ -2099,7 +2133,7 @@ class vimconnector(vimconn.vimconnector):
             self.logger.debug("ParseError in response from NSX Manager {}".format(Err.message), exc_info=True)
 
 
-    def action_vminstance(self, vm__vim_uuid=None, action_dict=None):
+    def action_vminstance(self, vm__vim_uuid=None, action_dict=None, created_items={}):
         """Send and action over a VM instance from VIM
         Returns the vm_id if the action was successfully sent to the VIM"""
 
@@ -2109,7 +2143,7 @@ class vimconnector(vimconn.vimconnector):
 
         vdc = self.get_vdc_details()
         if vdc is None:
-            return -1, "Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)
+            raise  vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name))
 
         vapp_name = self.get_namebyvappid(vdc, vm__vim_uuid)
         if vapp_name is None:
@@ -2161,7 +2195,7 @@ class vimconnector(vimconn.vimconnector):
                 reboot_task = the_vapp.reboot()
             else:
                 raise vimconn.vimconnException("action_vminstance: Invalid action {} or action is None.".format(action_dict))
-            return vm__vim_uuid
+            return None
         except Exception as exp :
             self.logger.debug("action_vminstance: Failed with Exception {}".format(exp))
             raise vimconn.vimconnException("action_vminstance: Failed with Exception {}".format(exp))
@@ -4014,7 +4048,6 @@ class vimconnector(vimconn.vimconnector):
                                                                            "affinity".format(exp))
 
 
-
     def cloud_init(self, vapp, cloud_config):
         """
         Method to inject ssh-key
@@ -4024,7 +4057,8 @@ class vimconnector(vimconn.vimconnector):
                 '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
+                'user-data': (optional) can be a string with the text script to be passed directly to cloud-init,
+                    or a list of strings, each one contains a script to be passed, usually with a MIMEmultipart file
                 '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:
@@ -4034,9 +4068,10 @@ class vimconnector(vimconn.vimconnector):
                     '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
         """
-
         try:
-            if isinstance(cloud_config, dict):
+            if not isinstance(cloud_config, dict):
+                raise Exception("cloud_init : parameter cloud_config is not a dictionary")
+            else:
                 key_pairs = []
                 userdata = []
                 if "key-pairs" in cloud_config:
@@ -4045,68 +4080,94 @@ class vimconnector(vimconn.vimconnector):
                 if "users" in cloud_config:
                     userdata = cloud_config["users"]
 
-            for key in key_pairs:
-                for user in userdata:
-                    if 'name' in user: user_name = user['name']
-                    if 'key-pairs' in user and len(user['key-pairs']) > 0:
-                        for user_key in user['key-pairs']:
-                            customize_script = """
-                        #!/bin/bash
-                        echo performing customization tasks with param $1 at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
-                        if [ "$1" = "precustomization" ];then
-                            echo performing precustomization tasks   on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
-                            if [ ! -d /root/.ssh ];then
-                                mkdir /root/.ssh
-                                chown root:root /root/.ssh
-                                chmod 700 /root/.ssh
-                                touch /root/.ssh/authorized_keys
-                                chown root:root /root/.ssh/authorized_keys
-                                chmod 600 /root/.ssh/authorized_keys
-                                # make centos with selinux happy
-                                which restorecon && restorecon -Rv /root/.ssh
-                                echo '{key}' >> /root/.ssh/authorized_keys
-                            else
-                                touch /root/.ssh/authorized_keys
-                                chown root:root /root/.ssh/authorized_keys
-                                chmod 600 /root/.ssh/authorized_keys
-                                echo '{key}' >> /root/.ssh/authorized_keys
-                            fi
-                            if [ -d /home/{user_name} ];then
-                                if [ ! -d /home/{user_name}/.ssh ];then
-                                    mkdir /home/{user_name}/.ssh
-                                    chown {user_name}:{user_name} /home/{user_name}/.ssh
-                                    chmod 700 /home/{user_name}/.ssh
-                                    touch /home/{user_name}/.ssh/authorized_keys
-                                    chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys
-                                    chmod 600 /home/{user_name}/.ssh/authorized_keys
-                                    # make centos with selinux happy
-                                    which restorecon && restorecon -Rv /home/{user_name}/.ssh
-                                    echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys
-                                else
-                                    touch /home/{user_name}/.ssh/authorized_keys
-                                    chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys
-                                    chmod 600 /home/{user_name}/.ssh/authorized_keys
-                                    echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys
-                                fi
-                            fi
-                        fi""".format(key=key, user_name=user_name, user_key=user_key)
-
-                            for vm in vapp._get_vms():
-                                vm_name = vm.name
-                                task = vapp.customize_guest_os(vm_name, customization_script=customize_script)
-                                if isinstance(task, GenericTask):
-                                    self.vca.block_until_completed(task)
-                                    self.logger.info("cloud_init : customized guest os task "\
-                                                        "completed for VM {}".format(vm_name))
-                                else:
-                                    self.logger.error("cloud_init : task for customized guest os"\
-                                                               "failed for VM {}".format(vm_name))
+                self.logger.debug("cloud_init : Guest os customization started..")
+                customize_script = self.format_script(key_pairs=key_pairs, users_list=userdata)
+                self.guest_customization(vapp, customize_script)
+
         except Exception as exp:
             self.logger.error("cloud_init : exception occurred while injecting "\
                                                                        "ssh-key")
             raise vimconn.vimconnException("cloud_init : Error {} failed to inject "\
                                                                "ssh-key".format(exp))
 
+    def format_script(self, key_pairs=[], users_list=[]):
+        bash_script = """
+        #!/bin/bash
+        echo performing customization tasks with param $1 at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
+        if [ "$1" = "precustomization" ];then
+            echo performing precustomization tasks   on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log
+        """
+
+        keys = "\n".join(key_pairs)
+        if keys:
+            keys_data = """
+            if [ ! -d /root/.ssh ];then
+                mkdir /root/.ssh
+                chown root:root /root/.ssh
+                chmod 700 /root/.ssh
+                touch /root/.ssh/authorized_keys
+                chown root:root /root/.ssh/authorized_keys
+                chmod 600 /root/.ssh/authorized_keys
+                # make centos with selinux happy
+                which restorecon && restorecon -Rv /root/.ssh
+            else
+                touch /root/.ssh/authorized_keys
+                chown root:root /root/.ssh/authorized_keys
+                chmod 600 /root/.ssh/authorized_keys
+            fi
+            echo '{key}' >> /root/.ssh/authorized_keys
+            """.format(key=keys)
+
+            bash_script+= keys_data
+
+        for user in users_list:
+            if 'name' in user: user_name = user['name']
+            if 'key-pairs' in user:
+                user_keys = "\n".join(user['key-pairs'])
+            else:
+                user_keys = None
+
+            add_user_name = """
+                useradd -d /home/{user_name} -m -g users -s /bin/bash {user_name}
+                """.format(user_name=user_name)
+
+            bash_script+= add_user_name
+
+            if user_keys:
+                user_keys_data = """
+                mkdir /home/{user_name}/.ssh
+                chown {user_name}:{user_name} /home/{user_name}/.ssh
+                chmod 700 /home/{user_name}/.ssh
+                touch /home/{user_name}/.ssh/authorized_keys
+                chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys
+                chmod 600 /home/{user_name}/.ssh/authorized_keys
+                # make centos with selinux happy
+                which restorecon && restorecon -Rv /home/{user_name}/.ssh
+                echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys
+                """.format(user_name=user_name,user_key=user_keys)
+
+                bash_script+= user_keys_data
+
+        return bash_script+"\n\tfi"
+
+    def guest_customization(self, vapp, customize_script):
+        """
+        Method to customize guest os
+        vapp - Vapp object
+        customize_script - Customize script to be run at first boot of VM.
+        """
+        for vm in vapp._get_vms():
+            vm_name = vm.name
+            task = vapp.customize_guest_os(vm_name, customization_script=customize_script)
+            if isinstance(task, GenericTask):
+                self.vca.block_until_completed(task)
+                self.logger.info("guest_customization : customized guest os task "\
+                                             "completed for VM {}".format(vm_name))
+            else:
+                self.logger.error("guest_customization : task for customized guest os"\
+                                                    "failed for VM {}".format(vm_name))
+                raise vimconn.vimconnException("guest_customization : failed to perform"\
+                                       "guest os customization on VM {}".format(vm_name))
 
     def add_new_disk(self, vapp_uuid, disk_size):
         """
@@ -4576,7 +4637,7 @@ class vimconnector(vimconn.vimconnector):
                             for sriov_net in sriov_nets:
                                 network_name = sriov_net.get('net_id')
                                 dvs_portgr_name = self.create_dvPort_group(network_name)
-                                if sriov_net.get('type') == "VF":
+                                if sriov_net.get('type') == "VF" or sriov_net.get('type') == "SR-IOV":
                                     #add vlan ID ,Modify portgroup for vlan ID
                                     self.configure_vlanID(content, vcenter_conect, network_name)