X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_ro%2Fvimconn_vmware.py;h=f343eeac92e06e32d579d3edb8ae7d7e84cc4bf7;hb=1d55a23c9cd32f17bb7a6f21e5b6c2b476c5804f;hp=c65482bcd70ff6b58efa31806d03ab6fe5298ca3;hpb=b7ff355a95cf7ce07874aa5dc1d4267541277778;p=osm%2FRO.git diff --git a/osm_ro/vimconn_vmware.py b/osm_ro/vimconn_vmware.py index c65482bc..f343eeac 100644 --- a/osm_ro/vimconn_vmware.py +++ b/osm_ro/vimconn_vmware.py @@ -29,6 +29,9 @@ from progressbar import Percentage, Bar, ETA, FileTransferSpeed, ProgressBar import vimconn import os +import shutil +import subprocess +import tempfile import traceback import itertools import requests @@ -76,7 +79,7 @@ DEFAULT_IP_PROFILE = {'dhcp_count':50, INTERVAL_TIME = 5 MAX_WAIT_TIME = 1800 -API_VERSION = '5.9' +API_VERSION = '27.0' __author__ = "Mustafa Bayramov, Arpita Kate, Sachin Bhangare, Prakash Kasar" __date__ = "$09-Mar-2018 11:09:29$" @@ -297,13 +300,13 @@ class vimconnector(vimconn.vimconnector): Returns: The return client object that latter can be used to connect to vcloud director as admin for provider vdc """ - self.logger.debug("Logging into vCD {} as admin.".format(self.org_name)) try: host = self.url org = 'System' client_as_admin = Client(host, verify_ssl_certs=False) + client_as_admin.set_highest_supported_version() client_as_admin.set_credentials(BasicLoginCredentials(self.admin_user, org, self.admin_password)) except Exception as e: raise vimconn.vimconnException( @@ -317,13 +320,13 @@ class vimconnector(vimconn.vimconnector): Returns: The return client object that latter can be used to connect to vCloud director as admin for VDC """ - try: self.logger.debug("Logging into vCD {} as {} to datacenter {}.".format(self.org_name, self.user, self.org_name)) host = self.url client = Client(host, verify_ssl_certs=False) + client.set_highest_supported_version() client.set_credentials(BasicLoginCredentials(self.user, self.org_name, self.passwd)) except: raise vimconn.vimconnConnectionException("Can't connect to a vCloud director org: " @@ -500,17 +503,35 @@ class vimconnector(vimconn.vimconnector): return vdclist - def new_network(self, net_name, net_type, ip_profile=None, shared=False): + def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None): """Adds a tenant network to VIM - net_name is the name - net_type can be 'bridge','data'.'ptp'. - ip_profile is a dict containing the IP parameters of the network - shared is a boolean - Returns the network identifier""" + Params: + 'net_name': name of the network + 'net_type': one of: + 'bridge': overlay isolated network + 'data': underlay E-LAN network for Passthrough and SRIOV interfaces + 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces. + 'ip_profile': is a dict containing the IP parameters of the network + 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented) + 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y + 'gateway_address': (Optional) ip_schema, that is X.X.X.X + 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X] + 'dhcp_enabled': True or False + 'dhcp_start_address': ip_schema, first IP to grant + 'dhcp_count': number of IPs to grant. + 'shared': if this network can be seen/use by other tenants/organization + 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network + Returns a tuple with the network 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_network. Can be used to store created segments, created l2gw connections, 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.debug("new_network tenant {} net_type {} ip_profile {} shared {}" .format(net_name, net_type, ip_profile, shared)) + created_items = {} isshared = 'false' if shared: isshared = 'true' @@ -524,7 +545,7 @@ class vimconnector(vimconn.vimconnector): network_uuid = self.create_network(network_name=net_name, net_type=net_type, ip_profile=ip_profile, isshared=isshared) if network_uuid is not None: - return network_uuid + return network_uuid, created_items else: raise vimconn.vimconnUnexpectedResponse("Failed create a new network {}".format(net_name)) @@ -771,11 +792,12 @@ class vimconnector(vimconn.vimconnector): return filter_dict - def delete_network(self, net_id): + def delete_network(self, net_id, created_items=None): """ - Method Deletes a tenant network from VIM, provide the network id. - - Returns the network identifier or raise an exception + Removes a tenant network from VIM and its associated elements + :param net_id: VIM identifier of the network, provided by method new_network + :param created_items: dictionary with extra items to be deleted. provided by method new_network + Returns the network identifier or raises an exception upon error or when network is not found """ # ############# Stub code for SRIOV ################# @@ -1015,8 +1037,7 @@ class vimconnector(vimconn.vimconnector): """ for catalog in catalogs: if catalog['name'] == catalog_name: - return True - return False + return catalog['id'] def create_vimcatalog(self, vca=None, catalog_name=None): """ Create new catalog entry in vCloud director. @@ -1026,16 +1047,19 @@ class vimconnector(vimconn.vimconnector): 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. + Returns catalog id if catalog created else None. """ try: - result = vca.create_catalog(catalog_name, catalog_name) - if result is not None: - return True + lxml_catalog_element = vca.create_catalog(catalog_name, catalog_name) + if lxml_catalog_element: + id_attr_value = lxml_catalog_element.get('id') # 'urn:vcloud:catalog:7490d561-d384-4dac-8229-3575fd1fc7b4' + return id_attr_value.split(':')[-1] catalogs = vca.list_catalogs() - except: - return False + except Exception as ex: + self.logger.error( + 'create_vimcatalog(): Creation of catalog "{}" failed with error: {}'.format(catalog_name, ex)) + raise return self.catalog_exists(catalog_name, catalogs) # noinspection PyIncorrectDocstring @@ -1305,8 +1329,7 @@ class vimconnector(vimconn.vimconnector): if len(catalogs) == 0: self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name)) - result = self.create_vimcatalog(org, catalog_md5_name) - if not result: + if self.create_vimcatalog(org, catalog_md5_name) is None: raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name)) result = self.upload_vimimage(vca=org, catalog_name=catalog_md5_name, @@ -1326,8 +1349,7 @@ class vimconnector(vimconn.vimconnector): # 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(org, catalog_md5_name) - if not result: + if self.create_vimcatalog(org, catalog_md5_name) is None: raise vimconn.vimconnException("Failed create new catalog {} ".format(catalog_md5_name)) result = self.upload_vimimage(vca=org, catalog_name=catalog_md5_name, @@ -1523,7 +1545,7 @@ class vimconnector(vimconn.vimconnector): vmname_andid = ''.join(new_vm_name) for net in net_list: - if net['type'] == "SR-IOV" or net['type'] == "PCI-PASSTHROUGH": + if net['type'] == "PCI-PASSTHROUGH": raise vimconn.vimconnNotSupportedException( "Current vCD version does not support type : {}".format(net['type'])) @@ -1554,7 +1576,6 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: " "(Failed retrieve catalog information {})".format(name, image_id)) - # Set vCPU and Memory based on flavor. vm_cpus = None vm_memory = None @@ -1644,13 +1665,12 @@ class vimconnector(vimconn.vimconnector): else: result = (response.content).replace("\n"," ") - src = re.search(' 0: @@ -1918,7 +1937,8 @@ class vimconnector(vimconn.vimconnector): self.logger.info("new_vminstance(): Attaching net {} to vapp".format(interface_net_name)) self.connect_vapp_to_org_vdc_network(vapp_id, nets[0].get('name')) - type_list = ('PF', 'PCI-PASSTHROUGH', 'VF', 'SR-IOV', 'VFnotShared') + type_list = ('PF', 'PCI-PASSTHROUGH', 'VFnotShared') + nic_type = 'VMXNET3' if 'type' in net and net['type'] not in type_list: # fetching nic type from vnf if 'model' in net: @@ -1938,7 +1958,6 @@ class vimconnector(vimconn.vimconnector): else: self.logger.info("new_vminstance(): adding network adapter "\ "to a network {}".format(nets[0].get('name'))) - nic_type = 'VMXNET3' if net['type'] in ['SR-IOV', 'VF']: nic_type = net['type'] self.add_network_adapter_to_vms(vapp, nets[0].get('name'), @@ -1950,41 +1969,36 @@ class vimconnector(vimconn.vimconnector): # cloud-init for ssh-key injection if cloud_config: - self.cloud_init(vapp,cloud_config) - - # ############# Stub code for SRIOV ################# - #Add SRIOV -# if len(sriov_net_info) > 0: -# self.logger.info("Need to add SRIOV adapters {} into VM {}".format(sriov_net_info, -# vmname_andid )) -# sriov_status, vm_obj, vcenter_conect = self.add_sriov(vapp_uuid, -# sriov_net_info, -# vmname_andid) -# if sriov_status: -# self.logger.info("Added SRIOV {} to VM {}".format( -# sriov_net_info, -# vmname_andid) -# ) -# reserve_memory = True -# else: -# self.logger.info("Fail to add SRIOV {} to VM {}".format( -# sriov_net_info, -# vmname_andid) -# ) + # Create a catalog which will be carrying the config drive ISO + # This catalog is deleted during vApp deletion. The catalog name carries + # vApp UUID and thats how it gets identified during its deletion. + config_drive_catalog_name = 'cfg_drv-' + vapp_uuid + self.logger.info('new_vminstance(): Creating catalog "{}" to carry config drive ISO'.format( + config_drive_catalog_name)) + config_drive_catalog_id = self.create_vimcatalog(org, config_drive_catalog_name) + if config_drive_catalog_id is None: + error_msg = "new_vminstance(): Failed to create new catalog '{}' to carry the config drive " \ + "ISO".format(config_drive_catalog_name) + raise Exception(error_msg) + + # Create config-drive ISO + _, userdata = self._create_user_data(cloud_config) + # self.logger.debug('new_vminstance(): The userdata for cloud-init: {}'.format(userdata)) + iso_path = self.create_config_drive_iso(userdata) + self.logger.debug('new_vminstance(): The ISO is successfully created. Path: {}'.format(iso_path)) + + self.logger.info('new_vminstance(): uploading iso to catalog {}'.format(config_drive_catalog_name)) + self.upload_iso_to_catalog(config_drive_catalog_id, iso_path) + # Attach the config-drive ISO to the VM + self.logger.info('new_vminstance(): Attaching the config-drive ISO to the VM') + # The ISO remains in INVALID_STATE right after the PUT request (its a blocking call though) + time.sleep(5) + self.insert_media_to_vm(vapp, config_drive_catalog_id) + shutil.rmtree(os.path.dirname(iso_path), ignore_errors=True) # If VM has PCI devices or SRIOV reserve memory for VM if reserve_memory: - memReserve = vm_obj.config.hardware.memoryMB - spec = vim.vm.ConfigSpec() - spec.memoryAllocation = vim.ResourceAllocationInfo(reservation=memReserve) - task = vm_obj.ReconfigVM_Task(spec=spec) - if task: - result = self.wait_for_vcenter_task(task, vcenter_conect) - self.logger.info("Reserved memory {} MB for " - "VM VM status: {}".format(str(memReserve), result)) - else: - self.logger.info("Fail to reserved memory {} to VM {}".format( - str(memReserve), str(vm_obj))) + self.reserve_memory_for_all_vms(vapp, memory_mb) self.logger.debug("new_vminstance(): starting power on vApp {} ".format(vmname_andid)) @@ -1997,7 +2011,11 @@ class vimconnector(vimconn.vimconnector): self.logger.error("new_vminstance(): failed to power on vApp "\ "{}".format(vmname_andid)) - except Exception as exp : + except Exception as exp: + try: + self.delete_vminstance(vapp_uuid) + except Exception as exp2: + self.logger.error("new_vminstance rollback fail {}".format(exp2)) # it might be a case if specific mandatory entry in dict is empty or some other pyVcloud exception self.logger.error("new_vminstance(): Failed create new vm instance {} with exception {}" .format(name, exp)) @@ -2117,6 +2135,83 @@ class vimconnector(vimconn.vimconnector): else: raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name)) + def create_config_drive_iso(self, user_data): + tmpdir = tempfile.mkdtemp() + iso_path = os.path.join(tmpdir, 'ConfigDrive.iso') + latest_dir = os.path.join(tmpdir, 'openstack', 'latest') + os.makedirs(latest_dir) + with open(os.path.join(latest_dir, 'meta_data.json'), 'w') as meta_file_obj, \ + open(os.path.join(latest_dir, 'user_data'), 'w') as userdata_file_obj: + userdata_file_obj.write(user_data) + meta_file_obj.write(json.dumps({"availability_zone": "nova", + "launch_index": 0, + "name": "ConfigDrive", + "uuid": str(uuid.uuid4())} + ) + ) + genisoimage_cmd = 'genisoimage -J -r -V config-2 -o {iso_path} {source_dir_path}'.format( + iso_path=iso_path, source_dir_path=tmpdir) + self.logger.info('create_config_drive_iso(): Creating ISO by running command "{}"'.format(genisoimage_cmd)) + try: + FNULL = open(os.devnull, 'w') + subprocess.check_call(genisoimage_cmd, shell=True, stdout=FNULL) + except subprocess.CalledProcessError as e: + shutil.rmtree(tmpdir, ignore_errors=True) + error_msg = 'create_config_drive_iso(): Exception while running genisoimage command: {}'.format(e) + self.logger.error(error_msg) + raise Exception(error_msg) + return iso_path + + def upload_iso_to_catalog(self, catalog_id, iso_file_path): + if not os.path.isfile(iso_file_path): + error_msg = "upload_iso_to_catalog(): Given iso file is not present. Given path: {}".format(iso_file_path) + self.logger.error(error_msg) + raise Exception(error_msg) + iso_file_stat = os.stat(iso_file_path) + xml_media_elem = ''' + + ISO image for config-drive + '''.format(iso_name=os.path.basename(iso_file_path), iso_size=iso_file_stat.st_size) + headers = {'Accept':'application/*+xml;version=' + API_VERSION, + 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']} + headers['Content-Type'] = 'application/vnd.vmware.vcloud.media+xml' + catalog_href = self.url + '/api/catalog/' + catalog_id + '/action/upload' + response = self.perform_request(req_type='POST', url=catalog_href, headers=headers, data=xml_media_elem) + + if response.status_code != 201: + error_msg = "upload_iso_to_catalog(): Failed to POST an action/upload request to {}".format(catalog_href) + self.logger.error(error_msg) + raise Exception(error_msg) + + catalogItem = XmlElementTree.fromstring(response.content) + entity = [child for child in catalogItem if child.get("type") == "application/vnd.vmware.vcloud.media+xml"][0] + entity_href = entity.get('href') + + response = self.perform_request(req_type='GET', url=entity_href, headers=headers) + if response.status_code != 200: + raise Exception("upload_iso_to_catalog(): Failed to GET entity href {}".format(entity_href)) + + match = re.search(r'\s+?\s+?\s+?', response.text, re.DOTALL) + if match: + media_upload_href = match.group(1) + else: + raise Exception('Could not parse the upload URL for the media file from the last response') + upload_iso_task = self.get_task_from_response(response.content) + headers['Content-Type'] = 'application/octet-stream' + response = self.perform_request(req_type='PUT', + url=media_upload_href, + headers=headers, + data=open(iso_file_path, 'rb')) + + if response.status_code != 200: + raise Exception('PUT request to "{}" failed'.format(media_upload_href)) + result = self.client.get_task_monitor().wait_for_success(task=upload_iso_task) + if result.get('status') != 'success': + raise Exception('The upload iso task failed with status {}'.format(result.get('status'))) def get_vcd_availibility_zones(self,respool_href, headers): """ Method to find presence of av zone is VIM resource pool @@ -2642,13 +2737,12 @@ class vimconnector(vimconn.vimconnector): try: vapp_name = self.get_namebyvappid(vm__vim_uuid) - vapp_resource = vdc_obj.get_vapp(vapp_name) - vapp = VApp(self.client, resource=vapp_resource) 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) - else: - self.logger.info("Deleting vApp {} and UUID {}".format(vapp_name, vm__vim_uuid)) + self.logger.info("Deleting vApp {} and UUID {}".format(vapp_name, vm__vim_uuid)) + vapp_resource = vdc_obj.get_vapp(vapp_name) + vapp = VApp(self.client, resource=vapp_resource) # Delete vApp and wait for status change if task executed and vApp is None. @@ -2727,6 +2821,17 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("delete_vminstance(): Failed delete uuid {} ".format(vm__vim_uuid)) else: self.logger.info("Deleted vm instance {} sccessfully".format(vm__vim_uuid)) + config_drive_catalog_name, config_drive_catalog_id = 'cfg_drv-' + vm__vim_uuid, None + catalog_list = self.get_image_list() + try: + config_drive_catalog_id = [catalog_['id'] for catalog_ in catalog_list + if catalog_['name'] == config_drive_catalog_name][0] + except IndexError: + pass + if config_drive_catalog_id: + self.logger.debug('delete_vminstance(): Found a config drive catalog {} matching ' + 'vapp_name"{}". Deleting it.'.format(config_drive_catalog_id, vapp_name)) + self.delete_image(config_drive_catalog_id) return vm__vim_uuid except: self.logger.debug(traceback.format_exc()) @@ -3032,7 +3137,7 @@ class vimconnector(vimconn.vimconnector): else: self.logger.error("action_vminstance: Failed to {} vApp: {}".format(action, vapp_name)) - def get_vminstance_console(self, vm_id, console_type="vnc"): + def get_vminstance_console(self, vm_id, console_type="novnc"): """ Get a console for the virtual machine Params: @@ -3046,7 +3151,57 @@ class vimconnector(vimconn.vimconnector): port: the http, ssh, ... port suffix: extra text, e.g. the http path and query string """ - raise vimconn.vimconnNotImplemented("Should have implemented this") + console_dict = {} + + if console_type==None or console_type=='novnc': + + url_rest_call = "{}/api/vApp/vm-{}/screen/action/acquireMksTicket".format(self.url, vm_id) + + headers = {'Accept':'application/*+xml;version=' + API_VERSION, + 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']} + response = self.perform_request(req_type='POST', + url=url_rest_call, + headers=headers) + + 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, + response.content, + response.status_code)) + raise vimconn.vimconnException("get_vminstance_console : Failed to get "\ + "VM Mks ticket details") + s = re.search("(.*?)",response.content) + console_dict['server'] = s.group(1) if s else None + s1 = re.search("(\d+)",response.content) + console_dict['port'] = s1.group(1) if s1 else None + + + url_rest_call = "{}/api/vApp/vm-{}/screen/action/acquireTicket".format(self.url, vm_id) + + headers = {'Accept':'application/*+xml;version=' + API_VERSION, + 'x-vcloud-authorization': self.client._session.headers['x-vcloud-authorization']} + response = self.perform_request(req_type='POST', + url=url_rest_call, + headers=headers) + + 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, + response.content, + response.status_code)) + raise vimconn.vimconnException("get_vminstance_console : Failed to get "\ + "VM console details") + s = re.search(">.*?/(vm-\d+.*) - - {10:s} + {9:s} - {11:s} + {10:s} """.format(escape(network_name), is_inherited, gateway_address, subnet_address, dns1, dns2_text, dhcp_enabled, - dhcp_start_address, dhcp_end_address, available_networks, + dhcp_start_address, dhcp_end_address, fence_mode, isshared) headers['Content-Type'] = 'application/vnd.vmware.vcloud.orgVdcNetwork+xml' @@ -4769,6 +4923,15 @@ class vimconnector(vimconn.vimconnector): namespaces["xmlns"] = "http://www.vmware.com/vcloud/v1.5" nwcfglist = newelem.findall(".//xmlns:NetworkConfig", namespaces) + # VCD 9.7 returns an incorrect parentnetwork element. Fix it before PUT operation + parentnetworklist = newelem.findall(".//xmlns:ParentNetwork", namespaces) + if parentnetworklist: + for pn in parentnetworklist: + if "href" not in pn.keys(): + id_val = pn.get("id") + href_val = "{}/api/network/{}".format(self.url, id_val) + pn.set("href", href_val) + newstr = """ @@ -5032,6 +5195,10 @@ class vimconnector(vimconn.vimconnector): "network connection section") data = response.content data = data.split('' not in data: self.logger.debug("add_network_adapter PrimaryNIC not in data nic_type {}".format(nic_type)) item = """{} @@ -5041,7 +5208,7 @@ class vimconnector(vimconn.vimconnector): {} {} """.format(primary_nic_index, network_name, nicIndex, - allocation_mode, nic_type) + allocation_mode, vcd_netadapter_type) # Stub for ip_address feature if ip_address: ip_tag = '{}'.format(ip_address) @@ -5060,7 +5227,7 @@ class vimconnector(vimconn.vimconnector): {} {} """.format(network_name, nicIndex, - allocation_mode, nic_type) + allocation_mode, vcd_netadapter_type) # Stub for ip_address feature if ip_address: ip_tag = '{}'.format(ip_address) @@ -6173,10 +6340,17 @@ class vimconnector(vimconn.vimconnector): if iso_name and media_id: data =""" + xmlns="http://www.vmware.com/vcloud/versions" xmlns:ns2="http://schemas.dmtf.org/ovf/envelope/1" + xmlns:ns3="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" + xmlns:ns4="http://schemas.dmtf.org/wbem/wscim/1/common" + xmlns:ns5="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" + xmlns:ns6="http://www.vmware.com/vcloud/v1.5" + xmlns:ns7="http://www.vmware.com/schema/ovf" + xmlns:ns8="http://schemas.dmtf.org/ovf/environment/1" + xmlns:ns9="http://www.vmware.com/vcloud/extension/v1.5"> """.format(iso_name, media_id, @@ -6194,9 +6368,10 @@ class vimconnector(vimconn.vimconnector): headers=headers) 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") + error_msg = "insert_media_to_vm() : Failed to insert CD-ROM to vm. Reason {}. " \ + "Status code {}".format(response.text, response.status_code) + self.logger.error(error_msg) + raise vimconn.vimconnException(error_msg) else: task = self.get_task_from_response(response.content) result = self.client.get_task_monitor().wait_for_success(task=task) @@ -6310,6 +6485,7 @@ class vimconnector(vimconn.vimconnector): self.org_name)) host = self.url client = Client(host, verify_ssl_certs=False) + client.set_highest_supported_version() client.set_credentials(BasicLoginCredentials(self.user, self.org_name, self.passwd)) # connection object self.client = client