vmware / pyvmomi

VMware vSphere API Python Bindings
Apache License 2.0
2.22k stars 766 forks source link

CheckRelocate gives success. but, relocate comes with an error like "Currently connected network interface 'Network adapter 1' uses network 'HA-VM-Network-site-1', which is not accessible." #1041

Closed NAVEEVXB closed 1 year ago

NAVEEVXB commented 1 year ago

Describe the bug

I am trying to do a cross vcenter migration using pyvmomi. I have set the network as well and assigned them as part of the deviceChange property. code for that is as follows. here is the code that I have written,

def migrate_vms(self, vm, check=True):
    vm_migrate_task_list = []
    retObj = {
        "task" : None,
        "msg" : ""
    }
    if not (self.source_connected and self.target_connected):
        raise ConnectException("No Connection to Source/Destination. Aborting")
        exit()
    resourcePool = None
    dest_host = None
    vmFolder = None
    datastore = None
    network = None
    hosts = []
    vmObj = None

    if vm['cluster'] != "":  #and vm['network'] != ""
        #now, based on cluster, get the cluster object.
        #get the default resource pool for the cluster,if resource pool is not specified.

        dest_host = None

        target_hosts = self.propObject.findObjInList(self.target_obj.host_result,"cluster-id",vm['cluster-id'])
        for h in target_hosts:
            host = self.propObject.get_object(self.target_obj.si.RetrieveContent(), \
                        [vim.HostSystem],h['name'])
            vmotion_enabled = host.summary.config.vmotionEnabled
            #summary.runtime.inMaintenanceMode
            #summary.runtime.inQuarantineMode
            #summary.runtime.connectionState
            if host.summary.runtime.inMaintenanceMode or host.summary.runtime.connectionState != 'connected' \
                or host.summary.runtime.inQuarantineMode or host.summary.runtime.powerState != 'poweredOn' \
                or not host.summary.config.vmotionEnabled:
                vmotion_enabled = False
            if host.summary.runtime.connectionState=="connected" and vmotion_enabled:
                hosts.append(host)

        if len(hosts)>0:
            content = self.target_obj.si.RetrieveContent()
            if len(hosts)==1:
                dest_host=self.propObject.get_object(content,[vim.HostSystem],hosts[0].name)
            else:
                rand_no = randrange(len(hosts))
                dest_host=self.propObject.get_object(content,[vim.HostSystem],hosts[rand_no].name)

        if dest_host is not None:
            if vm['datastore']!="":
                datastore = self.propObject.get_object(content,[vim.Datastore],vm['datastore'])

            #network = self.propObject.get_object(content,vim.Datastore,vm['network'])
            if vm['resource-pool']!="":
                resourcePool = self.propObject.get_object(content,[vim.ResourcePool],vm['resource-pool'])
            else:
                managed_entity =  self.propObject.get_object(content,[vim.ManagedEntity],vm['cluster'])
                resourcePool = managed_entity.resourcePool

            """
            if resourcePool is None:
                raise ConnectException("Invalid Paramters. Working Resource Pool not found")
                exit()
            """
            if vm['folder']!="":
                vmFolder=self.propObject.get_object(content,[vim.Folder],vm['folder'])
            if vm['network']!="":
                nw = self.propObject.get_object(content,[vim.Network],vm['network'])

            content = self.source_obj.si.RetrieveContent()
            vmObj= self.propObject.get_object(content,[vim.VirtualMachine],vm['vm-name'])

            if vmObj is None:
                raise ConnectException("Unable to find the VM. Please retry")
                return None

            spec = vim.vm.RelocateSpec()

            if datastore is not None:
                #disks = self.propObject.collect_template_disks(vm)
                #spec.disk = self.propObject.construct_locator(disks,datastore)
                spec.datastore = datastore

            spec.host = dest_host
            #test = vim.vm.check.ProvisioningChecker(vm['vm-id'],self.source_obj.si._stub)
            if vmFolder is not None:
                spec.folder = vmFolder

            if resourcePool is not None:
                spec.pool = resourcePool

            if nw is not None:
                network_specs = []
                for nic in vmObj.config.hardware.device:
                    if isinstance(nic, vim.vm.device.VirtualEthernetCard):
                        network_spec = vim.vm.device.VirtualDeviceSpec()
                        network_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
                        network_spec.device = nic
                        network_spec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
                        network_spec.device.backing.network = nw
                        network_spec.device.backing.deviceName = nw.name
                        network_specs.append(network_spec)

                spec.deviceChange = network_specs   

            if check:
                #perform feasibility checks
                #test = vim.vm.check.ProvisioningChecker(vm['vm-id'],self.source_obj.si._stub)
                #task = test.CheckRelocate(vmObj,spec)
                task = content.vmProvisioningChecker.CheckRelocate(vm=vmObj, spec=spec)
                retObj['task'] = task
            else:
                task = vmObj.Relocate(spec=spec)   
                retObj['task'] = task          

            return retObj

