X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_ro%2Fvimconn_vmware.py;h=d1c397797a43c50ab242b3da01e772def6a848f1;hb=66eba6ece53cd85d0efbe8b4ff4f414c812b347b;hp=5b044581d513b68e03ba03ef1a8002723169673e;hpb=05a8b7bc29197345f9718796c110d6cf3c2ad176;p=osm%2FRO.git diff --git a/osm_ro/vimconn_vmware.py b/osm_ro/vimconn_vmware.py index 5b044581..d1c39779 100644 --- a/osm_ro/vimconn_vmware.py +++ b/osm_ro/vimconn_vmware.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- ## -# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. -# This file is part of openmano +# Copyright 2016-2017 VMware Inc. +# This file is part of ETSI OSM # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -18,7 +18,7 @@ # under the License. # # For those usages not covered by the Apache License, Version 2.0 please -# contact with: nfvlabs@tid.es +# contact: osslegalrouting@vmware.com ## """ @@ -357,6 +357,11 @@ class vimconnector(vimconn.vimconnector): Returns: The return vca object that letter can be used to connect to vcloud direct as admin """ + vca = self.connect() + if not vca: + raise vimconn.vimconnConnectionException("self.connect() is failed.") + + self.vca = vca try: if self.org_uuid is None: org_dict = self.get_org_list() @@ -422,9 +427,61 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnException("Failed create tenant {}".format(tenant_name)) def delete_tenant(self, tenant_id=None): - """Delete a tenant from VIM""" - 'Returns the tenant identifier' - raise vimconn.vimconnNotImplemented("Should have implemented this") + """ Delete a tenant from VIM + Args: + tenant_id is tenant_id to be deleted. + + Return: + returns the tenant identifier in UUID format. + If action is failed method will throw exception + """ + vca = self.connect_as_admin() + if not vca: + raise vimconn.vimconnConnectionException("self.connect() is failed") + + if tenant_id is not None: + if vca.vcloud_session and vca.vcloud_session.organization: + #Get OrgVDC + url_list = [self.vca.host, '/api/vdc/', tenant_id] + orgvdc_herf = ''.join(url_list) + response = Http.get(url=orgvdc_herf, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code != requests.codes.ok: + self.logger.debug("delete_tenant():GET REST API call {} failed. "\ + "Return status code {}".format(orgvdc_herf, + response.status_code)) + raise vimconn.vimconnNotFoundException("Fail to get tenant {}".format(tenant_id)) + + lxmlroot_respond = lxmlElementTree.fromstring(response.content) + namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix} + namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5" + vdc_remove_href = lxmlroot_respond.find("xmlns:Link[@rel='remove']",namespaces).attrib['href'] + vdc_remove_href = vdc_remove_href + '?recursive=true&force=true' + + #Delete OrgVDC + response = Http.delete(url=vdc_remove_href, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code == 202: + delete_vdc_task = taskType.parseString(response.content, True) + if type(delete_vdc_task) is GenericTask: + self.vca.block_until_completed(delete_vdc_task) + self.logger.info("Deleted tenant with ID {}".format(tenant_id)) + return tenant_id + else: + self.logger.debug("delete_tenant(): DELETE REST API call {} failed. "\ + "Return status code {}".format(vdc_remove_href, + response.status_code)) + raise vimconn.vimconnException("Fail to delete tenant with ID {}".format(tenant_id)) + else: + self.logger.debug("delete_tenant():Incorrect tenant ID {}".format(tenant_id)) + raise vimconn.vimconnNotFoundException("Fail to get tenant {}".format(tenant_id)) + def get_tenant_list(self, filter_dict={}): """Obtain tenants of VIM @@ -495,19 +552,16 @@ class vimconnector(vimconn.vimconnector): """ self.logger.debug("get_vcd_network_list(): retrieving network list for vcd {}".format(self.tenant_name)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") if not self.tenant_name: raise vimconn.vimconnConnectionException("Tenant name is empty.") - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}".format(self.tenant_name)) vdc_uuid = vdc.get_id().split(":")[3] - networks = vca.get_networks(vdc.get_name()) + networks = self.vca.get_networks(vdc.get_name()) network_list = [] try: for network in networks: @@ -553,21 +607,18 @@ class vimconnector(vimconn.vimconnector): List can be empty """ - self.logger.debug("get_vcd_network_list(): retrieving network list for vcd {}".format(self.tenant_name)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") + self.logger.debug("get_network_list(): retrieving network list for vcd {}".format(self.tenant_name)) if not self.tenant_name: raise vimconn.vimconnConnectionException("Tenant name is empty.") - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}.".format(self.tenant_name)) try: vdcid = vdc.get_id().split(":")[3] - networks = vca.get_networks(vdc.get_name()) + networks = self.vca.get_networks(vdc.get_name()) network_list = [] for network in networks: @@ -613,15 +664,11 @@ class vimconnector(vimconn.vimconnector): """Method obtains network details of net_id VIM network Return a dict with the fields at filter_dict (see get_network_list) plus some VIM specific>}, ...]""" - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - try: - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() vdc_id = vdc.get_id().split(":")[3] - networks = vca.get_networks(vdc.get_name()) + networks = self.vca.get_networks(vdc.get_name()) filter_dict = {} for network in networks: @@ -652,10 +699,6 @@ class vimconnector(vimconn.vimconnector): Returns the network identifier or raise an exception """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() for tenant {} is failed.".format(self.tenant_name)) - # ############# Stub code for SRIOV ################# # dvport_group = self.get_dvport_group(net_id) # if dvport_group: @@ -693,10 +736,6 @@ class vimconnector(vimconn.vimconnector): """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - dict_entry = {} try: for net in net_list: @@ -754,6 +793,13 @@ class vimconnector(vimconn.vimconnector): cpu = flavor_data.get(FLAVOR_VCPUS_KEY, 1) disk = flavor_data.get(FLAVOR_DISK_KEY, 1) + if not isinstance(ram, int): + raise vimconn.vimconnException("Non-integer value for ram") + elif not isinstance(cpu, int): + raise vimconn.vimconnException("Non-integer value for cpu") + elif not isinstance(disk, int): + raise vimconn.vimconnException("Non-integer value for disk") + extended_flv = flavor_data.get("extended") if extended_flv: numas=extended_flv.get("numas") @@ -801,12 +847,81 @@ class vimconnector(vimconn.vimconnector): def delete_image(self, image_id): """ - - :param image_id: - :return: + Deletes a tenant image from VIM + Args: + image_id is ID of Image to be deleted + Return: + returns the image identifier in UUID format or raises an exception on error """ + vca = self.connect_as_admin() + if not vca: + raise vimconn.vimconnConnectionException("self.connect() is failed") + # Get Catalog details + url_list = [self.vca.host, '/api/catalog/', image_id] + catalog_herf = ''.join(url_list) + response = Http.get(url=catalog_herf, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code != requests.codes.ok: + self.logger.debug("delete_image():GET REST API call {} failed. "\ + "Return status code {}".format(catalog_herf, + response.status_code)) + raise vimconn.vimconnNotFoundException("Fail to get image {}".format(image_id)) + + lxmlroot_respond = lxmlElementTree.fromstring(response.content) + namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix} + namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5" + + catalogItems_section = lxmlroot_respond.find("xmlns:CatalogItems",namespaces) + catalogItems = catalogItems_section.iterfind("xmlns:CatalogItem",namespaces) + for catalogItem in catalogItems: + catalogItem_href = catalogItem.attrib['href'] + + #GET details of catalogItem + response = Http.get(url=catalogItem_href, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code != requests.codes.ok: + self.logger.debug("delete_image():GET REST API call {} failed. "\ + "Return status code {}".format(catalog_herf, + response.status_code)) + raise vimconn.vimconnNotFoundException("Fail to get catalogItem {} for catalog {}".format( + catalogItem, + image_id)) + + lxmlroot_respond = lxmlElementTree.fromstring(response.content) + namespaces = {prefix:uri for prefix,uri in lxmlroot_respond.nsmap.iteritems() if prefix} + namespaces["xmlns"]= "http://www.vmware.com/vcloud/v1.5" + catalogitem_remove_href = lxmlroot_respond.find("xmlns:Link[@rel='remove']",namespaces).attrib['href'] + + #Remove catalogItem + response = Http.delete(url= catalogitem_remove_href, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + if response.status_code == requests.codes.no_content: + self.logger.debug("Deleted Catalog item {}".format(catalogItem)) + else: + raise vimconn.vimconnException("Fail to delete Catalog Item {}".format(catalogItem)) + + #Remove catalog + url_list = [self.vca.host, '/api/admin/catalog/', image_id] + catalog_remove_herf = ''.join(url_list) + response = Http.delete(url= catalog_remove_herf, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code == requests.codes.no_content: + self.logger.debug("Deleted Catalog {}".format(image_id)) + return image_id + else: + raise vimconn.vimconnException("Fail to delete Catalog {}".format(image_id)) - raise vimconn.vimconnNotImplemented("Should have implemented this") def catalog_exists(self, catalog_name, catalogs): """ @@ -1077,9 +1192,6 @@ class vimconnector(vimconn.vimconnector): Return: if image uploaded correct method will provide image catalog UUID. """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") if not path: raise vimconn.vimconnException("Image path can't be None.") @@ -1104,21 +1216,21 @@ class vimconnector(vimconn.vimconnector): "vdc catalog name {}".format(filename, catalog_name, path, catalog_md5_name)) try: - catalogs = vca.get_catalogs() + catalogs = self.vca.get_catalogs() except Exception as exp: self.logger.debug("Failed get catalogs() with Exception {} ".format(exp)) raise vimconn.vimconnException("Failed get catalogs() with Exception {} ".format(exp)) if len(catalogs) == 0: self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name)) - result = self.create_vimcatalog(vca, catalog_md5_name) + result = self.create_vimcatalog(self.vca, catalog_md5_name) if not result: raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name)) - result = self.upload_vimimage(vca=vca, catalog_name=catalog_md5_name, + result = self.upload_vimimage(vca=self.vca, catalog_name=catalog_md5_name, media_name=filename, medial_file_name=path, progress=progress) if not result: raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_name)) - return self.get_catalogid(catalog_name, vca.get_catalogs()) + return self.get_catalogid(catalog_name, self.vca.get_catalogs()) else: for catalog in catalogs: # search for existing catalog if we find same name we return ID @@ -1127,20 +1239,20 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Found existing catalog entry for {} " "catalog id {}".format(catalog_name, self.get_catalogid(catalog_md5_name, catalogs))) - return self.get_catalogid(catalog_md5_name, vca.get_catalogs()) + return self.get_catalogid(catalog_md5_name, self.vca.get_catalogs()) # if we didn't find existing catalog we create a new one and upload image. self.logger.debug("Creating new catalog entry {} - {}".format(catalog_name, catalog_md5_name)) - result = self.create_vimcatalog(vca, catalog_md5_name) + result = self.create_vimcatalog(self.vca, catalog_md5_name) if not result: raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name)) - result = self.upload_vimimage(vca=vca, catalog_name=catalog_md5_name, + result = self.upload_vimimage(vca=self.vca, catalog_name=catalog_md5_name, media_name=filename, medial_file_name=path, progress=progress) if not result: raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_md5_name)) - return self.get_catalogid(catalog_md5_name, vca.get_catalogs()) + return self.get_catalogid(catalog_md5_name, self.vca.get_catalogs()) def get_image_list(self, filter_dict={}): '''Obtain tenant images from VIM @@ -1153,12 +1265,10 @@ class vimconnector(vimconn.vimconnector): [{}, ...] List can be empty ''' - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") + try: image_list = [] - catalogs = vca.get_catalogs() + catalogs = self.vca.get_catalogs() if len(catalogs) == 0: return image_list else: @@ -1229,7 +1339,7 @@ class vimconnector(vimconn.vimconnector): return False return False - def get_namebyvappid(self, vca=None, vdc=None, vapp_uuid=None): + def get_namebyvappid(self, vdc=None, vapp_uuid=None): """Method returns vApp name from vCD and lookup done by vapp_id. Args: @@ -1248,8 +1358,13 @@ class vimconnector(vimconn.vimconnector): # we care only about UUID the rest doesn't matter vappid = ref.href.split("vapp")[1][1:] if vappid == vapp_uuid: - response = Http.get(ref.href, headers=vca.vcloud_session.get_vcloud_headers(), verify=vca.verify, + response = Http.get(ref.href, headers=self.vca.vcloud_session.get_vcloud_headers(), verify=self.vca.verify, logger=self.logger) + + #Retry login if session expired & retry sending request + if response.status_code == 403: + response = self.retry_rest('GET', ref.href) + tree = XmlElementTree.fromstring(response.content) return tree.attrib['name'] except Exception as e: @@ -1257,36 +1372,63 @@ 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)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") #new vm name = vmname + tenant_id + uuid new_vm_name = [name, '-', str(uuid.uuid4())] @@ -1298,11 +1440,15 @@ class vimconnector(vimconn.vimconnector): # return vapp_uuid # we check for presence of VDC, Catalog entry and Flavor. - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: raise vimconn.vimconnNotFoundException( "new_vminstance(): Failed create vApp {}: (Failed retrieve VDC information)".format(name)) - catalogs = vca.get_catalogs() + catalogs = self.vca.get_catalogs() + if catalogs is None: + #Retry once, if failed by refreshing token + self.get_token() + catalogs = self.vca.get_catalogs() if catalogs is None: raise vimconn.vimconnNotFoundException( "new_vminstance(): Failed create vApp {}: (Failed retrieve catalogs list)".format(name)) @@ -1319,6 +1465,7 @@ class vimconnector(vimconn.vimconnector): vm_cpus = None vm_memory = None vm_disk = None + numas = None if flavor_id is not None: if flavor_id not in vimconnector.flavorlist: @@ -1352,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] @@ -1371,19 +1518,26 @@ class vimconnector(vimconn.vimconnector): # use: 'data', 'bridge', 'mgmt' # create vApp. Set vcpu and ram based on flavor id. try: - vapptask = vca.create_vapp(self.tenant_name, vmname_andid, templateName, - self.get_catalogbyid(image_id, catalogs), - network_name=None, # None while creating vapp - network_mode=network_mode, - vm_name=vmname_andid, - vm_cpus=vm_cpus, # can be None if flavor is None - vm_memory=vm_memory) # can be None if flavor is None + for retry in (1,2): + vapptask = self.vca.create_vapp(self.tenant_name, vmname_andid, templateName, + self.get_catalogbyid(image_id, catalogs), + network_name=None, # None while creating vapp + network_mode=network_mode, + vm_name=vmname_andid, + vm_cpus=vm_cpus, # can be None if flavor is None + vm_memory=vm_memory) # can be None if flavor is None + + if not vapptask and retry==1: + self.get_token() # Retry getting token + continue + else: + break if vapptask is None or vapptask is False: raise vimconn.vimconnUnexpectedResponse( "new_vminstance(): failed to create vApp {}".format(vmname_andid)) if type(vapptask) is VappTask: - vca.block_until_completed(vapptask) + self.vca.block_until_completed(vapptask) except Exception as exp: raise vimconn.vimconnUnexpectedResponse( @@ -1391,14 +1545,14 @@ class vimconnector(vimconn.vimconnector): # we should have now vapp in undeployed state. try: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) - vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid) + vapp_uuid = self.get_vappid(self.get_vdc_details(), vmname_andid) + except Exception as exp: raise vimconn.vimconnUnexpectedResponse( "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}" .format(vmname_andid, exp)) - if vapp is None: + if vapp_uuid is None: raise vimconn.vimconnUnexpectedResponse( "new_vminstance(): Failed to retrieve vApp {} after creation".format( vmname_andid)) @@ -1410,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 @@ -1433,6 +1587,8 @@ class vimconnector(vimconn.vimconnector): pci_devices_info, vmname_andid) ) + + vapp = self.vca.get_vapp(self.get_vdc_details(), vmname_andid) # Modify vm disk if vm_disk: #Assuming there is only one disk in ovf and fast provisioning in organization vDC is disabled @@ -1444,7 +1600,12 @@ class vimconnector(vimconn.vimconnector): if disk_list: added_existing_disk = False for disk in disk_list: - if "image_id" in disk and disk["image_id"] is not None: + if 'device_type' in disk and disk['device_type'] == 'cdrom': + image_id = disk['image_id'] + # Adding CD-ROM to VM + # will revisit code once specification ready to support this feature + self.insert_media_to_vm(vapp, image_id) + elif "image_id" in disk and disk["image_id"] is not None: self.logger.debug("Adding existing disk from image {} to vm {} ".format( disk["image_id"] , vapp_uuid)) self.add_existing_disk(catalogs=catalogs, @@ -1459,7 +1620,7 @@ class vimconnector(vimconn.vimconnector): if added_existing_disk: time.sleep(5) added_existing_disk = False - self.add_new_disk(vca, vapp_uuid, disk['size']) + self.add_new_disk(vapp_uuid, disk['size']) if numas: # Assigning numa affinity setting @@ -1482,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'] @@ -1495,15 +1660,17 @@ class vimconnector(vimconn.vimconnector): - NONE (No IP addressing mode specified.)""" if primary_netname is not None: - nets = filter(lambda n: n.name == interface_net_name, vca.get_networks(self.tenant_name)) + nets = filter(lambda n: n.name == interface_net_name, self.vca.get_networks(self.tenant_name)) if len(nets) == 1: self.logger.info("new_vminstance(): Found requested network: {}".format(nets[0].name)) + + vapp = self.vca.get_vapp(self.get_vdc_details(), vmname_andid) task = vapp.connect_to_network(nets[0].name, nets[0].href) if type(task) is GenericTask: - vca.block_until_completed(task) + 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: @@ -1524,6 +1691,7 @@ class vimconnector(vimconn.vimconnector): net) nicIndex += 1 + vapp = self.vca.get_vapp(self.get_vdc_details(), vmname_andid) # cloud-init for ssh-key injection if cloud_config: self.cloud_init(vapp,cloud_config) @@ -1532,7 +1700,7 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("new_vminstance(): Deploying vApp {} ".format(name)) deploytask = vapp.deploy(powerOn=False) if type(deploytask) is GenericTask: - vca.block_until_completed(deploytask) + self.vca.block_until_completed(deploytask) # ############# Stub code for SRIOV ################# #Add SRIOV @@ -1562,35 +1730,39 @@ 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)) + + vapp = self.vca.get_vapp(self.get_vdc_details(), vmname_andid) poweron_task = vapp.poweron() if type(poweron_task) is GenericTask: - vca.block_until_completed(poweron_task) + self.vca.block_until_completed(poweron_task) except Exception as exp : # it might be a case if specific mandatory entry in dict is empty or some other pyVcloud exception - self.logger.debug("new_vminstance(): Failed create new vm instance {}".format(name, exp)) - raise vimconn.vimconnException("new_vminstance(): Failed create new vm instance {}".format(name, exp)) + self.logger.debug("new_vminstance(): Failed create new vm instance {} with exception {}" + .format(name, exp)) + raise vimconn.vimconnException("new_vminstance(): Failed create new vm instance {} with exception {}" + .format(name, exp)) # check if vApp deployed and if that the case return vApp UUID otherwise -1 wait_time = 0 vapp_uuid = None while wait_time <= MAX_WAIT_TIME: try: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) + vapp = self.vca.get_vapp(self.get_vdc_details(), vmname_andid) except Exception as exp: raise vimconn.vimconnUnexpectedResponse( "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}" .format(vmname_andid, exp)) if vapp and vapp.me.deployed: - vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid) + vapp_uuid = self.get_vappid(self.get_vdc_details(), vmname_andid) break else: self.logger.debug("new_vminstance(): Wait for vApp {} to deploy".format(name)) @@ -1599,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)) @@ -1622,11 +1794,8 @@ class vimconnector(vimconn.vimconnector): """Returns the VM instance information from VIM""" self.logger.debug("Client requesting vm instance {} ".format(vim_vm_uuid)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: raise vimconn.vimconnConnectionException( "Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)) @@ -1660,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: @@ -1671,11 +1840,8 @@ class vimconnector(vimconn.vimconnector): """ self.logger.debug("Client requesting delete vm instance {} ".format(vm__vim_uuid)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: self.logger.debug("delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format( self.tenant_name)) @@ -1683,7 +1849,7 @@ class vimconnector(vimconn.vimconnector): "delete_vminstance(): Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)) try: - vapp_name = self.get_namebyvappid(vca, vdc, vm__vim_uuid) + vapp_name = self.get_namebyvappid(vdc, vm__vim_uuid) if vapp_name is None: self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid) @@ -1691,7 +1857,7 @@ class vimconnector(vimconn.vimconnector): self.logger.info("Deleting vApp {} and UUID {}".format(vapp_name, vm__vim_uuid)) # Delete vApp and wait for status change if task executed and vApp is None. - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) + vapp = self.vca.get_vapp(self.get_vdc_details(), vapp_name) if vapp: if vapp.me.deployed: @@ -1700,14 +1866,14 @@ class vimconnector(vimconn.vimconnector): powered_off = False wait_time = 0 while wait_time <= MAX_WAIT_TIME: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) + vapp = self.vca.get_vapp(self.get_vdc_details(), vapp_name) if not vapp: self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid) power_off_task = vapp.poweroff() if type(power_off_task) is GenericTask: - result = vca.block_until_completed(power_off_task) + result = self.vca.block_until_completed(power_off_task) if result: powered_off = True break @@ -1726,14 +1892,14 @@ class vimconnector(vimconn.vimconnector): wait_time = 0 undeployed = False while wait_time <= MAX_WAIT_TIME: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) + vapp = self.vca.get_vapp(self.get_vdc_details(), vapp_name) if not vapp: self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid) undeploy_task = vapp.undeploy(action='powerOff') if type(undeploy_task) is GenericTask: - result = vca.block_until_completed(undeploy_task) + result = self.vca.block_until_completed(undeploy_task) if result: undeployed = True break @@ -1748,14 +1914,14 @@ class vimconnector(vimconn.vimconnector): # delete vapp self.logger.info("Start deletion of vApp {} ".format(vapp_name)) - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) + vapp = self.vca.get_vapp(self.get_vdc_details(), vapp_name) if vapp is not None: wait_time = 0 result = False while wait_time <= MAX_WAIT_TIME: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) + vapp = self.vca.get_vapp(self.get_vdc_details(), vapp_name) if not vapp: self.logger.debug("delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) return -1, "delete_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid) @@ -1763,8 +1929,8 @@ class vimconnector(vimconn.vimconnector): delete_task = vapp.delete() if type(delete_task) is GenericTask: - vca.block_until_completed(delete_task) - result = vca.block_until_completed(delete_task) + self.vca.block_until_completed(delete_task) + result = self.vca.block_until_completed(delete_task) if result: break else: @@ -1780,7 +1946,7 @@ class vimconnector(vimconn.vimconnector): self.logger.debug(traceback.format_exc()) raise vimconn.vimconnException("delete_vminstance(): Failed delete vm instance {}".format(vm__vim_uuid)) - if vca.get_vapp(vca.get_vdc(self.tenant_name), vapp_name) is None: + if self.vca.get_vapp(self.get_vdc_details(), vapp_name) is None: self.logger.info("Deleted vm instance {} sccessfully".format(vm__vim_uuid)) return vm__vim_uuid else: @@ -1812,25 +1978,21 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Client requesting refresh vm status for {} ".format(vm_list)) - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - - vdc = vca.get_vdc(self.tenant_name) + vdc = self.get_vdc_details() if vdc is None: raise vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)) vms_dict = {} nsx_edge_list = [] for vmuuid in vm_list: - vmname = self.get_namebyvappid(vca, vdc, vmuuid) + vmname = self.get_namebyvappid(self.get_vdc_details(), vmuuid) if vmname is not None: try: - the_vapp = vca.get_vapp(vdc, vmname) + vm_pci_details = self.get_vm_pci_details(vmuuid) + the_vapp = self.vca.get_vapp(self.get_vdc_details(), vmname) vm_info = the_vapp.get_vms_details() vm_status = vm_info[0]['status'] - vm_pci_details = self.get_vm_pci_details(vmuuid) vm_info[0].update(vm_pci_details) vm_dict = {'status': vcdStatusCode2manoFormat[the_vapp.me.get_status()], @@ -1971,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""" @@ -1979,15 +2141,11 @@ class vimconnector(vimconn.vimconnector): if vm__vim_uuid is None or action_dict is None: raise vimconn.vimconnException("Invalid request. VM id or action is None.") - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - - vdc = vca.get_vdc(self.tenant_name) + 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(vca, vdc, vm__vim_uuid) + vapp_name = self.get_namebyvappid(vdc, vm__vim_uuid) if vapp_name is None: self.logger.debug("action_vminstance(): Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) raise vimconn.vimconnException("Failed to get vm by given {} vm uuid".format(vm__vim_uuid)) @@ -1995,7 +2153,7 @@ class vimconnector(vimconn.vimconnector): self.logger.info("Action_vminstance vApp {} and UUID {}".format(vapp_name, vm__vim_uuid)) try: - the_vapp = vca.get_vapp(vdc, vapp_name) + the_vapp = self.vca.get_vapp(vdc, vapp_name) # TODO fix all status if "start" in action_dict: vm_info = the_vapp.get_vms_details() @@ -2003,28 +2161,28 @@ class vimconnector(vimconn.vimconnector): self.logger.info("action_vminstance: Power on vApp: {}".format(vapp_name)) if vm_status == "Suspended" or vm_status == "Powered off": power_on_task = the_vapp.poweron() - result = vca.block_until_completed(power_on_task) + result = self.vca.block_until_completed(power_on_task) self.instance_actions_result("start", result, vapp_name) elif "rebuild" in action_dict: self.logger.info("action_vminstance: Rebuild vApp: {}".format(vapp_name)) rebuild_task = the_vapp.deploy(powerOn=True) - result = vca.block_until_completed(rebuild_task) + result = self.vca.block_until_completed(rebuild_task) self.instance_actions_result("rebuild", result, vapp_name) elif "pause" in action_dict: self.logger.info("action_vminstance: pause vApp: {}".format(vapp_name)) pause_task = the_vapp.undeploy(action='suspend') - result = vca.block_until_completed(pause_task) + result = self.vca.block_until_completed(pause_task) self.instance_actions_result("pause", result, vapp_name) elif "resume" in action_dict: self.logger.info("action_vminstance: resume vApp: {}".format(vapp_name)) power_task = the_vapp.poweron() - result = vca.block_until_completed(power_task) + result = self.vca.block_until_completed(power_task) self.instance_actions_result("resume", result, vapp_name) elif "shutoff" in action_dict or "shutdown" in action_dict: action_name , value = action_dict.items()[0] self.logger.info("action_vminstance: {} vApp: {}".format(action_name, vapp_name)) power_off_task = the_vapp.undeploy(action='powerOff') - result = vca.block_until_completed(power_off_task) + result = self.vca.block_until_completed(power_off_task) if action_name == "shutdown": self.instance_actions_result("shutdown", result, vapp_name) else: @@ -2037,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)) @@ -2121,10 +2279,6 @@ class vimconnector(vimconn.vimconnector): The return network name. """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - if not network_uuid: return None @@ -2151,10 +2305,6 @@ class vimconnector(vimconn.vimconnector): network_uuid: network_id """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed.") - if not network_name: self.logger.debug("get_network_id_by_name() : Network name is empty") return None @@ -2184,18 +2334,18 @@ class vimconnector(vimconn.vimconnector): The return XML respond """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - - url_list = [vca.host, '/api/org'] + url_list = [self.vca.host, '/api/org'] vm_list_rest_call = ''.join(url_list) - if not (not vca.vcloud_session or not vca.vcloud_session.organization): + if not (not self.vca.vcloud_session or not self.vca.vcloud_session.organization): response = Http.get(url=vm_list_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + response = self.retry_rest('GET', vm_list_rest_call) + if response.status_code == requests.codes.ok: return response.content @@ -2213,21 +2363,22 @@ class vimconnector(vimconn.vimconnector): The return XML respond """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - if org_uuid is None: return None - url_list = [vca.host, '/api/org/', org_uuid] + url_list = [self.vca.host, '/api/org/', org_uuid] vm_list_rest_call = ''.join(url_list) - if not (not vca.vcloud_session or not vca.vcloud_session.organization): + if not (not self.vca.vcloud_session or not self.vca.vcloud_session.organization): response = Http.get(url=vm_list_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + #Retry login if session expired & retry sending request + if response.status_code == 403: + response = self.retry_rest('GET', vm_list_rest_call) + if response.status_code == requests.codes.ok: return response.content @@ -2248,9 +2399,6 @@ class vimconnector(vimconn.vimconnector): """ org_dict = {} - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") if org_uuid is None: return org_dict @@ -2288,9 +2436,6 @@ class vimconnector(vimconn.vimconnector): """ org_dict = {} - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") content = self.list_org_action() try: @@ -2462,21 +2607,22 @@ class vimconnector(vimconn.vimconnector): The return XML respond """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - if network_uuid is None: return None - url_list = [vca.host, '/api/network/', network_uuid] + url_list = [self.vca.host, '/api/network/', network_uuid] vm_list_rest_call = ''.join(url_list) - if not (not vca.vcloud_session or not vca.vcloud_session.organization): + if not (not self.vca.vcloud_session or not self.vca.vcloud_session.organization): response = Http.get(url=vm_list_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + #Retry login if session expired & retry sending request + if response.status_code == 403: + response = self.retry_rest('GET', vm_list_rest_call) + if response.status_code == requests.codes.ok: return response.content @@ -2746,6 +2892,11 @@ class vimconnector(vimconn.vimconnector): #Unused in case of Underlay (data/ptp) network interface. fence_mode="bridged" is_inherited='false' + dns_list = dns_address.split(";") + dns1 = dns_list[0] + dns2_text = "" + if len(dns_list) >= 2: + dns2_text = "\n {}\n".format(dns_list[1]) data = """ Openmano created @@ -2754,22 +2905,22 @@ class vimconnector(vimconn.vimconnector): {1:s} {2:s} {3:s} - {4:s} - {5:s} + {4:s}{5:s} + {6:s} - {6:s} - {7:s} + {7:s} + {8:s} - - {9:s} + + {10:s} - {10:s} + {11:s} """.format(escape(network_name), is_inherited, gateway_address, - subnet_address, dns_address, dhcp_enabled, + subnet_address, dns1, dns2_text, dhcp_enabled, dhcp_start_address, dhcp_end_address, available_networks, fence_mode, isshared) @@ -2896,8 +3047,7 @@ class vimconnector(vimconn.vimconnector): # application/vnd.vmware.admin.providervdc+xml # we need find a template from witch we instantiate VDC if child.tag.split("}")[1] == 'VdcTemplate': - if child.attrib.get('type') == 'application/vnd.vmware.admin.vdcTemplate+xml' and child.attrib.get( - 'name') == 'openmano': + if child.attrib.get('type') == 'application/vnd.vmware.admin.vdcTemplate+xml': vdc_template_ref = child.attrib.get('href') except: self.logger.debug("Failed parse respond for rest api call {}".format(vm_list_rest_call)) @@ -2920,6 +3070,11 @@ class vimconnector(vimconn.vimconnector): headers['Content-Type'] = 'application/vnd.vmware.vcloud.instantiateVdcTemplateParams+xml' response = Http.post(url=vm_list_rest_call, headers=headers, data=data, verify=vca.verify, logger=vca.logger) + + vdc_task = taskType.parseString(response.content, True) + if type(vdc_task) is GenericTask: + self.vca.block_until_completed(vdc_task) + # if we all ok we respond with content otherwise by default None if response.status_code >= 200 and response.status_code < 300: return response.content @@ -3034,7 +3189,7 @@ class vimconnector(vimconn.vimconnector): if need_admin_access: vca = self.connect_as_admin() else: - vca = self.connect() + vca = self.vca if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed") @@ -3050,6 +3205,10 @@ class vimconnector(vimconn.vimconnector): verify=vca.verify, logger=vca.logger) + if response.status_code == 403: + if need_admin_access == False: + response = self.retry_rest('GET', get_vapp_restcall) + if response.status_code != requests.codes.ok: self.logger.debug("REST API call {} failed. Return status code {}".format(get_vapp_restcall, response.status_code)) @@ -3156,21 +3315,20 @@ class vimconnector(vimconn.vimconnector): def acuire_console(self, vm_uuid=None): - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") if vm_uuid is None: return None - if not (not vca.vcloud_session or not vca.vcloud_session.organization): + if not (not self.vca.vcloud_session or not self.vca.vcloud_session.organization): vm_dict = self.get_vapp_details_rest(self, vapp_uuid=vm_uuid) console_dict = vm_dict['acquireTicket'] console_rest_call = console_dict['href'] response = Http.post(url=console_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + if response.status_code == 403: + response = self.retry_rest('POST', console_rest_call) if response.status_code == requests.codes.ok: return response.content @@ -3227,17 +3385,17 @@ class vimconnector(vimconn.vimconnector): Returns: The return network uuid or return None """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") if disk_href is None or disk_size is None: return None - if vca.vcloud_session and vca.vcloud_session.organization: + if self.vca.vcloud_session and self.vca.vcloud_session.organization: response = Http.get(url=disk_href, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + response = self.retry_rest('GET', disk_href) if response.status_code != requests.codes.ok: self.logger.debug("GET REST API call {} failed. Return status code {}".format(disk_href, @@ -3259,13 +3417,17 @@ class vimconnector(vimconn.vimconnector): xml_declaration=True) #Send PUT request to modify disk size - headers = vca.vcloud_session.get_vcloud_headers() + headers = self.vca.vcloud_session.get_vcloud_headers() headers['Content-Type'] = 'application/vnd.vmware.vcloud.rasdItemsList+xml; charset=ISO-8859-1' response = Http.put(url=disk_href, data=data, headers=headers, - verify=vca.verify, logger=self.logger) + verify=self.vca.verify, logger=self.logger) + + if response.status_code == 403: + add_headers = {'Content-Type': headers['Content-Type']} + response = self.retry_rest('PUT', disk_href, add_headers, data) if response.status_code != 202: self.logger.debug("PUT REST API call {} failed. Return status code {}".format(disk_href, @@ -3273,7 +3435,7 @@ class vimconnector(vimconn.vimconnector): else: modify_disk_task = taskType.parseString(response.content, True) if type(modify_disk_task) is GenericTask: - status = vca.block_until_completed(modify_disk_task) + status = self.vca.block_until_completed(modify_disk_task) return status return None @@ -3658,9 +3820,6 @@ class vimconnector(vimconn.vimconnector): Returns: None """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("Failed to connect vCloud director") try: ip_address = None @@ -3681,12 +3840,16 @@ class vimconnector(vimconn.vimconnector): for vms in vapp._get_vms(): vm_id = (vms.id).split(':')[-1] - url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(vca.host, vm_id) + url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(self.vca.host, vm_id) response = Http.get(url=url_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + response = self.retry_rest('GET', url_rest_call) + if response.status_code != 200: self.logger.error("REST call {} failed reason : {}"\ "status code : {}".format(url_rest_call, @@ -3724,11 +3887,16 @@ class vimconnector(vimconn.vimconnector): data = data.replace('\n','\n{}\n'.format(new_item)) - headers = vca.vcloud_session.get_vcloud_headers() + headers = self.vca.vcloud_session.get_vcloud_headers() headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml' response = Http.put(url=url_rest_call, headers=headers, data=data, - verify=vca.verify, - logger=vca.logger) + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + add_headers = {'Content-Type': headers['Content-Type']} + response = self.retry_rest('PUT', url_rest_call, add_headers, data) + if response.status_code != 202: self.logger.error("REST call {} failed reason : {}"\ "status code : {} ".format(url_rest_call, @@ -3739,7 +3907,7 @@ class vimconnector(vimconn.vimconnector): else: nic_task = taskType.parseString(response.content, True) if isinstance(nic_task, GenericTask): - vca.block_until_completed(nic_task) + self.vca.block_until_completed(nic_task) self.logger.info("add_network_adapter_to_vms(): VM {} conneced to "\ "default NIC type".format(vm_id)) else: @@ -3749,12 +3917,16 @@ class vimconnector(vimconn.vimconnector): for vms in vapp._get_vms(): vm_id = (vms.id).split(':')[-1] - url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(vca.host, vm_id) + url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(self.vca.host, vm_id) response = Http.get(url=url_rest_call, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + response = self.retry_rest('GET', url_rest_call) + if response.status_code != 200: self.logger.error("REST call {} failed reason : {}"\ "status code : {}".format(url_rest_call, @@ -3793,11 +3965,15 @@ class vimconnector(vimconn.vimconnector): data = data.replace('\n','\n{}\n'.format(new_item)) - headers = vca.vcloud_session.get_vcloud_headers() + headers = self.vca.vcloud_session.get_vcloud_headers() headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml' response = Http.put(url=url_rest_call, headers=headers, data=data, - verify=vca.verify, - logger=vca.logger) + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + add_headers = {'Content-Type': headers['Content-Type']} + response = self.retry_rest('PUT', url_rest_call, add_headers, data) if response.status_code != 202: self.logger.error("REST call {} failed reason : {}"\ @@ -3809,7 +3985,7 @@ class vimconnector(vimconn.vimconnector): else: nic_task = taskType.parseString(response.content, True) if isinstance(nic_task, GenericTask): - vca.block_until_completed(nic_task) + self.vca.block_until_completed(nic_task) self.logger.info("add_network_adapter_to_vms(): VM {} "\ "conneced to NIC type {}".format(vm_id, nic_type)) else: @@ -3872,7 +4048,6 @@ class vimconnector(vimconn.vimconnector): "affinity".format(exp)) - def cloud_init(self, vapp, cloud_config): """ Method to inject ssh-key @@ -3882,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: @@ -3892,12 +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 """ - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("Failed to connect vCloud director") - 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: @@ -3906,70 +4080,96 @@ 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): - 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, vca, vapp_uuid, disk_size): + def add_new_disk(self, vapp_uuid, disk_size): """ Method to create an empty vm disk @@ -3991,7 +4191,7 @@ class vimconnector(vimconn.vimconnector): if vm_details and "vm_virtual_hardware" in vm_details: self.logger.info("Adding disk to VM: {} disk size:{}GB".format(vm_details["name"], disk_size)) disk_href = vm_details["vm_virtual_hardware"]["disk_edit_href"] - status = self.add_new_disk_rest(vca, disk_href, disk_size_mb) + status = self.add_new_disk_rest(disk_href, disk_size_mb) except Exception as exp: msg = "Error occurred while creating new disk {}.".format(exp) @@ -4005,7 +4205,7 @@ class vimconnector(vimconn.vimconnector): self.rollback_newvm(vapp_uuid, msg) - def add_new_disk_rest(self, vca, disk_href, disk_size_mb): + def add_new_disk_rest(self, disk_href, disk_size_mb): """ Retrives vApp Disks section & add new empty disk @@ -4016,11 +4216,14 @@ class vimconnector(vimconn.vimconnector): Returns: Status of add new disk task """ status = False - if vca.vcloud_session and vca.vcloud_session.organization: + if self.vca.vcloud_session and self.vca.vcloud_session.organization: response = Http.get(url=disk_href, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) + headers=self.vca.vcloud_session.get_vcloud_headers(), + verify=self.vca.verify, + logger=self.vca.logger) + + if response.status_code == 403: + response = self.retry_rest('GET', disk_href) if response.status_code != requests.codes.ok: self.logger.error("add_new_disk_rest: GET REST API call {} failed. Return status code {}" @@ -4059,13 +4262,17 @@ class vimconnector(vimconn.vimconnector): new_data = new_data.replace('\n', '\n{}\n'.format(new_item)) # Send PUT request to modify virtual hardware section with new disk - headers = vca.vcloud_session.get_vcloud_headers() + headers = self.vca.vcloud_session.get_vcloud_headers() headers['Content-Type'] = 'application/vnd.vmware.vcloud.rasdItemsList+xml; charset=ISO-8859-1' response = Http.put(url=disk_href, data=new_data, headers=headers, - verify=vca.verify, logger=self.logger) + verify=self.vca.verify, logger=self.logger) + + if response.status_code == 403: + add_headers = {'Content-Type': headers['Content-Type']} + response = self.retry_rest('PUT', disk_href, add_headers, new_data) if response.status_code != 202: self.logger.error("PUT REST API call {} failed. Return status code {}. Response Content:{}" @@ -4073,7 +4280,7 @@ class vimconnector(vimconn.vimconnector): else: add_disk_task = taskType.parseString(response.content, True) if type(add_disk_task) is GenericTask: - status = vca.block_until_completed(add_disk_task) + status = self.vca.block_until_completed(add_disk_task) if not status: self.logger.error("Add new disk REST task failed to add {} MB disk".format(disk_size_mb)) @@ -4430,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) @@ -4788,3 +4995,212 @@ class vimconnector(vimconn.vimconnector): break return obj + + def insert_media_to_vm(self, vapp, image_id): + """ + Method to insert media CD-ROM (ISO image) from catalog to vm. + vapp - vapp object to get vm id + Image_id - image id for cdrom to be inerted to vm + """ + # create connection object + vca = self.connect() + try: + # fetching catalog details + rest_url = "{}/api/catalog/{}".format(vca.host,image_id) + response = Http.get(url=rest_url, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + + if response.status_code != 200: + self.logger.error("REST call {} failed reason : {}"\ + "status code : {}".format(url_rest_call, + response.content, + response.status_code)) + raise vimconn.vimconnException("insert_media_to_vm(): Failed to get "\ + "catalog details") + # searching iso name and id + iso_name,media_id = self.get_media_details(vca, response.content) + + if iso_name and media_id: + data =""" + + + """.format(iso_name, media_id, + vca.host,media_id) + + for vms in vapp._get_vms(): + vm_id = (vms.id).split(':')[-1] + + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Type'] = 'application/vnd.vmware.vcloud.mediaInsertOrEjectParams+xml' + rest_url = "{}/api/vApp/vm-{}/media/action/insertMedia".format(vca.host,vm_id) + + response = Http.post(url=rest_url, + headers=headers, + data=data, + verify=vca.verify, + logger=vca.logger) + + if response.status_code != 202: + self.logger.error("Failed to insert CD-ROM to vm") + raise vimconn.vimconnException("insert_media_to_vm() : Failed to insert"\ + "ISO image to vm") + else: + task = taskType.parseString(response.content, True) + if isinstance(task, GenericTask): + vca.block_until_completed(task) + self.logger.info("insert_media_to_vm(): Sucessfully inserted media ISO"\ + " image to vm {}".format(vm_id)) + except Exception as exp: + self.logger.error("insert_media_to_vm() : exception occurred "\ + "while inserting media CD-ROM") + raise vimconn.vimconnException(message=exp) + + + def get_media_details(self, vca, content): + """ + Method to get catalog item details + vca - connection object + content - Catalog details + Return - Media name, media id + """ + cataloghref_list = [] + try: + if content: + vm_list_xmlroot = XmlElementTree.fromstring(content) + for child in vm_list_xmlroot.iter(): + if 'CatalogItem' in child.tag: + cataloghref_list.append(child.attrib.get('href')) + if cataloghref_list is not None: + for href in cataloghref_list: + if href: + response = Http.get(url=href, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + if response.status_code != 200: + self.logger.error("REST call {} failed reason : {}"\ + "status code : {}".format(href, + response.content, + response.status_code)) + raise vimconn.vimconnException("get_media_details : Failed to get "\ + "catalogitem details") + list_xmlroot = XmlElementTree.fromstring(response.content) + for child in list_xmlroot.iter(): + if 'Entity' in child.tag: + if 'media' in child.attrib.get('href'): + name = child.attrib.get('name') + media_id = child.attrib.get('href').split('/').pop() + return name,media_id + else: + self.logger.debug("Media name and id not found") + return False,False + except Exception as exp: + self.logger.error("get_media_details : exception occurred "\ + "getting media details") + raise vimconn.vimconnException(message=exp) + + + def retry_rest(self, method, url, add_headers=None, data=None): + """ Method to get Token & retry respective REST request + Args: + api - REST API - Can be one of 'GET' or 'PUT' or 'POST' + url - request url to be used + add_headers - Additional headers (optional) + data - Request payload data to be passed in request + Returns: + response - Response of request + """ + response = None + + #Get token + self.get_token() + + headers=self.vca.vcloud_session.get_vcloud_headers() + + if add_headers: + headers.update(add_headers) + + if method == 'GET': + response = Http.get(url=url, + headers=headers, + verify=self.vca.verify, + logger=self.vca.logger) + elif method == 'PUT': + response = Http.put(url=url, + data=data, + headers=headers, + verify=self.vca.verify, + logger=self.logger) + elif method == 'POST': + response = Http.post(url=url, + headers=headers, + data=data, + verify=self.vca.verify, + logger=self.vca.logger) + elif method == 'DELETE': + response = Http.delete(url=url, + headers=headers, + verify=self.vca.verify, + logger=self.vca.logger) + return response + + + def get_token(self): + """ Generate a new token if expired + + Returns: + The return vca object that letter can be used to connect to vCloud director as admin for VDC + """ + vca = None + + try: + self.logger.debug("Generate token for vca {} as {} to datacenter {}.".format(self.org_name, + self.user, + self.org_name)) + vca = VCA(host=self.url, + username=self.user, + service_type=STANDALONE, + version=VCAVERSION, + verify=False, + log=False) + + result = vca.login(password=self.passwd, org=self.org_name) + if result is True: + result = vca.login(token=vca.token, org=self.org_name, org_url=vca.vcloud_session.org_url) + if result is True: + self.logger.info( + "Successfully generated token for vcloud direct org: {} as user: {}".format(self.org_name, self.user)) + #Update vca + self.vca = vca + return + + except: + raise vimconn.vimconnConnectionException("Can't connect to a vCloud director org: " + "{} as user: {}".format(self.org_name, self.user)) + + if not vca or not result: + raise vimconn.vimconnConnectionException("self.connect() is failed while reconnecting") + + + def get_vdc_details(self): + """ Get VDC details using pyVcloud Lib + + Returns vdc object + """ + vdc = self.vca.get_vdc(self.tenant_name) + + #Retry once, if failed by refreshing token + if vdc is None: + self.get_token() + vdc = self.vca.get_vdc(self.tenant_name) + + return vdc + +