This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new f6ceeab3b3a server: Enforce strict host tag check (#9017)
f6ceeab3b3a is described below
commit f6ceeab3b3ab5e75163387705bb3e1480999a100
Author: Vishesh <[email protected]>
AuthorDate: Tue Jun 25 14:42:17 2024 +0530
server: Enforce strict host tag check (#9017)
Documentation PR:
https://github.com/apache/cloudstack-documentation/pull/398
Currently, an administrator can break host tag compatibility for a VM
administrator by certain operations:
* deploy/start VM on a specific host
* migrate VM
* restore VM
* scale VM
This PR allows the user to specify tags which must be checked during these
operations.
Global Settings
1. `vm.strict.host.tags` - A comma-separated list of tags for strict host
check (Default - empty)
2. `vm.strict.resource.limit.host.tag.check` - Determines whether the
resource limits tags are considered strict or not (Default - true)
During the above operations, we now check and throw an error if host tags
compatibility is being broken for tags specified in `vm.strict.host.tags`. If
`vm.strict.resource.limit.host.tag.check` is set to `true`, tags set in
`resource.limit.host.tags` are also checked during these operations.
---
.github/workflows/ci.yml | 1 +
.../src/main/java/com/cloud/host/HostVO.java | 46 +-
.../src/test/java/com/cloud/host/HostVOTest.java | 53 +-
.../deploy/DeploymentPlanningManagerImpl.java | 5 +-
.../src/main/java/com/cloud/vm/UserVmManager.java | 33 ++
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 57 ++-
.../cloudstack/vm/UnmanagedVMsManagerImpl.java | 2 +-
.../java/com/cloud/vm/UserVmManagerImplTest.java | 40 ++
test/integration/smoke/test_vm_strict_host_tags.py | 552 +++++++++++++++++++++
tools/marvin/marvin/config/test_data.py | 19 +-
tools/marvin/marvin/lib/base.py | 3 +-
11 files changed, 766 insertions(+), 45 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4fe612cad5d..133e2c35b4e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,6 +134,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 3e64d20d0e2..1a507da7957 100644
--- a/engine/schema/src/main/java/com/cloud/host/HostVO.java
+++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java
@@ -16,13 +16,13 @@
// under the License.
package com.cloud.host;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.persistence.Column;
@@ -45,6 +45,7 @@ import javax.persistence.Transient;
import org.apache.cloudstack.util.HypervisorTypeConverter;
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
import
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -768,27 +769,48 @@ public class HostVO implements Host {
this.uuid = uuid;
}
- public boolean checkHostServiceOfferingAndTemplateTags(ServiceOffering
serviceOffering, VirtualMachineTemplate template) {
- 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<>();
}
- if (getHostTags() == null) {
- return false;
- }
- HashSet<String> hostTagsSet = new HashSet<>(getHostTags());
- List<String> tags = new ArrayList<>();
+ List<String> hostTagsList = getHostTags();
+ HashSet<String> hostTagsSet = CollectionUtils.isNotEmpty(hostTagsList)
? new HashSet<>(hostTagsList) : new HashSet<>();
+ HashSet<String> tags = new HashSet<>();
if (StringUtils.isNotEmpty(serviceOffering.getHostTag())) {
tags.addAll(Arrays.asList(serviceOffering.getHostTag().split(",")));
}
- if (StringUtils.isNotEmpty(template.getTemplateTag()) &&
!tags.contains(template.getTemplateTag())) {
+ if (StringUtils.isNotEmpty(template.getTemplateTag())) {
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;
+ }
+ List<String> hostTagsList = getHostTags();
+ HashSet<String> hostTagsSet = CollectionUtils.isNotEmpty(hostTagsList)
? new HashSet<>(hostTagsList) : new HashSet<>();
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<>();
+ }
+ List<String> hostTagsList = getHostTags();
+ HashSet<String> hostTagsSet = CollectionUtils.isNotEmpty(hostTagsList)
? new HashSet<>(hostTagsList) : new HashSet<>();
+ 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 cd9ac3cc172..3262c4cc291 100755
--- a/engine/schema/src/test/java/com/cloud/host/HostVOTest.java
+++ b/engine/schema/src/test/java/com/cloud/host/HostVOTest.java
@@ -20,14 +20,18 @@ import com.cloud.offering.ServiceOffering;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.vm.VirtualMachine;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
import java.util.Arrays;
+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;
-import org.junit.Test;
-import org.junit.Before;
-import org.mockito.Mockito;
public class HostVOTest {
HostVO host;
@@ -37,7 +41,7 @@ public class HostVOTest {
public void setUp() throws Exception {
host = new HostVO();
offering = new ServiceOfferingVO("TestSO", 0, 0, 0, 0, 0,
- false, "TestSO", false,VirtualMachine.Type.User,false);
+ false, "TestSO", false, VirtualMachine.Type.User, false);
}
@Test
@@ -52,14 +56,14 @@ public class HostVOTest {
@Test
public void testRightTag() {
- host.setHostTags(Arrays.asList("tag1","tag2"), false);
+ host.setHostTags(Arrays.asList("tag1", "tag2"), false);
offering.setHostTag("tag2,tag1");
assertTrue(host.checkHostServiceOfferingTags(offering));
}
@Test
public void testWrongTag() {
- host.setHostTags(Arrays.asList("tag1","tag2"), false);
+ host.setHostTags(Arrays.asList("tag1", "tag2"), false);
offering.setHostTag("tag2,tag4");
assertFalse(host.checkHostServiceOfferingTags(offering));
}
@@ -87,40 +91,59 @@ public class HostVOTest {
@Test
public void testEitherNoSOOrTemplate() {
- assertFalse(host.checkHostServiceOfferingAndTemplateTags(null,
Mockito.mock(VirtualMachineTemplate.class)));
-
assertFalse(host.checkHostServiceOfferingAndTemplateTags(Mockito.mock(ServiceOffering.class),
null));
+ assertFalse(host.checkHostServiceOfferingAndTemplateTags(null,
Mockito.mock(VirtualMachineTemplate.class), null));
+
assertFalse(host.checkHostServiceOfferingAndTemplateTags(Mockito.mock(ServiceOffering.class),
null, null));
}
@Test
public void testNoTagOfferingTemplate() {
- assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering,
Mockito.mock(VirtualMachineTemplate.class)));
+ 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
public void testRightTagOfferingTemplate() {
host.setHostTags(Arrays.asList("tag1", "tag2"), false);
offering.setHostTag("tag2,tag1");
- assertTrue(host.checkHostServiceOfferingAndTemplateTags(offering,
Mockito.mock(VirtualMachineTemplate.class)));
+ 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));
+ 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));
+ 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
public void testWrongOfferingTag() {
- host.setHostTags(Arrays.asList("tag1","tag2"), false);
+ host.setHostTags(Arrays.asList("tag1", "tag2"), false);
offering.setHostTag("tag2,tag4");
VirtualMachineTemplate template =
Mockito.mock(VirtualMachineTemplate.class);
Mockito.when(template.getTemplateTag()).thenReturn("tag1");
- assertFalse(host.checkHostServiceOfferingAndTemplateTags(offering,
template));
+ 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");
- assertFalse(host.checkHostServiceOfferingAndTemplateTags(offering,
template));
+ 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/deploy/DeploymentPlanningManagerImpl.java
b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
index 784f6451c13..71977e79ece 100644
--- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
+++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
+import com.cloud.vm.UserVmManager;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.VMTemplateDao;
@@ -282,7 +283,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>,
Configurable {
boolean storageMigrationNeededDuringClusterMigration = false;
for (Volume volume : volumes) {
StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
- if (List.of(ScopeType.HOST,
ScopeType.CLUSTER).contains(pool.getScope())) {
+ if (pool != null && List.of(ScopeType.HOST,
ScopeType.CLUSTER).contains(pool.getScope())) {
storageMigrationNeededDuringClusterMigration = true;
break;
}
@@ -777,7 +778,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>,
Configurable {
VirtualMachineTemplate template = vmProfile.getTemplate();
if (offering.getHostTag() != null || template.getTemplateTag() !=
null) {
_hostDao.loadHostTags(host);
- if (!host.checkHostServiceOfferingAndTemplateTags(offering,
template)) {
+ if (!host.checkHostServiceOfferingAndTemplateTags(offering,
template, UserVmManager.getStrictHostTags())) {
logger.debug("Service Offering host tag or template tag does
not match the last host of this VM");
return false;
}
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index 7cd1a98fd74..0dc7a7bb73f 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -17,9 +17,12 @@
package com.cloud.vm;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import com.cloud.utils.StringUtils;
import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
import org.apache.cloudstack.framework.config.ConfigKey;
@@ -38,6 +41,8 @@ import com.cloud.template.VirtualMachineTemplate;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
+import static com.cloud.user.ResourceLimitService.ResourceLimitHostTags;
+
/**
*
*
@@ -59,6 +64,22 @@ public interface UserVmManager extends UserVmService {
"Destroys the VM's root volume when the VM is destroyed.",
true, ConfigKey.Scope.Domain);
+ ConfigKey<String> StrictHostTags = new ConfigKey<>(
+ "Advanced",
+ String.class,
+ "vm.strict.host.tags",
+ "",
+ "A comma-separated list of tags which must match during operations
like modifying the compute" +
+ "offering for an instance, and starting or live migrating
an instance to a specific host.",
+ true);
+ ConfigKey<Boolean> EnforceStrictResourceLimitHostTagCheck = new
ConfigKey<Boolean>(
+ "Advanced",
+ Boolean.class,
+ "vm.strict.resource.limit.host.tag.check",
+ "true",
+ "If set to true, tags specified in `resource.limit.host.tags` are
also included in vm.strict.host.tags.",
+ true);
+
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode";
@@ -125,6 +146,18 @@ public interface UserVmManager extends UserVmService {
void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String
eventType);
+ static Set<String> getStrictHostTags() {
+ String strictHostTags = StrictHostTags.value();
+ Set<String> strictHostTagsSet = new HashSet<>();
+ if (StringUtils.isNotEmpty(strictHostTags)) {
+ strictHostTagsSet.addAll(List.of(strictHostTags.split(",")));
+ }
+ if (EnforceStrictResourceLimitHostTagCheck.value() &&
StringUtils.isNotEmpty(ResourceLimitHostTags.value())) {
+
strictHostTagsSet.addAll(List.of(ResourceLimitHostTags.value().split(",")));
+ }
+ return strictHostTagsSet;
+ }
+
void persistDeviceBusInfo(UserVmVO paramUserVmVO, String paramString);
boolean checkIfDynamicScalingCanBeEnabled(VirtualMachine vm,
ServiceOffering offering, VirtualMachineTemplate template, Long zoneId);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 6406d75e866..80286a3daec 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -1965,9 +1965,11 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
HostVO instanceHost = _hostDao.findById(vmInstance.getHostId());
_hostDao.loadHostTags(instanceHost);
- if
(!instanceHost.checkHostServiceOfferingAndTemplateTags(newServiceOfferingVO,
template)) {
- logger.error(String.format("Cannot upgrade VM [%s] as the new
service offering [%s] does not have the required host tags %s.", vmInstance,
newServiceOfferingVO,
- instanceHost.getHostTags()));
+ Set<String> strictHostTags = UserVmManager.getStrictHostTags();
+ if
(!instanceHost.checkHostServiceOfferingAndTemplateTags(newServiceOfferingVO,
template, strictHostTags)) {
+ logger.error("Cannot upgrade VM {} as the new service offering
{} does not have the required host tags {}.",
+ vmInstance, newServiceOfferingVO,
+
instanceHost.getHostServiceOfferingAndTemplateMissingTags(newServiceOfferingVO,
template, strictHostTags));
return false;
}
}
@@ -2076,6 +2078,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()));
}
@@ -2088,12 +2091,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());
}
@@ -5450,11 +5455,14 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
boolean isRootAdmin =
_accountService.isRootAdmin(callerAccount.getId());
Pod destinationPod = getDestinationPod(podId, isRootAdmin);
Cluster destinationCluster = getDestinationCluster(clusterId,
isRootAdmin);
- Host destinationHost = getDestinationHost(hostId, isRootAdmin,
isExplicitHost);
+ HostVO destinationHost = getDestinationHost(hostId, isRootAdmin,
isExplicitHost);
DataCenterDeployment plan = null;
boolean deployOnGivenHost = false;
if (destinationHost != null) {
logger.debug("Destination Host to deploy the VM is specified,
specifying a deployment plan to deploy the VM");
+ _hostDao.loadHostTags(destinationHost);
+ validateStrictHostTagCheck(vm, destinationHost);
+
final ServiceOfferingVO offering =
serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId());
Pair<Boolean, Boolean> cpuCapabilityAndCapacity =
_capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(destinationHost, offering,
false);
if (!cpuCapabilityAndCapacity.first() ||
!cpuCapabilityAndCapacity.second()) {
@@ -5630,8 +5638,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
return destinationCluster;
}
- private Host getDestinationHost(Long hostId, boolean isRootAdmin, boolean
isExplicitHost) {
- Host destinationHost = null;
+ private HostVO getDestinationHost(Long hostId, boolean isRootAdmin,
boolean isExplicitHost) {
+ HostVO destinationHost = null;
if (hostId != null) {
if (isExplicitHost && !isRootAdmin) {
throw new PermissionDeniedException(
@@ -6673,6 +6681,31 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
+ 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();
+ 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()));
+ }
+ }
+
private DeployDestination checkVmMigrationDestination(VMInstanceVO vm,
Host srcHost, Host destinationHost) throws VirtualMachineMigrationException {
if (destinationHost == null) {
return null;
@@ -6698,6 +6731,10 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
throw new CloudRuntimeException("Cannot migrate VM, VM is DPDK
enabled VM but destination host is not DPDK enabled");
}
+ HostVO destinationHostVO = _hostDao.findById(destinationHost.getId());
+ _hostDao.loadHostTags(destinationHostVO);
+ validateStrictHostTagCheck(vm, destinationHostVO);
+
checkHostsDedication(vm, srcHost.getId(), destinationHost.getId());
// call to core process
@@ -6707,7 +6744,6 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
DeployDestination dest = new DeployDestination(dcVO, pod, cluster,
destinationHost);
// check max guest vm limit for the destinationHost
- HostVO destinationHostVO = _hostDao.findById(destinationHost.getId());
if (_capacityMgr.checkIfHostReachMaxGuestLimit(destinationHostVO)) {
if (logger.isDebugEnabled()) {
logger.debug("Host name: " + destinationHost.getName() + ",
hostId: " + destinationHost.getId()
@@ -8389,7 +8425,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {EnableDynamicallyScaleVm,
AllowDiskOfferingChangeDuringScaleVm, AllowUserExpungeRecoverVm,
VmIpFetchWaitInterval, VmIpFetchTrialMax,
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers,
AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
- KvmAdditionalConfigAllowList,
XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList,
DestroyRootVolumeOnVmDestruction};
+ KvmAdditionalConfigAllowList,
XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList,
DestroyRootVolumeOnVmDestruction,
+ EnforceStrictResourceLimitHostTagCheck, StrictHostTags};
}
@Override
diff --git
a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
index 73144ac2966..b1c653d3f92 100644
--- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
@@ -447,7 +447,7 @@ public class UnmanagedVMsManagerImpl implements
UnmanagedVMsManager {
return true;
}
hostDao.loadHostTags(host);
- return host.checkHostServiceOfferingAndTemplateTags(serviceOffering,
template);
+ return host.checkHostServiceOfferingAndTemplateTags(serviceOffering,
template, UserVmManager.getStrictHostTags());
}
private boolean storagePoolSupportsDiskOffering(StoragePool pool,
DiskOffering diskOffering) {
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index d117c460034..39128f21c87 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -1524,6 +1524,46 @@ public class UserVmManagerImplTest {
}
@Test
+ public void testValidateStrictHostTagCheckPass() {
+ ServiceOfferingVO serviceOffering =
Mockito.mock(ServiceOfferingVO.class);
+ VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+
+ VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+ HostVO destinationHostVO = Mockito.mock(HostVO.class);
+
+
Mockito.when(_serviceOfferingDao.findByIdIncludingRemoved(1L)).thenReturn(serviceOffering);
+
Mockito.when(templateDao.findByIdIncludingRemoved(2L)).thenReturn(template);
+
+ Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
+ Mockito.when(vm.getTemplateId()).thenReturn(2L);
+
+
Mockito.when(destinationHostVO.checkHostServiceOfferingAndTemplateTags(Mockito.any(ServiceOffering.class),
Mockito.any(VirtualMachineTemplate.class), Mockito.anySet())).thenReturn(true);
+
+ userVmManagerImpl.validateStrictHostTagCheck(vm, destinationHostVO);
+
+ Mockito.verify(
+ destinationHostVO, Mockito.times(1)
+
).checkHostServiceOfferingAndTemplateTags(Mockito.any(ServiceOffering.class),
Mockito.any(VirtualMachineTemplate.class), Mockito.anySet());
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void testValidateStrictHostTagCheckFail() {
+ ServiceOfferingVO serviceOffering =
Mockito.mock(ServiceOfferingVO.class);
+ VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+
+ VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+ HostVO destinationHostVO = Mockito.mock(HostVO.class);
+
+
Mockito.when(_serviceOfferingDao.findByIdIncludingRemoved(1L)).thenReturn(serviceOffering);
+
Mockito.when(templateDao.findByIdIncludingRemoved(2L)).thenReturn(template);
+
+ Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
+ Mockito.when(vm.getTemplateId()).thenReturn(2L);
+
+
Mockito.when(destinationHostVO.checkHostServiceOfferingAndTemplateTags(Mockito.any(ServiceOffering.class),
Mockito.any(VirtualMachineTemplate.class),
Mockito.anySet())).thenReturn(false);
+ userVmManagerImpl.validateStrictHostTagCheck(vm, destinationHostVO);
+ }
+
public void testGetRootVolumeSizeForVmRestore() {
VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
Mockito.when(template.getSize()).thenReturn(10L * GiB_TO_BYTES);
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..163869c8fae
--- /dev/null
+++ b/test/integration/smoke/test_vm_strict_host_tags.py
@@ -0,0 +1,552 @@
+# 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.
+
+# Import Local Modules
+from marvin.cloudstackAPI import (expungeVirtualMachine, 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
+
+ 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()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ if self.host_h1:
+ Host.update(self.apiclient, id=self.host_h1.id,
hosttags="h1,t1,v1")
+ self.updateConfiguration("vm.strict.host.tags", "")
+ self.updateConfiguration("vm.strict.resource.limit.host.tag.check",
"true")
+ self.updateConfiguration("resource.limit.host.tags", "")
+ self.cleanup = []
+
+ def tearDown(self):
+ self.cleanup_vm_for_template(self.template_t1.id)
+ self.cleanup_vm_for_template(self.template_t2.id)
+ super(TestVMDeploymentPlannerStrictTags, self).tearDown()
+
+ def cleanup_vm_for_template(self, templateid):
+ vm_list = VirtualMachine.list(self.apiclient, listall=True,
templateid=templateid)
+ if type(vm_list) is list:
+ for vm in vm_list:
+ self.expunge_vm(vm)
+
+ def expunge_vm(self, vm):
+ try:
+ cmd = expungeVirtualMachine.expungeVirtualMachineCmd()
+ cmd.id = vm.id
+ self.apiclient.expungeVirtualMachine(cmd)
+ except Exception as e:
+ self.debug("Failed to expunge VM: %s" % e)
+
+ @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 vm " 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
+
+ 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()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ if self.host_h1:
+ Host.update(self.apiclient, id=self.host_h1.id,
hosttags="h1,t1,v1,h2,t2,v2")
+ self.updateConfiguration("vm.strict.host.tags", "")
+ self.updateConfiguration("vm.strict.resource.limit.host.tag.check",
"true")
+ self.updateConfiguration("resource.limit.host.tags", "")
+ self.cleanup = []
+
+ def tearDown(self):
+ self.cleanup_vm_for_template(self.template_t1.id)
+ self.cleanup_vm_for_template(self.template_t2.id)
+ super(TestScaleVMStrictTags, self).tearDown()
+
+ def cleanup_vm_for_template(self, templateid):
+ vm_list = VirtualMachine.list(self.apiclient, listall=True,
templateid=templateid)
+ if type(vm_list) is list:
+ for vm in vm_list:
+ self.expunge_vm(vm)
+
+ def expunge_vm(self, vm):
+ try:
+ cmd = expungeVirtualMachine.expungeVirtualMachineCmd()
+ cmd.id = vm.id
+ self.apiclient.expungeVirtualMachine(cmd)
+ except Exception as e:
+ self.debug("Failed to expunge VM: %s" % e)
+
+ @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 vm " 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
+
+ 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()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ if self.host_h1:
+ Host.update(self.apiclient, id=self.host_h1.id,
hosttags="h1,t1,v1")
+ self.updateConfiguration("vm.strict.host.tags", "")
+ self.updateConfiguration("vm.strict.resource.limit.host.tag.check",
"true")
+ self.updateConfiguration("resource.limit.host.tags", "")
+ self.cleanup = []
+
+ def tearDown(self):
+ self.cleanup_vm_for_template(self.template_t1.id)
+ self.cleanup_vm_for_template(self.template_t2.id)
+ super(TestRestoreVMStrictTags, self).tearDown()
+
+ def cleanup_vm_for_template(self, templateid):
+ vm_list = VirtualMachine.list(self.apiclient, listall=True,
templateid=templateid)
+ if type(vm_list) is list:
+ for vm in vm_list:
+ self.expunge_vm(vm)
+
+ def expunge_vm(self, vm):
+ try:
+ cmd = expungeVirtualMachine.expungeVirtualMachineCmd()
+ cmd.id = vm.id
+ self.apiclient.expungeVirtualMachine(cmd)
+ except Exception as e:
+ self.debug("Failed to expunge VM: %s" % e)
+
+ @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 restored")
+
+ @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 vm " 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 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()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ if self.host_h1:
+ Host.update(self.apiclient, id=self.host_h1.id,
hosttags="h1,t1,v1")
+ self.updateConfiguration("vm.strict.host.tags", "")
+ self.updateConfiguration("vm.strict.resource.limit.host.tag.check",
"true")
+ self.updateConfiguration("resource.limit.host.tags", "")
+ self.cleanup = []
+
+ def tearDown(self):
+ self.cleanup_vm_for_template(self.template_t1.id)
+ self.cleanup_vm_for_template(self.template_t2.id)
+ super(TestMigrateVMStrictTags, self).tearDown()
+
+ def cleanup_vm_for_template(self, templateid):
+ vm_list = VirtualMachine.list(self.apiclient, listall=True,
templateid=templateid)
+ if type(vm_list) is list:
+ for vm in vm_list:
+ self.expunge_vm(vm)
+
+ def expunge_vm(self, vm):
+ try:
+ cmd = expungeVirtualMachine.expungeVirtualMachineCmd()
+ cmd.id = vm.id
+ self.apiclient.expungeVirtualMachine(cmd)
+ except Exception as e:
+ self.debug("Failed to expunge VM: %s" % e)
+
+ @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 6acf6a8ad63..9c69d33a4ca 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -782,6 +782,8 @@ class VirtualMachine:
cmd.virtualmachineid = self.id
if templateid:
cmd.templateid = templateid
+ if expunge:
+ cmd.expunge = expunge
if diskofferingid:
cmd.diskofferingid = diskofferingid
if rootdisksize:
@@ -794,7 +796,6 @@ class VirtualMachine:
'key': key,
'value': value
})
-
return apiclient.restoreVirtualMachine(cmd)
def get_ssh_client(