From 97ffe0711ec3abbadda01b1c92ca050fe4cac59e Mon Sep 17 00:00:00 2001 From: Koushik Das Date: Fri, 2 Dec 2016 23:08:32 +0530 Subject: [PATCH] CLOUDSTACK-9650: Allow starting VMs regardless of cpu/memory cluster.disablethreshold setting Introduced a global configuration flag 'cluster.threshold.enabled'. By default the flag is true. If the value is false, then a VM can be started in a cluster even if the cluster thresholds are crossed. However, for a new VM deployment the cluster threshold will always be honoured. --- .../deploy/DeploymentClusterPlanner.java | 10 +++ .../cloud/vm/VirtualMachineManagerImpl.java | 7 ++ .../implicitplanner/ImplicitPlannerTest.java | 8 ++ .../src/com/cloud/deploy/FirstFitPlanner.java | 20 ++++- .../src/com/cloud/vm/UserVmManagerImpl.java | 1 + .../vm/DeploymentPlanningManagerImplTest.java | 11 +++ .../com/cloud/vm/FirstFitPlannerTest.java | 76 ++++++++++++++++++- 7 files changed, 129 insertions(+), 4 deletions(-) diff --git a/api/src/com/cloud/deploy/DeploymentClusterPlanner.java b/api/src/com/cloud/deploy/DeploymentClusterPlanner.java index 6c09a6d5106..c2f077628c8 100644 --- a/api/src/com/cloud/deploy/DeploymentClusterPlanner.java +++ b/api/src/com/cloud/deploy/DeploymentClusterPlanner.java @@ -29,6 +29,7 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner { static final String ClusterCPUCapacityDisableThresholdCK = "cluster.cpu.allocated.capacity.disablethreshold"; static final String ClusterMemoryCapacityDisableThresholdCK = "cluster.memory.allocated.capacity.disablethreshold"; + static final String ClusterThresholdEnabledCK = "cluster.threshold.enabled"; static final ConfigKey ClusterCPUCapacityDisableThreshold = new ConfigKey( @@ -46,6 +47,15 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner { "0.85", "Percentage (as a value between 0 and 1) of memory utilization above which allocators will disable using the cluster for low memory available. Keep the corresponding notification threshold lower than this to be notified beforehand.", true, ConfigKey.Scope.Cluster, null); + static final ConfigKey ClusterThresholdEnabled = + new ConfigKey( + "Advanced", + Boolean.class, + ClusterThresholdEnabledCK, + "true", + "Enable/Disable cluster thresholds. If disabled, an instance can start in a cluster even though the threshold may be crossed.", + false, + ConfigKey.Scope.Zone); /** * This is called to determine list of possible clusters where a virtual diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index 99693ba3a6f..e1ce6978779 100644 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -1052,6 +1052,13 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac _resourceMgr.updateGPUDetails(destHostId, gpuDevice.getGroupDetails()); } + // Remove the information on whether it was a deploy vm request.The deployvm=true information + // is set only when the vm is being deployed. When a vm is started from a stop state the + // information isn't set, + if (_uservmDetailsDao.findDetail(vm.getId(), "deployvm") != null) { + _uservmDetailsDao.removeDetail(vm.getId(), "deployvm"); + } + startedVm = vm; if (s_logger.isDebugEnabled()) { s_logger.debug("Start completed for VM " + vm); diff --git a/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java index 754f36e8edb..79cb1b4596d 100644 --- a/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java +++ b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -94,6 +94,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachineProfileImpl; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; @RunWith(SpringJUnit4ClassRunner.class) @@ -121,6 +122,8 @@ public class ImplicitPlannerTest { @Inject UserVmDao vmDao; @Inject + UserVmDetailsDao vmDetailsDao; + @Inject VMInstanceDao vmInstanceDao; @Inject VolumeDao volsDao; @@ -520,6 +523,11 @@ public class ImplicitPlannerTest { return Mockito.mock(UserVmDao.class); } + @Bean + public UserVmDetailsDao userVmDetailsDao() { + return Mockito.mock(UserVmDetailsDao.class); + } + @Bean public VMInstanceDao vmInstanceDao() { return Mockito.mock(VMInstanceDao.class); diff --git a/server/src/com/cloud/deploy/FirstFitPlanner.java b/server/src/com/cloud/deploy/FirstFitPlanner.java index 640834b2665..c3df4174ada 100644 --- a/server/src/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/com/cloud/deploy/FirstFitPlanner.java @@ -66,6 +66,7 @@ import com.cloud.utils.component.AdapterBase; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPlanner, Configurable, DeploymentPlanner { @@ -89,6 +90,8 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla @Inject protected UserVmDao vmDao; @Inject + protected UserVmDetailsDao vmDetailsDao; + @Inject protected VMInstanceDao vmInstanceDao; @Inject protected VolumeDao volsDao; @@ -309,6 +312,16 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla protected void removeClustersCrossingThreshold(List clusterListForVmAllocation, ExcludeList avoid, VirtualMachineProfile vmProfile, DeploymentPlan plan) { + // Check if cluster threshold for cpu/memory has to be checked or not. By default we + // always check cluster threshold isn't crossed. However, the check may be skipped for + // starting (not deploying) an instance. + VirtualMachine vm = vmProfile.getVirtualMachine(); + Map details = vmDetailsDao.listDetailsKeyPairs(vm.getId()); + Boolean isThresholdEnabled = ClusterThresholdEnabled.value(); + if (!(isThresholdEnabled || (details != null && details.containsKey("deployvm")))) { + return; + } + List capacityList = getCapacitiesForCheckingThreshold(); List clustersCrossingThreshold = new ArrayList(); @@ -323,12 +336,13 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla if (clusterListForVmAllocation == null || clusterListForVmAllocation.size() == 0) { return; } + if (capacity == Capacity.CAPACITY_TYPE_CPU) { clustersCrossingThreshold = - capacityDao.listClustersCrossingThreshold(capacity, plan.getDataCenterId(), ClusterCPUCapacityDisableThreshold.key(), cpu_requested); + capacityDao.listClustersCrossingThreshold(capacity, plan.getDataCenterId(), ClusterCPUCapacityDisableThreshold.key(), cpu_requested); } else if (capacity == Capacity.CAPACITY_TYPE_MEMORY) { clustersCrossingThreshold = - capacityDao.listClustersCrossingThreshold(capacity, plan.getDataCenterId(), ClusterMemoryCapacityDisableThreshold.key(), ram_requested); + capacityDao.listClustersCrossingThreshold(capacity, plan.getDataCenterId(), ClusterMemoryCapacityDisableThreshold.key(), ram_requested); } if (clustersCrossingThreshold != null && clustersCrossingThreshold.size() != 0) { @@ -565,6 +579,6 @@ public class FirstFitPlanner extends AdapterBase implements DeploymentClusterPla @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {ClusterCPUCapacityDisableThreshold, ClusterMemoryCapacityDisableThreshold}; + return new ConfigKey[] {ClusterCPUCapacityDisableThreshold, ClusterMemoryCapacityDisableThreshold, ClusterThresholdEnabled}; } } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 17cbbdeceac..d60b8c16895 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -3582,6 +3582,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir for (String key : customParameters.keySet()) { vm.setDetail(key, customParameters.get(key)); } + vm.setDetail("deployvm", "true"); _vmDao.saveDetails(vm); s_logger.debug("Allocating in the DB for vm"); diff --git a/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java b/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java index 72960e31459..2e878175df0 100644 --- a/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java +++ b/server/test/com/cloud/vm/DeploymentPlanningManagerImplTest.java @@ -94,6 +94,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.user.AccountManager; import com.cloud.utils.component.ComponentContext; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; @RunWith(SpringJUnit4ClassRunner.class) @@ -130,6 +131,9 @@ public class DeploymentPlanningManagerImplTest { @Inject DedicatedResourceDao _dedicatedDao; + @Inject + UserVmDetailsDao vmDetailsDao; + private static long domainId = 5L; private static long dataCenterId = 1L; @@ -150,6 +154,8 @@ public class DeploymentPlanningManagerImplTest { VMInstanceVO vm = new VMInstanceVO(); Mockito.when(vmProfile.getVirtualMachine()).thenReturn(vm); + Mockito.when(vmDetailsDao.listDetailsKeyPairs(Matchers.anyLong())).thenReturn(null); + Mockito.when(_dcDao.findById(Matchers.anyLong())).thenReturn(dc); Mockito.when(dc.getId()).thenReturn(dataCenterId); @@ -382,6 +388,11 @@ public class DeploymentPlanningManagerImplTest { return Mockito.mock(UserVmDao.class); } + @Bean + public UserVmDetailsDao userVmDetailsDao() { + return Mockito.mock(UserVmDetailsDao.class); + } + @Bean public VMInstanceDao vmInstanceDao() { return Mockito.mock(VMInstanceDao.class); diff --git a/server/test/com/cloud/vm/FirstFitPlannerTest.java b/server/test/com/cloud/vm/FirstFitPlannerTest.java index b7b103b2d98..41dd8c0922e 100644 --- a/server/test/com/cloud/vm/FirstFitPlannerTest.java +++ b/server/test/com/cloud/vm/FirstFitPlannerTest.java @@ -32,7 +32,12 @@ import javax.inject.Inject; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.framework.config.ConfigDepot; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.test.utils.SpringUtils; import org.junit.After; @@ -63,6 +68,7 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentClusterPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.FirstFitPlanner; import com.cloud.exception.InsufficientServerCapacityException; @@ -86,6 +92,7 @@ import com.cloud.user.AccountVO; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; @RunWith(SpringJUnit4ClassRunner.class) @@ -101,6 +108,8 @@ public class FirstFitPlannerTest { @Inject UserVmDao vmDao; @Inject + UserVmDetailsDao vmDetailsDao; + @Inject ConfigurationDao configDao; @Inject CapacityDao capacityDao; @@ -114,6 +123,10 @@ public class FirstFitPlannerTest { HostGpuGroupsDao hostGpuGroupsDao; @Inject HostTagsDao hostTagsDao; + @Inject + ConfigDepotImpl configDepot; + @Inject + ScopedConfigStorage scopedStorage; private static long domainId = 1L; long dataCenterId = 1L; @@ -126,6 +139,8 @@ public class FirstFitPlannerTest { @Before public void setUp() { + ConfigKey.init(configDepot); + when(configDao.getValue(Mockito.anyString())).thenReturn(null); when(configDao.getValue(Config.ImplicitHostTags.key())).thenReturn("GPU"); @@ -138,6 +153,7 @@ public class FirstFitPlannerTest { @After public void tearDown() { + ConfigKey.init(null); CallContext.unregister(); } @@ -157,7 +173,50 @@ public class FirstFitPlannerTest { reorderedClusterList.add(6L); reorderedClusterList.add(2L); - assertTrue("Reordered cluster list is not ownering the implict host tags", (clusterList.equals(reorderedClusterList))); + assertTrue("Reordered cluster list is not honoring the implict host tags", (clusterList.equals(reorderedClusterList))); + } + + @Test + public void checkClusterReorderingForDeployVMWithThresholdCheckDisabled() throws InsufficientServerCapacityException { + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = mock(ExcludeList.class); + initializeForTest(vmProfile, plan, avoids); + List clustersCrossingThreshold = initializeForClusterThresholdDisabled(); + + Map details = new HashMap(); + details.put("deployvm", "true"); + when(vmDetailsDao.listDetailsKeyPairs(vmProfile.getVirtualMachine().getId())).thenReturn(details); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + assertTrue("Reordered cluster list have clusters exceeding threshold", (!clusterList.containsAll(clustersCrossingThreshold))); + } + + @Test + public void checkClusterReorderingForStartVMWithThresholdCheckDisabled() throws InsufficientServerCapacityException { + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = mock(ExcludeList.class); + initializeForTest(vmProfile, plan, avoids); + List clustersCrossingThreshold = initializeForClusterThresholdDisabled(); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + assertTrue("Reordered cluster list does not have clusters exceeding threshold", (clusterList.containsAll(clustersCrossingThreshold))); + } + + private List initializeForClusterThresholdDisabled() { + when(configDepot.global()).thenReturn(configDao); + + ConfigurationVO config = mock(ConfigurationVO.class); + when(config.getValue()).thenReturn(String.valueOf(false)); + when(configDao.findById(DeploymentClusterPlanner.ClusterThresholdEnabled.key())).thenReturn(config); + + List clustersCrossingThreshold = new ArrayList(); + clustersCrossingThreshold.add(3L); + when(capacityDao.listClustersCrossingThreshold( + Mockito.anyShort(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(clustersCrossingThreshold); + + return clustersCrossingThreshold; } private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDeployment plan, ExcludeList avoids) { @@ -311,6 +370,11 @@ public class FirstFitPlannerTest { return Mockito.mock(UserVmDao.class); } + @Bean + public UserVmDetailsDao userVmDetailsDao() { + return Mockito.mock(UserVmDetailsDao.class); + } + @Bean public VMInstanceDao vmInstanceDao() { return Mockito.mock(VMInstanceDao.class); @@ -376,6 +440,16 @@ public class FirstFitPlannerTest { return Mockito.mock(ResourceManager.class); } + @Bean + public ConfigDepot configDepot() { + return Mockito.mock(ConfigDepotImpl.class); + } + + @Bean + public ScopedConfigStorage configStorage() { + return Mockito.mock(ScopedConfigStorage.class); + } + public static class Library implements TypeFilter { @Override public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {