python-3.xvmwarevspherevcenterpyvmomi

unable to attach virtual hardware (storage and network) while creating new VM using pyvmomi


I'm attempting to create a new VM using pyvmomi. I'm using snippets from the pyvmomi community samples. As of now I've been able to come up with the following:

"""
Example for creating a VM
"""

import sys
from pyVmomi import vim
from pyVim.task import WaitForTask
import atexit
import ssl

from pyVim import connect
from pyVmomi import vim
import pdb
import sys
import os
import math

def vconnect(hostIP, username=None, password=None, port=None):
    if (True):
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE  # disable our certificate checking for lab
    else:
        context = ssl.create_default_context()
        context.options |= ssl.OP_NO_TLSv1_3

    #pdb.set_trace()
    hostIP = hostIP.strip('[\'\"]')
    username = username.strip('[\'\"]')
    password = password.strip('[\'\"]')
    port = port.strip('[\'\"]')

    if (port):
        if '@' not in username:
            sys.exit("Please include domain name with username")
        service_instance = connect.SmartConnect(host=str(hostIP),  # build python connection to vSphere
                                                #user="Administrator@vsphere.local",
                                                user=username,
                                                pwd=password,
                                                port=int(port),
                                                sslContext=context)
    else:
        service_instance = connect.SmartConnect(host=str(hostIP),  # build python connection to vSphere
                                                user=username,
                                                pwd=password,
                                                sslContext=context)

    atexit.register(connect.Disconnect, service_instance)  # build disconnect logic
    return service_instance

def create_vm(si, vm_name, minMHz, minMem, minStorage, datacenter_name=None, host_ip=None, datastore_name=None):
    content = si.RetrieveContent()

    container = content.rootFolder  # starting point to look into
    viewType = [vim.ResourcePool]  # object types to look for
    recursive = True  # whether we should look into it recursively
    containerView = content.viewManager.CreateContainerView(container, viewType, recursive)  # create container view
    resourcePools = containerView.view

    datastoreFound = False
    SelectedHost = None
    SelectedDS = None
    SelectedPool = None
    for pool in resourcePools:
        # get pool quick stats
        quickStats = pool.summary.quickStats

        # get memory info
        ConfiguredHostMem = pool.summary.configuredMemoryMB
        HostMemUsage = quickStats.hostMemoryUsage
        GuestConsumedMem = quickStats.guestMemoryUsage
        GuestPrivateMem = quickStats.privateMemory
        sharedMem = quickStats.sharedMemory
        AvailMem = ConfiguredHostMem - HostMemUsage

        # get Compute info
        CurrentCPUDemand = quickStats.overallCpuDemand
        MaxcpuAllocation = pool.summary.config.cpuAllocation.limit
        AvailMHz = MaxcpuAllocation - CurrentCPUDemand
        print(f"Available Mem is {AvailMem}MB and available compute capacity is {AvailMHz}MHz for {pool.name}")


        if 'mhz' in minMHz.lower():
            minMHz = int([x for x in minMHz.lower().split('mhz')][0])
        if 'mb' in minMem.lower():
            minMem = int([x for x in minMem.lower().split('mb')][0])
        if 'gb' in minStorage.lower():
            minStorage = int([x for x in minStorage.lower().split('gb')][0])

        if ((AvailMem < minMem) or (AvailMHz < minMHz)):
            print(f"Unable to allocate enough resources on the \"{pool.name}\" resource pool")
            continue

        # get host info
        cpu = pool.parent
        hostList = cpu.host

        for host in hostList:
            if host.summary.overallStatus.lower() is 'red':
                continue
            else:
                dsList = host.datastore
                for datastore in dsList:
                    freespace = datastore.summary.freeSpace
                    freespace = math.floor(freespace/(1024*1024*1024))
                    if (freespace) < int(minStorage):
                        continue
                    else:
                        SelectedHost = host
                        SelectedDS = datastore
                        SelectedPool = pool
                        datastoreFound = True
                        break
                if (datastoreFound):
                    break
        
        if (datastoreFound):
            print(f"Selected datastore \"{datastore.summary.name}\" with {freespace}GB on it")
            break

    if not (datastoreFound):
        print(f"Unable to allocate enough resources on any resource pool, quitting")
        sys.exit(1)
    else:
        datastore_path = '[' + SelectedDS.name + '] ' + name
        config = create_config_spec(SelectedDS.name, vm_name, minMem, SelectedHost.network[0], int(minStorage))

        vmfolder = SelectedDS.parent.parent.vmFolder
        try:
            WaitForTask(vmfolder.CreateVm(config, pool=SelectedPool, host=SelectedHost))
            print(f"VM {vm_name} created")
        except vim.fault.DuplicateName:
            print("VM duplicate name: %s" % vm_name, file=sys.stderr)
        except vim.fault.AlreadyExists:
            print("VM name %s already exists." % vm_name, file=sys.stderr)

