From b6ffe795090a19b8a5155f63010b3ee725896d0a Mon Sep 17 00:00:00 2001 From: bayramov Date: Wed, 28 Sep 2016 11:50:56 +0400 Subject: [PATCH] Refactored code and change return error code to exceptions. Adjusted class now it inherited from base. In vmwarecli added - Provides capability to create and delete VDC for specific organization. - Create, delete and manage network for specific VDC - List deployed VM's , VAPPs, VDSs, Organization - View detail information about VM / Vapp , Organization etc - Operate with images upload / boot / power on etc Signed-off-by: bayramov --- vimconn_vmware.py | 342 +++++++++++++++++++++++------------- vmwarerecli.py | 437 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 576 insertions(+), 203 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index 99f50432..ff457ce7 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -104,6 +104,37 @@ flavorlist = {} class vimconnector(vimconn.vimconnector): def __init__(self, uuid=None, name=None, tenant_id=None, tenant_name=None, url=None, url_admin=None, user=None, passwd=None, log_level="ERROR", config={}): + """ + Constructor create vmware connector to vCloud director. + + By default construct doesn't validate connection state. So client can create object with None arguments. + If client specified username , password and host and VDC name. Connector initialize other missing attributes. + + a) It initialize organization UUID + b) Initialize tenant_id/vdc ID. (This information derived from tenant name) + + Args: + uuid - is organization uuid. + name - is organization name that must be presented in vCloud director. + tenant_id - is VDC uuid it must be presented in vCloud director + tenant_name - is VDC name. + url - is hostname or ip address of vCloud director + url_admin - same as above. + user - is user that administrator for organization. Caller must make sure that + username has right privileges. + + password - is password for a user. + + VMware connector also requires PVDC administrative privileges and separate account. + This variables must be passed via config argument dict contains keys + + dict['admin_username'] + dict['admin_password'] + + Returns: + Nothing. + """ + vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config) self.id = uuid @@ -202,14 +233,15 @@ class vimconnector(vimconn.vimconnector): raise KeyError("Invalid key '%s'" % str(index)) def connect_as_admin(self): - """ Method connect as admin user to vCloud director. + """ Method connect as pvdc admin user to vCloud director. + There are certain action that can be done only by provider vdc admin user. + Organization creation / provider network creation etc. Returns: The return vca object that letter can be used to connect to vcloud direct as admin for provider vdc """ self.logger.debug("Logging in to a vca {} as admin.".format(self.name)) - service_type = STANDALONE vca_admin = VCA(host=self.url, username=self.admin_user, @@ -235,22 +267,24 @@ class vimconnector(vimconn.vimconnector): The return vca object that letter can be used to connect to vCloud director as admin for VDC """ - service_type = 'standalone' - version = '5.9' - - self.logger.debug("Logging in to a vca {} as {} to datacenter {}.".format(self.name, self.user, self.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.name) - if not result: + try: + self.logger.debug("Logging in to a vca {} as {} to datacenter {}.".format(self.name, self.user, self.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.name) + if not result: + raise vimconn.vimconnConnectionException("Can't connect to a vCloud director as: {}".format(self.user)) + result = vca.login(token=vca.token, org=self.name, org_url=vca.vcloud_session.org_url) + if result is True: + self.logger.info("Successfully logged to a vcloud direct org: {} as user: {}".format(self.name, self.user)) + + except: raise vimconn.vimconnConnectionException("Can't connect to a vCloud director as: {}".format(self.user)) - result = vca.login(token=vca.token, org=self.name, org_url=vca.vcloud_session.org_url) - if result is True: - self.logger.info("Successfully logged to a vcloud direct org: {} as user: {}".format(self.name, self.user)) return vca @@ -283,12 +317,8 @@ class vimconnector(vimconn.vimconnector): # we have two case if we want to initialize VDC ID or VDC name at run time # tenant_name provided but no tenant id - print ("Setting vdc from name {} {} {} {}".format(self.tenant_id, self.tenant_name, - org_details_dict.has_key('vdcs'), org_details_dict)) if self.tenant_id is None and self.tenant_name is not None and org_details_dict.has_key('vdcs'): - print ("Setting vdc from name") vdcs_dict = org_details_dict['vdcs'] - print ("Searching vdc UUUID") for vdc in vdcs_dict: if vdcs_dict[vdc] == self.tenant_name: self.tenant_id = vdc @@ -299,7 +329,6 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnException("Tenant name indicated but not present in vcloud director.") # case two we have tenant_id but we don't have tenant name so we find and set it. if self.tenant_id is not None and self.tenant_name is None and org_details_dict.has_key('vdcs'): - print ("setting vdc from id") vdcs_dict = org_details_dict['vdcs'] for vdc in vdcs_dict: if vdc == self.tenant_id: @@ -316,11 +345,16 @@ class vimconnector(vimconn.vimconnector): self.org_uuid = None def new_tenant(self, tenant_name=None, tenant_description=None): - """Adds a new tenant to VIM with this name and description + """ Method adds a new tenant to VIM with this name. + This action requires access to create VDC action in vCloud director. - :param tenant_name: - :param tenant_description: - :return: returns the tenant identifier + Args: + tenant_name is tenant_name to be created. + tenant_description not used for this call + + Return: + returns the tenant identifier in UUID format. + If action is failed method will throw vimconn.vimconnException method """ vdc_task = self.create_vdc(vdc_name=tenant_name) if vdc_task is not None: @@ -336,16 +370,15 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnNotImplemented("Should have implemented this") def get_tenant_list(self, filter_dict={}): - '''Obtain tenants of VIM + """Obtain tenants of VIM filter_dict can contain the following keys: name: filter by tenant name id: filter by tenant uuid/id - Returns the tenant list of dictionaries: + Returns the tenant list of dictionaries: [{'name':', 'id':', ...}, ...] - ''' - + """ org_dict = self.get_org(self.org_uuid) vdcs_dict = org_dict['vdcs'] @@ -353,25 +386,29 @@ class vimconnector(vimconn.vimconnector): try: for k in vdcs_dict: entry = {'name': vdcs_dict[k], 'id': k} - filtered_entry = entry.copy() - filtered_dict = set(entry.keys()) - set(filter_dict) - for unwanted_key in filtered_dict: del entry[unwanted_key] - if filter_dict == entry: - vdclist.append(filtered_entry) + # if caller didn't specify dictionary we return all tenants. + if filter_dict is not None and filter_dict: + filtered_entry = entry.copy() + filtered_dict = set(entry.keys()) - set(filter_dict) + for unwanted_key in filtered_dict: del entry[unwanted_key] + if filter_dict == entry: + vdclist.append(filtered_entry) + else: + vdclist.append(entry) except: self.logger.debug("Error in get_tenant_list()") self.logger.debug(traceback.format_exc()) - pass + raise vimconn.vimconnException("Incorrect state. {}") return vdclist def new_network(self, net_name, net_type, ip_profile=None, shared=False): - '''Adds a tenant network to VIM + """Adds a tenant network to VIM net_name is the name net_type can be 'bridge','data'.'ptp'. TODO: this need to be revised - ip_profile is a dict containing the IP parameters of the network + ip_profile is a dict containing the IP parameters of the network shared is a boolean - Returns the network identifier''' + Returns the network identifier""" self.logger.debug( "new_network tenant {} net_type {} ip_profile {} shared {}".format(net_name, net_type, ip_profile, shared)) @@ -430,7 +467,7 @@ class vimconnector(vimconn.vimconnector): return network_list def get_network_list(self, filter_dict={}): - '''Obtain tenant networks of VIM + """Obtain tenant networks of VIM Filter_dict can be: name: network name OR/AND id: network uuid OR/AND @@ -444,7 +481,7 @@ class vimconnector(vimconn.vimconnector): Returns the network list of dictionaries: [{}, ...] List can be empty - ''' + """ vca = self.connect() if not vca: @@ -480,11 +517,14 @@ class vimconnector(vimconn.vimconnector): filter_entry["type"] = "bridge" filtered_entry = filter_entry.copy() - # we remove all the key : value we dont' care and match only - # respected field - filtered_dict = set(filter_entry.keys()) - set(filter_dict) - for unwanted_key in filtered_dict: del filter_entry[unwanted_key] - if filter_dict == filter_entry: + if filter_dict is not None and filter_dict: + # we remove all the key : value we don't care and match only + # respected field + filtered_dict = set(filter_entry.keys()) - set(filter_dict) + for unwanted_key in filtered_dict: del filter_entry[unwanted_key] + if filter_dict == filter_entry: + network_list.append(filtered_entry) + else: network_list.append(filtered_entry) except: self.logger.debug("Error in get_vcd_network_list") @@ -662,13 +702,26 @@ class vimconnector(vimconn.vimconnector): return True return False - def create_vimcatalog(self, vca, catalog_name): - """Create Catalog entry in VIM""" - task = vca.create_catalog(catalog_name, catalog_name) - result = vca.block_until_completed(task) - if not result: + def create_vimcatalog(self, vca=None, catalog_name=None): + """ Create new catalog entry in vcloud director. + + + Args + vca: vCloud director. + catalog_name catalog that client wish to create. Note no validation done for a name. + Client must make sure that provide valid string representation. + + Return (bool) True if catalog created. + + """ + try: + task = vca.create_catalog(catalog_name, catalog_name) + result = vca.block_until_completed(task) + if not result: + return False + catalogs = vca.get_catalogs() + except: return False - catalogs = vca.get_catalogs() return self.catalog_exists(catalog_name, catalogs) def upload_ovf(self, vca, catalog_name, item_name, media_file_name, description='', display_progress=False, @@ -795,28 +848,68 @@ class vimconnector(vimconn.vimconnector): # TODO add named parameters for readbility return self.upload_ovf(vca, catalog_name, media_name.split(".")[0], medial_file_name, medial_file_name, True) - def get_catalogid(self, catalog_name, catalogs): + def validate_uuid4(self, uuid_string=None): + """ Method validate correct format of UUID. + + Return: true if string represent valid uuid + """ + try: + val = uuid.UUID(uuid_string, version=4) + except ValueError: + return False + return True + + def get_catalogid(self, catalog_name=None, catalogs=None): + """ Method check catalog and return catalog ID in UUID format. + + Args + catalog_name: catalog name as string + catalogs: list of catalogs. + + Return: catalogs uuid + """ + for catalog in catalogs: if catalog.name == catalog_name: catalog_id = catalog.get_id().split(":") return catalog_id[3] return None - def get_catalogbyid(self, catalog_id, catalogs): + def get_catalogbyid(self, catalog_uuid=None, catalogs=None): + """ Method check catalog and return catalog name lookup done by catalog UUID. + + Args + catalog_name: catalog name as string + catalogs: list of catalogs. + + Return: catalogs name or None + """ + + if not self.validate_uuid4(uuid_string=catalog_uuid): + return None + for catalog in catalogs: - catalogid = catalog.get_id().split(":")[3] - if catalogid == catalog_id: + catalog_id = catalog.get_id().split(":")[3] + if catalog_id == catalog_uuid: return catalog.name return None - def get_image_id_from_path(self, path): - '''Get the image id from image path in the VIM database''' - '''Returns: - 0,"Image not found" if there are no images with that path - 1,image-id if there is one image with that path - <0,message if there was an error (Image not found, error contacting VIM, more than 1 image with that path, etc.) - ''' + def get_image_id_from_path(self, path=None): + """ Method upload OVF image to vCloud director. + + Each OVF image represented as single catalog entry in vcloud director. + The method check for existing catalog entry. The check done by file name without file extension. + + if given catalog name already present method will respond with existing catalog uuid otherwise + it will create new catalog entry and upload OVF file to newly created catalog. + + If method can't create catalog entry or upload a file it will throw exception. + Args + path: valid path to OVF file. + + Return: if image uploaded correct method will provide image catalog UUID. + """ vca = self.connect() if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed") @@ -827,9 +920,8 @@ class vimconnector(vimconn.vimconnector): flname, file_extension = os.path.splitext(path) if file_extension != '.ovf': self.logger.debug("Wrong file extension {}".format(file_extension)) - return -1, "Wrong container. vCloud director supports only OVF." + raise vimconn.vimconnException("Wrong container. vCloud director supports only OVF.") catalog_name = os.path.splitext(filename)[0] - self.logger.debug("File name {} Catalog Name {} file path {}".format(filename, catalog_name, path)) self.logger.debug("Catalog name {}".format(catalog_name)) @@ -838,10 +930,10 @@ class vimconnector(vimconn.vimconnector): self.logger.info("Creating new catalog entry {} in vcloud director".format(catalog_name)) result = self.create_vimcatalog(vca, catalog_name) if not result: - return -1, "Failed create new catalog {} ".format(catalog_name) + raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_name)) result = self.upload_vimimage(vca, catalog_name, filename, path) if not result: - return -1, "Failed create vApp template for catalog {} ".format(catalog_name) + raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_name)) return self.get_catalogid(catalog_name, vca.get_catalogs()) else: for catalog in catalogs: @@ -858,10 +950,10 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Creating new catalog entry".format(catalog_name)) result = self.create_vimcatalog(vca, catalog_name) if not result: - return -1, "Failed create new catalog {} ".format(catalog_name) + raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_name)) result = self.upload_vimimage(vca, catalog_name, filename, path) if not result: - return -1, "Failed create vApp template for catalog {} ".format(catalog_name) + raise vimconn.vimconnException("Failed create vApp template for catalog {} ".format(catalog_name)) return self.get_catalogid(catalog_name, vca.get_catalogs()) @@ -873,13 +965,9 @@ class vimconnector(vimconn.vimconnector): vdc: The VDC object. vapp_name: is application vappp name identifier - Returns: + Returns: The return vApp name otherwise None """ - - """ Take vdc object and vApp name and returns vapp uuid or None - """ - if vdc is None or vapp_name is None: return None # UUID has following format https://host/api/vApp/vapp-30da58a3-e7c7-4d09-8f68-d4c8201169cf @@ -950,7 +1038,7 @@ class vimconnector(vimconn.vimconnector): return None return None - def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None): + def new_vminstance(self, name=None, description="", start=False, image_id=None, flavor_id=None, net_list={}, cloud_config=None): """Adds a VM instance to VIM Params: start: indicates if VM must start or boot in pause mode. Ignored @@ -989,42 +1077,61 @@ class vimconnector(vimconn.vimconnector): vdc = vca.get_vdc(self.tenant_name) if vdc is None: raise vimconn.vimconnUnexpectedResponse( - "new_vminstance(): Failed create vApp {}: (Failed reprieve VDC information)".format(name)) + "new_vminstance(): Failed create vApp {}: (Failed retrieve VDC information)".format(name)) catalogs = vca.get_catalogs() if catalogs is None: raise vimconn.vimconnUnexpectedResponse( - "new_vminstance(): Failed create vApp {}: Failed create vApp {}: (Failed reprieve Catalog information)".format( - name)) - - flavor = flavorlist[flavor_id] - if catalogs is None: - raise vimconn.vimconnUnexpectedResponse( - "new_vminstance(): Failed create vApp {}: (Failed reprieve Flavor information)".format(name)) + "new_vminstance(): Failed create vApp {}: Failed create vApp {}: " + "(Failed retrieve catalog information)".format(name)) + + vm_cpus = None + vm_memory = None + if flavor_id is not None: + flavor = flavorlist[flavor_id] + if flavor is None: + raise vimconn.vimconnUnexpectedResponse( + "new_vminstance(): Failed create vApp {}: (Failed retrieve flavor information)".format(name)) + else: + try: + vm_cpus = flavor['vcpus'] + vm_memory= flavor['ram'] + except KeyError: + raise vimconn.vimconnException("Corrupted flavor. {}".format(flavor_id)) # image upload creates template name as catalog name space Template. - templateName = self.get_catalogbyid(image_id, catalogs) + ' Template' + + print image_id + + templateName = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs) + ' Template' power_on = 'false' if start: power_on = 'true' # client must provide at least one entry in net_list if not we report error - primary_net = net_list[0] - if primary_net is None: - raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed network list is empty.".format(name)) + primary_net = None + primary_net_name = None + if net_list is not None and len(net_list) > 0: + primary_net = net_list[0] + if primary_net is None: + raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed network list is empty.".format(name)) - primary_net_id = primary_net['net_id'] - primary_net_name = self.get_network_name_by_id(primary_net_id) - network_mode = primary_net['use'] + else: + try: + primary_net_id = primary_net['net_id'] + primary_net_name = self.get_network_name_by_id(primary_net_id) + network_mode = primary_net['use'] + except KeyError: + raise vimconn.vimconnException("Corrupted flavor. {}".format(primary_net)) # use: 'data', 'bridge', 'mgmt' # create vApp. Set vcpu and ram based on flavor id. vapptask = vca.create_vapp(self.tenant_name, name, templateName, self.get_catalogbyid(image_id, catalogs), - network_name=primary_net_name, + network_name=primary_net_name, # can be None if net_list None network_mode='bridged', vm_name=name, - vm_cpus=flavor['vcpus'], - vm_memory=flavor['ram']) + vm_cpus=vm_cpus, # can be None if flavor is None + vm_memory=vm_memory) # can be None if flavor is None if vapptask is None or vapptask is False: raise vimconn.vimconnUnexpectedResponse("new_vminstance(): failed deploy vApp {}".format(name)) @@ -1055,7 +1162,7 @@ class vimconnector(vimconn.vimconnector): vca.block_until_completed(task) # connect network to VM # TODO figure out mapping between openmano representation to vCloud director. - # one idea use first nic as managment DHCP all remaining in bridge mode + # one idea use first nic as management DHCP all remaining in bridge mode task = vapp.connect_vms(nets[0].name, connection_index=nicIndex, connections_primary_index=nicIndex, ip_allocation_mode='DHCP') @@ -1329,10 +1436,10 @@ class vimconnector(vimconn.vimconnector): the_vapp.poweron() elif "pause" in action_dict: pass - ##server.pause() + ## server.pause() elif "resume" in action_dict: pass - ##server.resume() + ## server.resume() elif "shutoff" in action_dict or "shutdown" in action_dict: the_vapp.shutdown() elif "forceOff" in action_dict: @@ -1503,15 +1610,16 @@ class vimconnector(vimconn.vimconnector): Method retrieves available organization in vCloud Director Args: - vca - is active VCA connection. - vdc_name - is a vdc name that will be used to query vms action + org_uuid - is a organization uuid. Returns: - The return dictionary and key for each entry vapp UUID + The return dictionary with following key + "network" - for network list under the org + "catalogs" - for network list under the org + "vdcs" - for vdc list under org """ org_dict = {} - vca = self.connect() if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed") @@ -1670,7 +1778,7 @@ class vimconnector(vimconn.vimconnector): def get_vapp(self, vdc_name=None, vapp_name=None, isuuid=False): """ - Method retrieves VM's list deployed vCloud director. It returns a dictionary + Method retrieves VM deployed vCloud director. It returns VM attribute as dictionary contains a list of all VM's deployed for queried VDC. The key for a dictionary is VM UUID @@ -1694,23 +1802,21 @@ class vimconnector(vimconn.vimconnector): try: vm_list_xmlroot = XmlElementTree.fromstring(content) for vm_xml in vm_list_xmlroot: - if vm_xml.tag.split("}")[1] == 'VMRecord': + if vm_xml.tag.split("}")[1] == 'VMRecord' and vm_xml.attrib['isVAppTemplate'] == 'false': + # lookup done by UUID if isuuid: - # lookup done by UUID if vapp_name in vm_xml.attrib['container']: rawuuid = vm_xml.attrib['href'].split('/')[-1:] if 'vm-' in rawuuid[0]: - # vm in format vm-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove - # vm and use raw UUID as key vm_dict[rawuuid[0][3:]] = vm_xml.attrib - # lookup done by Name - else: - if vapp_name in vm_xml.attrib['name']: - rawuuid = vm_xml.attrib['href'].split('/')[-1:] - if 'vm-' in rawuuid[0]: - vm_dict[rawuuid[0][3:]] = vm_xml.attrib - # vm in format vm-e63d40e7-4ff5-4c6d-851f-96c1e4da86a5 we remove - # vm and use raw UUID as key + break + # lookup done by Name + else: + if vapp_name in vm_xml.attrib['name']: + rawuuid = vm_xml.attrib['href'].split('/')[-1:] + if 'vm-' in rawuuid[0]: + vm_dict[rawuuid[0][3:]] = vm_xml.attrib + break except: pass @@ -1999,7 +2105,6 @@ class vimconnector(vimconn.vimconnector): xml_content = self.create_vdc_from_tmpl_rest(vdc_name=vdc_name) if xml_content is not None: - print xml_content try: task_resp_xmlroot = XmlElementTree.fromstring(xml_content) for child in task_resp_xmlroot: @@ -2025,8 +2130,6 @@ class vimconnector(vimconn.vimconnector): """ self.logger.info("Creating new vdc {}".format(vdc_name)) - print ("Creating new vdc {}".format(vdc_name)) - vca = self.connect() if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed") @@ -2096,7 +2199,6 @@ class vimconnector(vimconn.vimconnector): """ self.logger.info("Creating new vdc {}".format(vdc_name)) - print ("Creating new vdc {}".format(vdc_name)) vca = self.connect_as_admin() if not vca: @@ -2135,7 +2237,6 @@ class vimconnector(vimconn.vimconnector): return None response = self.get_provider_rest(vca=vca) - print response try: vm_list_xmlroot = XmlElementTree.fromstring(response) for child in vm_list_xmlroot: @@ -2147,8 +2248,6 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Respond body {}".format(response)) return None - print "Add vdc {}".format(add_vdc_rest_url) - print "Provider ref {}".format(provider_vdc_ref) if add_vdc_rest_url is not None and provider_vdc_ref is not None: data = """ {1:s} ReservationPool @@ -2163,14 +2262,11 @@ class vimconnector(vimconn.vimconnector): escape(vdc_name), provider_vdc_ref) - print data headers = vca.vcloud_session.get_vcloud_headers() headers['Content-Type'] = 'application/vnd.vmware.admin.createVdcParams+xml' response = Http.post(url=add_vdc_rest_url, headers=headers, data=data, verify=vca.verify, logger=vca.logger) - print response.status_code - print response.content # if we all ok we respond with content otherwise by default None if response.status_code == 201: return response.content diff --git a/vmwarerecli.py b/vmwarerecli.py index c28a1bc4..547abf96 100755 --- a/vmwarerecli.py +++ b/vmwarerecli.py @@ -19,43 +19,61 @@ # contact with: mbayramov@vmware.com ## -''' +""" -Provide standalone application to work with vCloud director rest api. +Standalone application that leverage openmano vmware connector work with vCloud director rest api. + - Provides capability to create and delete VDC for specific organization. + - Create, delete and manage network for specific VDC + - List deployed VM's , VAPPs, VDSs, Organization + - View detail information about VM / Vapp , Organization etc + - Operate with images upload / boot / power on etc + + Usage example. + + List organization created in vCloud director + vmwarecli.py -u admin -p qwerty123 -c 172.16.254.206 -U Administrator -P qwerty123 -o test -v TEF list org + + List VDC for particular organization + vmwarecli.py -u admin -p qwerty123 -c 172.16.254.206 -U Administrator -P qwerty123 -o test -v TEF list vdc + + Upload image + python vmwarerecli.py image upload /Users/spyroot/Developer/Openmano/Ro/vnfs/cirros/cirros.ovf + + Boot Image + python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF image boot cirros cirros + + View vApp + python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF view vapp 90bd2b4e-f782-46cf-b5e2-c3817dcf6633 -u + + List VMS + python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF list vms + + List VDC in OSM format + python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF list vdc -o + +Mustaafa Bayramov mbayramov@vmware.com -''' +""" import os -import requests -import logging -import sys import argparse import traceback +import uuid from xml.etree import ElementTree as ET from pyvcloud import Http -from pyvcloud.vcloudair import VCA -from pyvcloud.schema.vcd.v1_5.schemas.vcloud.networkType import * -from pyvcloud.schema.vcd.v1_5.schemas.vcloud import sessionType, organizationType, \ - vAppType, organizationListType, vdcType, catalogType, queryRecordViewType, \ - networkType, vcloudType, taskType, diskType, vmsType, vdcTemplateListType, mediaType -from xml.sax.saxutils import escape import logging -import json import vimconn import time import uuid -import httplib import urllib3 import requests from vimconn_vmware import vimconnector from requests.packages.urllib3.exceptions import InsecureRequestWarning from prettytable import PrettyTable -from xml.etree import ElementTree as XmlElementTree -from prettytable import PrettyTable requests.packages.urllib3.disable_warnings(InsecureRequestWarning) @@ -63,7 +81,7 @@ __author__ = "Mustafa Bayramov" __date__ = "$16-Sep-2016 11:09:29$" -#TODO move to main vim +# TODO move to main vim def delete_network_action(vca=None, network_uuid=None): """ Method leverages vCloud director and query network based on network uuid @@ -110,7 +128,8 @@ def print_vapp(vapp_dict=None): # 'href': 'https://172.16.254.206/api/vAppTemplate/vm-129e22e8-08dc-4cb6-8358-25f635e65d3b', # 'isBusy': 'false', 'isDeployed': 'false', 'isInMaintenanceMode': 'false', 'isVAppTemplate': 'true', # 'networkName': 'nat', 'isDeleted': 'false', 'catalogName': 'Cirros', - # 'containerName': 'Cirros Template', # 'container': 'https://172.16.254.206/api/vAppTemplate/vappTemplate-b966453d-c361-4505-9e38-ccef45815e5d', + # 'containerName': 'Cirros Template', # 'container': + # 'https://172.16.254.206/api/vAppTemplate/vappTemplate-b966453d-c361-4505-9e38-ccef45815e5d', # 'name': 'Cirros', 'pvdcHighestSupportedHardwareVersion': '11', 'isPublished': 'false', # 'numberOfCpus': '1', 'vdc': 'https://172.16.254.206/api/vdc/a5056f85-418c-4bfd-8041-adb0f48be9d9', # 'guestOs': 'Other (32-bit)', 'isVdcEnabled': 'true'} @@ -118,24 +137,25 @@ def print_vapp(vapp_dict=None): if vapp_dict is None: return - vm_table = PrettyTable(['vapp uuid', + vm_table = PrettyTable(['vm uuid', 'vapp name', - 'vdc uuid', + 'vapp uuid', 'network name', - 'category name', - 'storageProfileName', - 'vcpu', 'memory', 'hw ver']) + 'storage name', + 'vcpu', 'memory', 'hw ver','deployed','status']) for k in vapp_dict: entry = [] entry.append(k) entry.append(vapp_dict[k]['containerName']) - entry.append(vapp_dict[k]['vdc'].split('/')[-1:][0]) + # vm-b1f5cd4c-2239-4c89-8fdc-a41ff18e0d61 + entry.append(vapp_dict[k]['container'].split('/')[-1:][0][5:]) entry.append(vapp_dict[k]['networkName']) - entry.append(vapp_dict[k]['catalogName']) entry.append(vapp_dict[k]['storageProfileName']) entry.append(vapp_dict[k]['numberOfCpus']) entry.append(vapp_dict[k]['memoryMB']) entry.append(vapp_dict[k]['pvdcHighestSupportedHardwareVersion']) + entry.append(vapp_dict[k]['isDeployed']) + entry.append(vapp_dict[k]['status']) vm_table.add_row(entry) @@ -167,7 +187,7 @@ def print_vm_list(vm_dict=None): """ Method takes vapp_dict and print in tabular format Args: - org_dict: dictionary of organization where key is org uuid. + vm_dict: dictionary of organization where key is org uuid. Returns: The return nothing @@ -197,7 +217,7 @@ def print_vm_list(vm_dict=None): pass -def print_org_details(org_dict=None): +def print_vdc_list(org_dict=None): """ Method takes vapp_dict and print in tabular format Args: @@ -209,36 +229,73 @@ def print_org_details(org_dict=None): if org_dict is None: return try: - network_dict = {} - catalogs_dict = {} vdcs_dict = {} - - if org_dict.has_key('networks'): - network_dict = org_dict['networks'] - if org_dict.has_key('vdcs'): vdcs_dict = org_dict['vdcs'] - - if org_dict.has_key('catalogs'): - catalogs_dict = org_dict['catalogs'] - vdc_table = PrettyTable(['vdc uuid', 'vdc name']) for k in vdcs_dict: entry = [k, vdcs_dict[k]] vdc_table.add_row(entry) + print vdc_table + except KeyError: + logger.error("wrong key {}".format(KeyError.message)) + logger.logger.debug(traceback.format_exc()) + + +def print_network_list(org_dict=None): + """ Method print network list. + + Args: + org_dict: dictionary of organization that contain key networks with a list of all + network for for specific VDC + + Returns: + The return nothing + """ + if org_dict is None: + return + try: + network_dict = {} + if org_dict.has_key('networks'): + network_dict = org_dict['networks'] network_table = PrettyTable(['network uuid', 'network name']) for k in network_dict: entry = [k, network_dict[k]] network_table.add_row(entry) + print network_table + + except KeyError: + logger.error("wrong key {}".format(KeyError.message)) + logger.logger.debug(traceback.format_exc()) + + +def print_org_details(org_dict=None): + """ Method takes vapp_dict and print in tabular format + + Args: + org_dict: dictionary of organization where key is org uuid. + + Returns: + The return nothing + """ + if org_dict is None: + return + try: + catalogs_dict = {} + + print_vdc_list(org_dict=org_dict) + print_network_list(org_dict=org_dict) + + if org_dict.has_key('catalogs'): + catalogs_dict = org_dict['catalogs'] + catalog_table = PrettyTable(['catalog uuid', 'catalog name']) for k in catalogs_dict: entry = [k, catalogs_dict[k]] catalog_table.add_row(entry) - print vdc_table - print network_table print catalog_table except KeyError: @@ -264,6 +321,27 @@ def delete_actions(vim=None, action=None, namespace=None): def list_actions(vim=None, action=None, namespace=None): + """ Method provide list object from VDC action + + Args: + vim - is vcloud director vim connector. + action - is action for list ( vdc / org etc) + namespace - must contain VDC / Org information. + + Returns: + The return nothing + """ + + org_id = None + myorgs = vim.get_org_list() + for org in myorgs: + if myorgs[org] == namespace.vcdorg: + org_id = org + break + else: + print(" Invalid organization.") + return + if action == 'vms' or namespace.action == 'vms': vm_dict = vim.get_vm_list(vdc_name=namespace.vcdvdc) print_vm_list(vm_dict=vm_dict) @@ -271,19 +349,58 @@ def list_actions(vim=None, action=None, namespace=None): vapp_dict = vim.get_vapp_list(vdc_name=namespace.vcdvdc) print_vapp(vapp_dict=vapp_dict) elif action == 'networks' or namespace.action == 'networks': - print "Requesting networks" - # var = OrgVdcNetworkType.get_status() + if namespace.osm: + osm_print(vim.get_network_list(filter_dict={})) + else: + print_network_list(vim.get_org(org_uuid=org_id)) elif action == 'vdc' or namespace.action == 'vdc': - vm_dict = vim.get_vm_list(vdc_name=namespace.vcdvdc) - print_vm_list(vm_dict=vm_dict) + if namespace.osm: + osm_print(vim.get_tenant_list(filter_dict=None)) + else: + print_vdc_list(vim.get_org(org_uuid=org_id)) elif action == 'org' or namespace.action == 'org': - logger.debug("Listing avaliable orgs") print_org(org_dict=vim.get_org_list()) else: return None +def print_network_details(network_dict=None): + try: + network_table = PrettyTable(network_dict.keys()) + entry = [network_dict.values()] + network_table.add_row(entry[0]) + print network_table + except KeyError: + logger.error("wrong key {}".format(KeyError.message)) + logger.logger.debug(traceback.format_exc()) + + +def osm_print(generic_dict=None): + + try: + for element in generic_dict: + table = PrettyTable(element.keys()) + entry = [element.values()] + table.add_row(entry[0]) + print table + except KeyError: + logger.error("wrong key {}".format(KeyError.message)) + logger.logger.debug(traceback.format_exc()) + + def view_actions(vim=None, action=None, namespace=None): + org_id = None + orgs = vim.get_org_list() + for org in orgs: + if orgs[org] == namespace.vcdorg: + org_id = org + break + else: + print(" Invalid organization.") + return + + myorg = vim.get_org(org_uuid=org_id) + # view org if action == 'org' or namespace.action == 'org': org_id = None @@ -303,16 +420,21 @@ def view_actions(vim=None, action=None, namespace=None): # view vapp action if action == 'vapp' or namespace.action == 'vapp': - if namespace.vapp_name is not None and namespace.uuid == False: + print namespace.vapp_name + if namespace.vapp_name is not None and namespace.uuid: logger.debug("Requesting vapp {} for vdc {}".format(namespace.vapp_name, namespace.vcdvdc)) - vapp_dict = {} + vapp_uuid = namespace.vapp_name # if request based on just name we need get UUID if not namespace.uuid: - vappid = vim.get_vappid(vdc=namespace.vcdvdc, vapp_name=namespace.vapp_name) + vapp_uuid = vim.get_vappid(vdc=namespace.vcdvdc, vapp_name=namespace.vapp_name) + if vapp_uuid is None: + print("Can't find vapp by given name {}".format(namespace.vapp_name)) + return - vapp_dict = vim.get_vapp(vdc_name=namespace.vcdvdc, vapp_name=vappid, isuuid=True) - print_vapp(vapp_dict=vapp_dict) + vapp_dict = vim.get_vapp(vdc_name=namespace.vcdvdc, vapp_name=vapp_uuid, isuuid=True) + if vapp_dict is not None: + print_vapp(vapp_dict=vapp_dict) # view network if action == 'network' or namespace.action == 'network': @@ -321,14 +443,19 @@ def view_actions(vim=None, action=None, namespace=None): # if request name based we need find UUID # TODO optimize it or move to external function if not namespace.uuid: - org_dict = vim.get_org_list() - for org in org_dict: - org_net = vim.get_org(org)['networks'] - for network in org_net: - if org_net[network] == namespace.network_name: - network_uuid = network + if not myorg.has_key('networks'): + print("Network {} is undefined in vcloud director for org {} vdc {}".format(namespace.network_name, + vim.name, + vim.tenant_name)) + return + + my_org_net = myorg['networks'] + for network in my_org_net: + if my_org_net[network] == namespace.network_name: + network_uuid = network + break - print vim.get_vcd_network(network_uuid=network_uuid) + print print_network_details(network_dict=vim.get_vcd_network(network_uuid=network_uuid)) def create_actions(vim=None, action=None, namespace=None): @@ -359,26 +486,159 @@ def create_actions(vim=None, action=None, namespace=None): return None -def vmwarecli(command=None, action=None, namespace=None): +def validate_uuid4(uuid_string): + """Function validate that string contain valid uuid4 + + Args: + uuid_string - valid UUID string + + Returns: + The return true if string contain valid UUID format + """ + try: + val = uuid.UUID(uuid_string, version=4) + except ValueError: + return False + return True + + +def upload_image(vim=None, image_file=None): + """Function upload image to vcloud director + + Args: + image_file - valid UUID string + Returns: + The return true if image uploaded correctly + """ + try: + catalog_uuid = vim.get_image_id_from_path(path=image_file) + if catalog_uuid is not None and validate_uuid4(catalog_uuid): + print("Image uploaded and uuid {}".format(catalog_uuid)) + return True + except: + print("Failed uploaded {} image".format(image_file)) + + return False + + +def boot_image(vim=None, image_name=None, vm_name=None): + """ Function boot image that resided in vcloud director. + The image name can be UUID of name. + + Args: + image_name - image identified by UUID or text string. + vm_name + + Returns: + The return true if image uploaded correctly + """ + + vim_catalog = None + try: + catalogs = vim.vca.get_catalogs() + if not validate_uuid4(image_name): + vim_catalog = vim.get_catalogid(catalog_name=image_name, catalogs=catalogs) + if vim_catalog is None: + return None + else: + vim_catalog = vim.get_catalogid(catalog_name=image_name, catalogs=catalogs) + if vim_catalog is None: + return None + + vm_uuid = vim.new_vminstance(name=vm_name, image_id=vim_catalog) + if vm_uuid is not None and validate_uuid4(vm_uuid): + print("Image booted and vm uuid {}".format(vm_uuid)) + vapp_dict = vim.get_vapp(vdc_name=namespace.vcdvdc, vapp_name=vm_uuid, isuuid=True) + if vapp_dict is not None: + print_vapp(vapp_dict=vapp_dict) + return True + except: + print("Failed uploaded {} image".format(image_name)) + + +def image_action(vim=None, action=None, namespace=None): + """ Function present set of action to manipulate with image. + - upload image + - boot image. + - delete image ( not yet done ) + + Args: + vim - vcloud director connector + action - string (upload/boot etc) + namespace - contain other attributes image name etc + + Returns: + The return nothing + """ + + if action == 'upload' or namespace.action == 'upload': + upload_image(vim=vim, image_file=namespace.image) + elif action == 'boot' or namespace.action == 'boot': + boot_image(vim=vim, image_name=namespace.image, vm_name=namespace.vmname) + else: + return None + + +def vmwarecli(command=None, action=None, namespace=None): logger.debug("Namespace {}".format(namespace)) urllib3.disable_warnings() + vcduser = None + vcdpasword = None + vcdhost = None + vcdorg = None + + if hasattr(__builtins__, 'raw_input'): + input = raw_input + + if namespace.vcdvdc is None: + while True: + vcduser = input("Enter vcd username: ") + if vcduser is not None and len(vcduser) > 0: + break + else: + vcduser = namespace.vcduser + if namespace.vcdpassword is None: - vcdpasword = input("vcd password ") + while True: + vcdpasword = input("Please enter vcd password: ") + if vcdpasword is not None and len(vcdpasword) > 0: + break else: vcdpasword = namespace.vcdpassword - vim = vimconnector(uuid=None, - name=namespace.org_name, - tenant_id=None, - tenant_name=namespace.vcdvdc, - url=namespace.vcdhost, - url_admin=namespace.vcdhost, - user=namespace.vcduser, - passwd=namespace.vcdpassword, - log_level="DEBUG", - config={'admin_username': namespace.vcdamdin, 'admin_password': namespace.vcdadminpassword}) - vim.vca = vim.connect() + + if namespace.vcdhost is None: + while True: + vcdhost = input("Please enter vcd host name or ip: ") + if vcdhost is not None and len(vcdhost) > 0: + break + else: + vcdhost = namespace.vcdhost + + if namespace.vcdorg is None: + while True: + vcdorg = input("Please enter vcd organization name: ") + if vcdorg is not None and len(vcdorg) > 0: + break + else: + vcdorg = namespace.vcdorg + + try: + vim = vimconnector(uuid=None, + name=vcdorg, + tenant_id=None, + tenant_name=namespace.vcdvdc, + url=vcdhost, + url_admin=vcdhost, + user=vcduser, + passwd=vcdpasword, + log_level="DEBUG", + config={'admin_username': namespace.vcdamdin, 'admin_password': namespace.vcdadminpassword}) + vim.vca = vim.connect() + except vimconn.vimconnConnectionException: + print("Failed connect to vcloud director. Please check credential and hostname.") + return # list if command == 'list' or namespace.command == 'list': @@ -401,6 +661,11 @@ def vmwarecli(command=None, action=None, namespace=None): logger.debug("Client requested create action") create_actions(vim=vim, action=action, namespace=namespace) + # image action + if command == 'image' or namespace.command == 'image': + logger.debug("Client requested create action") + image_action(vim=vim, action=action, namespace=namespace) + if __name__ == '__main__': defaults = {'vcdvdc': 'default', @@ -423,11 +688,17 @@ if __name__ == '__main__': parser_subparsers = parser.add_subparsers(help='commands', dest='command') sub = parser_subparsers.add_parser('list', help='List objects (VMs, vApps, networks)') sub_subparsers = sub.add_subparsers(dest='action') - view_vms = sub_subparsers.add_parser('vms', help='list - all vm deployed in vCloud director') - view_vapps = sub_subparsers.add_parser('vapps', help='list - all vapps deployed in vCloud director') - view_network = sub_subparsers.add_parser('networks', help='list - all networks deployed') - view_vdc = sub_subparsers.add_parser('vdc', help='list - list all vdc for organization accessible to you') - view_vdc = sub_subparsers.add_parser('org', help='list - list of organizations accessible to you.') + + list_vms = sub_subparsers.add_parser('vms', help='list - all vm deployed in vCloud director') + list_vapps = sub_subparsers.add_parser('vapps', help='list - all vapps deployed in vCloud director') + list_network = sub_subparsers.add_parser('networks', help='list - all networks deployed') + list_network.add_argument('-o', '--osm', default=False, action='store_true', help='provide view in OSM format') + + #list vdc + list_vdc = sub_subparsers.add_parser('vdc', help='list - list all vdc for organization accessible to you') + list_vdc.add_argument('-o', '--osm', default=False, action='store_true', help='provide view in OSM format') + + list_org = sub_subparsers.add_parser('org', help='list - list of organizations accessible to you.') create_sub = parser_subparsers.add_parser('create') create_sub_subparsers = create_sub.add_subparsers(dest='action') @@ -492,11 +763,18 @@ if __name__ == '__main__': help='- View VDC based and action based on provided vdc uuid') view_org.add_argument('-u', '--uuid', default=False, action='store_true', help='view org based on uuid') - # - # view_org.add_argument('uuid', default=False, action='store', - # help='- View Organization and action based on provided uuid.') - # view_org.add_argument('name', default=False, action='store_true', - # help='- View Organization and action based on provided name') + # upload image action + image_sub = parser_subparsers.add_parser('image') + image_subparsers = image_sub.add_subparsers(dest='action') + upload_parser = image_subparsers.add_parser('upload') + upload_parser.add_argument('image', default=False, action='store', help='- valid path to OVF image ') + upload_parser.add_argument('catalog', default=False, action='store_true', help='- catalog name') + + # boot vm action + boot_parser = image_subparsers.add_parser('boot') + boot_parser.add_argument('image', default=False, action='store', help='- Image name') + boot_parser.add_argument('vmname', default=False, action='store', help='- VM name') + boot_parser.add_argument('-u', '--uuid', default=False, action='store_true', help='view org based on uuid') namespace = parser.parse_args() # put command_line args to mapping @@ -520,4 +798,3 @@ if __name__ == '__main__': # main entry point. vmwarecli(namespace=namespace) - -- 2.25.1