DaanHoogland commented on a change in pull request #3606: [WIP DO NOT MERGE] VM ingestion URL: https://github.com/apache/cloudstack/pull/3606#discussion_r342638176
########## File path: server/src/main/java/org/apache/cloudstack/vm/VmImportManagerImpl.java ########## @@ -0,0 +1,1189 @@ +// 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 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.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.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.Storage; +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.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 VmImportManagerImpl implements VmImportService { + public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; + private static final Logger LOGGER = Logger.getLogger(VmImportManagerImpl.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; + + protected Gson gson; + + public VmImportManagerImpl() { + 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(UnmanagedInstance 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.class.getSimpleName().toLowerCase()); + + if (instance.getDisks() != null) { + for (UnmanagedInstance.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 (UnmanagedInstance.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()); + 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) { + } + 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.Shutdowned); + 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 UnmanagedInstance 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 not valid")); + } + if (memory == null || memory == 0) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Memory for VM 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(UnmanagedInstance.PowerState.PowerOff)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %d CPU cores does not matches 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(UnmanagedInstance.PowerState.PowerOff)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMB memory does not matches 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(UnmanagedInstance.PowerState.PowerOff)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Service offering (%s) %dMHz CPU speed does not matches 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<UnmanagedInstance.Nic> nics, final Map<String, Network.IpAddresses> callerNicIpAddressMap) { + Map<String, Network.IpAddresses> nicIpAddresses = new HashMap<>(); + for (UnmanagedInstance.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 UnmanagedInstance.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.equals("VMFS")) { + List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(cluster.getId()); + pools.addAll(primaryDataStoreDao.listByDataCenterId(zone.getId())); + for (StoragePool pool : pools) { + if (pool.getPoolType() != Storage.StoragePoolType.VMFS) { + continue; + } + if (pool.getPath().endsWith(dsName)) { + storagePool = pool; + break; + } + } + } else { + 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) { + 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 void checkUnmanagedDiskAndOfferingForImport(UnmanagedInstance.Disk disk, DiskOffering diskOffering, ServiceOffering serviceOffering, final Account owner, final DataCenter zone, final Cluster cluster, final boolean migrateAllowed) + throws ServerApiException, PermissionDeniedException, ResourceAllocationException { + if (diskOffering == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Disk offering for disk ID: %s not found during VM import", disk.getDiskId())); + } + 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.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.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 (!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", diskOffering.getUuid(), storagePool.getUuid(), disk.getDiskId())); + } + } + + private void checkUnmanagedDiskAndOfferingForImport(List<UnmanagedInstance.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 (UnmanagedInstance.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(UnmanagedInstance.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; + } + if (nic.getVlan() != null && nic.getVlan() != 0 && (Strings.isNullOrEmpty(network.getBroadcastUri().toString()) || !network.getBroadcastUri().toString().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(), network.getBroadcastUri().toString(), nic.getNicId(), nic.getVlan())); + } + } + + private void checkUnmanagedNicAndNetworkHostnameForImport(UnmanagedInstance.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(UnmanagedInstance.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<UnmanagedInstance.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 (UnmanagedInstance.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(UnmanagedInstance.Disk disk, VirtualMachine vm, Cluster cluster, DiskOffering diskOffering, + Volume.Type type, String name, Long diskSize, 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, + diskOffering.getMinIops(), diskOffering.getMaxIops(), vm, template, owner, deviceId, storagePool.getId(), path, disk.getChainInfo()); + + return new Pair<DiskProfile, StoragePool>(profile, storagePool); + } + + private NicProfile importNic(UnmanagedInstance.Nic nic, VirtualMachine vm, Network network, Network.IpAddresses ipAddresses, boolean isDefaultNic) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { + Pair<NicProfile, Integer> result = networkOrchestrationService.importNic(nic.getMacAddress(), 0, network, isDefaultNic, vm, ipAddresses); + 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) { + nicDao.removeNicsForInstance(userVm.getId()); Review comment: log a warning?! ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services