def create_config_spec(datastore_name, name, memory, network, sizeGB, guest="otherGuest",
                       annotation="Sample", cpus=1):
    config = vim.vm.ConfigSpec()
    config.annotation = annotation
    config.memoryMB = int(memory)
    config.guestId = guest
    config.name = name
    config.numCPUs = cpus

    # List of all device controllers we plan to attach to this VM
    vmControllers = []

    # Adding the NIC
    nicspec = vim.vm.device.VirtualDeviceSpec()
    nicspec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
    nic_type = vim.vm.device.VirtualVmxnet3()
    nicspec.device = nic_type
    nicspec.device.deviceInfo = vim.Description()
    nicspec.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
    nicspec.device.backing.network = network
    nicspec.device.backing.deviceName = network.name
    nicspec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
    nicspec.device.connectable.startConnected = True
    nicspec.device.connectable.allowGuestControl = True
    vmControllers.append(nicspec)

    # SCSI controller
    scsi_ctlr = vim.vm.device.VirtualDeviceSpec()
    scsi_ctlr.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
    scsi_ctlr.device = vim.vm.device.ParaVirtualSCSIController()
    scsi_ctlr.device.deviceInfo = vim.Description()
    scsi_ctlr.device.slotInfo = vim.vm.device.VirtualDevice.PciBusSlotInfo()
    scsi_ctlr.device.slotInfo.pciSlotNumber = 16
    scsi_ctlr.device.controllerKey = 100
    scsi_ctlr.device.unitNumber = 3
    scsi_ctlr.device.busNumber = 0
    scsi_ctlr.device.hotAddRemove = True
    scsi_ctlr.device.sharedBus = 'noSharing'
    scsi_ctlr.device.scsiCtlrUnitNumber = 7
    vmControllers.append(scsi_ctlr)    

    # vDisk
    unit_number = 0
    controller = scsi_ctlr.device # this is the controller we defined above
    disk_spec = vim.vm.device.VirtualDeviceSpec()
    disk_spec.fileOperation = "create"
    disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
    disk_spec.device = vim.vm.device.VirtualDisk()
    disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
    disk_spec.device.backing.diskMode = 'persistent'
    disk_spec.device.backing.fileName = '[%s]%s.vmdk' % ( datastore_name, name )
    disk_spec.device.unitNumber = unit_number
    disk_spec.device.capacityInKB = sizeGB * 1024 * 1024
    disk_spec.device.controllerKey = controller.key
    vmControllers.append(disk_spec)
    
    # finalizing the spec
    files = vim.vm.FileInfo()
    files.vmPathName = "[" + datastore_name + "] " + vm_name
    config.files = files
    return config

def main():
    try:
        vcsahostIP = sys.argv[1]
        vcsaUserName = sys.argv[2]
        vcsaPasswd = sys.argv[3]
        vcsaAdminPort = sys.argv[4]
        vmName = sys.argv[5]
        minimumMHz = sys.argv[6]
        minimumMBs = sys.argv[7]
        minimumGB = sys.argv[8]
        
    except IndexError as e:
        print("usage:\n listVMs.py vcsahostIP vcsaUserName vcsaPasswd vcsaAdminport vmName memory-in-MB compute-capacity-MHz disk-space-GB")
        print(e)
        sys.exit(1)

    si = vconnect(vcsahostIP,vcsaUserName,vcsaPasswd,vcsaAdminPort)
    create_vm(si, vmName, minimumMHz, minimumMBs, minimumGB)

# start this thing
if __name__ == "__main__":
    main()

It does create a VM but it has neither the NIC or the storage controller attached to it: enter image description here

I wonder what's happening and looking for a heads up here


Solution

  • I, perhaps, missed the most important link in the chain viz. attaching vmControllers to the ConfigSpec.

    Before

    return config
    

    I should have done

    config.deviceChange = vmControllers
    

    After making this change, it works now.