Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 34 additions & 12 deletions engine/schema/src/main/java/com/cloud/host/HostVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,6 +45,7 @@
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;

Expand Down Expand Up @@ -768,27 +769,48 @@ public void setUuid(String uuid) {
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;
Expand Down
53 changes: 38 additions & 15 deletions engine/schema/src/test/java/com/cloud/host/HostVOTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
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;
Expand All @@ -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
Expand All @@ -52,14 +56,14 @@ public void testNoTag() {

@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));
}
Expand Down Expand Up @@ -87,40 +91,59 @@ public void checkHostServiceOfferingTagsTestRuleTagWithNullServiceTag() {

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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;
Expand Down Expand Up @@ -282,7 +283,7 @@ protected void avoidOtherClustersForDeploymentIfMigrationDisabled(VirtualMachine
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;
}
Expand Down Expand Up @@ -778,7 +779,7 @@ protected boolean checkVmProfileAndHost(final VirtualMachineProfile vmProfile, f
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;
}
Expand Down
33 changes: 33 additions & 0 deletions server/src/main/java/com/cloud/vm/UserVmManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -38,6 +41,8 @@
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;

import static com.cloud.user.ResourceLimitService.ResourceLimitHostTags;

/**
*
*
Expand All @@ -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";
Expand Down Expand Up @@ -127,6 +148,18 @@ UserVm updateVirtualMachine(long id, String displayName, String group, Boolean h

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);
Expand Down
Loading