This is an automated email from the ASF dual-hosted git repository. vishesh pushed a commit to branch enforce-strict-host-tag-check in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 189242658cc76cce3f0d7e959aafc7a483fff277 Author: Vishesh <[email protected]> AuthorDate: Wed Apr 24 17:24:42 2024 +0530 Add e2e tests --- .github/workflows/ci.yml | 1 + .../src/main/java/com/cloud/host/HostVO.java | 27 +- .../src/test/java/com/cloud/host/HostVOTest.java | 18 + .../main/java/com/cloud/vm/UserVmManagerImpl.java | 33 +- .../java/com/cloud/vm/UserVmManagerImplTest.java | 8 +- .../cloudstack/vm/UnmanagedVMsManagerImplTest.java | 1 - test/integration/smoke/test_vm_strict_host_tags.py | 452 +++++++++++++++++++++ tools/marvin/marvin/config/test_data.py | 19 +- tools/marvin/marvin/lib/base.py | 9 +- 9 files changed, 542 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fac2d6266fa..360afdb95ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,7 @@ jobs: smoke/test_usage smoke/test_usage_events smoke/test_vm_deployment_planner + smoke/test_vm_strict_host_tags smoke/test_vm_schedule smoke/test_vm_life_cycle smoke/test_vm_lifecycle_unmanage_import diff --git a/engine/schema/src/main/java/com/cloud/host/HostVO.java b/engine/schema/src/main/java/com/cloud/host/HostVO.java index d5f99a07b42..58e93f9d7aa 100644 --- a/engine/schema/src/main/java/com/cloud/host/HostVO.java +++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java @@ -768,12 +768,9 @@ public class HostVO implements Host { this.uuid = uuid; } - public boolean checkHostServiceOfferingAndTemplateTags(ServiceOffering serviceOffering, VirtualMachineTemplate template, Set<String> strictHostTags) { - if (serviceOffering == null || template == null) { - return false; - } + private Set<String> getHostServiceOfferingAndTemplateStrictTags(ServiceOffering serviceOffering, VirtualMachineTemplate template, Set<String> strictHostTags) { if (StringUtils.isEmpty(serviceOffering.getHostTag()) && StringUtils.isEmpty(template.getTemplateTag())) { - return true; + return new HashSet<>(); } HashSet<String> hostTagsSet = new HashSet<>(getHostTags()); HashSet<String> tags = new HashSet<>(); @@ -784,12 +781,32 @@ public class HostVO implements Host { tags.add(template.getTemplateTag()); } tags.removeIf(tag -> !strictHostTags.contains(tag)); + tags.removeAll(hostTagsSet); + return tags; + } + + public boolean checkHostServiceOfferingAndTemplateTags(ServiceOffering serviceOffering, VirtualMachineTemplate template, Set<String> strictHostTags) { + if (serviceOffering == null || template == null) { + return false; + } + Set<String> tags = getHostServiceOfferingAndTemplateStrictTags(serviceOffering, template, strictHostTags); if (tags.isEmpty()) { return true; } + HashSet<String> hostTagsSet = new HashSet<>(getHostTags()); return hostTagsSet.containsAll(tags); } + public Set<String> getHostServiceOfferingAndTemplateMissingTags(ServiceOffering serviceOffering, VirtualMachineTemplate template, Set<String> strictHostTags) { + Set<String> tags = getHostServiceOfferingAndTemplateStrictTags(serviceOffering, template, strictHostTags); + if (tags.isEmpty()) { + return new HashSet<>(); + } + HashSet<String> hostTagsSet = new HashSet<>(getHostTags()); + tags.removeAll(hostTagsSet); + return tags; + } + public boolean checkHostServiceOfferingTags(ServiceOffering serviceOffering) { if (serviceOffering == null) { return false; diff --git a/engine/schema/src/test/java/com/cloud/host/HostVOTest.java b/engine/schema/src/test/java/com/cloud/host/HostVOTest.java index a49c70511c8..3262c4cc291 100755 --- a/engine/schema/src/test/java/com/cloud/host/HostVOTest.java +++ b/engine/schema/src/test/java/com/cloud/host/HostVOTest.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -97,7 +98,9 @@ public class HostVOTest { @Test public void testNoTagOfferingTemplate() { assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, Mockito.mock(VirtualMachineTemplate.class), Collections.emptySet())); + assertTrue(host.getHostServiceOfferingAndTemplateMissingTags(offering, Mockito.mock(VirtualMachineTemplate.class), Collections.emptySet()).isEmpty()); assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, Mockito.mock(VirtualMachineTemplate.class), Set.of("tag1", "tag2"))); + assertTrue(host.getHostServiceOfferingAndTemplateMissingTags(offering, Mockito.mock(VirtualMachineTemplate.class), Set.of("tag1", "tag2")).isEmpty()); } @Test @@ -105,15 +108,25 @@ public class HostVOTest { host.setHostTags(Arrays.asList("tag1", "tag2"), false); offering.setHostTag("tag2,tag1"); assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, Mockito.mock(VirtualMachineTemplate.class), Set.of("tag1"))); + Set<String> actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, Mockito.mock(VirtualMachineTemplate.class), Set.of("tag1")); + assertTrue(actualMissingTags.isEmpty()); + host.setHostTags(Arrays.asList("tag1", "tag2", "tag3"), false); offering.setHostTag("tag2,tag1"); VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); Mockito.when(template.getTemplateTag()).thenReturn("tag3"); assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, template, Set.of("tag2", "tag3"))); + actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, template, Set.of("tag2", "tag3")); + assertTrue(actualMissingTags.isEmpty()); host.setHostTags(List.of("tag3"), false); offering.setHostTag(null); assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, template, Set.of("tag3"))); + actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, template, Set.of("tag3")); + assertTrue(actualMissingTags.isEmpty()); + assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering, template, Set.of("tag2", "tag1"))); + actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, template, Set.of("tag2", "tag1")); + assertTrue(actualMissingTags.isEmpty()); } @Test @@ -123,9 +136,14 @@ public class HostVOTest { VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); Mockito.when(template.getTemplateTag()).thenReturn("tag1"); assertFalse(host.checkHostServiceOfferingAndTemplateTags(offering, template, Set.of("tag1", "tag2", "tag3", "tag4"))); + Set<String> actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, template, Set.of("tag1", "tag2", "tag3", "tag4")); + assertEquals(Set.of("tag4"), actualMissingTags); + offering.setHostTag("tag1,tag2"); template = Mockito.mock(VirtualMachineTemplate.class); Mockito.when(template.getTemplateTag()).thenReturn("tag3"); + actualMissingTags = host.getHostServiceOfferingAndTemplateMissingTags(offering, template, Set.of("tag1", "tag2", "tag3", "tag4")); assertFalse(host.checkHostServiceOfferingAndTemplateTags(offering, template, Set.of("tag1", "tag2", "tag3", "tag4"))); + assertEquals(Set.of("tag3"), actualMissingTags); } } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 346d383eae8..a90183c0b83 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -2075,6 +2075,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Check disable threshold for cluster is not crossed HostVO host = _hostDao.findById(vmInstance.getHostId()); + _hostDao.loadDetails(host); if (_capacityMgr.checkIfClusterCrossesThreshold(host.getClusterId(), cpuDiff, memoryDiff)) { throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance.toString())); } @@ -2087,12 +2088,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir _resourceLimitMgr.updateVmResourceCountForServiceOfferingChange(caller.getAccountId(), vmInstance.isDisplay(), (long) currentCpu, (long) newCpu, (long) currentMemory, (long) newMemory, currentServiceOffering, newServiceOffering, template); - // #1 Check existing host has capacity + + // #1 Check existing host has capacity & and the correct tags if (!excludes.shouldAvoid(ApiDBUtils.findHostById(vmInstance.getHostId()))) { existingHostHasCapacity = _capacityMgr.checkIfHostHasCpuCapability(vmInstance.getHostId(), newCpu, newSpeed) && _capacityMgr.checkIfHostHasCapacity(vmInstance.getHostId(), cpuDiff, ByteScaleUtils.mebibytesToBytes(memoryDiff), false, _capacityMgr.getClusterOverProvisioningFactor(host.getClusterId(), Capacity.CAPACITY_TYPE_CPU), - _capacityMgr.getClusterOverProvisioningFactor(host.getClusterId(), Capacity.CAPACITY_TYPE_MEMORY), false); + _capacityMgr.getClusterOverProvisioningFactor(host.getClusterId(), Capacity.CAPACITY_TYPE_MEMORY), false) + && checkEnforceStrictHostTagCheck(vmInstance, host); excludes.addHost(vmInstance.getHostId()); } @@ -5490,7 +5493,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (destinationHost != null) { logger.debug("Destination Host to deploy the VM is specified, specifying a deployment plan to deploy the VM"); _hostDao.loadHostTags(destinationHost); - checkEnforceStrictHostTagCheck(vm, destinationHost); + validateStrictHostTagCheck(vm, destinationHost); final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); Pair<Boolean, Boolean> cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(destinationHost, offering, false); @@ -6705,16 +6708,26 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - protected void checkEnforceStrictHostTagCheck(VMInstanceVO vm, HostVO host) { + protected boolean checkEnforceStrictHostTagCheck(VMInstanceVO vm, HostVO host) { ServiceOffering serviceOffering = serviceOfferingDao.findByIdIncludingRemoved(vm.getServiceOfferingId()); VirtualMachineTemplate template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); + return checkEnforceStrictHostTagCheck(host, serviceOffering, template); + } + private boolean checkEnforceStrictHostTagCheck(HostVO host, ServiceOffering serviceOffering, VirtualMachineTemplate template) { Set<String> strictHostTags = UserVmManager.getStrictHostTags(); - if (!host.checkHostServiceOfferingAndTemplateTags(serviceOffering, template, strictHostTags)) { - s_logger.error(String.format( - "Cannot deploy VM: %s to host : %s due to tag mismatch." + - " strictHosts: %s serviceOffering tags: %s, template tags: %s", - vm, host, strictHostTags, serviceOffering.getHostTag(), template.getTemplateTag())); + return host.checkHostServiceOfferingAndTemplateTags(serviceOffering, template, strictHostTags); + } + + protected void validateStrictHostTagCheck(VMInstanceVO vm, HostVO host) { + ServiceOffering serviceOffering = serviceOfferingDao.findByIdIncludingRemoved(vm.getServiceOfferingId()); + VirtualMachineTemplate template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); + + if (!checkEnforceStrictHostTagCheck(host, serviceOffering, template)) { + Set<String> missingTags = host.getHostServiceOfferingAndTemplateMissingTags(serviceOffering, template, UserVmManager.getStrictHostTags()); + logger.error("Cannot deploy VM: {} to host : {} due to tag mismatch. host tags: {}, " + + "strict host tags: {} serviceOffering tags: {}, template tags: {}, missing tags: {}", + vm, host, host.getHostTags(), UserVmManager.getStrictHostTags(), serviceOffering.getHostTag(), template.getTemplateTag(), missingTags); throw new InvalidParameterValueException(String.format("Cannot deploy VM, destination host: %s " + "is not compatible for the VM", host.getName())); } @@ -6747,7 +6760,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir HostVO destinationHostVO = _hostDao.findById(destinationHost.getId()); _hostDao.loadHostTags(destinationHostVO); - checkEnforceStrictHostTagCheck(vm, destinationHostVO); + validateStrictHostTagCheck(vm, destinationHostVO); checkHostsDedication(vm, srcHost.getId(), destinationHost.getId()); diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 0821e641ac5..6cc46a5e4fd 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -1565,7 +1565,7 @@ public class UserVmManagerImplTest { } @Test - public void testCheckEnforceStrictHostTagCheckPass() { + public void testValidateStrictHostTagCheckPass() { ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); @@ -1580,7 +1580,7 @@ public class UserVmManagerImplTest { Mockito.when(destinationHostVO.checkHostServiceOfferingAndTemplateTags(Mockito.any(ServiceOffering.class), Mockito.any(VirtualMachineTemplate.class), Mockito.anySet())).thenReturn(true); - userVmManagerImpl.checkEnforceStrictHostTagCheck(vm, destinationHostVO); + userVmManagerImpl.validateStrictHostTagCheck(vm, destinationHostVO); Mockito.verify( destinationHostVO, Mockito.times(1) @@ -1588,7 +1588,7 @@ public class UserVmManagerImplTest { } @Test(expected = InvalidParameterValueException.class) - public void testCheckEnforceStrictHostTagCheckFail() { + public void testValidateStrictHostTagCheckFail() { ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); VMTemplateVO template = Mockito.mock(VMTemplateVO.class); @@ -1602,6 +1602,6 @@ public class UserVmManagerImplTest { Mockito.when(vm.getTemplateId()).thenReturn(2L); Mockito.when(destinationHostVO.checkHostServiceOfferingAndTemplateTags(Mockito.any(ServiceOffering.class), Mockito.any(VirtualMachineTemplate.class), Mockito.anySet())).thenReturn(false); - userVmManagerImpl.checkEnforceStrictHostTagCheck(vm, destinationHostVO); + userVmManagerImpl.validateStrictHostTagCheck(vm, destinationHostVO); } } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 761949bf65d..81358d99ae7 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -302,7 +302,6 @@ public class UnmanagedVMsManagerImplTest { HostVO hostVO = Mockito.mock(HostVO.class); when(hostVO.isInMaintenanceStates()).thenReturn(false); hosts.add(hostVO); - when(hostVO.checkHostServiceOfferingAndTemplateTags(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(true); when(resourceManager.listHostsInClusterByStatus(anyLong(), any(Status.class))).thenReturn(hosts); when(resourceManager.listAllUpAndEnabledHostsInOneZoneByHypervisor(any(Hypervisor.HypervisorType.class), anyLong())).thenReturn(hosts); List<VMTemplateStoragePoolVO> templates = new ArrayList<>(); diff --git a/test/integration/smoke/test_vm_strict_host_tags.py b/test/integration/smoke/test_vm_strict_host_tags.py new file mode 100644 index 00000000000..af1eba5f887 --- /dev/null +++ b/test/integration/smoke/test_vm_strict_host_tags.py @@ -0,0 +1,452 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for CM Deployment Planner +""" +# Import Local Modules +from marvin.cloudstackAPI import (updateConfiguration) +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (Account, ServiceOffering, Template, Host, VirtualMachine) +from marvin.lib.common import (get_domain, get_zone) +from nose.plugins.attrib import attr + + +class TestVMDeploymentPlannerStrictTags(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestVMDeploymentPlannerStrictTags, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + # Create an account, network, VM and IP addresses + cls.account = Account.create(cls.apiclient, cls.services["account"], domainid=cls.domain.id) + cls.service_offering_h1 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h1"]) + cls.service_offering_h2 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h2"]) + + cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t1") + cls.template_t1.download(cls.apiclient) + + cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t2") + cls.template_t2.download(cls.apiclient) + + hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, type='Routing') + cls.host_h1 = hosts[0] if len(hosts) >= 1 else None + + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="h1,t1,v1") + + cls._cleanup = [cls.account, cls.service_offering_h1, cls.service_offering_h2, cls.template_t1, cls.template_t2] + + @classmethod + def tearDownClass(cls): + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="") + cls.updateConfiguration("vm.strict.host.tags", "") + cls.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + cls.updateConfiguration("resource.limit.host.tags", "") + super(TestVMDeploymentPlannerStrictTags, cls).tearDownClass() + + @classmethod + def updateConfiguration(self, name, value): + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = name + cmd.value = value + self.apiclient.updateConfiguration(cmd) + + def deploy_vm(self, destination_id, template_id, service_offering_id): + return VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, + templateid=template_id, serviceofferingid=service_offering_id, + hostid=destination_id) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_01_deploy_vm_on_specific_host_without_strict_tags(self): + self.updateConfiguration("vm.strict.host.tags", "") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_02_deploy_vm_on_any_host_without_strict_tags(self): + self.updateConfiguration("vm.strict.host.tags", "") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "") + + vm = self.deploy_vm(None, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertIsNotNone(vm, "VM instance was not deployed") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_03_deploy_vm_on_specific_host_with_strict_tags_success(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "false") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_04_deploy_vm_on_any_host_with_strict_tags_success(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "false") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(None, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertIsNotNone(vm, "VM instance was not deployed") + + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + + vm = self.deploy_vm(None, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_05_deploy_vm_on_specific_host_with_strict_tags_failure(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + try: + vm = self.deploy_vm(self.host_h1.id, self.template_t2.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.fail("VM should not be deployed") + except Exception as e: + self.assertTrue("Cannot deploy VM, destination host" in str(e)) + + try: + vm = self.deploy_vm(self.host_h1.id, self.template_t2.id, self.service_offering_h2.id) + self._cleanup.append(vm) + self.fail("VM should not be deployed") + except Exception as e: + self.assertTrue("Cannot deploy VM, destination host" in str(e)) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_06_deploy_vm_on_any_host_with_strict_tags_failure(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + try: + vm = self.deploy_vm(None, self.template_t2.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.fail("VM should not be deployed") + except Exception as e: + self.assertTrue("No suitable host found for follow compute offering tags: t2" in str(e)) + + +class TestScaleVMStrictTags(cloudstackTestCase): + @classmethod + def setUpClass(cls): + testClient = super(TestScaleVMStrictTags, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + # Create an account, network, VM and IP addresses + cls.account = Account.create(cls.apiclient, cls.services["account"], domainid=cls.domain.id) + cls.service_offering_h1 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h1"]) + cls.service_offering_h2 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h2"]) + + cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t1") + cls.template_t1.download(cls.apiclient) + + cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t2") + cls.template_t2.download(cls.apiclient) + + hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, type='Routing') + cls.host_h1 = hosts[0] if len(hosts) >= 1 else None + + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="h1,t1,v1,h2,t2,v2") + + cls._cleanup = [cls.account, cls.service_offering_h1, cls.service_offering_h2, cls.template_t1, cls.template_t2] + + @classmethod + def tearDownClass(cls): + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="") + cls.updateConfiguration("vm.strict.host.tags", "") + cls.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + cls.updateConfiguration("resource.limit.host.tags", "") + super(TestScaleVMStrictTags, cls).tearDownClass() + + @classmethod + def updateConfiguration(self, name, value): + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = name + cmd.value = value + self.apiclient.updateConfiguration(cmd) + + def deploy_vm(self, destination_id, template_id, service_offering_id): + return VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, + templateid=template_id, serviceofferingid=service_offering_id, + hostid=destination_id) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_01_scale_vm_strict_tags_success(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + vm.stop(self.apiclient) + vm.scale(self.apiclient, serviceOfferingId=self.service_offering_h2.id) + vm.start(self.apiclient) + scaled_vm = VirtualMachine.list(self.apiclient, id=vm.id, listall=True)[0] + self.assertEqual(scaled_vm.serviceofferingid, self.service_offering_h2.id, "VM was not scaled") + self.assertEqual(self.host_h1.id, scaled_vm.hostid, "VM was not scaled") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_02_scale_vm_strict_tags_failure(self): + if self.host_h1: + Host.update(self.apiclient, id=self.host_h1.id, hosttags="h1,t1,v1") + + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + try: + vm.stop(self.apiclient) + vm.scale(self.apiclient, serviceOfferingId=self.service_offering_h2.id) + vm.start(self.apiclient) + self.fail("VM should not be be able scale and start") + except Exception as e: + self.assertTrue("No suitable host found for follow compute offering tags: h2" in str(e)) + + +class TestRestoreVMStrictTags(cloudstackTestCase): + @classmethod + def setUpClass(cls): + testClient = super(TestRestoreVMStrictTags, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + # Create an account, network, VM and IP addresses + cls.account = Account.create(cls.apiclient, cls.services["account"], domainid=cls.domain.id) + cls.service_offering_h1 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h1"]) + cls.service_offering_h2 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h2"]) + + cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t1") + cls.template_t1.download(cls.apiclient) + + cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t2") + cls.template_t2.download(cls.apiclient) + + hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, type='Routing') + cls.host_h1 = hosts[0] if len(hosts) >= 1 else None + + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="h1,t1,v1") + + cls._cleanup = [cls.account, cls.service_offering_h1, cls.service_offering_h2, cls.template_t1, cls.template_t2] + + @classmethod + def tearDownClass(cls): + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="") + cls.updateConfiguration("vm.strict.host.tags", "") + cls.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + cls.updateConfiguration("resource.limit.host.tags", "") + super(TestRestoreVMStrictTags, cls).tearDownClass() + + @classmethod + def updateConfiguration(self, name, value): + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = name + cmd.value = value + self.apiclient.updateConfiguration(cmd) + + def deploy_vm(self, destination_id, template_id, service_offering_id): + return VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, + templateid=template_id, serviceofferingid=service_offering_id, + hostid=destination_id) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_01_restore_vm_strict_tags_success(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + + vm.restore(self.apiclient, templateid=self.template_t2.id, expunge=True) + restored_vm = VirtualMachine.list(self.apiclient, id=vm.id, listall=True)[0] + self.assertEqual(restored_vm.templateid, self.template_t2.id, "VM was not scaled") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_02_restore_vm_strict_tags_failure(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + try: + vm.restore(self.apiclient, templateid=self.template_t2.id, expunge=True) + self.fail("VM should not be restored") + except Exception as e: + self.assertTrue("No suitable host found for follow compute offering tags: t2" in str(e)) + +class TestMigrateVMStrictTags(cloudstackTestCase): + @classmethod + def setUpClass(cls): + testClient = super(TestMigrateVMStrictTags, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + hosts = Host.list(cls.apiclient, zoneid=cls.zone.id, type='Routing') + cls.host_h1 = hosts[0] if len(hosts) >= 1 else None + cls.host_h2 = None + if len(hosts) >= 2: + for host in hosts[1:]: + if host.clusterid == cls.host_h1.clusterid: + cls.host_h2 = host + break + + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="h1,t1,v1") + + if not cls.host_h2: + cls.skipTest("There are not enough hosts to run this test") + + # Create an account, network, VM and IP addresses + cls.account = Account.create(cls.apiclient, cls.services["account"], domainid=cls.domain.id) + cls.service_offering_h1 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h1"]) + cls.service_offering_h2 = ServiceOffering.create(cls.apiclient, cls.services["service_offering_h2"]) + + cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t1") + cls.template_t1.download(cls.apiclient) + + cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower(), templatetag="t2") + cls.template_t2.download(cls.apiclient) + + cls._cleanup = [cls.account, cls.service_offering_h1, cls.service_offering_h2, cls.template_t1, cls.template_t2] + + @classmethod + def tearDownClass(cls): + if cls.host_h1: + Host.update(cls.apiclient, id=cls.host_h1.id, hosttags="") + if cls.host_h2: + Host.update(cls.apiclient, id=cls.host_h2.id, hosttags="") + cls.updateConfiguration("vm.strict.host.tags", "") + cls.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + cls.updateConfiguration("resource.limit.host.tags", "") + super(TestMigrateVMStrictTags, cls).tearDownClass() + + @classmethod + def updateConfiguration(self, name, value): + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = name + cmd.value = value + self.apiclient.updateConfiguration(cmd) + + def deploy_vm(self, destination_id, template_id, service_offering_id): + return VirtualMachine.create(self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, + templateid=template_id, serviceofferingid=service_offering_id, + hostid=destination_id) + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_01_migrate_vm_strict_tags_success(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + Host.update(self.apiclient, id=self.host_h2.id, hosttags="h1,t1,v1") + vm.migrate(self.apiclient, self.host_h2.id) + migrated_vm = VirtualMachine.list(self.apiclient, id=vm.id, listall=True)[0] + self.assertEqual(migrated_vm.hostid, self.host_h2.id, "VM was not migratd") + + @attr(tags=["advanced", "advancedns", "ssh", "smoke"], required_hardware="false") + def test_02_migrate_vm_strict_tags_failure(self): + self.updateConfiguration("vm.strict.host.tags", "v1,v2") + self.updateConfiguration("vm.strict.resource.limit.host.tag.check", "true") + self.updateConfiguration("resource.limit.host.tags", "h1,h2,t1,t2") + + vm = self.deploy_vm(self.host_h1.id, self.template_t1.id, self.service_offering_h1.id) + self._cleanup.append(vm) + + self.assertEqual(self.host_h1.id, vm.hostid, "VM instance was not deployed on target host ID") + Host.update(self.apiclient, id=self.host_h2.id, hosttags="h2,t2,v2") + try: + vm.migrate(self.apiclient, self.host_h2.id) + VirtualMachine.list(self.apiclient, id=vm.id, listall=True)[0] + self.fail("VM should not be migrated") + except Exception as e: + self.assertTrue("Cannot deploy VM, destination host:" in str(e)) diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py index ef9bfd774f7..e96dba1c4d5 100644 --- a/tools/marvin/marvin/config/test_data.py +++ b/tools/marvin/marvin/config/test_data.py @@ -177,9 +177,9 @@ test_data = { "service_offering_h2": { "name": "Tagged h2 Small Instance", "displaytext": "Tagged h2 Small Instance", - "cpunumber": 1, - "cpuspeed": 100, - "memory": 256, + "cpunumber": 2, + "cpuspeed": 200, + "memory": 512, "hosttags": "h2" }, "disk_offering": { @@ -1034,7 +1034,18 @@ test_data = { "requireshvm": "True", "ispublic": "True", "deployasis": "True" - } + }, + "simulator": { + "name": "tiny-simulator", + "displaytext": "tiny simulator", + "format": "vhd", + "hypervisor": "simulator", + "ostype": "Other Linux (64-bit)", + "url": "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina.vhd.bz2", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True" + }, }, "test_ovf_templates": [ { diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 04d4e6810c4..c17fe93be7c 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -776,12 +776,14 @@ class VirtualMachine: if response[0] == FAIL: raise Exception(response[1]) - def restore(self, apiclient, templateid=None): + def restore(self, apiclient, templateid=None, expunge=None): """Restore the instance""" cmd = restoreVirtualMachine.restoreVirtualMachineCmd() cmd.virtualmachineid = self.id if templateid: cmd.templateid = templateid + if expunge: + cmd.expunge = expunge return apiclient.restoreVirtualMachine(cmd) def get_ssh_client( @@ -1457,7 +1459,7 @@ class Template: @classmethod def register(cls, apiclient, services, zoneid=None, account=None, domainid=None, hypervisor=None, - projectid=None, details=None, randomize_name=True): + projectid=None, details=None, randomize_name=True, templatetag=None): """Create template from URL""" # Create template from Virtual machine and Volume ID @@ -1522,6 +1524,9 @@ class Template: if details: cmd.details = details + if templatetag: + cmd.templatetag = templatetag + if "directdownload" in services: cmd.directdownload = services["directdownload"] if "checksum" in services:
