You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2021/02/01 09:58:43 UTC

[cloudstack] branch 4.15 updated (1bccb95 -> b6b778f0)

This is an automated email from the ASF dual-hosted git repository.

dahn pushed a change to branch 4.15
in repository https://gitbox.apache.org/repos/asf/cloudstack.git.


    from 1bccb95  Fix merge issue from 74bae56642b224e9ccf54bf6ad3dd73b4cf13f41
     add e4972c9  doc: fix typo in install notes (#4633)
     add 890e847  server: throw exception when update vm nic on L2 network (#4625)
     add 1913c68  server: keep networks order and ips while move a vm with multiple networks (#4602)
     add a44fb11  server: add possibility to scale vm to current customer offerings (#4622)
     add 313ae1f  server: fix wrong error message when create isolated network without SourceNat (#4624)
     add e9dda98  kvm: Use Q35 chipset for UEFI x86_64 (#4576)
     add 9b45ec2  server: select root disk based on user input during vm import (#4591)
     new b6b778f0 Merge release branch 4.14 to 4.15

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 INSTALL.md                                         |  2 +-
 .../service/NetworkOrchestrationService.java       |  2 +-
 .../engine/orchestration/NetworkOrchestrator.java  | 13 +--
 .../kvm/resource/LibvirtComputingResource.java     |  2 +-
 .../java/com/cloud/api/query/QueryManagerImpl.java |  4 +-
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 99 ++++++++++++++--------
 .../cloudstack/vm/UnmanagedVMsManagerImpl.java     | 38 +++++++--
 .../java/com/cloud/vpc/MockNetworkManagerImpl.java |  4 +-
 8 files changed, 113 insertions(+), 51 deletions(-)


[cloudstack] 01/01: Merge release branch 4.14 to 4.15

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch 4.15
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit b6b778f0036ed6387856f5b504fbde2afdc96722
Merge: 1bccb95 9b45ec2
Author: Daan Hoogland <da...@onecht.net>
AuthorDate: Mon Feb 1 09:57:35 2021 +0000

    Merge release branch 4.14 to 4.15
    
    * 4.14:
      server: select root disk based on user input during vm import (#4591)
      kvm: Use Q35 chipset for UEFI x86_64 (#4576)
      server: fix wrong error message when create isolated network without SourceNat (#4624)
      server: add possibility to scale vm to current customer offerings (#4622)
      server: keep networks order and ips while move a vm with multiple networks (#4602)
      server: throw exception when update vm nic on L2 network (#4625)
      doc: fix typo in install notes (#4633)

 INSTALL.md                                         |  2 +-
 .../service/NetworkOrchestrationService.java       |  2 +-
 .../engine/orchestration/NetworkOrchestrator.java  | 13 +--
 .../kvm/resource/LibvirtComputingResource.java     |  2 +-
 .../java/com/cloud/api/query/QueryManagerImpl.java |  4 +-
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 99 ++++++++++++++--------
 .../cloudstack/vm/UnmanagedVMsManagerImpl.java     | 38 +++++++--
 .../java/com/cloud/vpc/MockNetworkManagerImpl.java |  4 +-
 8 files changed, 113 insertions(+), 51 deletions(-)

diff --cc server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
index 532f5f9,0000000..501ca63
mode 100644,000000..100644
--- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
@@@ -1,1367 -1,0 +1,1395 @@@
 +// Licensed to the Apache Software Foundation (ASF) under one
 +// or more contributor license agreements.  See the NOTICE file
 +// distributed with this work for additional information
 +// regarding copyright ownership.  The ASF licenses this file
 +// to you under the Apache License, Version 2.0 (the
 +// "License"); you may not use this file except in compliance
 +// with the License.  You may obtain a copy of the License at
 +//
 +//   http://www.apache.org/licenses/LICENSE-2.0
 +//
 +// Unless required by applicable law or agreed to in writing,
 +// software distributed under the License is distributed on an
 +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +// KIND, either express or implied.  See the License for the
 +// specific language governing permissions and limitations
 +// under the License.
 +
 +package org.apache.cloudstack.vm;
 +
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +
 +import javax.inject.Inject;
 +
 +import com.cloud.agent.api.PrepareUnmanageVMInstanceAnswer;
 +import com.cloud.agent.api.PrepareUnmanageVMInstanceCommand;
 +import com.cloud.event.ActionEvent;
 +import com.cloud.exception.UnsupportedServiceException;
 +import com.cloud.storage.Snapshot;
 +import com.cloud.storage.SnapshotVO;
 +import com.cloud.storage.dao.SnapshotDao;
 +import com.cloud.vm.NicVO;
 +import com.cloud.vm.UserVmVO;
 +import com.cloud.vm.dao.UserVmDao;
 +import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 +import org.apache.cloudstack.api.ApiConstants;
 +import org.apache.cloudstack.api.ApiErrorCode;
 +import org.apache.cloudstack.api.ResponseGenerator;
 +import org.apache.cloudstack.api.ResponseObject;
 +import org.apache.cloudstack.api.ServerApiException;
 +import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
 +import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
 +import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd;
 +import org.apache.cloudstack.api.response.ListResponse;
 +import org.apache.cloudstack.api.response.NicResponse;
 +import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse;
 +import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
 +import org.apache.cloudstack.api.response.UserVmResponse;
 +import org.apache.cloudstack.context.CallContext;
 +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 +import org.apache.cloudstack.framework.config.ConfigKey;
 +import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
 +import org.apache.commons.collections.CollectionUtils;
 +import org.apache.commons.collections.MapUtils;
 +import org.apache.log4j.Logger;
 +
 +import com.cloud.agent.AgentManager;
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.GetUnmanagedInstancesAnswer;
 +import com.cloud.agent.api.GetUnmanagedInstancesCommand;
 +import com.cloud.capacity.CapacityManager;
 +import com.cloud.configuration.Config;
 +import com.cloud.configuration.Resource;
 +import com.cloud.dc.DataCenter;
 +import com.cloud.dc.dao.ClusterDao;
 +import com.cloud.dc.dao.DataCenterDao;
 +import com.cloud.deploy.DataCenterDeployment;
 +import com.cloud.deploy.DeployDestination;
 +import com.cloud.deploy.DeploymentPlanner;
 +import com.cloud.deploy.DeploymentPlanningManager;
 +import com.cloud.event.EventTypes;
 +import com.cloud.event.UsageEventUtils;
 +import com.cloud.exception.InsufficientAddressCapacityException;
 +import com.cloud.exception.InsufficientCapacityException;
 +import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
 +import com.cloud.exception.InvalidParameterValueException;
 +import com.cloud.exception.PermissionDeniedException;
 +import com.cloud.exception.ResourceAllocationException;
 +import com.cloud.host.Host;
 +import com.cloud.host.HostVO;
 +import com.cloud.host.Status;
 +import com.cloud.host.dao.HostDao;
 +import com.cloud.hypervisor.Hypervisor;
 +import com.cloud.network.Network;
 +import com.cloud.network.NetworkModel;
 +import com.cloud.network.Networks;
 +import com.cloud.network.dao.NetworkDao;
 +import com.cloud.network.dao.NetworkVO;
 +import com.cloud.offering.DiskOffering;
 +import com.cloud.offering.ServiceOffering;
 +import com.cloud.org.Cluster;
 +import com.cloud.resource.ResourceManager;
 +import com.cloud.serializer.GsonHelper;
 +import com.cloud.server.ManagementService;
 +import com.cloud.service.ServiceOfferingVO;
 +import com.cloud.service.dao.ServiceOfferingDao;
 +import com.cloud.storage.GuestOS;
 +import com.cloud.storage.GuestOSHypervisor;
 +import com.cloud.storage.StoragePool;
 +import com.cloud.storage.VMTemplateStoragePoolVO;
 +import com.cloud.storage.VMTemplateVO;
 +import com.cloud.storage.Volume;
 +import com.cloud.storage.VolumeApiService;
 +import com.cloud.storage.VolumeVO;
 +import com.cloud.storage.dao.DiskOfferingDao;
 +import com.cloud.storage.dao.GuestOSDao;
 +import com.cloud.storage.dao.GuestOSHypervisorDao;
 +import com.cloud.storage.dao.VMTemplateDao;
 +import com.cloud.storage.dao.VMTemplatePoolDao;
 +import com.cloud.storage.dao.VolumeDao;
 +import com.cloud.template.VirtualMachineTemplate;
 +import com.cloud.user.Account;
 +import com.cloud.user.AccountService;
 +import com.cloud.user.ResourceLimitService;
 +import com.cloud.user.UserVO;
 +import com.cloud.user.dao.UserDao;
 +import com.cloud.uservm.UserVm;
 +import com.cloud.utils.Pair;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +import com.cloud.utils.net.NetUtils;
 +import com.cloud.vm.DiskProfile;
 +import com.cloud.vm.NicProfile;
 +import com.cloud.vm.UserVmManager;
 +import com.cloud.vm.VMInstanceVO;
 +import com.cloud.vm.VirtualMachine;
 +import com.cloud.vm.VirtualMachineManager;
 +import com.cloud.vm.VirtualMachineProfile;
 +import com.cloud.vm.VirtualMachineProfileImpl;
 +import com.cloud.vm.VmDetailConstants;
 +import com.cloud.vm.dao.NicDao;
 +import com.cloud.vm.dao.VMInstanceDao;
 +import com.google.common.base.Strings;
 +import com.google.gson.Gson;
 +
 +public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
 +    public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso";
 +    private static final Logger LOGGER = Logger.getLogger(UnmanagedVMsManagerImpl.class);
 +
 +    @Inject
 +    private AgentManager agentManager;
 +    @Inject
 +    private DataCenterDao dataCenterDao;
 +    @Inject
 +    private ClusterDao clusterDao;
 +    @Inject
 +    private HostDao hostDao;
 +    @Inject
 +    private AccountService accountService;
 +    @Inject
 +    private UserDao userDao;
 +    @Inject
 +    private VMTemplateDao templateDao;
 +    @Inject
 +    private VMTemplatePoolDao templatePoolDao;
 +    @Inject
 +    private ServiceOfferingDao serviceOfferingDao;
 +    @Inject
 +    private DiskOfferingDao diskOfferingDao;
 +    @Inject
 +    private ResourceManager resourceManager;
 +    @Inject
 +    private ResourceLimitService resourceLimitService;
 +    @Inject
 +    private UserVmManager userVmManager;
 +    @Inject
 +    private ResponseGenerator responseGenerator;
 +    @Inject
 +    private VolumeOrchestrationService volumeManager;
 +    @Inject
 +    private VolumeDao volumeDao;
 +    @Inject
 +    private PrimaryDataStoreDao primaryDataStoreDao;
 +    @Inject
 +    private NetworkDao networkDao;
 +    @Inject
 +    private NetworkOrchestrationService networkOrchestrationService;
 +    @Inject
 +    private VMInstanceDao vmDao;
 +    @Inject
 +    private CapacityManager capacityManager;
 +    @Inject
 +    private VolumeApiService volumeApiService;
 +    @Inject
 +    private DeploymentPlanningManager deploymentPlanningManager;
 +    @Inject
 +    private VirtualMachineManager virtualMachineManager;
 +    @Inject
 +    private ManagementService managementService;
 +    @Inject
 +    private NicDao nicDao;
 +    @Inject
 +    private NetworkModel networkModel;
 +    @Inject
 +    private ConfigurationDao configurationDao;
 +    @Inject
 +    private GuestOSDao guestOSDao;
 +    @Inject
 +    private GuestOSHypervisorDao guestOSHypervisorDao;
 +    @Inject
 +    private VMSnapshotDao vmSnapshotDao;
 +    @Inject
 +    private SnapshotDao snapshotDao;
 +    @Inject
 +    private UserVmDao userVmDao;
 +
 +    protected Gson gson;
 +
 +    public UnmanagedVMsManagerImpl() {
 +        gson = GsonHelper.getGsonLogger();
 +    }
 +
 +    private VMTemplateVO createDefaultDummyVmImportTemplate() {
 +        VMTemplateVO template = null;
 +        try {
 +            template = VMTemplateVO.createSystemIso(templateDao.getNextInSequence(Long.class, "id"), VM_IMPORT_DEFAULT_TEMPLATE_NAME, VM_IMPORT_DEFAULT_TEMPLATE_NAME, true,
 +                    "", true, 64, Account.ACCOUNT_ID_SYSTEM, "",
 +                    "VM Import Default Template", false, 1);
 +            template.setState(VirtualMachineTemplate.State.Inactive);
 +            template = templateDao.persist(template);
 +            if (template == null) {
 +                return null;
 +            }
 +            templateDao.remove(template.getId());
 +            template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
 +        } catch (Exception e) {
 +            LOGGER.error("Unable to create default dummy template for VM import", e);
 +        }
 +        return template;
 +    }
 +
 +    private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) {
 +        UnmanagedInstanceResponse response = new UnmanagedInstanceResponse();
 +        response.setName(instance.getName());
 +        if (cluster != null) {
 +            response.setClusterId(cluster.getUuid());
 +        }
 +        if (host != null) {
 +            response.setHostId(host.getUuid());
 +        }
 +        response.setPowerState(instance.getPowerState().toString());
 +        response.setCpuCores(instance.getCpuCores());
 +        response.setCpuSpeed(instance.getCpuSpeed());
 +        response.setCpuCoresPerSocket(instance.getCpuCoresPerSocket());
 +        response.setMemory(instance.getMemory());
 +        response.setOperatingSystemId(instance.getOperatingSystemId());
 +        response.setOperatingSystem(instance.getOperatingSystem());
 +        response.setObjectName("unmanagedinstance");
 +
 +        if (instance.getDisks() != null) {
 +            for (UnmanagedInstanceTO.Disk disk : instance.getDisks()) {
 +                UnmanagedInstanceDiskResponse diskResponse = new UnmanagedInstanceDiskResponse();
 +                diskResponse.setDiskId(disk.getDiskId());
 +                if (!Strings.isNullOrEmpty(disk.getLabel())) {
 +                    diskResponse.setLabel(disk.getLabel());
 +                }
 +                diskResponse.setCapacity(disk.getCapacity());
 +                diskResponse.setController(disk.getController());
 +                diskResponse.setControllerUnit(disk.getControllerUnit());
 +                diskResponse.setPosition(disk.getPosition());
 +                diskResponse.setImagePath(disk.getImagePath());
 +                diskResponse.setDatastoreName(disk.getDatastoreName());
 +                diskResponse.setDatastoreHost(disk.getDatastoreHost());
 +                diskResponse.setDatastorePath(disk.getDatastorePath());
 +                diskResponse.setDatastoreType(disk.getDatastoreType());
 +                response.addDisk(diskResponse);
 +            }
 +        }
 +
 +        if (instance.getNics() != null) {
 +            for (UnmanagedInstanceTO.Nic nic : instance.getNics()) {
 +                NicResponse nicResponse = new NicResponse();
 +                nicResponse.setId(nic.getNicId());
 +                nicResponse.setNetworkName(nic.getNetwork());
 +                nicResponse.setMacAddress(nic.getMacAddress());
 +                if (!Strings.isNullOrEmpty(nic.getAdapterType())) {
 +                    nicResponse.setAdapterType(nic.getAdapterType());
 +                }
 +                if (!CollectionUtils.isEmpty(nic.getIpAddress())) {
 +                    nicResponse.setIpAddresses(nic.getIpAddress());
 +                }
 +                nicResponse.setVlanId(nic.getVlan());
 +                nicResponse.setIsolatedPvlanId(nic.getPvlan());
 +                nicResponse.setIsolatedPvlanType(nic.getPvlanType());
 +                response.addNic(nicResponse);
 +            }
 +        }
 +        return response;
 +    }
 +
 +    private List<String> getAdditionalNameFilters(Cluster cluster) {
 +        List<String> additionalNameFilter = new ArrayList<>();
 +        if (cluster == null) {
 +            return additionalNameFilter;
 +        }
 +        if (cluster.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
 +            // VMWare considers some templates as VM and they are not filtered by VirtualMachineMO.isTemplate()
 +            List<VMTemplateStoragePoolVO> templates = templatePoolDao.listAll();
 +            for (VMTemplateStoragePoolVO template : templates) {
 +                additionalNameFilter.add(template.getInstallPath());
 +            }
 +
 +            // VMWare considers some removed volumes as VM
 +            List<VolumeVO> volumes = volumeDao.findIncludingRemovedByZone(cluster.getDataCenterId());
 +            for (VolumeVO volumeVO : volumes) {
 +                if (volumeVO.getRemoved() == null) {
 +                    continue;
 +                }
 +                if (Strings.isNullOrEmpty(volumeVO.getChainInfo())) {
 +                    continue;
 +                }
 +                List<String> volumeFileNames = new ArrayList<>();
 +                try {
 +                    VirtualMachineDiskInfo diskInfo = gson.fromJson(volumeVO.getChainInfo(), VirtualMachineDiskInfo.class);
 +                    String[] files = diskInfo.getDiskChain();
 +                    if (files.length == 1) {
 +                        continue;
 +                    }
 +                    boolean firstFile = true;
 +                    for (final String file : files) {
 +                        if (firstFile) {
 +                            firstFile = false;
 +                            continue;
 +                        }
 +                        String path = file;
 +                        String[] split = path.split(" ");
 +                        path = split[split.length - 1];
 +                        split = path.split("/");
 +                        ;
 +                        path = split[split.length - 1];
 +                        split = path.split("\\.");
 +                        path = split[0];
 +                        if (!Strings.isNullOrEmpty(path)) {
 +                            if (!additionalNameFilter.contains(path)) {
 +                                volumeFileNames.add(path);
 +                            }
 +                            if (path.contains("-")) {
 +                                split = path.split("-");
 +                                path = split[0];
 +                                if (!Strings.isNullOrEmpty(path) && !path.equals("ROOT") && !additionalNameFilter.contains(path)) {
 +                                    volumeFileNames.add(path);
 +                                }
 +                            }
 +                        }
 +                    }
 +                } catch (Exception e) {
 +                    LOGGER.warn(String.format("Unable to find volume file name for volume ID: %s while adding filters unmanaged VMs", volumeVO.getUuid()), e);
 +                }
 +                if (!volumeFileNames.isEmpty()) {
 +                    additionalNameFilter.addAll(volumeFileNames);
 +                }
 +            }
 +        }
 +        return additionalNameFilter;
 +    }
 +
 +    private List<String> getHostManagedVms(Host host) {
 +        List<String> managedVms = new ArrayList<>();
 +        List<VMInstanceVO> instances = vmDao.listByHostId(host.getId());
 +        for (VMInstanceVO instance : instances) {
 +            managedVms.add(instance.getInstanceName());
 +        }
 +        instances = vmDao.listByLastHostIdAndStates(host.getId(),
 +                VirtualMachine.State.Stopped, VirtualMachine.State.Destroyed,
 +                VirtualMachine.State.Expunging, VirtualMachine.State.Error,
 +                VirtualMachine.State.Unknown, VirtualMachine.State.Shutdown);
 +        for (VMInstanceVO instance : instances) {
 +            managedVms.add(instance.getInstanceName());
 +        }
 +        return managedVms;
 +    }
 +
 +    private boolean hostSupportsServiceOffering(HostVO host, ServiceOffering serviceOffering) {
 +        if (host == null) {
 +            return false;
 +        }
 +        if (serviceOffering == null) {
 +            return false;
 +        }
 +        if (Strings.isNullOrEmpty(serviceOffering.getHostTag())) {
 +            return true;
 +        }
 +        hostDao.loadHostTags(host);
 +        return host.getHostTags() != null && host.getHostTags().contains(serviceOffering.getHostTag());
 +    }
 +
 +    private boolean storagePoolSupportsDiskOffering(StoragePool pool, DiskOffering diskOffering) {
 +        if (pool == null) {
 +            return false;
 +        }
 +        if (diskOffering == null) {
 +            return false;
 +        }
 +        return volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOffering.getTags());
 +    }
 +
 +    private boolean storagePoolSupportsServiceOffering(StoragePool pool, ServiceOffering serviceOffering) {
 +        if (pool == null) {
 +            return false;
 +        }
 +        if (serviceOffering == null) {
 +            return false;
 +        }
 +        return volumeApiService.doesTargetStorageSupportDiskOffering(pool, serviceOffering.getTags());
 +    }
 +
 +    private ServiceOfferingVO getUnmanagedInstanceServiceOffering(final UnmanagedInstanceTO instance, ServiceOfferingVO serviceOffering, final Account owner, final DataCenter zone, final Map<String, String> details)
 +            throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
 +        if (instance == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM is not valid"));
 +        }
 +        if (serviceOffering == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering is not valid"));
 +        }
 +        accountService.checkAccess(owner, serviceOffering, zone);
 +        final Integer cpu = instance.getCpuCores();
 +        final Integer memory = instance.getMemory();
 +        Integer cpuSpeed = instance.getCpuSpeed() == null ? 0 : instance.getCpuSpeed();
 +        if (cpu == null || cpu == 0) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("CPU cores for VM (%s) not valid", instance.getName()));
 +        }
 +        if (memory == null || memory == 0) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Memory for VM (%s) not valid", instance.getName()));
 +        }
 +        if (serviceOffering.isDynamic()) {
 +            if (details.containsKey(VmDetailConstants.CPU_SPEED)) {
 +                try {
 +                    cpuSpeed = Integer.parseInt(details.get(VmDetailConstants.CPU_SPEED));
 +                } catch (Exception e) {
 +                }
 +            }
 +            Map<String, String> parameters = new HashMap<>();
 +            parameters.put(VmDetailConstants.CPU_NUMBER, String.valueOf(cpu));
 +            parameters.put(VmDetailConstants.MEMORY, String.valueOf(memory));
 +            if (serviceOffering.getSpeed() == null && cpuSpeed > 0) {
 +                parameters.put(VmDetailConstants.CPU_SPEED, String.valueOf(cpuSpeed));
 +            }
 +            serviceOffering.setDynamicFlag(true);
 +            userVmManager.validateCustomParameters(serviceOffering, parameters);
 +            serviceOffering = serviceOfferingDao.getComputeOffering(serviceOffering, parameters);
 +        } else {
 +            if (!cpu.equals(serviceOffering.getCpu()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %d CPU cores do not match VM CPU cores %d and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getCpu(), cpu, instance.getPowerState()));
 +            }
 +            if (!memory.equals(serviceOffering.getRamSize()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMB memory does not match VM memory %dMB and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getRamSize(), memory, instance.getPowerState()));
 +            }
 +            if (cpuSpeed != null && cpuSpeed > 0 && !cpuSpeed.equals(serviceOffering.getSpeed()) && !instance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOff)) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMHz CPU speed does not match VM CPU speed %dMHz and VM is not in powered off state (Power state: %s)", serviceOffering.getUuid(), serviceOffering.getSpeed(), cpuSpeed, instance.getPowerState()));
 +            }
 +        }
 +        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.cpu, new Long(serviceOffering.getCpu()));
 +        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.memory, new Long(serviceOffering.getRamSize()));
 +        return serviceOffering;
 +    }
 +
 +    private Map<String, Network.IpAddresses> getNicIpAddresses(final List<UnmanagedInstanceTO.Nic> nics, final Map<String, Network.IpAddresses> callerNicIpAddressMap) {
 +        Map<String, Network.IpAddresses> nicIpAddresses = new HashMap<>();
 +        for (UnmanagedInstanceTO.Nic nic : nics) {
 +            Network.IpAddresses ipAddresses = null;
 +            if (MapUtils.isNotEmpty(callerNicIpAddressMap) && callerNicIpAddressMap.containsKey(nic.getNicId())) {
 +                ipAddresses = callerNicIpAddressMap.get(nic.getNicId());
 +            }
 +            // If IP is set to auto-assign, check NIC doesn't have more that one IP from SDK
 +            if (ipAddresses != null && ipAddresses.getIp4Address() != null && ipAddresses.getIp4Address().equals("auto") && !CollectionUtils.isEmpty(nic.getIpAddress())) {
 +                if (nic.getIpAddress().size() > 1) {
 +                    throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Multiple IP addresses (%s, %s) present for nic ID: %s. IP address cannot be assigned automatically, only single IP address auto-assigning supported", nic.getIpAddress().get(0), nic.getIpAddress().get(1), nic.getNicId()));
 +                }
 +                String address = nic.getIpAddress().get(0);
 +                if (NetUtils.isValidIp4(address)) {
 +                    ipAddresses.setIp4Address(address);
 +                }
 +            }
 +            if (ipAddresses != null) {
 +                nicIpAddresses.put(nic.getNicId(), ipAddresses);
 +            }
 +        }
 +        return nicIpAddresses;
 +    }
 +
 +    private StoragePool getStoragePool(final UnmanagedInstanceTO.Disk disk, final DataCenter zone, final Cluster cluster) {
 +        StoragePool storagePool = null;
 +        final String dsHost = disk.getDatastoreHost();
 +        final String dsPath = disk.getDatastorePath();
 +        final String dsType = disk.getDatastoreType();
 +        final String dsName = disk.getDatastoreName();
 +        if (dsType != null) {
 +            List<StoragePoolVO> pools = primaryDataStoreDao.listPoolByHostPath(dsHost, dsPath);
 +            for (StoragePool pool : pools) {
 +                if (pool.getDataCenterId() == zone.getId() &&
 +                        (pool.getClusterId() == null || pool.getClusterId().equals(cluster.getId()))) {
 +                    storagePool = pool;
 +                    break;
 +                }
 +            }
 +        }
 +
 +        if (storagePool == null) {
 +            List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(cluster.getId());
 +            pools.addAll(primaryDataStoreDao.listByDataCenterId(zone.getId()));
 +            for (StoragePool pool : pools) {
 +                if (pool.getPath().endsWith(dsName)) {
 +                    storagePool = pool;
 +                    break;
 +                }
 +            }
 +        }
 +        if (storagePool == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Storage pool for disk %s(%s) with datastore: %s not found in zone ID: %s", disk.getLabel(), disk.getDiskId(), disk.getDatastoreName(), zone.getUuid()));
 +        }
 +        return storagePool;
 +    }
 +
++    private Pair<UnmanagedInstanceTO.Disk, List<UnmanagedInstanceTO.Disk>> getRootAndDataDisks(List<UnmanagedInstanceTO.Disk> disks, final Map<String, Long> dataDiskOfferingMap) {
++        UnmanagedInstanceTO.Disk rootDisk = null;
++        List<UnmanagedInstanceTO.Disk> dataDisks = new ArrayList<>();
++        if (disks.size() == 1) {
++            rootDisk = disks.get(0);
++            return new Pair<>(rootDisk, dataDisks);
++        }
++        Set<String> callerDiskIds = dataDiskOfferingMap.keySet();
++        if (callerDiskIds.size() != disks.size() - 1) {
++            String msg = String.format("VM has total %d disks for which %d disk offering mappings provided. %d disks need a disk offering for import", disks.size(), callerDiskIds.size(), disks.size()-1);
++            LOGGER.error(String.format("%s. %s parameter can be used to provide disk offerings for the disks", msg, ApiConstants.DATADISK_OFFERING_LIST));
++            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, msg);
++        }
++        List<String> diskIdsWithoutOffering = new ArrayList<>();
++        for (UnmanagedInstanceTO.Disk disk : disks) {
++            String diskId = disk.getDiskId();
++            if (!callerDiskIds.contains(diskId)) {
++                diskIdsWithoutOffering.add(diskId);
++                rootDisk = disk;
++            } else {
++                dataDisks.add(disk);
++            }
++        }
++        if (diskIdsWithoutOffering.size() > 1) {
++            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM has total %d disks, disk offering mapping not provided for %d disks. Disk IDs that may need a disk offering - %s", disks.size(), diskIdsWithoutOffering.size()-1, String.join(", ", diskIdsWithoutOffering)));
++        }
++        return new Pair<>(rootDisk, dataDisks);
++    }
++
 +    private void checkUnmanagedDiskAndOfferingForImport(UnmanagedInstanceTO.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
 +            throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
 +        if (serviceOffering == null && diskOffering == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID: %s not found during VM import", disk.getDiskId()));
 +        }
 +        if (diskOffering != null) {
 +            accountService.checkAccess(owner, diskOffering, zone);
 +        }
 +        resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.volume);
 +        if (disk.getCapacity() == null || disk.getCapacity() == 0) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Size of disk(ID: %s) is found invalid during VM import", disk.getDiskId()));
 +        }
 +        if (diskOffering != null && !diskOffering.isCustomized() && diskOffering.getDiskSize() == 0) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Size of fixed disk offering(ID: %s) is found invalid during VM import", diskOffering.getUuid()));
 +        }
 +        if (diskOffering != null && !diskOffering.isCustomized() && diskOffering.getDiskSize() < disk.getCapacity()) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Size of disk offering(ID: %s) %dGB is found less than the size of disk(ID: %s) %dGB during VM import", diskOffering.getUuid(), (diskOffering.getDiskSize() / Resource.ResourceType.bytesToGiB), disk.getDiskId(), (disk.getCapacity() / (Resource.ResourceType.bytesToGiB))));
 +        }
 +        StoragePool storagePool = getStoragePool(disk, zone, cluster);
 +        if (diskOffering != null && !migrateAllowed && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) {
 +            throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", diskOffering.getUuid(), storagePool.getUuid(), disk.getDiskId()));
 +        }
 +        if (serviceOffering != null && !migrateAllowed && !storagePoolSupportsServiceOffering(storagePool, serviceOffering)) {
 +            throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", serviceOffering.getUuid(), storagePool.getUuid(), disk.getDiskId()));
 +        }
 +    }
 +
 +    private void checkUnmanagedDiskAndOfferingForImport(List<UnmanagedInstanceTO.Disk> disks, final Map<String, Long> diskOfferingMap, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed)
 +            throws ServerApiException, PermissionDeniedException, ResourceAllocationException {
 +        String diskController = null;
 +        for (UnmanagedInstanceTO.Disk disk : disks) {
 +            if (disk == null) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve disk details for VM"));
 +            }
 +            if (!diskOfferingMap.containsKey(disk.getDiskId())) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID: %s not found during VM import", disk.getDiskId()));
 +            }
 +            if (Strings.isNullOrEmpty(diskController)) {
 +                diskController = disk.getController();
 +            } else {
 +                if (!diskController.equals(disk.getController())) {
 +                    throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Multiple data disk controllers of different type (%s, %s) are not supported for import. Please make sure that all data disk controllers are of the same type", diskController, disk.getController()));
 +                }
 +            }
 +            checkUnmanagedDiskAndOfferingForImport(disk, diskOfferingDao.findById(diskOfferingMap.get(disk.getDiskId())), null, owner, zone, cluster, migrateAllowed);
 +        }
 +    }
 +
 +    private void checkUnmanagedNicAndNetworkForImport(UnmanagedInstanceTO.Nic nic, Network network, final DataCenter zone, final Account owner, final boolean autoAssign) throws ServerApiException {
 +        if (nic == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
 +        }
 +        if (network == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
 +        }
 +        if (network.getDataCenterId() != zone.getId()) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network(ID: %s) for nic(ID: %s) belongs to a different zone than VM to be imported", network.getUuid(), nic.getNicId()));
 +        }
 +        networkModel.checkNetworkPermissions(owner, network);
 +        if (!autoAssign && network.getGuestType().equals(Network.GuestType.Isolated)) {
 +            return;
 +        }
 +
 +        String networkBroadcastUri = network.getBroadcastUri() == null ? null : network.getBroadcastUri().toString();
 +        if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() == null &&
 +                (Strings.isNullOrEmpty(networkBroadcastUri) ||
 +                        !networkBroadcastUri.equals(String.format("vlan://%d", nic.getVlan())))) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) vlan://%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan()));
 +        }
 +        if (nic.getVlan() != null && nic.getVlan() != 0 && nic.getPvlan() != null && nic.getPvlan() != 0 &&
 +                (Strings.isNullOrEmpty(network.getBroadcastUri().toString()) ||
 +                        !networkBroadcastUri.equals(String.format("pvlan://%d-i%d", nic.getVlan(), nic.getPvlan())))) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("PVLAN of network(ID: %s) %s is found different from the VLAN of nic(ID: %s) pvlan://%d-i%d during VM import", network.getUuid(), networkBroadcastUri, nic.getNicId(), nic.getVlan(), nic.getPvlan()));
 +        }
 +    }
 +
 +    private void checkUnmanagedNicAndNetworkHostnameForImport(UnmanagedInstanceTO.Nic nic, Network network, final String hostName) throws ServerApiException {
 +        if (nic == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
 +        }
 +        if (network == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
 +        }
 +        // Check for duplicate hostname in network, get all vms hostNames in the network
 +        List<String> hostNames = vmDao.listDistinctHostNames(network.getId());
 +        if (CollectionUtils.isNotEmpty(hostNames) && hostNames.contains(hostName)) {
 +            throw new InvalidParameterValueException("The vm with hostName " + hostName + " already exists in the network domain: " + network.getNetworkDomain() + "; network="
 +                    + network);
 +        }
 +    }
 +
 +    private void checkUnmanagedNicIpAndNetworkForImport(UnmanagedInstanceTO.Nic nic, Network network, final Network.IpAddresses ipAddresses) throws ServerApiException {
 +        if (nic == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve NIC details during VM import"));
 +        }
 +        if (network == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Network for nic ID: %s not found during VM import", nic.getNicId()));
 +        }
 +        // Check IP is assigned for non L2 networks
 +        if (!network.getGuestType().equals(Network.GuestType.L2) && (ipAddresses == null || Strings.isNullOrEmpty(ipAddresses.getIp4Address()))) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC(ID: %s) needs a valid IP address for it to be associated with network(ID: %s). %s parameter of API can be used for this", nic.getNicId(), network.getUuid(), ApiConstants.NIC_IP_ADDRESS_LIST));
 +        }
 +        // If network is non L2, IP v4 is assigned and not set to auto-assign, check it is available for network
 +        if (!network.getGuestType().equals(Network.GuestType.L2) && ipAddresses != null && !Strings.isNullOrEmpty(ipAddresses.getIp4Address()) && !ipAddresses.getIp4Address().equals("auto")) {
 +            Set<Long> ips = networkModel.getAvailableIps(network, ipAddresses.getIp4Address());
 +            if (CollectionUtils.isEmpty(ips) || !ips.contains(NetUtils.ip2Long(ipAddresses.getIp4Address()))) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("IP address %s for NIC(ID: %s) is not available in network(ID: %s)", ipAddresses.getIp4Address(), nic.getNicId(), network.getUuid()));
 +            }
 +        }
 +    }
 +
 +    private Map<String, Long> getUnmanagedNicNetworkMap(List<UnmanagedInstanceTO.Nic> nics, final Map<String, Long> callerNicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap, final DataCenter zone, final String hostName, final Account owner) throws ServerApiException {
 +        Map<String, Long> nicNetworkMap = new HashMap<>();
 +        String nicAdapter = null;
 +        for (UnmanagedInstanceTO.Nic nic : nics) {
 +            if (Strings.isNullOrEmpty(nicAdapter)) {
 +                nicAdapter = nic.getAdapterType();
 +            } else {
 +                if (!nicAdapter.equals(nic.getAdapterType())) {
 +                    throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Multiple network adapter of different type (%s, %s) are not supported for import. Please make sure that all network adapters are of the same type", nicAdapter, nic.getAdapterType()));
 +                }
 +            }
 +            Network network = null;
 +            Network.IpAddresses ipAddresses = null;
 +            if (MapUtils.isNotEmpty(callerNicIpAddressMap) && callerNicIpAddressMap.containsKey(nic.getNicId())) {
 +                ipAddresses = callerNicIpAddressMap.get(nic.getNicId());
 +            }
 +            if (!callerNicNetworkMap.containsKey(nic.getNicId())) {
 +                if (nic.getVlan() != null && nic.getVlan() != 0) {
 +                    // Find a suitable network
 +                    List<NetworkVO> networks = networkDao.listByZone(zone.getId());
 +                    for (NetworkVO networkVO : networks) {
 +                        if (networkVO.getTrafficType() == Networks.TrafficType.None || Networks.TrafficType.isSystemNetwork(networkVO.getTrafficType())) {
 +                            continue;
 +                        }
 +                        try {
 +                            checkUnmanagedNicAndNetworkForImport(nic, networkVO, zone, owner, true);
 +                            network = networkVO;
 +                        } catch (Exception e) {
 +                        }
 +                        if (network != null) {
 +                            checkUnmanagedNicAndNetworkHostnameForImport(nic, network, hostName);
 +                            checkUnmanagedNicIpAndNetworkForImport(nic, network, ipAddresses);
 +                            break;
 +                        }
 +                    }
 +                }
 +            } else {
 +                network = networkDao.findById(callerNicNetworkMap.get(nic.getNicId()));
 +                checkUnmanagedNicAndNetworkForImport(nic, network, zone, owner, false);
 +                checkUnmanagedNicAndNetworkHostnameForImport(nic, network, hostName);
 +                checkUnmanagedNicIpAndNetworkForImport(nic, network, ipAddresses);
 +            }
 +            if (network == null) {
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Suitable network for nic(ID: %s) not found during VM import", nic.getNicId()));
 +            }
 +            nicNetworkMap.put(nic.getNicId(), network.getId());
 +        }
 +        return nicNetworkMap;
 +    }
 +
 +    private Pair<DiskProfile, StoragePool> importDisk(UnmanagedInstanceTO.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering,
 +                                                      Volume.Type type, String name, Long diskSize, Long minIops, Long maxIops, VirtualMachineTemplate template,
 +                                                      Account owner, Long deviceId) {
 +        final DataCenter zone = dataCenterDao.findById(vm.getDataCenterId());
 +        final String path = Strings.isNullOrEmpty(disk.getFileBaseName()) ? disk.getImagePath() : disk.getFileBaseName();
 +        String chainInfo = disk.getChainInfo();
 +        if (Strings.isNullOrEmpty(chainInfo)) {
 +            VirtualMachineDiskInfo diskInfo = new VirtualMachineDiskInfo();
 +            diskInfo.setDiskDeviceBusName(String.format("%s%d:%d", disk.getController(), disk.getControllerUnit(), disk.getPosition()));
 +            diskInfo.setDiskChain(new String[]{disk.getImagePath()});
 +            chainInfo = gson.toJson(diskInfo);
 +        }
 +        StoragePool storagePool = getStoragePool(disk, zone, cluster);
 +        DiskProfile profile = volumeManager.importVolume(type, name, diskOffering, diskSize,
 +                minIops, maxIops, vm, template, owner, deviceId, storagePool.getId(), path, chainInfo);
 +
 +        return new Pair<DiskProfile, StoragePool>(profile, storagePool);
 +    }
 +
 +    private NicProfile importNic(UnmanagedInstanceTO.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, boolean isDefaultNic, boolean forced) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
 +        Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), 0, network, isDefaultNic, vm, ipAddresses, forced);
 +        if (result == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("NIC ID: %s import failed", nic.getNicId()));
 +        }
 +        return result.first();
 +    }
 +
 +    private void cleanupFailedImportVM(final UserVm userVm) {
 +        if (userVm == null) {
 +            return;
 +        }
 +        VirtualMachineProfile profile = new VirtualMachineProfileImpl(userVm);
 +        // Remove all volumes
 +        volumeDao.deleteVolumesByInstance(userVm.getId());
 +        // Remove all nics
 +        try {
 +            networkOrchestrationService.release(profile, true);
 +        } catch (Exception e) {
 +            LOGGER.error(String.format("Unable to release NICs for unsuccessful import unmanaged VM: %s", userVm.getInstanceName()), e);
 +            nicDao.removeNicsForInstance(userVm.getId());
 +        }
 +        // Remove vm
 +        vmDao.remove(userVm.getId());
 +    }
 +
 +    private UserVm migrateImportedVM(HostVO sourceHost, VirtualMachineTemplate template, ServiceOfferingVO serviceOffering, UserVm userVm, final Account owner, List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList) {
 +        UserVm vm = userVm;
 +        if (vm == null) {
 +            LOGGER.error(String.format("Failed to check migrations need during VM import"));
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to check migrations need during VM import"));
 +        }
 +        if (sourceHost == null || serviceOffering == null || diskProfileStoragePoolList == null) {
 +            LOGGER.error(String.format("Failed to check migrations need during import, VM: %s", userVm.getInstanceName()));
 +            cleanupFailedImportVM(vm);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to check migrations need during import, VM: %s", userVm.getInstanceName()));
 +        }
 +        if (!hostSupportsServiceOffering(sourceHost, serviceOffering)) {
 +            LOGGER.debug(String.format("VM %s needs to be migrated", vm.getUuid()));
 +            final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, template, serviceOffering, owner, null);
 +            DeploymentPlanner.ExcludeList excludeList = new DeploymentPlanner.ExcludeList();
 +            excludeList.addHost(sourceHost.getId());
 +            final DataCenterDeployment plan = new DataCenterDeployment(sourceHost.getDataCenterId(), sourceHost.getPodId(), sourceHost.getClusterId(), null, null, null);
 +            DeployDestination dest = null;
 +            try {
 +                dest = deploymentPlanningManager.planDeployment(profile, plan, excludeList, null);
 +            } catch (Exception e) {
 +                LOGGER.warn(String.format("VM import failed for unmanaged vm: %s during vm migration, finding deployment destination", vm.getInstanceName()), e);
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during vm migration, finding deployment destination", vm.getInstanceName()));
 +            }
 +            if (dest != null) {
 +                if (LOGGER.isDebugEnabled()) {
 +                    LOGGER.debug(" Found " + dest + " for migrating the vm to");
 +                }
 +            }
 +            if (dest == null) {
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during vm migration, no deployment destination found", vm.getInstanceName()));
 +            }
 +            try {
 +                if (vm.getState().equals(VirtualMachine.State.Stopped)) {
 +                    VMInstanceVO vmInstanceVO = vmDao.findById(userVm.getId());
 +                    vmInstanceVO.setHostId(dest.getHost().getId());
 +                    vmInstanceVO.setLastHostId(dest.getHost().getId());
 +                    vmDao.update(vmInstanceVO.getId(), vmInstanceVO);
 +                } else {
 +                    virtualMachineManager.migrate(vm.getUuid(), sourceHost.getId(), dest);
 +                }
 +                vm = userVmManager.getUserVm(vm.getId());
 +            } catch (Exception e) {
 +                LOGGER.error(String.format("VM import failed for unmanaged vm: %s during vm migration", vm.getInstanceName()), e);
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during vm migration. %s", userVm.getInstanceName(), e.getMessage()));
 +            }
 +        }
 +        for (Pair<DiskProfile, StoragePool> diskProfileStoragePool : diskProfileStoragePoolList) {
 +            if (diskProfileStoragePool == null ||
 +                    diskProfileStoragePool.first() == null ||
 +                    diskProfileStoragePool.second() == null) {
 +                continue;
 +            }
 +            DiskProfile profile = diskProfileStoragePool.first();
 +            DiskOffering dOffering = diskOfferingDao.findById(profile.getDiskOfferingId());
 +            if (dOffering == null) {
 +                continue;
 +            }
 +            VolumeVO volumeVO = volumeDao.findById(profile.getVolumeId());
 +            if (volumeVO == null) {
 +                continue;
 +            }
 +            boolean poolSupportsOfferings = storagePoolSupportsDiskOffering(diskProfileStoragePool.second(), dOffering);
 +            if (poolSupportsOfferings && profile.getType() == Volume.Type.ROOT) {
 +                poolSupportsOfferings = storagePoolSupportsServiceOffering(diskProfileStoragePool.second(), serviceOffering);
 +            }
 +            if (poolSupportsOfferings) {
 +                continue;
 +            }
 +            LOGGER.debug(String.format("Volume %s needs to be migrated", volumeVO.getUuid()));
 +            Pair<List<? extends StoragePool>, List<? extends StoragePool>> poolsPair = managementService.listStoragePoolsForMigrationOfVolume(profile.getVolumeId());
 +            if (CollectionUtils.isEmpty(poolsPair.first()) && CollectionUtils.isEmpty(poolsPair.second())) {
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during volume ID: %s migration as no suitable pool(s) found", userVm.getInstanceName(), volumeVO.getUuid()));
 +            }
 +            List<? extends StoragePool> storagePools = poolsPair.second();
 +            StoragePool storagePool = null;
 +            if (CollectionUtils.isNotEmpty(storagePools)) {
 +                for (StoragePool pool : storagePools) {
 +                    if (diskProfileStoragePool.second().getId() != pool.getId() &&
 +                            storagePoolSupportsDiskOffering(pool, dOffering) &&
 +                            (!profile.getType().equals(Volume.Type.ROOT) ||
 +                                    profile.getType().equals(Volume.Type.ROOT) && storagePoolSupportsServiceOffering(pool, serviceOffering))) {
 +                        storagePool = pool;
 +                        break;
 +                    }
 +                }
 +            }
 +            // For zone-wide pools, at times, suitable storage pools are not returned therefore consider all pools.
 +            if (storagePool == null && CollectionUtils.isNotEmpty(poolsPair.first())) {
 +                storagePools = poolsPair.first();
 +                for (StoragePool pool : storagePools) {
 +                    if (diskProfileStoragePool.second().getId() != pool.getId() &&
 +                            storagePoolSupportsDiskOffering(pool, dOffering) &&
 +                            (!profile.getType().equals(Volume.Type.ROOT) ||
 +                                    profile.getType().equals(Volume.Type.ROOT) && storagePoolSupportsServiceOffering(pool, serviceOffering))) {
 +                        storagePool = pool;
 +                        break;
 +                    }
 +                }
 +            }
 +            if (storagePool == null) {
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during volume ID: %s migration as no suitable pool found", userVm.getInstanceName(), volumeVO.getUuid()));
 +            } else {
 +                LOGGER.debug(String.format("Found storage pool %s(%s) for migrating the volume %s to", storagePool.getName(), storagePool.getUuid(), volumeVO.getUuid()));
 +            }
 +            try {
 +                Volume volume = null;
 +                if (vm.getState().equals(VirtualMachine.State.Running)) {
 +                    volume = volumeManager.liveMigrateVolume(volumeVO, storagePool);
 +                } else {
 +                    volume = volumeManager.migrateVolume(volumeVO, storagePool);
 +                }
 +                if (volume == null) {
 +                    String msg = "";
 +                    if (vm.getState().equals(VirtualMachine.State.Running)) {
 +                        msg = String.format("Live migration for volume ID: %s to destination pool ID: %s failed", volumeVO.getUuid(), storagePool.getUuid());
 +                    } else {
 +                        msg = String.format("Migration for volume ID: %s to destination pool ID: %s failed", volumeVO.getUuid(), storagePool.getUuid());
 +                    }
 +                    LOGGER.error(msg);
 +                    throw new CloudRuntimeException(msg);
 +                }
 +            } catch (Exception e) {
 +                LOGGER.error(String.format("VM import failed for unmanaged vm: %s during volume migration", vm.getInstanceName()), e);
 +                cleanupFailedImportVM(vm);
 +                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm: %s during volume migration. %s", userVm.getInstanceName(), Strings.nullToEmpty(e.getMessage())));
 +            }
 +        }
 +        return userVm;
 +    }
 +
 +    private void publishVMUsageUpdateResourceCount(final UserVm userVm, ServiceOfferingVO serviceOfferingVO) {
 +        if (userVm == null || serviceOfferingVO == null) {
 +            LOGGER.error("Failed to publish usage records during VM import");
 +            cleanupFailedImportVM(userVm);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm during publishing usage records"));
 +        }
 +        try {
 +            if (!serviceOfferingVO.isDynamic()) {
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
 +                        userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplayVm());
 +            } else {
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, userVm.getAccountId(), userVm.getAccountId(), userVm.getDataCenterId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
 +                        userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.getDetails(), userVm.isDisplayVm());
 +            }
 +            if (userVm.getState() == VirtualMachine.State.Running) {
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_START, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), userVm.getHostName(), serviceOfferingVO.getId(), userVm.getTemplateId(),
 +                        userVm.getHypervisorType().toString(), VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplayVm());
 +            }
 +        } catch (Exception e) {
 +            LOGGER.error(String.format("Failed to publish usage records during VM import for unmanaged vm %s", userVm.getInstanceName()), e);
 +            cleanupFailedImportVM(userVm);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed for unmanaged vm %s during publishing usage records", userVm.getInstanceName()));
 +        }
 +        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.user_vm, userVm.isDisplayVm());
 +        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.cpu, userVm.isDisplayVm(), new Long(serviceOfferingVO.getCpu()));
 +        resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.memory, userVm.isDisplayVm(), new Long(serviceOfferingVO.getRamSize()));
 +        // Save usage event and update resource count for user vm volumes
 +        List<VolumeVO> volumes = volumeDao.findByInstance(userVm.getId());
 +        for (VolumeVO volume : volumes) {
 +            try {
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), volume.getDiskOfferingId(), null, volume.getSize(),
 +                        Volume.class.getName(), volume.getUuid(), volume.isDisplayVolume());
 +            } catch (Exception e) {
 +                LOGGER.error(String.format("Failed to publish volume ID: %s usage records during VM import", volume.getUuid()), e);
 +            }
 +            resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.volume, volume.isDisplayVolume());
 +            resourceLimitService.incrementResourceCount(userVm.getAccountId(), Resource.ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize());
 +        }
 +
 +        List<NicVO> nics = nicDao.listByVmId(userVm.getId());
 +        for (NicVO nic : nics) {
 +            try {
 +                NetworkVO network = networkDao.findById(nic.getNetworkId());
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(),
 +                        Long.toString(nic.getId()), network.getNetworkOfferingId(), null, 1L, VirtualMachine.class.getName(), userVm.getUuid(), userVm.isDisplay());
 +            } catch (Exception e) {
 +                LOGGER.error(String.format("Failed to publish network usage records during VM import. %s", Strings.nullToEmpty(e.getMessage())));
 +            }
 +        }
 +    }
 +
 +    private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedInstance, final String instanceName, final DataCenter zone, final Cluster cluster, final HostVO host,
 +                                                final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
 +                                                final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
 +                                                final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap,
 +                                                final Map<String, String> details, final boolean migrateAllowed, final boolean forced) {
 +        UserVm userVm = null;
 +
 +        ServiceOfferingVO validatedServiceOffering = null;
 +        try {
 +            validatedServiceOffering = getUnmanagedInstanceServiceOffering(unmanagedInstance, serviceOffering, owner, zone, details);
 +        } catch (Exception e) {
 +            LOGGER.error("Service offering for VM import not compatible", e);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import VM: %s. %s", unmanagedInstance.getName(), Strings.nullToEmpty(e.getMessage())));
 +        }
 +
 +        Map<String, String> allDetails = new HashMap<>(details);
 +        if (validatedServiceOffering.isDynamic()) {
 +            allDetails.put(VmDetailConstants.CPU_NUMBER, String.valueOf(validatedServiceOffering.getCpu()));
 +            allDetails.put(VmDetailConstants.MEMORY, String.valueOf(validatedServiceOffering.getRamSize()));
 +            if (serviceOffering.getSpeed() == null) {
 +                allDetails.put(VmDetailConstants.CPU_SPEED, String.valueOf(validatedServiceOffering.getSpeed()));
 +            }
 +        }
 +
 +        if (!migrateAllowed && !hostSupportsServiceOffering(host, validatedServiceOffering)) {
 +            throw new InvalidParameterValueException(String.format("Service offering: %s is not compatible with host: %s of unmanaged VM: %s", serviceOffering.getUuid(), host.getUuid(), instanceName));
 +        }
 +        // Check disks and supplied disk offerings
 +        List<UnmanagedInstanceTO.Disk> unmanagedInstanceDisks = unmanagedInstance.getDisks();
 +        if (CollectionUtils.isEmpty(unmanagedInstanceDisks)) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("No attached disks found for the unmanaged VM: %s", instanceName));
 +        }