Reproduction steps

1. 2. 3. ...

Expected behavior

Even when I do not change network or not initialize deviceChange property of relocateSpec, it gives similar error. What am I doing wrong?

Additional context

No response

prziborowski commented 1 year ago

When you say CheckRelocate gives success, are you basing that on the task not failing, or the task.info.result[] not containing any error objects? (e.g. assert len(task.info.result[0].error) == 0)

In your spec, I don't see service property. Did you just exclude that from your example?

NAVEEVXB commented 1 year ago

hi. Thanks for the reply. during the checkRelocate, the task.info.state comes back with success. error is empty

For the Service property it says it is not mandatory. I have not used it. I have provided destination host/resource pool and other details as part of relocateSpec. Is this mandatory for cross vcenter migration? if so, how can I setup?

NAVEEVXB commented 1 year ago

I tried to add the service property, but I am not able to get the sslThumbprint from the destnation host. host.config.sslThumbPrintData comes with len = 0 or sslThumbprintInfo comes with None. Why and how can I get the SSL Thumbprint info?

One other observation, when without providing sslThumbprint property, it gives an error "Authencity is failed for SSL verfication something..." and when I check the task object returned by the vcenter, it has a sslThumbprint data, which when I provide as value to sslThumbprint, it works like Magic. I am wondering, how does the vCenter task object get the sslThumbprint data and why I am not able to get it from the host?

prziborowski commented 1 year ago

For the Service property it says it is not mandatory.

It is only mandatory for doing a cross vCenter migration. You will need to provide a proper sslThumbprint value. There are various ways to get it, although I don't think any are built into pyVmomi itself. User likely would want to validate they are connecting to the correct vCenter...

   cmd = 'openssl s_client -connect %s:443 </dev/null 2>&1 |' % hostname + \
         'openssl x509 -noout -fingerprint|cut -d"=" -f 2'

this might be some subprocess command you run to generate it (assuming you have openssl cli). Based on https://community.rsa.com/t5/securid-knowledge-base/how-to-view-a-certificate-fingerprint-as-sha-256-sha-1-or-md5/ta-p/4230

hi. Thanks for the reply. during the checkRelocate, the task.info.state comes back with success. error is empty

You would want to look at the task.info.result value. The task itself generates a list of errors and warnings. Usually it runs successfully, so something like the UI has a localized list of messages to give back to the user. An error fault is limited in how it can be represented (typically just 1 localized string with some arguments substituted).

NAVEEVXB commented 1 year ago

Thanks @prziborowski for your help. I was able to resolve it successfully. Your feedback on setting the "Service" property was the missing link in the first place for cross vcenter migration activity. . I also used openssl to get the sshThumbprint from the destination host. Here is the latest code for your reference.

def connect(self):
    context = ssl.SSLContext(ssl.PROTOCOL_TLS)
    context.verify_mode  = ssl.CERT_REQUIRED
    try:
        si = SmartConnect(host=self.host_name, user=self.username, pwd=self.password, sslContext=context)
        self.si = si
        self.ssl = True
        #ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
    except Exception as ex:
        if 'CERTIFICATE_VERIFY_FAILED' in str(ex):
            try:
                context.verify_mode = ssl.CERT_NONE
                si = SmartConnect(host=self.host_name, user=self.username, pwd=self.password, sslContext=context)
                self.si = si
                self.ssl=False
                ctx = ssl.SSLContext()
            except Exception as ex:
                raise ConnectException(ex)
        else:
            raise ConnectException("Unable to Connect")
    finally:
        if self.si is not None:
            url = "https://" +  self.host_name
            self.siUUID = self.si.RetrieveContent().about.instanceUuid
            content = self.si.RetrieveContent()
            sm = content.sessionManager
            session = sm.AcquireCloneTicket()
            vc_cert = ssl.get_server_certificate((self.host_name, (443)))
            vc_pem = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                    vc_cert)
            vc_fingerprint = vc_pem.digest('sha1')  
            self.thumbprint = vc_fingerprint.decode()

Above code is for connecting to host, and I use this as a placeholder to collect sshThumbprint.

This is how I am setting up the service property.

                service = vim.ServiceLocator()
                credentials = vim.ServiceLocator.Credential()
                pwd = vim.ServiceLocator.NamePassword()

                pwd.username = self.target_username
                pwd.password = self.target_password
                credentials = pwd
                service.credential = credentials
                service.instanceUuid =  self.target_obj.siUUID
                service.url = "https://" + self.target_host_name  #+ ":443/sdk"
                #dest_host = vim.HostSystem
                #tick = dest_host.AcquireCimServicesTicket()
                service.sslThumbprint = self.target_obj.thumbprint
                spec.service = service

This worked like magic. Thanks again for your support.

Regards Naveen