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