-         final UnmanagedInstanceTO.Disk rootDisk = unmanagedInstance.getDisks().get(0);
++        Pair<UnmanagedInstanceTO.Disk, List<UnmanagedInstanceTO.Disk>> rootAndDataDisksPair = getRootAndDataDisks(unmanagedInstanceDisks, dataDiskOfferingMap);
++        final UnmanagedInstanceTO.Disk rootDisk = rootAndDataDisksPair.first();
++        final List<UnmanagedInstanceTO.Disk> dataDisks = rootAndDataDisksPair.second();
 +        if (rootDisk == null || Strings.isNullOrEmpty(rootDisk.getController())) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM import failed. Unable to retrieve root disk details for VM: %s ", instanceName));
 +        }
 +        allDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDisk.getController());
-         List<UnmanagedInstanceTO.Disk> dataDisks = new ArrayList<>();
 +        try {
 +            checkUnmanagedDiskAndOfferingForImport(rootDisk, null, validatedServiceOffering, owner, zone, cluster, migrateAllowed);
-             if (unmanagedInstanceDisks.size() > 1) { // Data disk(s) present
-                 dataDisks.addAll(unmanagedInstanceDisks);
-                 dataDisks.remove(0);
++            if (CollectionUtils.isNotEmpty(dataDisks)) { // Data disk(s) present
 +                checkUnmanagedDiskAndOfferingForImport(dataDisks, dataDiskOfferingMap, owner, zone, cluster, migrateAllowed);
 +                allDetails.put(VmDetailConstants.DATA_DISK_CONTROLLER, dataDisks.get(0).getController());
 +            }
 +            resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.volume, unmanagedInstanceDisks.size());
 +        } catch (ResourceAllocationException e) {
 +            LOGGER.error(String.format("Volume resource allocation error for owner: %s", owner.getUuid()), e);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Volume resource allocation error for owner: %s. %s", owner.getUuid(), Strings.nullToEmpty(e.getMessage())));
 +        }
 +        // Check NICs and supplied networks
 +        Map<String, Network.IpAddresses> nicIpAddressMap = getNicIpAddresses(unmanagedInstance.getNics(), callerNicIpAddressMap);
 +        Map<String, Long> allNicNetworkMap = getUnmanagedNicNetworkMap(unmanagedInstance.getNics(), nicNetworkMap, nicIpAddressMap, zone, hostName, owner);
 +        if (!CollectionUtils.isEmpty(unmanagedInstance.getNics())) {
 +            allDetails.put(VmDetailConstants.NIC_ADAPTER, unmanagedInstance.getNics().get(0).getAdapterType());
 +        }
 +        VirtualMachine.PowerState powerState = VirtualMachine.PowerState.PowerOff;
 +        if (unmanagedInstance.getPowerState().equals(UnmanagedInstanceTO.PowerState.PowerOn)) {
 +            powerState = VirtualMachine.PowerState.PowerOn;
 +        }
 +        try {
 +            userVm = userVmManager.importVM(zone, host, template, instanceName, displayName, owner,
 +                    null, caller, true, null, owner.getAccountId(), userId,
 +                    validatedServiceOffering, null, hostName,
 +                    cluster.getHypervisorType(), allDetails, powerState);
 +        } catch (InsufficientCapacityException ice) {
 +            LOGGER.error(String.format("Failed to import vm name: %s", instanceName), ice);
 +            throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ice.getMessage());
 +        }
 +        if (userVm == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import vm name: %s", instanceName));
 +        }
 +        List<Pair<DiskProfile, StoragePool>> diskProfileStoragePoolList = new ArrayList<>();
 +        try {
 +            if (rootDisk.getCapacity() == null || rootDisk.getCapacity() == 0) {
 +                throw new InvalidParameterValueException(String.format("Root disk ID: %s size is invalid", rootDisk.getDiskId()));
 +            }
 +            Long minIops = null;
 +            if (details.containsKey("minIops")) {
 +                minIops = Long.parseLong(details.get("minIops"));
 +            }
 +            Long maxIops = null;
 +            if (details.containsKey("maxIops")) {
 +                maxIops = Long.parseLong(details.get("maxIops"));
 +            }
 +            diskProfileStoragePoolList.add(importDisk(rootDisk, userVm, cluster, serviceOffering, Volume.Type.ROOT, String.format("ROOT-%d", userVm.getId()),
 +                    (rootDisk.getCapacity() / Resource.ResourceType.bytesToGiB), minIops, maxIops,
 +                    template, owner, null));
 +            for (UnmanagedInstanceTO.Disk disk : dataDisks) {
 +                if (disk.getCapacity() == null || disk.getCapacity() == 0) {
 +                    throw new InvalidParameterValueException(String.format("Disk ID: %s size is invalid", rootDisk.getDiskId()));
 +                }
 +                DiskOffering offering = diskOfferingDao.findById(dataDiskOfferingMap.get(disk.getDiskId()));
 +                diskProfileStoragePoolList.add(importDisk(disk, userVm, cluster, offering, Volume.Type.DATADISK, String.format("DATA-%d-%s", userVm.getId(), disk.getDiskId()),
 +                        (disk.getCapacity() / Resource.ResourceType.bytesToGiB), offering.getMinIops(), offering.getMaxIops(),
 +                        template, owner, null));
 +            }
 +        } catch (Exception e) {
 +            LOGGER.error(String.format("Failed to import volumes while importing vm: %s", instanceName), e);
 +            cleanupFailedImportVM(userVm);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import volumes while importing vm: %s. %s", instanceName, Strings.nullToEmpty(e.getMessage())));
 +        }
 +        try {
 +            boolean firstNic = true;
 +            for (UnmanagedInstanceTO.Nic nic : unmanagedInstance.getNics()) {
 +                Network network = networkDao.findById(allNicNetworkMap.get(nic.getNicId()));
 +                Network.IpAddresses ipAddresses = nicIpAddressMap.get(nic.getNicId());
 +                importNic(nic, userVm, network, ipAddresses, firstNic, forced);
 +                firstNic = false;
 +            }
 +        } catch (Exception e) {
 +            LOGGER.error(String.format("Failed to import NICs while importing vm: %s", instanceName), e);
 +            cleanupFailedImportVM(userVm);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to import NICs while importing vm: %s. %s", instanceName, Strings.nullToEmpty(e.getMessage())));
 +        }
 +        if (migrateAllowed) {
 +            userVm = migrateImportedVM(host, template, validatedServiceOffering, userVm, owner, diskProfileStoragePoolList);
 +        }
 +        publishVMUsageUpdateResourceCount(userVm, validatedServiceOffering);
 +        return userVm;
 +    }
 +
 +    @Override
 +    public ListResponse<UnmanagedInstanceResponse> listUnmanagedInstances(ListUnmanagedInstancesCmd cmd) {
 +        final Account caller = CallContext.current().getCallingAccount();
 +        if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
 +            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
 +        }
 +        final Long clusterId = cmd.getClusterId();
 +        if (clusterId == null) {
 +            throw new InvalidParameterValueException(String.format("Cluster ID cannot be null"));
 +        }
 +        final Cluster cluster = clusterDao.findById(clusterId);
 +        if (cluster == null) {
 +            throw new InvalidParameterValueException(String.format("Cluster ID: %d cannot be found", clusterId));
 +        }
 +        if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
 +            throw new InvalidParameterValueException(String.format("VM ingestion is currently not supported for hypervisor: %s", cluster.getHypervisorType().toString()));
 +        }
 +        List<HostVO> hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up);
 +        List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
 +        List<UnmanagedInstanceResponse> responses = new ArrayList<>();
 +        for (HostVO host : hosts) {
 +            if (host.isInMaintenanceStates()) {
 +                continue;
 +            }
 +            List<String> managedVms = new ArrayList<>();
 +            managedVms.addAll(additionalNameFilters);
 +            managedVms.addAll(getHostManagedVms(host));
 +
 +            GetUnmanagedInstancesCommand command = new GetUnmanagedInstancesCommand();
 +            command.setInstanceName(cmd.getName());
 +            command.setManagedInstancesNames(managedVms);
 +            Answer answer = agentManager.easySend(host.getId(), command);
 +            if (!(answer instanceof GetUnmanagedInstancesAnswer)) {
 +                continue;
 +            }
 +            GetUnmanagedInstancesAnswer unmanagedInstancesAnswer = (GetUnmanagedInstancesAnswer) answer;
 +            HashMap<String, UnmanagedInstanceTO> unmanagedInstances = new HashMap<>();
 +            unmanagedInstances.putAll(unmanagedInstancesAnswer.getUnmanagedInstances());
 +            Set<String> keys = unmanagedInstances.keySet();
 +            for (String key : keys) {
 +                responses.add(createUnmanagedInstanceResponse(unmanagedInstances.get(key), cluster, host));
 +            }
 +        }
 +        ListResponse<UnmanagedInstanceResponse> listResponses = new ListResponse<>();
 +        listResponses.setResponses(responses, responses.size());
 +        return listResponses;
 +    }
 +
 +    @Override
 +    public UserVmResponse importUnmanagedInstance(ImportUnmanagedInstanceCmd cmd) {
 +        final Account caller = CallContext.current().getCallingAccount();
 +        if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
 +            throw new PermissionDeniedException(String.format("Cannot perform this operation, Calling account is not root admin: %s", caller.getUuid()));
 +        }
 +        final Long clusterId = cmd.getClusterId();
 +        if (clusterId == null) {
 +            throw new InvalidParameterValueException(String.format("Cluster ID cannot be null"));
 +        }
 +        final Cluster cluster = clusterDao.findById(clusterId);
 +        if (cluster == null) {
 +            throw new InvalidParameterValueException(String.format("Cluster ID: %d cannot be found", clusterId));
 +        }
 +        if (cluster.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
 +            throw new InvalidParameterValueException(String.format("VM import is currently not supported for hypervisor: %s", cluster.getHypervisorType().toString()));
 +        }
 +        final DataCenter zone = dataCenterDao.findById(cluster.getDataCenterId());
 +        final String instanceName = cmd.getName();
 +        if (Strings.isNullOrEmpty(instanceName)) {
 +            throw new InvalidParameterValueException(String.format("Instance name cannot be empty"));
 +        }
 +        if (cmd.getDomainId() != null && Strings.isNullOrEmpty(cmd.getAccountName())) {
 +            throw new InvalidParameterValueException("domainid parameter must be specified with account parameter");
 +        }
 +        final Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
 +        long userId = CallContext.current().getCallingUserId();
 +        List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
 +        if (CollectionUtils.isNotEmpty(userVOs)) {
 +            userId = userVOs.get(0).getId();
 +        }
 +        VMTemplateVO template = null;
 +        final Long templateId = cmd.getTemplateId();
 +        if (templateId == null) {
 +            template = templateDao.findByName(VM_IMPORT_DEFAULT_TEMPLATE_NAME);
 +            if (template == null) {
 +                template = createDefaultDummyVmImportTemplate();
 +                if (template == null) {
 +                    throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid paramter for import", VM_IMPORT_DEFAULT_TEMPLATE_NAME, cluster.getHypervisorType().toString()));
 +                }
 +            }
 +        } else {
 +            template = templateDao.findById(templateId);
 +        }
 +        if (template == null) {
 +            throw new InvalidParameterValueException(String.format("Template ID: %d cannot be found", templateId));
 +        }
 +        final Long serviceOfferingId = cmd.getServiceOfferingId();
 +        if (serviceOfferingId == null) {
 +            throw new InvalidParameterValueException(String.format("Service offering ID cannot be null"));
 +        }
 +        final ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(serviceOfferingId);
 +        if (serviceOffering == null) {
 +            throw new InvalidParameterValueException(String.format("Service offering ID: %d cannot be found", serviceOfferingId));
 +        }
 +        accountService.checkAccess(owner, serviceOffering, zone);
 +        try {
 +            resourceLimitService.checkResourceLimit(owner, Resource.ResourceType.user_vm, 1);
 +        } catch (ResourceAllocationException e) {
 +            LOGGER.error(String.format("VM resource allocation error for account: %s", owner.getUuid()), e);
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("VM resource allocation error for account: %s. %s", owner.getUuid(), Strings.nullToEmpty(e.getMessage())));
 +        }
 +        String displayName = cmd.getDisplayName();
 +        if (Strings.isNullOrEmpty(displayName)) {
 +            displayName = instanceName;
 +        }
 +        String hostName = cmd.getHostName();
 +        if (Strings.isNullOrEmpty(hostName)) {
 +            if (!NetUtils.verifyDomainNameLabel(instanceName, true)) {
 +                throw new InvalidParameterValueException(String.format("Please provide hostname for the VM. VM name contains unsupported characters for it to be used as hostname"));
 +            }
 +            hostName = instanceName;
 +        }
 +        if (!NetUtils.verifyDomainNameLabel(hostName, true)) {
 +            throw new InvalidParameterValueException("Invalid VM hostname. VM hostname can contain ASCII letters 'a' through 'z', the digits '0' through '9', "
 +                    + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit");
 +        }
 +        if (cluster.getHypervisorType().equals(Hypervisor.HypervisorType.VMware) &&
 +                Boolean.parseBoolean(configurationDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()))) {
 +            // If global config vm.instancename.flag is set to true, then CS will set guest VM's name as it appears on the hypervisor, to its hostname.
 +            // In case of VMware since VM name must be unique within a DC, check if VM with the same hostname already exists in the zone.
 +            VMInstanceVO vmByHostName = vmDao.findVMByHostNameInZone(hostName, zone.getId());
 +            if (vmByHostName != null && vmByHostName.getState() != VirtualMachine.State.Expunging) {
 +                throw new InvalidParameterValueException(String.format("Failed to import VM: %s. There already exists a VM by the hostname: %s in zone: %s", instanceName, hostName, zone.getUuid()));
 +            }
 +        }
 +        final Map<String, Long> nicNetworkMap = cmd.getNicNetworkList();
 +        final Map<String, Network.IpAddresses> nicIpAddressMap = cmd.getNicIpAddressList();
 +        final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
 +        final Map<String, String> details = cmd.getDetails();
 +        final boolean forced = cmd.isForced();
 +        List<HostVO> hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up);
 +        UserVm userVm = null;
 +        List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
 +        for (HostVO host : hosts) {
 +            if (host.isInMaintenanceStates()) {
 +                continue;
 +            }
 +            List<String> managedVms = new ArrayList<>();
 +            managedVms.addAll(additionalNameFilters);
 +            managedVms.addAll(getHostManagedVms(host));
 +            GetUnmanagedInstancesCommand command = new GetUnmanagedInstancesCommand(instanceName);
 +            command.setManagedInstancesNames(managedVms);
 +            Answer answer = agentManager.easySend(host.getId(), command);
 +            if (!(answer instanceof GetUnmanagedInstancesAnswer)) {
 +                continue;
 +            }
 +            GetUnmanagedInstancesAnswer unmanagedInstancesAnswer = (GetUnmanagedInstancesAnswer) answer;
 +            HashMap<String, UnmanagedInstanceTO> unmanagedInstances = unmanagedInstancesAnswer.getUnmanagedInstances();
 +            if (MapUtils.isEmpty(unmanagedInstances)) {
 +                continue;
 +            }
 +            Set<String> names = unmanagedInstances.keySet();
 +            for (String name : names) {
 +                if (instanceName.equals(name)) {
 +                    UnmanagedInstanceTO unmanagedInstance = unmanagedInstances.get(name);
 +                    if (unmanagedInstance == null) {
 +                        throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve details for unmanaged VM: %s", name));
 +                    }
 +                    if (template.getName().equals(VM_IMPORT_DEFAULT_TEMPLATE_NAME)) {
 +                        String osName = unmanagedInstance.getOperatingSystem();
 +                        GuestOS guestOS = null;
 +                        if (!Strings.isNullOrEmpty(osName)) {
 +                            guestOS = guestOSDao.listByDisplayName(osName);
 +                        }
 +                        GuestOSHypervisor guestOSHypervisor = null;
 +                        if (guestOS != null) {
 +                            guestOSHypervisor = guestOSHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), host.getHypervisorType().toString(), host.getHypervisorVersion());
 +                        }
 +                        if (guestOSHypervisor == null && !Strings.isNullOrEmpty(unmanagedInstance.getOperatingSystemId())) {
 +                            guestOSHypervisor = guestOSHypervisorDao.findByOsNameAndHypervisor(unmanagedInstance.getOperatingSystemId(), host.getHypervisorType().toString(), host.getHypervisorVersion());
 +                        }
 +                        if (guestOSHypervisor == null) {
 +                            if (guestOS != null) {
 +                                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to find hypervisor guest OS ID: %s details for unmanaged VM: %s for hypervisor: %s version: %s. templateid parameter can be used to assign template for VM", guestOS.getUuid(), name, host.getHypervisorType().toString(), host.getHypervisorVersion()));
 +                            }
 +                            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Unable to retrieve guest OS details for unmanaged VM: %s with OS name: %s, OS ID: %s for hypervisor: %s version: %s. templateid parameter can be used to assign template for VM", name, osName, unmanagedInstance.getOperatingSystemId(), host.getHypervisorType().toString(), host.getHypervisorVersion()));
 +                        }
 +                        template.setGuestOSId(guestOSHypervisor.getGuestOsId());
 +                    }
 +                    userVm = importVirtualMachineInternal(unmanagedInstance, instanceName, zone, cluster, host,
 +                            template, displayName, hostName, caller, owner, userId,
 +                            serviceOffering, dataDiskOfferingMap,
 +                            nicNetworkMap, nicIpAddressMap,
 +                            details, cmd.getMigrateAllowed(), forced);
 +                    break;
 +                }
 +            }
 +            if (userVm != null) {
 +                break;
 +            }
 +        }
 +        if (userVm == null) {
 +            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to find unmanaged vm with name: %s in cluster: %s", instanceName, cluster.getUuid()));
 +        }
 +        return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
 +    }
 +
 +    @Override
 +    public List<Class<?>> getCommands() {
 +        final List<Class<?>> cmdList = new ArrayList<Class<?>>();
 +        cmdList.add(ListUnmanagedInstancesCmd.class);
 +        cmdList.add(ImportUnmanagedInstanceCmd.class);
 +        cmdList.add(UnmanageVMInstanceCmd.class);
 +        return cmdList;
 +    }
 +
 +    /**
 +     * Perform validations before attempting to unmanage a VM from CloudStack:
 +     * - VM must not have any associated volume snapshot
 +     * - VM must not have an attached ISO
 +     */
 +    private void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) {
 +        if (hasVolumeSnapshotsPriorToUnmanageVM(vmVO)) {
 +            throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() +
 +                    " as there are volume snapshots for its volume(s). Please remove snapshots before unmanaging.");
 +        }
 +
 +        if (hasISOAttached(vmVO)) {
 +            throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() +
 +                    " as there is an ISO attached. Please detach ISO before unmanaging.");
 +        }
 +    }
 +
 +    private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) {
 +        List<VolumeVO> volumes = volumeDao.findByInstance(vmVO.getId());
 +        for (VolumeVO volume : volumes) {
 +            List<SnapshotVO> snaps = snapshotDao.listByVolumeId(volume.getId());
 +            if (CollectionUtils.isNotEmpty(snaps)) {
 +                for (SnapshotVO snap : snaps) {
 +                    if (snap.getState() != Snapshot.State.Destroyed && snap.getRemoved() == null) {
 +                        return true;
 +                    }
 +                }
 +            }
 +        }
 +        return false;
 +    }
 +
 +    private boolean hasISOAttached(VMInstanceVO vmVO) {
 +        UserVmVO userVM = userVmDao.findById(vmVO.getId());
 +        if (userVM == null) {
 +            throw new InvalidParameterValueException("Could not find user VM with ID = " + vmVO.getUuid());
 +        }
 +        return userVM.getIsoId() != null;
 +    }
 +
 +    /**
 +     * Find a suitable host within the scope of the VM to unmanage to verify the VM exists
 +     */
 +    private Long findSuitableHostId(VMInstanceVO vmVO) {
 +        Long hostId = vmVO.getHostId();
 +        if (hostId == null) {
 +            long zoneId = vmVO.getDataCenterId();
 +            List<HostVO> hosts = hostDao.listAllHostsUpByZoneAndHypervisor(zoneId, vmVO.getHypervisorType());
 +            for (HostVO host : hosts) {
 +                if (host.isInMaintenanceStates() || host.getState() != Status.Up || host.getStatus() != Status.Up) {
 +                    continue;
 +                }
 +                hostId = host.getId();
 +                break;
 +            }
 +        }
 +
 +        if (hostId == null) {
 +            throw new CloudRuntimeException("Cannot find a host to verify if the VM to unmanage " +
 +                    "with id = " + vmVO.getUuid() + " exists.");
 +        }
 +        return hostId;
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VM_UNMANAGE, eventDescription = "unmanaging VM", async = true)
 +    public boolean unmanageVMInstance(long vmId) {
 +        VMInstanceVO vmVO = vmDao.findById(vmId);
 +        if (vmVO == null || vmVO.getRemoved() != null) {
 +            throw new InvalidParameterValueException("Could not find VM to unmanage, it is either removed or not existing VM");
 +        } else if (vmVO.getState() != VirtualMachine.State.Running && vmVO.getState() != VirtualMachine.State.Stopped) {
 +            throw new InvalidParameterValueException("VM with id = " + vmVO.getUuid() + " must be running or stopped to be unmanaged");
 +        } else if (vmVO.getHypervisorType() != Hypervisor.HypervisorType.VMware) {
 +            throw new UnsupportedServiceException("Unmanage VM is currently allowed for VMware VMs only");
 +        } else if (vmVO.getType() != VirtualMachine.Type.User) {
 +            throw new UnsupportedServiceException("Unmanage VM is currently allowed for guest VMs only");
 +        }
 +
 +        performUnmanageVMInstancePrechecks(vmVO);
 +
 +        Long hostId = findSuitableHostId(vmVO);
 +        String instanceName = vmVO.getInstanceName();
 +
 +        if (!existsVMToUnmanage(instanceName, hostId)) {
 +            throw new CloudRuntimeException("VM with id = " + vmVO.getUuid() + " is not found in the hypervisor");
 +        }
 +
 +        return userVmManager.unmanageUserVM(vmId);
 +    }
 +
 +    /**
 +     * Verify the VM to unmanage exists on the hypervisor
 +     */
 +    private boolean existsVMToUnmanage(String instanceName, Long hostId) {
 +        PrepareUnmanageVMInstanceCommand command = new PrepareUnmanageVMInstanceCommand();
 +        command.setInstanceName(instanceName);
 +        Answer ans = agentManager.easySend(hostId, command);
 +        if (!(ans instanceof PrepareUnmanageVMInstanceAnswer)) {
 +            throw new CloudRuntimeException("Error communicating with host " + hostId);
 +        }
 +        PrepareUnmanageVMInstanceAnswer answer = (PrepareUnmanageVMInstanceAnswer) ans;
 +        if (!answer.getResult()) {
 +            LOGGER.error("Error verifying VM " + instanceName + " exists on host with ID = " + hostId + ": " + answer.getDetails());
 +        }
 +        return answer.getResult();
 +    }
 +
 +    @Override
 +    public String getConfigComponentName() {
 +        return UnmanagedVMsManagerImpl.class.getSimpleName();
 +    }
 +
 +    @Override
 +    public ConfigKey<?>[] getConfigKeys() {
 +        return new ConfigKey<?>[] { UnmanageVMPreserveNic };
 +    }
 +}