protected StartAnswer execute()

in plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java [2033:2715]


    protected StartAnswer execute(StartCommand cmd) {
        VirtualMachineTO vmSpec = cmd.getVirtualMachine();
        boolean vmAlreadyExistsInVcenter = false;

        String existingVmName = null;
        VirtualMachineFileInfo existingVmFileInfo = null;
        VirtualMachineFileLayoutEx existingVmFileLayout = null;
        List<DatastoreMO> existingDatastores = new ArrayList<DatastoreMO>();
        String diskStoragePolicyId = null;
        String vmStoragePolicyId = null;
        VirtualMachineDefinedProfileSpec diskProfileSpec = null;
        VirtualMachineDefinedProfileSpec vmProfileSpec = null;


        DeployAsIsInfoTO deployAsIsInfo = vmSpec.getDeployAsIsInfo();
        boolean deployAsIs = deployAsIsInfo != null;

        Pair<String, String> names = composeVmNames(vmSpec);
        String vmInternalCSName = names.first();
        String vmNameOnVcenter = names.second();
        DiskTO rootDiskTO = null;
        String bootMode = getBootModeFromVmSpec(vmSpec, deployAsIs);
        Pair<String, String> controllerInfo = getControllerInfoFromVmSpec(vmSpec);

        Boolean systemVm = vmSpec.getType().isUsedBySystem();
        // Thus, vmInternalCSName always holds i-x-y, the cloudstack generated internal VM name.
        VmwareContext context = getServiceContext();
        DatacenterMO dcMo = null;
        try {
            VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME);

            VmwareHypervisorHost hyperHost = getHyperHost(context);
            dcMo = new DatacenterMO(hyperHost.getContext(), hyperHost.getHyperHostDatacenter());

            // Validate VM name is unique in Datacenter
            VirtualMachineMO vmInVcenter = dcMo.checkIfVmAlreadyExistsInVcenter(vmNameOnVcenter, vmInternalCSName);
            if (vmInVcenter != null) {
                vmAlreadyExistsInVcenter = true;
                String msg = "VM with name: " + vmNameOnVcenter + " already exists in vCenter.";
                logger.error(msg);
                throw new Exception(msg);
            }

            DiskTO[] specDisks = vmSpec.getDisks();
            String guestOsId = getGuestOsIdFromVmSpec(vmSpec, deployAsIs);
            DiskTO[] disks = validateDisks(vmSpec.getDisks());
            assert (disks.length > 0);
            NicTO[] nics = vmSpec.getNics();

            HashMap<String, Pair<ManagedObjectReference, DatastoreMO>> dataStoresDetails = inferDatastoreDetailsFromDiskInfo(hyperHost, context, disks, cmd);
            if ((dataStoresDetails == null) || (dataStoresDetails.isEmpty())) {
                String msg = "Unable to locate datastore details of the volumes to be attached";
                logger.error(msg);
                throw new Exception(msg);
            }

            VirtualMachineDiskInfoBuilder diskInfoBuilder = null;
            VirtualDevice[] nicDevices = null;
            VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName);
            DiskControllerType systemVmScsiControllerType = DiskControllerType.lsilogic;
            int firstScsiControllerBusNum = 0;
            int numScsiControllerForSystemVm = 1;
            boolean hasSnapshot = false;

            List<Pair<Integer, ManagedObjectReference>> diskDatastores = null;
            if (vmMo != null) {
                logger.info("VM " + vmInternalCSName + " already exists, tear down devices for reconfiguration");
                if (getVmPowerState(vmMo) != PowerState.PowerOff)
                    vmMo.safePowerOff(_shutdownWaitMs);

                // retrieve disk information before we tear down
                diskDatastores = vmMo.getAllDiskDatastores();
                diskInfoBuilder = vmMo.getDiskInfoBuilder();
                hasSnapshot = vmMo.hasSnapshot();
                nicDevices = vmMo.getNicDevices();

                tearDownVmDevices(vmMo, hasSnapshot, deployAsIs);
                ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, systemVmScsiControllerType,
                        numScsiControllerForSystemVm, firstScsiControllerBusNum, deployAsIs);
            } else {
                ManagedObjectReference morDc = hyperHost.getHyperHostDatacenter();
                assert (morDc != null);

                vmMo = hyperHost.findVmOnPeerHyperHost(vmInternalCSName);
                if (vmMo != null) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Found vm " + vmInternalCSName + " at other host, relocate to " + hyperHost.getHyperHostName());
                    }

                    takeVmFromOtherHyperHost(hyperHost, vmInternalCSName);

                    if (getVmPowerState(vmMo) != PowerState.PowerOff)
                        vmMo.safePowerOff(_shutdownWaitMs);

                    diskInfoBuilder = vmMo.getDiskInfoBuilder();
                    hasSnapshot = vmMo.hasSnapshot();
                    diskDatastores = vmMo.getAllDiskDatastores();

                    tearDownVmDevices(vmMo, hasSnapshot, deployAsIs);
                    ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, systemVmScsiControllerType,
                            numScsiControllerForSystemVm, firstScsiControllerBusNum, deployAsIs);
                } else {
                    // If a VM with the same name is found in a different cluster in the DC, unregister the old VM and configure a new VM (cold-migration).
                    VirtualMachineMO existingVmInDc = dcMo.findVm(vmInternalCSName);
                    if (existingVmInDc != null) {
                        logger.debug("Found VM: " + vmInternalCSName + " on a host in a different cluster. Unregistering the exisitng VM.");
                        existingVmName = existingVmInDc.getName();
                        existingVmFileInfo = existingVmInDc.getFileInfo();
                        existingVmFileLayout = existingVmInDc.getFileLayout();
                        existingDatastores = existingVmInDc.getAllDatastores();
                        existingVmInDc.unregisterVm();
                    }

                    if (deployAsIs) {
                        vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName);
                        if (vmMo == null) {
                            logger.info("Cloned deploy-as-is VM " + vmInternalCSName + " is not in this host, relocating it");
                            vmMo = takeVmFromOtherHyperHost(hyperHost, vmInternalCSName);
                        }
                    } else {
                        DiskTO rootDisk = null;
                        for (DiskTO vol : disks) {
                            if (vol.getType() == Volume.Type.ROOT) {
                                rootDisk = vol;
                            }
                        }
                        Pair<ManagedObjectReference, DatastoreMO> rootDiskDataStoreDetails = getDatastoreThatDiskIsOn(dataStoresDetails, rootDisk);
                        assert (vmSpec.getMinSpeed() != null) && (rootDiskDataStoreDetails != null);
                        DatastoreMO dsRootVolumeIsOn = rootDiskDataStoreDetails.second();
                        if (dsRootVolumeIsOn == null) {
                                String msg = "Unable to locate datastore details of root volume";
                                logger.error(msg);
                                throw new Exception(msg);
                            }
                        if (rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && rootDisk.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase(Storage.StoragePoolType.DatastoreCluster.toString())) {
                            if (diskInfoBuilder != null) {
                                DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, rootDisk, diskDatastores);
                                if (diskDatastoreMofromVM != null) {
                                    String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID);
                                    if (!actualPoolUuid.equalsIgnoreCase(rootDisk.getData().getDataStore().getUuid())) {
                                        dsRootVolumeIsOn = diskDatastoreMofromVM;
                                    }
                                }
                            }
                        }

                        boolean vmFolderExists = dsRootVolumeIsOn.folderExists(String.format("[%s]", dsRootVolumeIsOn.getName()), vmNameOnVcenter);
                        String vmxFileFullPath = dsRootVolumeIsOn.searchFileInSubFolders(vmNameOnVcenter + ".vmx", false, VmwareManager.s_vmwareSearchExcludeFolder.value());
                        if (vmFolderExists && vmxFileFullPath != null) { // VM can be registered only if .vmx is present.
                            registerVm(vmNameOnVcenter, dsRootVolumeIsOn);
                            vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName);
                            if (vmMo != null) {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Found registered vm " + vmInternalCSName + " at host " + hyperHost.getHyperHostName());
                                }
                            }
                            tearDownVm(vmMo);
                        } else if (!hyperHost.createBlankVm(vmNameOnVcenter, vmInternalCSName, vmSpec.getCpus(), vmSpec.getMaxSpeed().intValue(), getReservedCpuMHZ(vmSpec),
                                vmSpec.isLimitCpuUse(), (int) (vmSpec.getMaxRam() / ResourceType.bytesToMiB), getReservedMemoryMb(vmSpec), guestOsId, rootDiskDataStoreDetails.first(), false,
                                controllerInfo, systemVm)) {
                            throw new Exception("Failed to create VM. vmName: " + vmInternalCSName);
                        }
                    }
                }

                vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName);
                if (vmMo == null) {
                    throw new Exception("Failed to find the newly create or relocated VM. vmName: " + vmInternalCSName);
                }
            }
            if (deployAsIs && !vmMo.hasSnapshot()) {
                logger.info("Mapping VM disks to spec disks and tearing down datadisks (if any)");
                mapSpecDisksToClonedDisksAndTearDownDatadisks(vmMo, vmInternalCSName, specDisks);
            }

            int disksChanges = getDisksChangesNumberFromDisksSpec(disks, deployAsIs);
            int totalChangeDevices = disksChanges + nics.length;
            if (deployAsIsInfo != null && deployAsIsInfo.getProperties() != null) {
                totalChangeDevices++;
            }

            DiskTO volIso = null;
            if (vmSpec.getType() != VirtualMachine.Type.User) {
                // system VM needs a patch ISO
                totalChangeDevices++;
            } else {
                volIso = getIsoDiskTO(disks);
                if (volIso == null && !deployAsIs) {
                    totalChangeDevices++;
                }
            }

            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();

            int i = 0;
            int ideUnitNumber = !deployAsIs ? 0 : vmMo.getNextIDEDeviceNumber();
            int scsiUnitNumber = !deployAsIs ? 0 : vmMo.getNextScsiDiskDeviceNumber();
            int ideControllerKey = vmMo.getIDEDeviceControllerKey();
            int scsiControllerKey = vmMo.getScsiDeviceControllerKeyNoException();
            VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[totalChangeDevices];
            DiskTO[] sortedDisks = sortVolumesByDeviceId(disks);
            VmwareHelper.setBasicVmConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) (vmSpec.getMaxRam() / (1024 * 1024)),
                    getReservedMemoryMb(vmSpec), guestOsId, vmSpec.isLimitCpuUse(), deployAsIs);

            // Check for multi-cores per socket settings
            int numCoresPerSocket = 1;
            String coresPerSocket = vmSpec.getDetails().get(VmDetailConstants.CPU_CORE_PER_SOCKET);
            if (coresPerSocket != null) {
                String apiVersion = HypervisorHostHelper.getVcenterApiVersion(vmMo.getContext());
                // Property 'numCoresPerSocket' is supported since vSphere API 5.0
                if (apiVersion.compareTo("5.0") >= 0) {
                    numCoresPerSocket = NumbersUtil.parseInt(coresPerSocket, 1);
                    vmConfigSpec.setNumCoresPerSocket(numCoresPerSocket);
                }
            }

            // Check for hotadd settings
            vmConfigSpec.setMemoryHotAddEnabled(vmMo.isMemoryHotAddSupported(guestOsId) && vmSpec.isEnableDynamicallyScaleVm());
            String hostApiVersion = ((HostMO) hyperHost).getHostAboutInfo().getApiVersion();
            if (numCoresPerSocket > 1 && hostApiVersion.compareTo("5.0") < 0) {
                logger.warn("Dynamic scaling of CPU is not supported for Virtual Machines with multi-core vCPUs in case of ESXi hosts 4.1 and prior. Hence CpuHotAdd will not be"
                        + " enabled for Virtual Machine: " + vmInternalCSName);
                vmConfigSpec.setCpuHotAddEnabled(false);
            } else {
                vmConfigSpec.setCpuHotAddEnabled(vmMo.isCpuHotAddSupported(guestOsId) && vmSpec.isEnableDynamicallyScaleVm());
            }

            if(!vmMo.isMemoryHotAddSupported(guestOsId) && vmSpec.isEnableDynamicallyScaleVm()){
                logger.warn("hotadd of memory is not supported, dynamic scaling feature can not be applied to vm: " + vmInternalCSName);
            }

            if(!vmMo.isCpuHotAddSupported(guestOsId) && vmSpec.isEnableDynamicallyScaleVm()){
                logger.warn("hotadd of cpu is not supported, dynamic scaling feature can not be applied to vm: " + vmInternalCSName);
            }

            configNestedHVSupport(vmMo, vmSpec, vmConfigSpec);

            //
            // Setup ISO device
            //

            // prepare systemvm patch ISO
            if (vmSpec.getType() != VirtualMachine.Type.User) {
                // attach ISO (for patching of system VM)
                Pair<String, Long> secStoreUrlAndId = mgr.getSecondaryStorageStoreUrlAndId(Long.parseLong(_dcId));
                String secStoreUrl = secStoreUrlAndId.first();
                if (secStoreUrl == null) {
                    String msg = String.format("NFS secondary or cache storage of dc %s either doesn't have enough capacity (has reached %d%% usage threshold) or not ready yet, or non-NFS secondary storage is used",
                            _dcId, Math.round(CapacityManager.SecondaryStorageCapacityThreshold.value() * 100));
                    throw new Exception(msg);
                }

                ManagedObjectReference morSecDs = prepareSecondaryDatastoreOnHost(secStoreUrl);
                if (morSecDs == null) {
                    String msg = "Failed to prepare secondary storage on host, NFS secondary or cache store url: " + secStoreUrl + " in dc "+ _dcId;
                    throw new Exception(msg);
                }
                DatastoreMO secDsMo = new DatastoreMO(hyperHost.getContext(), morSecDs);

                deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                Pair<VirtualDevice, Boolean> isoInfo = VmwareHelper.prepareIsoDevice(vmMo,
                        null, secDsMo.getMor(), true, true, ideUnitNumber++, i + 1);
                deviceConfigSpecArray[i].setDevice(isoInfo.first());
                if (isoInfo.second()) {
                    if (logger.isDebugEnabled())
                        logger.debug("Prepare ISO volume at new device " + _gson.toJson(isoInfo.first()));
                    deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);
                } else {
                    if (logger.isDebugEnabled())
                        logger.debug("Prepare ISO volume at existing device " + _gson.toJson(isoInfo.first()));
                    deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.EDIT);
                }
                i++;
            } else if (!deployAsIs) {
                // Note: we will always plug a CDROM device
                if (volIso != null) {
                    for (DiskTO vol : disks) {
                        if (vol.getType() == Volume.Type.ISO) {
                            configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber++, i);
                            i++;
                        }
                    }
                } else {
                    deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                    Pair<VirtualDevice, Boolean> isoInfo = VmwareHelper.prepareIsoDevice(vmMo, null, null, true, true, ideUnitNumber++, i + 1);
                    deviceConfigSpecArray[i].setDevice(isoInfo.first());
                    if (isoInfo.second()) {
                        if (logger.isDebugEnabled())
                            logger.debug("Prepare ISO volume at existing device " + _gson.toJson(isoInfo.first()));

                        deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);
                    } else {
                        if (logger.isDebugEnabled())
                            logger.debug("Prepare ISO volume at existing device " + _gson.toJson(isoInfo.first()));

                        deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.EDIT);
                    }
                    i++;
                }
            }

            int controllerKey;
            Pair<String, String> chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(controllerInfo,vmMo, null, null);

            //
            // Setup ROOT/DATA disk devices
            //
            if (multipleIsosAtached(sortedDisks) && deployAsIs) {
                sortedDisks = getDisks(sortedDisks);
            }

            for (DiskTO vol : sortedDisks) {
                if (vol.getType() == Volume.Type.ISO) {
                    if (deployAsIs) {
                        configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber++, i);
                        i++;
                    }
                    continue;
                }

                if (deployAsIs && vol.getType() == Volume.Type.ROOT) {
                    rootDiskTO = vol;
                    resizeRootDiskOnVMStart(vmMo, rootDiskTO, hyperHost, context);
                    continue;
                }

                VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDisk(diskInfoBuilder, vol, hyperHost, context);
                String diskController = getDiskController(vmMo, matchingExistingDisk, vol, chosenDiskControllers, deployAsIs);
                if (DiskControllerType.getType(diskController) == DiskControllerType.ide) {
                    controllerKey = vmMo.getIDEControllerKey(ideUnitNumber);
                    if (vol.getType() == Volume.Type.DATADISK) {
                        // Could be result of flip due to user configured setting or "osdefault" for data disks
                        // Ensure maximum of 2 data volumes over IDE controller, 3 includeing root volume
                        if (vmMo.getNumberOfVirtualDisks() > 3) {
                            throw new CloudRuntimeException("Found more than 3 virtual disks attached to this VM [" + vmMo.getVmName() + "]. Unable to implement the disks over "
                                    + diskController + " controller, as maximum number of devices supported over IDE controller is 4 includeing CDROM device.");
                        }
                    }
                } else {
                    if (VmwareHelper.isReservedScsiDeviceNumber(scsiUnitNumber)) {
                        scsiUnitNumber++;
                    }

                    controllerKey = vmMo.getScsiDiskControllerKeyNoException(diskController, scsiUnitNumber);
                    if (controllerKey == -1) {
                        // This may happen for ROOT legacy VMs which doesn't have recommended disk controller when global configuration parameter 'vmware.root.disk.controller' is set to "osdefault"
                        // Retrieve existing controller and use.
                        Ternary<Integer, Integer, DiskControllerType> vmScsiControllerInfo = vmMo.getScsiControllerInfo();
                        DiskControllerType existingControllerType = vmScsiControllerInfo.third();
                        controllerKey = vmMo.getScsiDiskControllerKeyNoException(existingControllerType.toString(), scsiUnitNumber);
                    }
                }
                if (!hasSnapshot) {
                    deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();

                    VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData();
                    DataStoreTO primaryStore = volumeTO.getDataStore();
                    Map<String, String> details = vol.getDetails();
                    boolean managed = false;
                    String iScsiName = null;

                    if (details != null) {
                        managed = Boolean.parseBoolean(details.get(DiskTO.MANAGED));
                        iScsiName = details.get(DiskTO.IQN);
                    }

                    String primaryStoreUuid = primaryStore.getUuid();
                    // if the storage is managed, iScsiName should not be null
                    String datastoreName = managed ? VmwareResource.getDatastoreName(iScsiName) : primaryStoreUuid;
                    Pair<ManagedObjectReference, DatastoreMO> volumeDsDetails = dataStoresDetails.get(datastoreName);

                    assert (volumeDsDetails != null);
                    if (volumeDsDetails == null) {
                        throw new Exception("Primary datastore " + primaryStore.getUuid() + " is not mounted on host.");
                    }

                    if (vol.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && vol.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) {
                        if (diskInfoBuilder != null && matchingExistingDisk != null) {
                            String[] diskChain = matchingExistingDisk.getDiskChain();
                            if (diskChain != null && diskChain.length > 0) {
                                DatastoreFile file = new DatastoreFile(diskChain[0]);
                                if (!file.getFileBaseName().equalsIgnoreCase(volumeTO.getPath())) {
                                    if (logger.isInfoEnabled())
                                        logger.info("Detected disk-chain top file change on volume: " + volumeTO.getId() + " " + volumeTO.getPath() + " -> " + file.getFileBaseName());
                                    volumeTO.setPath(file.getFileBaseName());
                                }
                            }
                            DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, vol, diskDatastores);
                            if (diskDatastoreMofromVM != null) {
                                String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID);
                                if (actualPoolUuid != null && !actualPoolUuid.equalsIgnoreCase(primaryStore.getUuid())) {
                                    volumeDsDetails = new Pair<>(diskDatastoreMofromVM.getMor(), diskDatastoreMofromVM);
                                    if (logger.isInfoEnabled())
                                        logger.info("Detected datastore uuid change on volume: " + volumeTO.getId() + " " + primaryStore.getUuid() + " -> " + actualPoolUuid);
                                    ((PrimaryDataStoreTO)primaryStore).setUuid(actualPoolUuid);
                                }
                            }
                        }
                    }

                    String[] diskChain = syncDiskChain(dcMo, vmMo, vol, matchingExistingDisk, volumeDsDetails.second());

                    int deviceNumber = -1;
                    if (controllerKey == vmMo.getIDEControllerKey(ideUnitNumber)) {
                        deviceNumber = ideUnitNumber % VmwareHelper.MAX_ALLOWED_DEVICES_IDE_CONTROLLER;
                        ideUnitNumber++;
                    } else {
                        deviceNumber = scsiUnitNumber % VmwareHelper.MAX_ALLOWED_DEVICES_SCSI_CONTROLLER;
                        scsiUnitNumber++;
                    }

                    Long maxIops = volumeTO.getIopsWriteRate() + volumeTO.getIopsReadRate();
                    VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, controllerKey, diskChain, volumeDsDetails.first(), deviceNumber, i + 1, maxIops);
                    logger.debug(LogUtils.logGsonWithoutException("The following definitions will be used to start the VM: virtual device [%s], volume [%s].", device, volumeTO));

                    diskStoragePolicyId = volumeTO.getvSphereStoragePolicyId();
                    if (StringUtils.isNotEmpty(diskStoragePolicyId)) {
                        PbmProfileManagerMO profMgrMo = new PbmProfileManagerMO(context);
                        diskProfileSpec = profMgrMo.getProfileSpec(diskStoragePolicyId);
                        deviceConfigSpecArray[i].getProfile().add(diskProfileSpec);
                        if (logger.isDebugEnabled()) {
                            logger.debug(String.format("Adding vSphere storage profile: %s to virtual disk [%s]", diskStoragePolicyId, _gson.toJson(device)));
                        }
                    }
                    if (vol.getType() == Volume.Type.ROOT) {
                        rootDiskTO = vol;
                        vmStoragePolicyId = diskStoragePolicyId;
                        vmProfileSpec = diskProfileSpec;
                    }
                    deviceConfigSpecArray[i].setDevice(device);
                    deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);

                    if (logger.isDebugEnabled())
                        logger.debug("Prepare volume at new device " + _gson.toJson(device));

                    i++;
                } else {
                    if (controllerKey == vmMo.getIDEControllerKey(ideUnitNumber))
                        ideUnitNumber++;
                    else
                        scsiUnitNumber++;
                }
            }

            //
            // Setup USB devices
            //
            if (StringUtils.isNotBlank(guestOsId) && guestOsId.startsWith("darwin")) { //Mac OS
                VirtualDevice[] devices = vmMo.getMatchedDevices(new Class<?>[]{VirtualUSBController.class});
                if (devices.length == 0) {
                    logger.debug("No USB Controller device on VM Start. Add USB Controller device for Mac OS VM " + vmInternalCSName);

                    //For Mac OS X systems, the EHCI+UHCI controller is enabled by default and is required for USB mouse and keyboard access.
                    VirtualDevice usbControllerDevice = VmwareHelper.prepareUSBControllerDevice();
                    deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                    deviceConfigSpecArray[i].setDevice(usbControllerDevice);
                    deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);

                    if (logger.isDebugEnabled())
                        logger.debug("Prepare USB controller at new device " + _gson.toJson(deviceConfigSpecArray[i]));

                    i++;
                } else {
                    logger.debug("USB Controller device exists on VM Start for Mac OS VM " + vmInternalCSName);
                }
            }

            //
            // Setup NIC devices
            //
            VirtualDevice nic;
            int nicMask = 0;
            int nicCount = 0;

            VirtualEthernetCardType nicDeviceType;

            NiciraNvpApiVersion.logNiciraApiVersion();

            Map<String, String> nicUuidToDvSwitchUuid = new HashMap<String, String>();
            for (NicTO nicTo : sortNicsByDeviceId(nics)) {
                logger.info("Prepare NIC device based on NicTO: " + _gson.toJson(nicTo));

                String adapterTypeStr = deployAsIs ?
                        mapAdapterType(deployAsIsInfo.getNicAdapterMap().get(nicTo.getDeviceId())) :
                        vmSpec.getDetails().get(VmDetailConstants.NIC_ADAPTER);
                nicDeviceType = VirtualEthernetCardType.valueOf(adapterTypeStr);

                if (logger.isDebugEnabled()) {
                    logger.debug("VM " + vmInternalCSName + " will be started with NIC device type: " + nicDeviceType + " on NIC device " + nicTo.getDeviceId());
                }
                boolean configureVServiceInNexus = (nicTo.getType() == TrafficType.Guest) && (vmSpec.getDetails().containsKey("ConfigureVServiceInNexus"));
                VirtualMachine.Type vmType = cmd.getVirtualMachine().getType();
                Pair<ManagedObjectReference, String> networkInfo = prepareNetworkFromNicInfo(vmMo.getRunningHost(), nicTo, configureVServiceInNexus,
                        vmSpec.getNetworkIdToNetworkNameMap().getOrDefault(nicTo.getNetworkId(), null), vmType);
                if ((nicTo.getBroadcastType() != BroadcastDomainType.Lswitch)
                        || (nicTo.getBroadcastType() == BroadcastDomainType.Lswitch && NiciraNvpApiVersion.isApiVersionLowerThan("4.2"))) {
                    if (VmwareHelper.isDvPortGroup(networkInfo.first())) {
                        String dvSwitchUuid;
                        ManagedObjectReference dcMor = hyperHost.getHyperHostDatacenter();
                        DatacenterMO dataCenterMo = new DatacenterMO(context, dcMor);
                        ManagedObjectReference dvsMor = dataCenterMo.getDvSwitchMor(networkInfo.first());
                        dvSwitchUuid = dataCenterMo.getDvSwitchUuid(dvsMor);
                        logger.info("Preparing NIC device on dvSwitch : " + dvSwitchUuid);
                        nic = VmwareHelper.prepareDvNicDevice(vmMo, networkInfo.first(), nicDeviceType, networkInfo.second(), dvSwitchUuid,
                                nicTo.getMac(), i + 1, true, true);
                        if (nicTo.getUuid() != null) {
                            nicUuidToDvSwitchUuid.put(nicTo.getUuid(), dvSwitchUuid);
                        }
                    } else {
                        logger.info("Preparing NIC device on network " + networkInfo.second());
                        nic = VmwareHelper.prepareNicDevice(vmMo, networkInfo.first(), nicDeviceType, networkInfo.second(),
                                nicTo.getMac(), i + 1, true, true);
                    }
                } else {
                    //if NSX API VERSION >= 4.2, connect to br-int (nsx.network), do not create portgroup else previous behaviour
                    nic = VmwareHelper.prepareNicOpaque(vmMo, nicDeviceType, networkInfo.second(),
                            nicTo.getMac(), i + 1, true, true);
                }

                deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec();
                deviceConfigSpecArray[i].setDevice(nic);
                deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);

                if (logger.isDebugEnabled())
                    logger.debug("Prepare NIC at new device " + _gson.toJson(deviceConfigSpecArray[i]));

                // this is really a hacking for DomR, upon DomR startup, we will reset all the NIC allocation after eth3
                if (nicCount < 3)
                    nicMask |= (1 << nicCount);

                i++;
                nicCount++;
            }

            for (int j = 0; j < i; j++)
                vmConfigSpec.getDeviceChange().add(deviceConfigSpecArray[j]);

            //
            // Setup VM options
            //

            // pass boot arguments through machine.id & perform customized options to VMX
            ArrayList<OptionValue> extraOptions = new ArrayList<OptionValue>();
            configBasicExtraOption(extraOptions, vmSpec);

            if (deployAsIs) {
                setDeployAsIsProperties(vmMo, deployAsIsInfo, vmConfigSpec, hyperHost);
            }

            configNvpExtraOption(extraOptions, vmSpec, nicUuidToDvSwitchUuid);
            configCustomExtraOption(extraOptions, vmSpec);

            // config for NCC
            VirtualMachine.Type vmType = cmd.getVirtualMachine().getType();
            if (vmType.equals(VirtualMachine.Type.NetScalerVm)) {
                NicTO mgmtNic = vmSpec.getNics()[0];
                OptionValue option = new OptionValue();
                option.setKey("machine.id");
                option.setValue("ip=" + mgmtNic.getIp() + "&netmask=" + mgmtNic.getNetmask() + "&gateway=" + mgmtNic.getGateway());
                extraOptions.add(option);
            }

            configureVNC(vmSpec, extraOptions, vmConfigSpec, hyperHost, vmInternalCSName);

            // config video card
            configureVideoCard(vmMo, vmSpec, vmConfigSpec);

            setBootOptions(vmSpec, bootMode, vmConfigSpec);

            // Config vTPM
            configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);

            if (StringUtils.isNotEmpty(vmStoragePolicyId)) {
                vmConfigSpec.getVmProfile().add(vmProfileSpec);
                if (logger.isTraceEnabled()) {
                    logger.trace(String.format("Configuring the VM %s with storage policy: %s", vmInternalCSName, vmStoragePolicyId));
                }
            }

            //
            // Configure VM
            //
            if (!vmMo.configureVm(vmConfigSpec)) {
                throw new Exception("Failed to configure VM before start. vmName: " + vmInternalCSName);
            }

            if (vmSpec.getType() == VirtualMachine.Type.DomainRouter) {
                hyperHost.setRestartPriorityForVM(vmMo, DasVmPriority.HIGH.value());
            }

            // Resizing root disk only when explicit requested by user
            final Map<String, String> vmDetails = cmd.getVirtualMachine().getDetails();
            if (!deployAsIs && rootDiskTO != null && !hasSnapshot && (vmDetails != null && vmDetails.containsKey(ApiConstants.ROOT_DISK_SIZE))) {
                resizeRootDiskOnVMStart(vmMo, rootDiskTO, hyperHost, context);
            }

            //
            // Post Configuration
            //

            vmMo.setCustomFieldValue(CustomFieldConstants.CLOUD_NIC_MASK, String.valueOf(nicMask));
            postNvpConfigBeforeStart(vmMo, vmSpec);

            Map<String, Map<String, String>> iqnToData = new HashMap<>();

            postDiskConfigBeforeStart(vmMo, vmSpec, sortedDisks, ideControllerKey, scsiControllerKey, iqnToData, hyperHost, context);

            //
            // Power-on VM
            //
            if (powerOnVM(vmMo, vmInternalCSName, vmNameOnVcenter)) {
                logger.debug(String.format("VM %s has been started successfully with hostname %s.", vmInternalCSName, vmNameOnVcenter));
            } else {
                throw new Exception("Failed to start VM. vmName: " + vmInternalCSName + " with hostname " + vmNameOnVcenter);
            }

            StartAnswer startAnswer = new StartAnswer(cmd);

            startAnswer.setIqnToData(iqnToData);

            if (vmSpec.getType() != VirtualMachine.Type.User) {
                String controlIp = getControlIp(nics);
                // check if the router is up?
                for (int count = 0; count < 60; count++) {
                    final boolean result = _vrResource.connect(controlIp, 1, 5000);
                    if (result) {
                        break;
                    }
                }

                try {
                    String homeDir = System.getProperty("user.home");
                    File pemFile = new File(homeDir + "/.ssh/id_rsa");
                    FileUtil.scpPatchFiles(controlIp, VRScripts.CONFIG_CACHE_LOCATION, DefaultDomRSshPort, pemFile, systemVmPatchFiles, BASEPATH);
                    if (!_vrResource.isSystemVMSetup(vmInternalCSName, controlIp)) {
                        String errMsg = "Failed to patch systemVM";
                        logger.error(errMsg);
                        return new StartAnswer(cmd, errMsg);
                    }
                } catch (Exception e) {
                    String errMsg = "Failed to scp files to system VM. Patching of systemVM failed";
                    logger.error(errMsg, e);
                    return new StartAnswer(cmd, String.format("%s due to: %s", errMsg, e.getMessage()));
                }
            }

            // Since VM was successfully powered-on, if there was an existing VM in a different cluster that was unregistered, delete all the files associated with it.
            if (existingVmName != null && existingVmFileLayout != null) {
                List<String> vmDatastoreNames = new ArrayList<String>();
                for (DatastoreMO vmDatastore : vmMo.getAllDatastores()) {
                    vmDatastoreNames.add(vmDatastore.getName());
                }
                // Don't delete files that are in a datastore that is being used by the new VM as well (zone-wide datastore).
                List<String> skipDatastores = new ArrayList<String>();
                for (DatastoreMO existingDatastore : existingDatastores) {
                    if (vmDatastoreNames.contains(existingDatastore.getName())) {
                        skipDatastores.add(existingDatastore.getName());
                    }
                }
                deleteUnregisteredVmFiles(existingVmFileLayout, dcMo, true, skipDatastores);
            }

            return startAnswer;
        } catch (Throwable e) {
            StartAnswer startAnswer = new StartAnswer(cmd, createLogMessageException(e, cmd));
            if (vmAlreadyExistsInVcenter) {
                startAnswer.setContextParam("stopRetry", "true");
            }

            if (existingVmName != null && existingVmFileInfo != null) {
                logger.debug(String.format("Since VM start failed, registering back an existing VM: [%s] that was unregistered.", existingVmName));
                try {
                    DatastoreFile fileInDatastore = new DatastoreFile(existingVmFileInfo.getVmPathName());
                    DatastoreMO existingVmDsMo = new DatastoreMO(dcMo.getContext(), dcMo.findDatastore(fileInDatastore.getDatastoreName()));
                    registerVm(existingVmName, existingVmDsMo);
                } catch (Exception ex) {
                    String message = String.format("Failed to register an existing VM: [%s] due to [%s].", existingVmName, VmwareHelper.getExceptionMessage(ex));
                    logger.error(message, ex);
                }
            }
            return startAnswer;
        }
    }