From 6f27b1f459f8347ccaff1cc780980856a446705c Mon Sep 17 00:00:00 2001 From: SadiJr Date: Wed, 28 Feb 2024 04:49:10 -0300 Subject: [PATCH] Improve logs when adding components to avoid set (#7214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: SadiJr Co-authored-by: GaOrtiga <49285692+GaOrtiga@users.noreply.github.com> Co-authored-by: João Jandre <48719461+JoaoJandre@users.noreply.github.com> --- .../com/cloud/deploy/DeploymentPlanner.java | 24 +- .../cloud/vm/VirtualMachineManagerImpl.java | 14 +- .../allocator/impl/FirstFitAllocator.java | 14 +- .../deploy/DeploymentPlanningManagerImpl.java | 825 +++++++++--------- .../DeploymentPlanningManagerImplTest.java | 4 +- 5 files changed, 476 insertions(+), 405 deletions(-) diff --git a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java index e9f706ac1ce..354f9cfaac5 100644 --- a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java +++ b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java @@ -21,8 +21,12 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + import com.cloud.dc.DataCenter; import com.cloud.dc.Pod; +import com.cloud.exception.CloudException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.ResourceUnavailableException; @@ -75,7 +79,7 @@ public interface DeploymentPlanner extends Adapter { public static class ExcludeList implements Serializable { private static final long serialVersionUID = -482175549460148301L; - + protected static Logger LOGGER = LogManager.getLogger(ExcludeList.class); private Set _dcIds; private Set _podIds; private Set _clusterIds; @@ -104,13 +108,26 @@ public interface DeploymentPlanner extends Adapter { } } + private void logAvoid(Class scope, CloudException e) { + Long id = null; + if (e instanceof InsufficientCapacityException) { + id = ((InsufficientCapacityException) e).getId(); + } else if (e instanceof ResourceUnavailableException) { + id = ((ResourceUnavailableException) e).getResourceId(); + } else { + LOGGER.debug("Failed to log avoided component due to unexpected exception type [{}].", e.getMessage()); + return; + } + LOGGER.debug("Adding {} [{}] to the avoid set due to [{}].", scope.getSimpleName(), id, e.getMessage()); + } + public boolean add(InsufficientCapacityException e) { Class scope = e.getScope(); if (scope == null) { return false; } - + logAvoid(scope, e); if (Host.class.isAssignableFrom(scope)) { addHost(e.getId()); } else if (Pod.class.isAssignableFrom(scope)) { @@ -128,13 +145,14 @@ public interface DeploymentPlanner extends Adapter { return true; } + public boolean add(ResourceUnavailableException e) { Class scope = e.getScope(); if (scope == null) { return false; } - + logAvoid(scope, e); if (Host.class.isAssignableFrom(scope)) { addHost(e.getResourceId()); } else if (Pod.class.isAssignableFrom(scope)) { 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 f3ec3dd4761..87399389271 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -165,6 +165,7 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.deploy.DeploymentPlanningManagerImpl; import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; @@ -237,6 +238,7 @@ import com.cloud.user.User; import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; +import com.cloud.utils.LogUtils; import com.cloud.utils.Pair; import com.cloud.utils.Predicate; import com.cloud.utils.ReflectionUse; @@ -1093,6 +1095,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac public void orchestrateStart(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + logger.debug(() -> LogUtils.logGsonWithoutException("Trying to start VM [%s] using plan [%s] and planner [%s].", vmUuid, planToDeploy, planner)); final CallContext cctxt = CallContext.current(); final Account account = cctxt.getCallingAccount(); final User caller = cctxt.getCallingUser(); @@ -1116,10 +1119,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), vm.getPodIdToDeployIn(), null, null, null, null, ctx); if (planToDeploy != null && planToDeploy.getDataCenterId() != 0) { - if (logger.isDebugEnabled()) { - logger.debug("advanceStart: DeploymentPlan is provided, using dcId:" + planToDeploy.getDataCenterId() + ", podId: " + planToDeploy.getPodId() + - ", clusterId: " + planToDeploy.getClusterId() + ", hostId: " + planToDeploy.getHostId() + ", poolId: " + planToDeploy.getPoolId()); - } + VMInstanceVO finalVm = vm; + logger.debug(() -> DeploymentPlanningManagerImpl.logDeploymentWithoutException(finalVm, planToDeploy, planToDeploy.getAvoids(), planner)); plan = new DataCenterDeployment(planToDeploy.getDataCenterId(), planToDeploy.getPodId(), planToDeploy.getClusterId(), planToDeploy.getHostId(), planToDeploy.getPoolId(), planToDeploy.getPhysicalNetworkId(), ctx); @@ -1140,13 +1141,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac if (planToDeploy != null) { avoids = planToDeploy.getAvoids(); + ExcludeList finalAvoids = avoids; + logger.debug(() -> LogUtils.logGsonWithoutException("Avoiding components [%s] in deployment of VM [%s].", finalAvoids, vmUuid)); } if (avoids == null) { avoids = new ExcludeList(); } - if (logger.isDebugEnabled()) { - logger.debug("Deploy avoids pods: " + avoids.getPodsToAvoid() + ", clusters: " + avoids.getClustersToAvoid() + ", hosts: " + avoids.getHostsToAvoid()); - } boolean planChangedByVolume = false; boolean reuseVolume = true; diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index 1e92a80a280..36330d6685c 100644 --- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -27,6 +27,7 @@ import javax.naming.ConfigurationException; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.springframework.stereotype.Component; import com.cloud.agent.manager.allocator.HostAllocator; @@ -210,6 +211,10 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { // add all hosts that we are not considering to the avoid list List allhostsInCluster = _hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null); allhostsInCluster.removeAll(clusterHosts); + + logger.debug(() -> String.format("Adding hosts [%s] to the avoid set because these hosts do not support HA.", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields(allhostsInCluster, "uuid", "name"))); + for (HostVO host : allhostsInCluster) { avoid.addHost(host.getId()); } @@ -325,10 +330,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { //find number of guest VMs occupying capacity on this host. if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { - if (logger.isDebugEnabled()) { - logger.debug("Host name: " + host.getName() + ", hostId: " + host.getId() + - " already has max Running VMs(count includes system VMs), skipping this and trying other available hosts"); - } + logger.debug(() -> String.format("Adding host [%s] to the avoid set because this host already has the max number of running (user and/or system) VMs.", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields(host, "uuid", "name"))); avoid.addHost(host.getId()); continue; } @@ -337,7 +340,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.vgpuType.toString())) != null) { ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(serviceOfferingId, GPU.Keys.pciDevice.toString()); if(!_resourceMgr.isGPUDeviceAvailable(host.getId(), groupName.getValue(), offeringDetails.getValue())){ - logger.info("Host name: " + host.getName() + ", hostId: "+ host.getId() +" does not have required GPU devices available"); + logger.debug(String.format("Adding host [%s] to avoid set, because this host does not have required GPU devices available.", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields(host, "uuid", "name"))); avoid.addHost(host.getId()); continue; } diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 41a51bc7af5..d97fcef7453 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -46,7 +46,9 @@ import com.cloud.utils.fsm.StateMachine2; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -128,6 +130,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.AccountManager; import com.cloud.utils.DateUtil; +import com.cloud.utils.LogUtils; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; @@ -299,25 +302,30 @@ StateListener, Configurable { @Override public DeployDestination planDeployment(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoids, DeploymentPlanner planner) throws InsufficientServerCapacityException, AffinityConflictException { + logger.debug(logDeploymentWithoutException(vmProfile.getVirtualMachine(), plan, avoids, planner)); ServiceOffering offering = vmProfile.getServiceOffering(); - int cpu_requested = offering.getCpu() * offering.getSpeed(); - long ram_requested = offering.getRamSize() * 1024L * 1024L; + int cpuRequested = offering.getCpu() * offering.getSpeed(); + long ramRequested = offering.getRamSize() * 1024L * 1024L; VirtualMachine vm = vmProfile.getVirtualMachine(); DataCenter dc = _dcDao.findById(vm.getDataCenterId()); boolean volumesRequireEncryption = anyVolumeRequiresEncryption(_volsDao.findByInstance(vm.getId())); if (vm.getType() == VirtualMachine.Type.User || vm.getType() == VirtualMachine.Type.DomainRouter) { + logger.debug("Checking non dedicated resources to deploy VM [{}].", () -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm, "uuid", "type", "instanceName")); checkForNonDedicatedResources(vmProfile, dc, avoids); } - if (logger.isDebugEnabled()) { - logger.debug("DeploymentPlanner allocation algorithm: " + planner); - logger.debug("Trying to allocate a host and storage pools from dc:" + plan.getDataCenterId() + ", pod:" + plan.getPodId() + ",cluster:" + - plan.getClusterId() + ", requested cpu: " + cpu_requested + ", requested ram: " + toHumanReadableSize(ram_requested)); + logger.debug(() -> { + String datacenter = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(dc, "uuid", "name"); + String podVO = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(_podDao.findById(plan.getPodId()), "uuid", "name"); + String clusterVO = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(_clusterDao.findById(plan.getClusterId()), "uuid", "name"); + String vmDetails = ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm, "uuid", "type", "instanceName"); + return String.format("Trying to allocate a host and storage pools from datacenter [%s], pod [%s], cluster [%s], to deploy VM [%s] " + + "with requested CPU [%s] and requested RAM [%s].", datacenter, podVO, clusterVO, vmDetails, cpuRequested, toHumanReadableSize(ramRequested)); + }); - logger.debug("Is ROOT volume READY (pool already allocated)?: " + (plan.getPoolId() != null ? "Yes" : "No")); - } + logger.debug("ROOT volume [{}] {} to deploy VM [{}].", () -> getRootVolumeUuid(_volsDao.findByInstance(vm.getId())), () -> plan.getPoolId() != null ? "is ready" : "is not ready", vm::getUuid); avoidDisabledResources(vmProfile, dc, avoids); @@ -325,81 +333,7 @@ StateListener, Configurable { String uefiFlag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.UefiFlag); if (plan.getHostId() != null && haVmTag == null) { - Long hostIdSpecified = plan.getHostId(); - if (logger.isDebugEnabled()) { - logger.debug("DeploymentPlan has host_id specified, choosing this host: " + hostIdSpecified); - } - HostVO host = _hostDao.findById(hostIdSpecified); - if (host != null && StringUtils.isNotBlank(uefiFlag) && "yes".equalsIgnoreCase(uefiFlag)) { - DetailVO uefiHostDetail = _hostDetailsDao.findDetail(host.getId(), Host.HOST_UEFI_ENABLE); - if (uefiHostDetail == null || "false".equalsIgnoreCase(uefiHostDetail.getValue())) { - logger.debug("Cannot deploy to specified host as host does n't support uefi vm deployment, returning."); - return null; - - } - } - if (host == null) { - logger.debug("The specified host cannot be found"); - } else if (avoids.shouldAvoid(host)) { - logger.debug("The specified host is in avoid set"); - } else { - if (logger.isDebugEnabled()) { - logger.debug( - "Looking for suitable pools for this host under zone: " + host.getDataCenterId() + ", pod: " + host.getPodId() + ", cluster: " + host.getClusterId()); - } - - Pod pod = _podDao.findById(host.getPodId()); - - Cluster cluster = _clusterDao.findById(host.getClusterId()); - - boolean displayStorage = getDisplayStorageFromVmProfile(vmProfile); - if (vm.getHypervisorType() == HypervisorType.BareMetal) { - DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap(), displayStorage); - logger.debug("Returning Deployment Destination: " + dest); - return dest; - } - - // search for storage under the zone, pod, cluster of the host. - DataCenterDeployment lastPlan = - new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), hostIdSpecified, plan.getPoolId(), null, - plan.getReservationContext()); - - Pair>, List> result = findSuitablePoolsForVolumes(vmProfile, lastPlan, avoids, HostAllocator.RETURN_UPTO_ALL); - Map> suitableVolumeStoragePools = result.first(); - List readyAndReusedVolumes = result.second(); - - _hostDao.loadDetails(host); - if (volumesRequireEncryption && !Boolean.parseBoolean(host.getDetail(Host.HOST_VOLUME_ENCRYPTION))) { - logger.warn(String.format("VM's volumes require encryption support, and provided host %s can't handle it", host)); - return null; - } else { - logger.debug(String.format("Volume encryption requirements are met by provided host %s", host)); - } - - // choose the potential pool for this VM for this host - if (!suitableVolumeStoragePools.isEmpty()) { - List suitableHosts = new ArrayList(); - suitableHosts.add(host); - Pair> potentialResources = findPotentialDeploymentResources( - suitableHosts, suitableVolumeStoragePools, avoids, - getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes, plan.getPreferredHosts(), vm); - if (potentialResources != null) { - pod = _podDao.findById(host.getPodId()); - cluster = _clusterDao.findById(host.getClusterId()); - Map storageVolMap = potentialResources.second(); - // remove the reused vol<->pool from destination, since - // we don't have to prepare this volume. - for (Volume vol : readyAndReusedVolumes) { - storageVolMap.remove(vol); - } - DeployDestination dest = new DeployDestination(dc, pod, cluster, host, storageVolMap, displayStorage); - logger.debug("Returning Deployment Destination: " + dest); - return dest; - } - } - } - logger.debug("Cannot deploy to specified host, returning."); - return null; + return deployInSpecifiedHostWithoutHA(vmProfile, plan, avoids, planner, vm, dc, uefiFlag); } // call affinitygroup chain @@ -410,11 +344,14 @@ StateListener, Configurable { processor.process(vmProfile, plan, avoids); } } + logger.debug("DeploymentPlan [{}] has not specified host. Trying to find another destination to deploy VM [{}], avoiding pods [{}], clusters [{}] and hosts [{}].", + () -> plan.getClass().getSimpleName(), vmProfile::getUuid, () -> StringUtils.join(avoids.getPodsToAvoid(), ", "), () -> StringUtils.join(avoids.getClustersToAvoid(), ", "), + () -> StringUtils.join(avoids.getHostsToAvoid(), ", ")); + + + logger.debug("Deploy avoids pods: {}, clusters: {}, hosts: {}.", avoids.getPodsToAvoid(), avoids.getClustersToAvoid(), avoids.getHostsToAvoid()); + logger.debug("Deploy hosts with priorities {}, hosts have NORMAL priority by default", plan.getHostPriorities()); - if (logger.isDebugEnabled()) { - logger.debug("Deploy avoids pods: " + avoids.getPodsToAvoid() + ", clusters: " + avoids.getClustersToAvoid() + ", hosts: " + avoids.getHostsToAvoid()); - logger.debug("Deploy hosts with priorities " + plan.getHostPriorities() + " , hosts have NORMAL priority by default"); - } // call planners // DataCenter dc = _dcDao.findById(vm.getDataCenterId()); @@ -448,108 +385,11 @@ StateListener, Configurable { HostVO host = _hostDao.findById(vm.getLastHostId()); lastHost = host; - _hostDao.loadHostTags(host); - _hostDao.loadDetails(host); - ServiceOfferingDetailsVO offeringDetails = null; - if (host == null) { - logger.debug("The last host of this VM cannot be found"); - } else if (avoids.shouldAvoid(host)) { - logger.debug("The last host of this VM is in avoid set"); - } else if (plan.getClusterId() != null && host.getClusterId() != null - && !plan.getClusterId().equals(host.getClusterId())) { - logger.debug("The last host of this VM cannot be picked as the plan specifies different clusterId: " - + plan.getClusterId()); - } else if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { - logger.debug("The last Host, hostId: " + host.getId() + - " already has max Running VMs(count includes system VMs), skipping this and trying other available hosts"); - } else if ((offeringDetails = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null) { - ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString()); - if(!_resourceMgr.isGPUDeviceAvailable(host.getId(), groupName.getValue(), offeringDetails.getValue())){ - logger.debug("The last host of this VM does not have required GPU devices available"); - } - } else if (volumesRequireEncryption && !Boolean.parseBoolean(host.getDetail(Host.HOST_VOLUME_ENCRYPTION))) { - logger.warn(String.format("The last host of this VM %s does not support volume encryption, which is required by this VM.", host)); - } else { - if (host.getStatus() == Status.Up) { - if (checkVmProfileAndHost(vmProfile, host)) { - long cluster_id = host.getClusterId(); - ClusterDetailsVO cluster_detail_cpu = _clusterDetailsDao.findDetail(cluster_id, - "cpuOvercommitRatio"); - ClusterDetailsVO cluster_detail_ram = _clusterDetailsDao.findDetail(cluster_id, - "memoryOvercommitRatio"); - Float cpuOvercommitRatio = Float.parseFloat(cluster_detail_cpu.getValue()); - Float memoryOvercommitRatio = Float.parseFloat(cluster_detail_ram.getValue()); - boolean hostHasCpuCapability, hostHasCapacity = false; - hostHasCpuCapability = _capacityMgr.checkIfHostHasCpuCapability(host.getId(), offering.getCpu(), offering.getSpeed()); - - if (hostHasCpuCapability) { - // first check from reserved capacity - hostHasCapacity = _capacityMgr.checkIfHostHasCapacity(host.getId(), cpu_requested, ram_requested, true, cpuOvercommitRatio, memoryOvercommitRatio, true); - - // if not reserved, check the free capacity - if (!hostHasCapacity) - hostHasCapacity = _capacityMgr.checkIfHostHasCapacity(host.getId(), cpu_requested, ram_requested, false, cpuOvercommitRatio, memoryOvercommitRatio, true); - } - - boolean displayStorage = getDisplayStorageFromVmProfile(vmProfile); - if (hostHasCapacity - && hostHasCpuCapability) { - logger.debug("The last host of this VM is UP and has enough capacity"); - logger.debug("Now checking for suitable pools under zone: " + host.getDataCenterId() - + ", pod: " + host.getPodId() + ", cluster: " + host.getClusterId()); - - Pod pod = _podDao.findById(host.getPodId()); - Cluster cluster = _clusterDao.findById(host.getClusterId()); - if (vm.getHypervisorType() == HypervisorType.BareMetal) { - DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap(), displayStorage); - logger.debug("Returning Deployment Destination: " + dest); - return dest; - } - - // search for storage under the zone, pod, cluster - // of - // the last host. - DataCenterDeployment lastPlan = new DataCenterDeployment(host.getDataCenterId(), - host.getPodId(), host.getClusterId(), host.getId(), plan.getPoolId(), null); - Pair>, List> result = findSuitablePoolsForVolumes( - vmProfile, lastPlan, avoids, HostAllocator.RETURN_UPTO_ALL); - Map> suitableVolumeStoragePools = result.first(); - List readyAndReusedVolumes = result.second(); - - // choose the potential pool for this VM for this - // host - if (!suitableVolumeStoragePools.isEmpty()) { - List suitableHosts = new ArrayList(); - suitableHosts.add(host); - Pair> potentialResources = findPotentialDeploymentResources( - suitableHosts, suitableVolumeStoragePools, avoids, - getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes, plan.getPreferredHosts(), vm); - if (potentialResources != null) { - Map storageVolMap = potentialResources.second(); - // remove the reused vol<->pool from - // destination, since we don't have to - // prepare - // this volume. - for (Volume vol : readyAndReusedVolumes) { - storageVolMap.remove(vol); - } - DeployDestination dest = new DeployDestination(dc, pod, cluster, host, - storageVolMap, displayStorage); - logger.debug("Returning Deployment Destination: " + dest); - return dest; - } - } - } else { - logger.debug("The last host of this VM does not have enough capacity"); - } - } - } else { - logger.debug("The last host of this VM is not UP or is not enabled, host status is: " + host.getStatus().name() + ", host resource state is: " + - host.getResourceState()); - } + DeployDestination deployDestination = deployInVmLastHost(vmProfile, plan, avoids, planner, vm, dc, offering, cpuRequested, ramRequested, volumesRequireEncryption); + if (deployDestination != null) { + return deployDestination; } - logger.debug("Cannot choose the last host to deploy this VM "); } avoidOtherClustersForDeploymentIfMigrationDisabled(vm, lastHost, avoids); @@ -614,6 +454,208 @@ StateListener, Configurable { return dest; } + private DeployDestination deployInVmLastHost(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoids, + DeploymentPlanner planner, VirtualMachine vm, DataCenter dc, ServiceOffering offering, int cpuRequested, long ramRequested, + boolean volumesRequireEncryption) throws InsufficientServerCapacityException { + HostVO host = _hostDao.findById(vm.getLastHostId()); + _hostDao.loadHostTags(host); + _hostDao.loadDetails(host); + + if (canUseLastHost(host, avoids, plan, vm, offering, volumesRequireEncryption)) { + if (host.getStatus() != Status.Up) { + logger.debug("Cannot deploy VM [{}] to the last host [{}] because this host is not in UP state or is not enabled. Host current status [{}] and resource status [{}].", + vm.getUuid(), host.getUuid(), host.getState().name(), host.getResourceState()); + return null; + } + if (checkVmProfileAndHost(vmProfile, host)) { + long cluster_id = host.getClusterId(); + ClusterDetailsVO cluster_detail_cpu = _clusterDetailsDao.findDetail(cluster_id, "cpuOvercommitRatio"); + ClusterDetailsVO cluster_detail_ram = _clusterDetailsDao.findDetail(cluster_id, "memoryOvercommitRatio"); + float cpuOvercommitRatio = Float.parseFloat(cluster_detail_cpu.getValue()); + float memoryOvercommitRatio = Float.parseFloat(cluster_detail_ram.getValue()); + + boolean hostHasCpuCapability, hostHasCapacity = false; + hostHasCpuCapability = _capacityMgr.checkIfHostHasCpuCapability(host.getId(), offering.getCpu(), offering.getSpeed()); + + if (hostHasCpuCapability) { + // first check from reserved capacity + hostHasCapacity = _capacityMgr.checkIfHostHasCapacity(host.getId(), cpuRequested, ramRequested, true, cpuOvercommitRatio, memoryOvercommitRatio, true); + + // if not reserved, check the free capacity + if (!hostHasCapacity) + hostHasCapacity = _capacityMgr.checkIfHostHasCapacity(host.getId(), cpuRequested, ramRequested, false, cpuOvercommitRatio, memoryOvercommitRatio, true); + } + + boolean displayStorage = getDisplayStorageFromVmProfile(vmProfile); + if (!hostHasCapacity || !hostHasCpuCapability) { + logger.debug("Cannot deploy VM [{}] to the last host [{}] because this host does not have enough capacity to deploy this VM.", vm.getUuid(), host.getUuid()); + return null; + } + logger.debug("Last host [{}] of VM [{}] is UP and has enough capacity. Checking for suitable pools for this host under zone [{}], pod [{}] and cluster [{}].", + host.getUuid(), vm.getUuid(), host.getDataCenterId(), host.getPodId(), host.getClusterId()); + + Pod pod = _podDao.findById(host.getPodId()); + Cluster cluster = _clusterDao.findById(host.getClusterId()); + if (vm.getHypervisorType() == HypervisorType.BareMetal) { + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap(), displayStorage); + logger.debug("Returning Deployment Destination: {}.", dest); + return dest; + } + + // search for storage under the zone, pod, cluster + // of + // the last host. + DataCenterDeployment lastPlan = new DataCenterDeployment(host.getDataCenterId(), + host.getPodId(), host.getClusterId(), host.getId(), plan.getPoolId(), null); + Pair>, List> result = findSuitablePoolsForVolumes( + vmProfile, lastPlan, avoids, HostAllocator.RETURN_UPTO_ALL); + Map> suitableVolumeStoragePools = result.first(); + List readyAndReusedVolumes = result.second(); + + // choose the potential pool for this VM for this + // host + if (suitableVolumeStoragePools.isEmpty()) { + logger.debug("Cannot find suitable storage pools in host [{}] to deploy VM [{}]", host.getUuid(), vm.getUuid()); + return null; + } + List suitableHosts = new ArrayList(); + suitableHosts.add(host); + Pair> potentialResources = findPotentialDeploymentResources( + suitableHosts, suitableVolumeStoragePools, avoids, + getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes, plan.getPreferredHosts(), vm); + if (potentialResources != null) { + Map storageVolMap = potentialResources.second(); + // remove the reused vol<->pool from + // destination, since we don't have to + // prepare + // this volume. + for (Volume vol : readyAndReusedVolumes) { + storageVolMap.remove(vol); + } + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, storageVolMap, displayStorage); + logger.debug("Returning Deployment Destination: {}", dest); + return dest; + } + } + } + logger.debug("Cannot choose the last host to deploy this VM {}.", vm); + return null; + } + + private boolean canUseLastHost(HostVO host, ExcludeList avoids, DeploymentPlan plan, VirtualMachine vm, ServiceOffering offering, boolean volumesRequireEncryption) { + if (host == null) { + logger.warn("Could not find last host of VM [{}] with id [{}]. Skipping this and trying other available hosts.", vm.getUuid(), vm.getLastHostId()); + return false; + } + + if (avoids.shouldAvoid(host)) { + logger.warn("The last host [{}] of VM [{}] is in the avoid set. Skipping this and trying other available hosts.", host.getUuid(), vm.getUuid()); + return false; + } + + if (plan.getClusterId() != null && host.getClusterId() != null && !plan.getClusterId().equals(host.getClusterId())) { + logger.debug(() -> String.format("The last host [%s] of VM [%s] cannot be picked, as the plan [%s] specifies a different cluster [%s] to deploy this VM. Skipping this and trying other available hosts.", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields(host, "uuid", "clusterId"), vm.getUuid(), plan.getClass().getSimpleName(), plan.getClusterId())); + return false; + } + + if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { + logger.debug("Cannot deploy VM [{}] in the last host [{}] because this host already has the max number of running VMs (users and system VMs). Skipping this and trying other available hosts.", + vm.getUuid(), host.getUuid()); + return false; + } + + ServiceOfferingDetailsVO offeringDetails = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()); + ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString()); + if (offeringDetails != null && !_resourceMgr.isGPUDeviceAvailable(host.getId(), groupName.getValue(), offeringDetails.getValue())) { + logger.debug("Cannot deploy VM [{}] in the last host [{}] because this host does not have the required GPU devices available. Skipping this and trying other available hosts.", + vm.getUuid(), host.getUuid()); + return false; + } + + if (volumesRequireEncryption && !Boolean.parseBoolean(host.getDetail(Host.HOST_VOLUME_ENCRYPTION))) { + logger.warn("The last host of this VM {} does not support volume encryption, which is required by this VM.", host); + return false; + } + return true; + } + + private DeployDestination deployInSpecifiedHostWithoutHA(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoids, + DeploymentPlanner planner, VirtualMachine vm, DataCenter dc, String uefiFlag) + throws InsufficientServerCapacityException { + Long hostIdSpecified = plan.getHostId(); + logger.debug("DeploymentPlan [{}] has specified host [{}] without HA flag. Choosing this host to deploy VM [{}].", plan.getClass().getSimpleName(), hostIdSpecified, vm.getUuid()); + + HostVO host = _hostDao.findById(hostIdSpecified); + if (host != null && StringUtils.isNotBlank(uefiFlag) && "yes".equalsIgnoreCase(uefiFlag)) { + _hostDao.loadDetails(host); + if (MapUtils.isNotEmpty(host.getDetails()) && host.getDetails().containsKey(Host.HOST_UEFI_ENABLE) && "false".equalsIgnoreCase(host.getDetails().get(Host.HOST_UEFI_ENABLE))) { + logger.debug("Cannot deploy VM [{}] to specified host [{}] because this host does not support UEFI VM deployment, returning.", vm.getUuid(), host.getUuid()); + return null; + } + } + if (host == null) { + logger.debug("Cannot deploy VM [{}] to host [{}] because this host cannot be found.", vm.getUuid(), hostIdSpecified); + return null; + } + if (avoids.shouldAvoid(host)) { + logger.debug("Cannot deploy VM [{}] to host [{}] because this host is in the avoid set.", vm.getUuid(), host.getUuid()); + return null; + } + + logger.debug("Trying to find suitable pools for host [{}] under pod [{}], cluster [{}] and zone [{}], to deploy VM [{}].", + host.getUuid(), host.getDataCenterId(), host.getPodId(), host.getClusterId(), vm.getUuid()); + + Pod pod = _podDao.findById(host.getPodId()); + Cluster cluster = _clusterDao.findById(host.getClusterId()); + + boolean displayStorage = getDisplayStorageFromVmProfile(vmProfile); + if (vm.getHypervisorType() == HypervisorType.BareMetal) { + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap(), + displayStorage); + logger.debug("Returning Deployment Destination: {}.", dest); + return dest; + } + + DataCenterDeployment lastPlan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), + host.getClusterId(), hostIdSpecified, plan.getPoolId(), null, plan.getReservationContext()); + + Pair>, List> result = findSuitablePoolsForVolumes(vmProfile, lastPlan, + avoids, HostAllocator.RETURN_UPTO_ALL); + Map> suitableVolumeStoragePools = result.first(); + List readyAndReusedVolumes = result.second(); + + if (!suitableVolumeStoragePools.isEmpty()) { + List suitableHosts = new ArrayList(); + suitableHosts.add(host); + Pair> potentialResources = findPotentialDeploymentResources(suitableHosts, + suitableVolumeStoragePools, avoids, getPlannerUsage(planner, vmProfile, plan, avoids), + readyAndReusedVolumes, plan.getPreferredHosts(), vm); + if (potentialResources != null) { + pod = _podDao.findById(host.getPodId()); + cluster = _clusterDao.findById(host.getClusterId()); + Map storageVolMap = potentialResources.second(); + for (Volume vol : readyAndReusedVolumes) { + storageVolMap.remove(vol); + } + DeployDestination dest = new DeployDestination(dc, pod, cluster, host, storageVolMap, displayStorage); + logger.debug("Returning Deployment Destination: {}", dest); + return dest; + } + } + logger.debug("Cannot deploy VM [{}] under host [{}], because no suitable pools were found.", vmProfile.getUuid(), host.getUuid()); + return null; + } + + protected String getRootVolumeUuid(List volumes) { + for (Volume volume : volumes) { + if (volume.getVolumeType() == Volume.Type.ROOT) { + return volume.getUuid(); + } + } + return null; + } + protected boolean anyVolumeRequiresEncryption(List volumes) { for (Volume volume : volumes) { if (volume.getPassphraseId() != null) { @@ -636,32 +678,29 @@ StateListener, Configurable { return vmProfile == null || vmProfile.getTemplate() == null || !vmProfile.getTemplate().isDeployAsIs(); } - /** - * Adds disabled resources (Data centers, Pods, Clusters, and hosts) to exclude list (avoid) in case of disabled state. - */ - public void avoidDisabledResources(VirtualMachineProfile vmProfile, DataCenter dc, ExcludeList avoids) { - if (vmProfile.getType().isUsedBySystem() && isRouterDeployableInDisabledResources()) { - return; - } - - VMInstanceVO vm = _vmInstanceDao.findById(vmProfile.getId()); - AccountVO owner = accountDao.findById(vm.getAccountId()); - boolean isOwnerRoleIdAdmin = false; - - if (owner != null && owner.getRoleId() != null && owner.getRoleId() == ADMIN_ACCOUNT_ROLE_ID) { - isOwnerRoleIdAdmin = true; - } - - if (isOwnerRoleIdAdmin && isAdminVmDeployableInDisabledResources()) { - return; - } - - avoidDisabledDataCenters(dc, avoids); - avoidDisabledPods(dc, avoids); - avoidDisabledClusters(dc, avoids); - avoidDisabledHosts(dc, avoids); + /** + * Adds disabled resources (Data centers, Pods, Clusters, and hosts) to exclude + * list (avoid) in case of disabled state. + */ + public void avoidDisabledResources(VirtualMachineProfile vmProfile, DataCenter dc, ExcludeList avoids) { + if (vmProfile.getType().isUsedBySystem() && isRouterDeployableInDisabledResources()) { + return; } + VMInstanceVO vm = _vmInstanceDao.findById(vmProfile.getId()); + AccountVO owner = accountDao.findById(vm.getAccountId()); + boolean isOwnerRoleIdAdmin = owner != null && owner.getRoleId() != null && owner.getRoleId() == ADMIN_ACCOUNT_ROLE_ID; + + if (isOwnerRoleIdAdmin && isAdminVmDeployableInDisabledResources()) { + return; + } + + avoidDisabledDataCenters(dc, avoids); + avoidDisabledPods(dc, avoids); + avoidDisabledClusters(dc, avoids); + avoidDisabledHosts(dc, avoids); + } + /** * Returns the value of the ConfigKey 'allow.router.on.disabled.resources'. * @note this method allows mocking and testing with the respective ConfigKey parameter. @@ -683,6 +722,8 @@ StateListener, Configurable { */ protected void avoidDisabledHosts(DataCenter dc, ExcludeList avoids) { List disabledHosts = _hostDao.listDisabledByDataCenterId(dc.getId()); + logger.debug(() -> String.format("Adding hosts [%s] of datacenter [%s] to the avoid set, because these hosts are in the Disabled state.", + disabledHosts.stream().map(HostVO::getUuid).collect(Collectors.joining(", ")), dc.getUuid())); for (HostVO host : disabledHosts) { avoids.addHost(host.getId()); } @@ -695,6 +736,7 @@ StateListener, Configurable { List pods = _podDao.listAllPods(dc.getId()); for (Long podId : pods) { List disabledClusters = _clusterDao.listDisabledClusters(dc.getId(), podId); + logger.debug(() -> String.format("Adding clusters [%s] of pod [%s] to the void set because these clusters are in the Disabled state.", StringUtils.join(disabledClusters, ", "), podId)); avoids.addClusterList(disabledClusters); } } @@ -704,6 +746,7 @@ StateListener, Configurable { */ protected void avoidDisabledPods(DataCenter dc, ExcludeList avoids) { List disabledPods = _podDao.listDisabledPods(dc.getId()); + logger.debug(() -> String.format("Adding pods [%s] to the avoid set because these pods are in the Disabled state.", StringUtils.join(disabledPods, ", "))); avoids.addPodList(disabledPods); } @@ -712,6 +755,7 @@ StateListener, Configurable { */ protected void avoidDisabledDataCenters(DataCenter dc, ExcludeList avoids) { if (dc.getAllocationState() == Grouping.AllocationState.Disabled) { + logger.debug("Adding datacenter [{}] to the avoid set because this datacenter is in Disabled state.", dc.getUuid()); avoids.addDataCenter(dc.getId()); } } @@ -765,6 +809,8 @@ StateListener, Configurable { if (dedicatedZone != null && !_accountMgr.isRootAdmin(vmProfile.getOwner().getId())) { long accountDomainId = vmProfile.getOwner().getDomainId(); long accountId = vmProfile.getOwner().getAccountId(); + logger.debug("Zone [{}] is dedicated. Checking if account [{}] in domain [{}] can use this zone to deploy VM [{}].", + dedicatedZone.getUuid(), accountId, accountDomainId, vmProfile.getUuid()); // If a zone is dedicated to an account then all hosts in this zone // will be explicitly dedicated to @@ -784,7 +830,6 @@ StateListener, Configurable { if (!_affinityGroupService.isAffinityGroupAvailableInDomain(dedicatedZone.getAffinityGroupId(), accountDomainId)) { throw new CloudRuntimeException("Failed to deploy VM, Zone " + dc.getName() + " not available for the user domain " + vmProfile.getOwner()); } - } // check affinity group of type Explicit dedication exists. If No put @@ -809,111 +854,99 @@ StateListener, Configurable { //Only when the type is instance VM and not explicitly dedicated. if (vm.getType() == VirtualMachine.Type.User && !isExplicit) { - //add explicitly dedicated resources in avoidList - if (logger.isDebugEnabled()) { - logger.debug("Adding pods to avoid lists for non-explicit VM deployment: " + allPodsInDc); - } - avoids.addPodList(allPodsInDc); - if (logger.isDebugEnabled()) { - logger.debug("Adding clusters to avoid lists for non-explicit VM deployment: " + allClustersInDc); - } - avoids.addClusterList(allClustersInDc); - if (logger.isDebugEnabled()) { - logger.debug("Adding hosts to avoid lists for non-explicit VM deployment: " + allHostsInDc); - } - avoids.addHostList(allHostsInDc); + findAvoidSetForNonExplicitUserVM(avoids, vm, allPodsInDc, allClustersInDc, allHostsInDc); } //Handle the Virtual Router Case //No need to check the isExplicit. As both the cases are handled. if (vm.getType() == VirtualMachine.Type.DomainRouter) { - long vmAccountId = vm.getAccountId(); - long vmDomainId = vm.getDomainId(); - - //Lists all explicitly dedicated resources from vm account ID or domain ID. - List allPodsFromDedicatedID = new ArrayList(); - List allClustersFromDedicatedID = new ArrayList(); - List allHostsFromDedicatedID = new ArrayList(); - - //Whether the dedicated resources belong to Domain or not. If not, it may belongs to Account or no dedication. - List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); - - //For temporary storage and indexing. - List tempStorage; - - if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { - //The dedicated resource belongs to VM Account ID. - - tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allPodsFromDedicatedID.add(vo.getPodId()); - } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allClustersFromDedicatedID.add(vo.getClusterId()); - } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allHostsFromDedicatedID.add(vo.getHostId()); - } - - //Remove the dedicated ones from main list - allPodsInDc.removeAll(allPodsFromDedicatedID); - allClustersInDc.removeAll(allClustersFromDedicatedID); - allHostsInDc.removeAll(allHostsFromDedicatedID); - } - else { - //The dedicated resource belongs to VM Domain ID or No dedication. - - tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allPodsFromDedicatedID.add(vo.getPodId()); - } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, null, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allClustersFromDedicatedID.add(vo.getClusterId()); - } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, null, null, new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - - for(DedicatedResourceVO vo : tempStorage) { - allHostsFromDedicatedID.add(vo.getHostId()); - } - - //Remove the dedicated ones from main list - allPodsInDc.removeAll(allPodsFromDedicatedID); - allClustersInDc.removeAll(allClustersFromDedicatedID); - allHostsInDc.removeAll(allHostsFromDedicatedID); - } - - //Add in avoid list or no addition if no dedication - if (logger.isDebugEnabled()) { - logger.debug("Adding pods to avoid lists: " + allPodsInDc); - } - avoids.addPodList(allPodsInDc); - if (logger.isDebugEnabled()) { - logger.debug("Adding clusters to avoid lists: " + allClustersInDc); - } - avoids.addClusterList(allClustersInDc); - if (logger.isDebugEnabled()) { - logger.debug("Adding hosts to avoid lists: " + allHostsInDc); - } - avoids.addHostList(allHostsInDc); + findAvoiSetForRouterVM(avoids, vm, allPodsInDc, allClustersInDc, allHostsInDc); } } + private void findAvoiSetForRouterVM(ExcludeList avoids, VirtualMachine vm, List allPodsInDc, List allClustersInDc, List allHostsInDc) { + long vmAccountId = vm.getAccountId(); + long vmDomainId = vm.getDomainId(); + + List allPodsFromDedicatedID = new ArrayList(); + List allClustersFromDedicatedID = new ArrayList(); + List allHostsFromDedicatedID = new ArrayList(); + + List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); + + List tempStorage; + + if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { + tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allPodsFromDedicatedID.add(vo.getPodId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allClustersFromDedicatedID.add(vo.getClusterId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allHostsFromDedicatedID.add(vo.getHostId()); + } + + allPodsInDc.removeAll(allPodsFromDedicatedID); + allClustersInDc.removeAll(allClustersFromDedicatedID); + allHostsInDc.removeAll(allHostsFromDedicatedID); + } else { + tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allPodsFromDedicatedID.add(vo.getPodId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allClustersFromDedicatedID.add(vo.getClusterId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + + for (DedicatedResourceVO vo : tempStorage) { + allHostsFromDedicatedID.add(vo.getHostId()); + } + + allPodsInDc.removeAll(allPodsFromDedicatedID); + allClustersInDc.removeAll(allClustersFromDedicatedID); + allHostsInDc.removeAll(allHostsFromDedicatedID); + } + + logger.debug(() -> LogUtils.logGsonWithoutException("Adding pods [%s], clusters [%s] and hosts [%s] to the avoid list in the deploy process of VR VM [%s], " + + "because this VM is not dedicated to this components.", allPodsInDc, allClustersInDc, allHostsInDc, vm.getUuid())); + avoids.addPodList(allPodsInDc); + avoids.addClusterList(allClustersInDc); + avoids.addHostList(allHostsInDc); + } + + private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine vm, List allPodsInDc, List allClustersInDc, List allHostsInDc) { + logger.debug(() -> LogUtils.logGsonWithoutException("Adding pods [%s], clusters [%s] and hosts [%s] to the avoid list in the deploy process of user VM [%s], " + + "because this VM is not explicitly dedicated to these components.", allPodsInDc, allClustersInDc, allHostsInDc, vm.getUuid())); + avoids.addPodList(allPodsInDc); + avoids.addClusterList(allClustersInDc); + avoids.addHostList(allHostsInDc); + } + private void resetAvoidSet(ExcludeList avoidSet, ExcludeList removeSet) { if (avoidSet.getDataCentersToAvoid() != null && removeSet.getDataCentersToAvoid() != null) { avoidSet.getDataCentersToAvoid().removeAll(removeSet.getDataCentersToAvoid()); @@ -1246,14 +1279,15 @@ StateListener, Configurable { DeploymentPlanner.PlannerResourceUsage resourceUsageRequired, ExcludeList plannerAvoidOutput) { if (logger.isTraceEnabled()) { - logger.trace("ClusterId List to consider: " + clusterList); + logger.trace("ClusterId List to consider: {}.", clusterList); } for (Long clusterId : clusterList) { ClusterVO clusterVO = _clusterDao.findById(clusterId); if (clusterVO.getHypervisorType() != vmProfile.getHypervisorType()) { - logger.debug("Cluster: " + clusterId + " has HyperVisorType that does not match the VM, skipping this cluster"); + logger.debug("Adding cluster [{}] to the avoid set because the cluster's hypervisor [{}] does not match the VM [{}] hypervisor: [{}]. Skipping this cluster.", + clusterVO.getUuid(), clusterVO.getHypervisorType().name(), vmProfile.getUuid(), vmProfile.getHypervisorType().name()); avoid.addCluster(clusterVO.getId()); continue; } @@ -1564,14 +1598,17 @@ StateListener, Configurable { boolean hostHasEncryption = Boolean.parseBoolean(potentialHostVO.getDetail(Host.HOST_VOLUME_ENCRYPTION)); boolean hostMeetsEncryptionRequirements = !anyVolumeRequiresEncryption(new ArrayList<>(volumesOrderBySizeDesc)) || hostHasEncryption; - boolean plannerUsageFits = checkIfHostFitsPlannerUsage(potentialHost.getId(), resourceUsageRequired); + boolean hostFitsPlannerUsage = checkIfHostFitsPlannerUsage(potentialHost.getId(), resourceUsageRequired); - if (hostCanAccessPool && haveEnoughSpace && hostAffinityCheck && hostMeetsEncryptionRequirements && plannerUsageFits) { + if (hostCanAccessPool && haveEnoughSpace && hostAffinityCheck && hostMeetsEncryptionRequirements && hostFitsPlannerUsage) { logger.debug("Found a potential host " + "id: " + potentialHost.getId() + " name: " + potentialHost.getName() + " and associated storage pools for this VM"); volumeAllocationMap.clear(); return new Pair>(potentialHost, storage); } else { + logger.debug("Adding host [{}] to the avoid set because: can access Pool [{}], has enough space [{}], affinity check [{}], fits planner [{}] usage [{}].", + potentialHost.getUuid(), hostCanAccessPool, haveEnoughSpace, hostAffinityCheck, resourceUsageRequired.getClass().getSimpleName(), hostFitsPlannerUsage); + if (!hostMeetsEncryptionRequirements) { logger.debug("Potential host " + potentialHost + " did not meet encryption requirements of all volumes"); } @@ -1668,12 +1705,12 @@ StateListener, Configurable { // There should be at least the ROOT volume of the VM in usable state if (volumesTobeCreated.isEmpty()) { // OfflineVmwareMigration: find out what is wrong with the id of the vm we try to start - throw new CloudRuntimeException("Unable to create deployment, no usable volumes found for the VM: " + vmProfile.getId()); + throw new CloudRuntimeException("Unable to create deployment, no usable volumes found for the VM: " + vmProfile.getUuid()); } // don't allow to start vm that doesn't have a root volume if (_volsDao.findByInstanceAndType(vmProfile.getId(), Volume.Type.ROOT).isEmpty()) { - throw new CloudRuntimeException("Unable to prepare volumes for vm as ROOT volume is missing"); + throw new CloudRuntimeException(String.format("Unable to deploy VM [%s] because the ROOT volume is missing.", vmProfile.getUuid())); } // for each volume find list of suitable storage pools by calling the @@ -1685,7 +1722,7 @@ StateListener, Configurable { Set poolsToAvoidOutput = new HashSet<>(originalAvoidPoolSet); for (VolumeVO toBeCreated : volumesTobeCreated) { - logger.debug("Checking suitable pools for volume (Id, Type): (" + toBeCreated.getId() + "," + toBeCreated.getVolumeType().name() + ")"); + logger.debug("Checking suitable pools for volume [{}, {}] of VM [{}].", toBeCreated.getUuid(), toBeCreated.getVolumeType().name(), vmProfile.getUuid()); if (toBeCreated.getState() == Volume.State.Allocated && toBeCreated.getPoolId() != null) { toBeCreated.setPoolId(null); @@ -1701,71 +1738,17 @@ StateListener, Configurable { // volume is ready and the pool should be reused. // In this case, also check if rest of the volumes are ready and can // be reused. - if (plan.getPoolId() != null || (toBeCreated.getVolumeType() == Volume.Type.DATADISK && toBeCreated.getPoolId() != null && toBeCreated.getState() == Volume.State.Ready)) { - logger.debug("Volume has pool already allocated, checking if pool can be reused, poolId: " + toBeCreated.getPoolId()); - List suitablePools = new ArrayList(); - StoragePool pool = null; - if (toBeCreated.getPoolId() != null) { - pool = (StoragePool)dataStoreMgr.getPrimaryDataStore(toBeCreated.getPoolId()); - } else { - pool = (StoragePool)dataStoreMgr.getPrimaryDataStore(plan.getPoolId()); - } - - if (!pool.isInMaintenance()) { - if (!avoid.shouldAvoid(pool)) { - long exstPoolDcId = pool.getDataCenterId(); - long exstPoolPodId = pool.getPodId() != null ? pool.getPodId() : -1; - long exstPoolClusterId = pool.getClusterId() != null ? pool.getClusterId() : -1; - boolean canReusePool = false; - if (plan.getDataCenterId() == exstPoolDcId && plan.getPodId() == exstPoolPodId && plan.getClusterId() == exstPoolClusterId) { - canReusePool = true; - } else if (plan.getDataCenterId() == exstPoolDcId) { - DataStore dataStore = dataStoreMgr.getPrimaryDataStore(pool.getId()); - if (dataStore != null && dataStore.getScope() != null && dataStore.getScope().getScopeType() == ScopeType.ZONE) { - canReusePool = true; - } - } else { - logger.debug("Pool of the volume does not fit the specified plan, need to reallocate a pool for this volume"); - canReusePool = false; - } - - if (canReusePool) { - logger.debug("Planner need not allocate a pool for this volume since its READY"); - suitablePools.add(pool); - suitableVolumeStoragePools.put(toBeCreated, suitablePools); - if (!(toBeCreated.getState() == Volume.State.Allocated || toBeCreated.getState() == Volume.State.Creating)) { - readyAndReusedVolumes.add(toBeCreated); - } - continue; - } - } else { - logger.debug("Pool of the volume is in avoid set, need to reallocate a pool for this volume"); - } - } else { - logger.debug("Pool of the volume is in maintenance, need to reallocate a pool for this volume"); - } + if ((plan.getPoolId() != null || (toBeCreated.getVolumeType() == Volume.Type.DATADISK && toBeCreated.getPoolId() != null && toBeCreated.getState() == Volume.State.Ready)) && + checkIfPoolCanBeReused(vmProfile, plan, avoid, suitableVolumeStoragePools, readyAndReusedVolumes, toBeCreated)) { + continue; } - if (logger.isDebugEnabled()) { - logger.debug("We need to allocate new storagepool for this volume"); + if (!isRootAdmin(vmProfile) && !isEnabledForAllocation(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId())) { + logger.debug(String.format("Cannot find new storage pool to deploy volume [{}] of VM [{}] in cluster [{}] because allocation state is disabled. Returning.", + toBeCreated.getUuid(), vmProfile.getUuid(), plan.getClusterId())); + suitableVolumeStoragePools.clear(); + break; } - if (!isRootAdmin(vmProfile)) { - if (!isEnabledForAllocation(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId())) { - if (logger.isDebugEnabled()) { - logger.debug("Cannot allocate new storagepool for this volume in this cluster, allocation state is disabled"); - logger.debug("Cannot deploy to this specified plan, allocation state is disabled, returning."); - } - // Cannot find suitable storage pools under this cluster for - // this volume since allocation_state is disabled. - // - remove any suitable pools found for other volumes. - // All volumes should get suitable pools under this cluster; - // else we can't use this cluster. - suitableVolumeStoragePools.clear(); - break; - } - } - - logger.debug("Calling StoragePoolAllocators to find suitable pools"); DiskOfferingVO diskOffering = _diskOfferingDao.findById(toBeCreated.getDiskOfferingId()); @@ -1783,16 +1766,8 @@ StateListener, Configurable { useLocalStorage = diskOffering.isUseLocalStorage(); } diskProfile.setUseLocalStorage(useLocalStorage); - - boolean foundPotentialPools = false; - for (StoragePoolAllocator allocator : _storagePoolAllocators) { - final List suitablePools = allocator.allocateToPool(diskProfile, vmProfile, plan, avoid, returnUpTo); - if (suitablePools != null && !suitablePools.isEmpty()) { - checkForPreferredStoragePool(suitablePools, vmProfile.getVirtualMachine(), suitableVolumeStoragePools, toBeCreated); - foundPotentialPools = true; - break; - } - } + logger.debug(String.format("Calling StoragePoolAllocators to find suitable pools to allocate volume [{}] necessary to deploy VM [{}].", toBeCreated.getUuid(), vmProfile.getUuid())); + boolean foundPotentialPools = tryToFindPotentialPoolsToAlocateVolume(vmProfile, plan, avoid, returnUpTo, suitableVolumeStoragePools, toBeCreated, diskProfile); if (avoid.getPoolsToAvoid() != null) { poolsToAvoidOutput.addAll(avoid.getPoolsToAvoid()); @@ -1800,7 +1775,7 @@ StateListener, Configurable { } if (!foundPotentialPools) { - logger.debug("No suitable pools found for volume: " + toBeCreated + " under cluster: " + plan.getClusterId()); + logger.debug(String.format("No suitable pools found for volume [{}] used by VM [{}] under cluster: [{}].", toBeCreated.getUuid(), vmProfile.getUuid(), plan.getClusterId())); // No suitable storage pools found under this cluster for this // volume. - remove any suitable pools found for other volumes. // All volumes should get suitable pools under this cluster; @@ -1829,6 +1804,75 @@ StateListener, Configurable { return new Pair>, List>(suitableVolumeStoragePools, readyAndReusedVolumes); } + private boolean tryToFindPotentialPoolsToAlocateVolume(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, + Map> suitableVolumeStoragePools, VolumeVO toBeCreated, DiskProfile diskProfile) { + for (StoragePoolAllocator allocator : _storagePoolAllocators) { + logger.debug("Trying to find suitable pools to allocate volume [{}] necessary to deploy VM [{}], using StoragePoolAllocator: [{}].", + toBeCreated.getUuid(), vmProfile.getUuid(), allocator.getClass().getSimpleName()); + + final List suitablePools = allocator.allocateToPool(diskProfile, vmProfile, plan, avoid, returnUpTo); + if (suitablePools != null && !suitablePools.isEmpty()) { + logger.debug("StoragePoolAllocator [{}] found {} suitable pools to allocate volume [{}] necessary to deploy VM [{}].", + allocator.getClass().getSimpleName(), suitablePools.size(), toBeCreated.getUuid(), vmProfile.getUuid()); + checkForPreferredStoragePool(suitablePools, vmProfile.getVirtualMachine(), suitableVolumeStoragePools, toBeCreated); + return true; + } + } + return false; + } + + private boolean checkIfPoolCanBeReused(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, + Map> suitableVolumeStoragePools, List readyAndReusedVolumes, + VolumeVO toBeCreated) { + logger.debug("Volume [{}] of VM [{}] has pool [{}] already specified. Checking if this pool can be reused.", toBeCreated.getUuid(), vmProfile.getUuid(), toBeCreated.getPoolId()); + List suitablePools = new ArrayList(); + StoragePool pool = null; + if (toBeCreated.getPoolId() != null) { + pool = (StoragePool)dataStoreMgr.getPrimaryDataStore(toBeCreated.getPoolId()); + } else { + pool = (StoragePool)dataStoreMgr.getPrimaryDataStore(plan.getPoolId()); + } + + if (!pool.isInMaintenance()) { + if (!avoid.shouldAvoid(pool)) { + return canReusePool(vmProfile, plan, suitableVolumeStoragePools, readyAndReusedVolumes, toBeCreated, suitablePools, pool); + } else { + logger.debug("Pool [{}] of volume [{}] used by VM [{}] is in the avoid set. Need to reallocate a pool for this volume.", + pool.getUuid(), toBeCreated.getUuid(), vmProfile.getUuid()); + } + } else { + logger.debug("Pool [{}] of volume [{}] used by VM [{}] is in maintenance. Need to reallocate a pool for this volume.", + pool.getUuid(), toBeCreated.getUuid(), vmProfile.getUuid()); + } + return false; + } + + private boolean canReusePool(VirtualMachineProfile vmProfile, DeploymentPlan plan, + Map> suitableVolumeStoragePools, List readyAndReusedVolumes, + VolumeVO toBeCreated, List suitablePools, StoragePool pool) { + DataStore dataStore = dataStoreMgr.getPrimaryDataStore(pool.getId()); + + long exstPoolDcId = pool.getDataCenterId(); + long exstPoolPodId = pool.getPodId() != null ? pool.getPodId() : -1; + long exstPoolClusterId = pool.getClusterId() != null ? pool.getClusterId() : -1; + + if (plan.getDataCenterId() == exstPoolDcId && ((plan.getPodId() == exstPoolPodId && plan.getClusterId() == exstPoolClusterId) || + (dataStore != null && dataStore.getScope() != null && dataStore.getScope().getScopeType() == ScopeType.ZONE))) { + logger.debug("Pool [{}] of volume [{}] used by VM [{}] fits the specified plan. No need to reallocate a pool for this volume.", + pool.getUuid(), toBeCreated.getUuid(), vmProfile.getUuid()); + suitablePools.add(pool); + suitableVolumeStoragePools.put(toBeCreated, suitablePools); + if (!(toBeCreated.getState() == Volume.State.Allocated || toBeCreated.getState() == Volume.State.Creating)) { + readyAndReusedVolumes.add(toBeCreated); + } + return true; + } + + logger.debug("Pool [{}] of volume [{}] used by VM [{}] does not fit the specified plan. Need to reallocate a pool for this volume.", + pool.getUuid(), toBeCreated.getUuid(), vmProfile.getUuid()); + return false; + } + private void checkForPreferredStoragePool(List suitablePools, VirtualMachine vm, Map> suitableVolumeStoragePools, @@ -1972,6 +2016,9 @@ StateListener, Configurable { return true; } + public static String logDeploymentWithoutException(VirtualMachine vm, DeploymentPlan plan, ExcludeList avoids, DeploymentPlanner planner) { + return LogUtils.logGsonWithoutException("Trying to deploy VM [%s] and details: Plan [%s]; avoid list [%s] and planner: [%s].", vm, plan, avoids, planner); + } @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {allowRouterOnDisabledResource, allowAdminVmOnDisabledResource}; diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index ea04a09cf92..3afd3dc4a95 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -252,7 +252,9 @@ public class DeploymentPlanningManagerImplTest { Mockito.when(template.isDeployAsIs()).thenReturn(false); Mockito.when(templateDao.findById(Mockito.anyLong())).thenReturn(template); - VMInstanceVO vm = new VMInstanceVO(); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getType()).thenReturn(Type.Instance); + Mockito.when(vm.getLastHostId()).thenReturn(null); Mockito.when(vmProfile.getVirtualMachine()).thenReturn(vm); Mockito.when(vmProfile.getId()).thenReturn(instanceId);