From a755ecfce857cdf525f3d2895d68917ea6f905f3 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 8 Sep 2021 02:50:29 +0200 Subject: [PATCH] Migrate vm across clusters (#4534) * server: Optional destination host when migrate a vm * #4378: migrate systemvms/routers with optional host * Migrate vms across clusters After enabling maintenance mode on host, if no suitable hosts are found in the same cluster then search for hosts in different clusters having the same hypervisor type set global setting migrate.vm.across.clusters to true * search all clusters in zone when migrate vm across clusters if applicable * Honor migrate.vm.across.clusters when migrate vm without destination * Check MIGRATE_VM_ACROSS_CLUSTERS in zone setting * #4534 Fix Vms are migrated to same clusters in CloudStack caused by dedicated resources. * #4534 extract some codes to methods * fix #4534: an error in 'git merge' * fix #4534: remove useless methods in FirstFitPlanner.java * fix #4534: vms are stopped in host maintenance * fix #4534: across-cluster migration of vms with cluster-scoped pools is supported by vmware vmotion * fix #4534: migrate systemvms is only possible across clusters in same pod to avoid potential network errors. * fix #4534: code optimization Co-authored-by: Rakesh Venkatesh Co-authored-by: Sina Kashipazha Co-authored-by: Wei Zhou Co-authored-by: Sina Kashipazha --- .../com/cloud/vm/VirtualMachineManager.java | 7 +++ .../cloud/vm/VirtualMachineManagerImpl.java | 56 +++++++++++++++---- .../com/cloud/capacity/dao/CapacityDao.java | 4 +- .../cloud/capacity/dao/CapacityDaoImpl.java | 37 ++++++++---- .../implicitplanner/ImplicitPlannerTest.java | 4 +- .../ConfigurationManagerImpl.java | 4 +- .../com/cloud/deploy/FirstFitPlanner.java | 8 +-- .../cloud/resource/ResourceManagerImpl.java | 45 ++++++++++++++- .../java/com/cloud/vm/UserVmManagerImpl.java | 2 +- .../com/cloud/vm/FirstFitPlannerTest.java | 4 +- 10 files changed, 136 insertions(+), 35 deletions(-) diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 68183ad2add..c192f876aac 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -26,15 +26,18 @@ import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network; import com.cloud.offering.DiskOffering; @@ -246,6 +249,10 @@ public interface VirtualMachineManager extends Manager { UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException; + boolean checkIfVmHasClusterWideVolumes(Long vmId); + + DataCenterDeployment getMigrationDeployment(VirtualMachine vm, Host host, Long poolId, ExcludeList excludes); + /** * Returns true if the VM's Root volume is allocated at a local storage pool */ diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 1c77295a75a..4c498d114f9 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -135,6 +135,7 @@ import com.cloud.capacity.CapacityManager; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; +import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; @@ -250,6 +251,8 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.google.common.base.Strings; +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; + public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class); @@ -3368,9 +3371,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } - final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, poolId, null); final ExcludeList excludes = new ExcludeList(); excludes.addHost(hostId); + DataCenterDeployment plan = getMigrationDeployment(vm, host, poolId, excludes); DeployDestination dest = null; while (true) { @@ -3382,16 +3385,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2); throw new CloudRuntimeException("Unable to create deployment, affinity rules associted to the VM conflict"); } - - if (dest != null) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("Found destination " + dest + " for migrating to."); - } - } else { - if (s_logger.isDebugEnabled()) { - s_logger.debug("Unable to find destination for migrating the vm " + profile); - } - throw new InsufficientServerCapacityException("Unable to find a server to migrate to.", host.getClusterId()); + if (dest == null) { + s_logger.warn("Unable to find destination for migrating the vm " + profile); + throw new InsufficientServerCapacityException("Unable to find a server to migrate to.", DataCenter.class, host.getDataCenterId()); + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Found destination " + dest + " for migrating to."); } excludes.addHost(dest.getHost().getId()); @@ -3420,6 +3419,41 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } + /** + * Check if the virtual machine has any volume in cluster-wide pool + * @param vmId id of the virtual machine + * @return true if volume exists on cluster-wide pool else false + */ + @Override + public boolean checkIfVmHasClusterWideVolumes(Long vmId) { + final List volumesList = _volsDao.findCreatedByInstance(vmId); + + return volumesList.parallelStream() + .anyMatch(vol -> _storagePoolDao.findById(vol.getPoolId()).getScope().equals(ScopeType.CLUSTER)); + + } + + @Override + public DataCenterDeployment getMigrationDeployment(final VirtualMachine vm, final Host host, final Long poolId, final ExcludeList excludes) { + if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(host.getDataCenterId()) && + (HypervisorType.VMware.equals(host.getHypervisorType()) || !checkIfVmHasClusterWideVolumes(vm.getId()))) { + s_logger.info("Searching for hosts in the zone for vm migration"); + List clustersToExclude = _clusterDao.listAllClusters(host.getDataCenterId()); + List clusterList = _clusterDao.listByDcHyType(host.getDataCenterId(), host.getHypervisorType().toString()); + for (ClusterVO cluster : clusterList) { + clustersToExclude.remove(cluster.getId()); + } + for (Long clusterId : clustersToExclude) { + excludes.addCluster(clusterId); + } + if (VirtualMachine.systemVMs.contains(vm.getType())) { + return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), null, null, poolId, null); + } + return new DataCenterDeployment(host.getDataCenterId(), null, null, null, poolId, null); + } + return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, poolId, null); + } + protected class CleanupTask extends ManagedContextRunnable { @Override protected void runInContext() { diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java index f2735b819a9..dc04f76f2f2 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java @@ -27,7 +27,7 @@ import com.cloud.utils.db.GenericDao; public interface CapacityDao extends GenericDao { CapacityVO findByHostIdType(Long hostId, short capacityType); - List listClustersInZoneOrPodByHostCapacities(long id, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); + List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); List listHostsWithEnoughCapacity(int requiredCpu, long requiredRam, Long clusterId, String hostType); @@ -37,7 +37,7 @@ public interface CapacityDao extends GenericDao { List findNonSharedStorageForClusterPodZone(Long zoneId, Long podId, Long clusterId); - Pair, Map> orderClustersByAggregateCapacity(long id, short capacityType, boolean isZone); + Pair, Map> orderClustersByAggregateCapacity(long id, long vmId, short capacityType, boolean isZone); List findCapacityBy(Integer capacityType, Long zoneId, Long podId, Long clusterId); diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index 72d5b070761..fe6f2f4ce7b 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -75,13 +75,24 @@ public class CapacityDaoImpl extends GenericDaoBase implements + " AND host_capacity.host_id IN (SELECT capacity.host_id FROM `cloud`.`op_host_capacity` capacity JOIN `cloud`.`cluster_details` cluster_details ON (capacity.cluster_id= cluster_details.cluster_id) where capacity_type='0' AND cluster_details.name='memoryOvercommitRatio' AND ((total_capacity* cluster_details.value) - used_capacity ) >= ?)) "; private static final String ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_PART1 = - "SELECT capacity.cluster_id, SUM(used_capacity+reserved_capacity)/SUM(total_capacity ) FROM `cloud`.`op_host_capacity` capacity WHERE "; + "SELECT capacity.cluster_id, SUM(used_capacity+reserved_capacity)/SUM(total_capacity ) FROM `cloud`.`op_host_capacity` capacity "; private static final String ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_PART2 = - " AND capacity_type = ? AND cluster_details.name =? GROUP BY capacity.cluster_id ORDER BY SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) ASC"; + " AND capacity_type = ? GROUP BY capacity.cluster_id ORDER BY SUM(used_capacity+reserved_capacity)/SUM(total_capacity) ASC"; + + private static final String ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_JOIN_1 = + "JOIN host ON capacity.host_id = host.id " + + "LEFT JOIN (SELECT affinity_group.id, agvm.instance_id FROM affinity_group_vm_map agvm JOIN affinity_group ON agvm.affinity_group_id = affinity_group.id AND affinity_group.type='ExplicitDedication') AS ag ON ag.instance_id = ? " + + "LEFT JOIN dedicated_resources dr_pod ON dr_pod.pod_id IS NOT NULL AND dr_pod.pod_id = host.pod_id " + + "LEFT JOIN dedicated_resources dr_cluster ON dr_cluster.cluster_id IS NOT NULL AND dr_cluster.cluster_id = host.cluster_id " + + "LEFT JOIN dedicated_resources dr_host ON dr_host.host_id IS NOT NULL AND dr_host.host_id = host.id "; + + private static final String ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_JOIN_2 = + " AND ((ag.id IS NULL AND dr_pod.pod_id IS NULL AND dr_cluster.cluster_id IS NULL AND dr_host.host_id IS NULL) OR " + + "(dr_pod.affinity_group_id = ag.id OR dr_cluster.affinity_group_id = ag.id OR dr_host.affinity_group_id = ag.id))"; private static final String ORDER_CLUSTERS_BY_AGGREGATE_OVERCOMMIT_CAPACITY_PART1 = - "SELECT capacity.cluster_id, SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) FROM `cloud`.`op_host_capacity` capacity INNER JOIN `cloud`.`cluster_details` cluster_details ON (capacity.cluster_id = cluster_details.cluster_id) WHERE "; + "SELECT capacity.cluster_id, SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) FROM `cloud`.`op_host_capacity` capacity INNER JOIN `cloud`.`cluster_details` cluster_details ON (capacity.cluster_id = cluster_details.cluster_id) "; private static final String ORDER_CLUSTERS_BY_AGGREGATE_OVERCOMMIT_CAPACITY_PART2 = " AND capacity_type = ? AND cluster_details.name =? GROUP BY capacity.cluster_id ORDER BY SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) ASC"; @@ -572,7 +583,7 @@ public class CapacityDaoImpl extends GenericDaoBase implements } @Override - public List listClustersInZoneOrPodByHostCapacities(long id, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone) { + public List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); @@ -854,7 +865,7 @@ public class CapacityDaoImpl extends GenericDaoBase implements } @Override - public Pair, Map> orderClustersByAggregateCapacity(long id, short capacityTypeForOrdering, boolean isZone) { + public Pair, Map> orderClustersByAggregateCapacity(long id, long vmId, short capacityTypeForOrdering, boolean isZone) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); @@ -866,11 +877,14 @@ public class CapacityDaoImpl extends GenericDaoBase implements sql.append(ORDER_CLUSTERS_BY_AGGREGATE_OVERCOMMIT_CAPACITY_PART1); } + sql.append(ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_JOIN_1); if (isZone) { - sql.append(" data_center_id = ?"); + sql.append("WHERE capacity.capacity_state = 'Enabled' AND capacity.data_center_id = ?"); } else { - sql.append(" pod_id = ?"); + sql.append("WHERE capacity.capacity_state = 'Enabled' AND capacity.pod_id = ?"); } + sql.append(ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_JOIN_2); + if (capacityTypeForOrdering != Capacity.CAPACITY_TYPE_CPU && capacityTypeForOrdering != Capacity.CAPACITY_TYPE_MEMORY) { sql.append(ORDER_CLUSTERS_BY_AGGREGATE_CAPACITY_PART2); } else { @@ -879,13 +893,14 @@ public class CapacityDaoImpl extends GenericDaoBase implements try { pstmt = txn.prepareAutoCloseStatement(sql.toString()); - pstmt.setLong(1, id); - pstmt.setShort(2, capacityTypeForOrdering); + pstmt.setLong(1, vmId); + pstmt.setLong(2, id); + pstmt.setShort(3, capacityTypeForOrdering); if (capacityTypeForOrdering == Capacity.CAPACITY_TYPE_CPU) { - pstmt.setString(3, "cpuOvercommitRatio"); + pstmt.setString(4, "cpuOvercommitRatio"); } else if (capacityTypeForOrdering == Capacity.CAPACITY_TYPE_MEMORY) { - pstmt.setString(3, "memoryOvercommitRatio"); + pstmt.setString(4, "memoryOvercommitRatio"); } ResultSet rs = pstmt.executeQuery(); diff --git a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java index 1a3aed0ab61..a25ad9fd308 100644 --- a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java +++ b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -382,7 +382,7 @@ public class ImplicitPlannerTest { clustersWithEnoughCapacity.add(2L); clustersWithEnoughCapacity.add(3L); when( - capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, + capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, 12L, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); Map clusterCapacityMap = new HashMap(); @@ -390,7 +390,7 @@ public class ImplicitPlannerTest { clusterCapacityMap.put(2L, 2048D); clusterCapacityMap.put(3L, 2048D); Pair, Map> clustersOrderedByCapacity = new Pair, Map>(clustersWithEnoughCapacity, clusterCapacityMap); - when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersOrderedByCapacity); + when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, 12L, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersOrderedByCapacity); List disabledClusters = new ArrayList(); List clustersWithDisabledPods = new ArrayList(); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index afabf1bfd9e..7c18c600b04 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -439,6 +439,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public static final ConfigKey VM_USERDATA_MAX_LENGTH = new ConfigKey("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768", "Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true); + public static final ConfigKey MIGRATE_VM_ACROSS_CLUSTERS = new ConfigKey(Boolean.class, "migrate.vm.across.clusters", "Advanced", "false", + "Indicates whether the VM can be migrated to different cluster if no host is found in same cluster",true, ConfigKey.Scope.Zone, null); private static final String IOPS_READ_RATE = "IOPS Read"; private static final String IOPS_WRITE_RATE = "IOPS Write"; @@ -6535,6 +6537,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public ConfigKey[] getConfigKeys() { return new ConfigKey[] {SystemVMUseLocalStorage, IOPS_MAX_READ_LENGTH, IOPS_MAX_WRITE_LENGTH, BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, VM_SERVICE_OFFERING_MAX_CPU_CORES, - VM_SERVICE_OFFERING_MAX_RAM_SIZE, VM_USERDATA_MAX_LENGTH}; + VM_SERVICE_OFFERING_MAX_RAM_SIZE, VM_USERDATA_MAX_LENGTH, MIGRATE_VM_ACROSS_CLUSTERS}; } } diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index f10dde6ac71..f5263e21045 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -383,7 +383,7 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla long requiredRam = offering.getRamSize() * 1024L * 1024L; //list clusters under this zone by cpu and ram capacity - Pair, Map> clusterCapacityInfo = listClustersByCapacity(id, requiredCpu, requiredRam, avoid, isZone); + Pair, Map> clusterCapacityInfo = listClustersByCapacity(id, vmProfile.getId(), requiredCpu, requiredRam, avoid, isZone); List prioritizedClusterIds = clusterCapacityInfo.first(); if (!prioritizedClusterIds.isEmpty()) { if (avoid.getClustersToAvoid() != null) { @@ -441,7 +441,7 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla return podIdsByCapacity; } - protected Pair, Map> listClustersByCapacity(long id, int requiredCpu, long requiredRam, ExcludeList avoid, boolean isZone) { + protected Pair, Map> listClustersByCapacity(long id, long vmId, int requiredCpu, long requiredRam, ExcludeList avoid, boolean isZone) { //look at the aggregate available cpu and ram per cluster //although an aggregate value may be false indicator that a cluster can host a vm, it will at the least eliminate those clusters which definitely cannot @@ -456,11 +456,11 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla capacityType = Capacity.CAPACITY_TYPE_MEMORY; } - List clusterIdswithEnoughCapacity = capacityDao.listClustersInZoneOrPodByHostCapacities(id, requiredCpu, requiredRam, capacityType, isZone); + List clusterIdswithEnoughCapacity = capacityDao.listClustersInZoneOrPodByHostCapacities(id, vmId, requiredCpu, requiredRam, capacityType, isZone); if (s_logger.isTraceEnabled()) { s_logger.trace("ClusterId List having enough CPU and RAM capacity: " + clusterIdswithEnoughCapacity); } - Pair, Map> result = capacityDao.orderClustersByAggregateCapacity(id, capacityType, isZone); + Pair, Map> result = capacityDao.orderClustersByAggregateCapacity(id, vmId, capacityType, isZone); List clusterIdsOrderedByAggregateCapacity = result.first(); //only keep the clusters that have enough capacity to host this VM if (s_logger.isTraceEnabled()) { diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 01c1070db54..2b4e2334871 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.resource; +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; import static com.cloud.configuration.ConfigurationManagerImpl.SET_HOST_DOWN_TO_MAINTENANCE; import java.net.URI; @@ -1315,7 +1316,14 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, return true; } - final List hosts = listAllUpAndEnabledHosts(Host.Type.Routing, host.getClusterId(), host.getPodId(), host.getDataCenterId()); + List hosts = listAllUpAndEnabledHosts(Host.Type.Routing, host.getClusterId(), host.getPodId(), host.getDataCenterId()); + if (CollectionUtils.isEmpty(hosts)) { + s_logger.warn("Unable to find a host for vm migration in cluster: " + host.getClusterId()); + if (! isClusterWideMigrationPossible(host, vms, hosts)) { + return false; + } + } + for (final VMInstanceVO vm : vms) { if (hosts == null || hosts.isEmpty() || !answer.getMigrate() || _serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.vgpuType.toString()) != null) { @@ -1345,6 +1353,41 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, return true; } + private boolean isClusterWideMigrationPossible(Host host, List vms, List hosts) { + if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(host.getDataCenterId())) { + s_logger.info("Looking for hosts across different clusters in zone: " + host.getDataCenterId()); + Long podId = null; + for (final VMInstanceVO vm : vms) { + if (VirtualMachine.systemVMs.contains(vm.getType())) { + // SystemVMs can only be migrated to same pod + podId = host.getPodId(); + break; + } + } + hosts.addAll(listAllUpAndEnabledHosts(Host.Type.Routing, null, podId, host.getDataCenterId())); + if (CollectionUtils.isEmpty(hosts)) { + s_logger.warn("Unable to find a host for vm migration in zone: " + host.getDataCenterId()); + return false; + } + s_logger.info("Found hosts in the zone for vm migration: " + hosts); + if (HypervisorType.VMware.equals(host.getHypervisorType())) { + s_logger.debug("Skipping pool check of volumes on VMware environment because across-cluster vm migration is supported by vMotion"); + return true; + } + // Don't migrate vm if it has volumes on cluster-wide pool + for (final VMInstanceVO vm : vms) { + if (_vmMgr.checkIfVmHasClusterWideVolumes(vm.getId())) { + s_logger.warn(String.format("VM %s cannot be migrated across cluster as it has volumes on cluster-wide pool", vm)); + return false; + } + } + } else { + s_logger.warn(String.format("VMs cannot be migrated across cluster since %s is false for zone ID: %d", MIGRATE_VM_ACROSS_CLUSTERS.key(), host.getDataCenterId())); + return false; + } + return true; + } + /** * Looks for Hosts able to allocate the VM and migrates the VM with its volume. */ diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 31c138cd2ba..cac5bc9ebff 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -6025,9 +6025,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null); final Long srcHostId = srcHost.getId(); final Host host = _hostDao.findById(srcHostId); - final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, null, null); ExcludeList excludes = new ExcludeList(); excludes.addHost(srcHostId); + final DataCenterDeployment plan = _itMgr.getMigrationDeployment(vm, host, null, excludes); try { return _planningMgr.planDeployment(profile, plan, excludes, null); } catch (final AffinityConflictException e2) { diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java index 41deea2823f..bfca1fe5d98 100644 --- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java +++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java @@ -291,7 +291,7 @@ public class FirstFitPlannerTest { clustersWithEnoughCapacity.add(6L); when( - capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, + capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, 12L, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); Map clusterCapacityMap = new HashMap(); @@ -303,7 +303,7 @@ public class FirstFitPlannerTest { clusterCapacityMap.put(6L, 2048D); Pair, Map> clustersOrderedByCapacity = new Pair, Map>(clustersWithEnoughCapacity, clusterCapacityMap); - when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersOrderedByCapacity); + when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, 12L, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersOrderedByCapacity); List disabledClusters = new ArrayList(); List clustersWithDisabledPods = new ArrayList();