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:44 UTC

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

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 };
 +    }
 +}