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:


Reply via email to