mirror of https://github.com/apache/cloudstack.git
Fix listing service offerings with different host tags (#12919)
This commit is contained in:
parent
1ff9eec997
commit
b5858029bb
|
|
@ -45,4 +45,9 @@ public interface HostTagsDao extends GenericDao<HostTagVO, Long> {
|
|||
HostTagResponse newHostTagResponse(HostTagVO hostTag);
|
||||
|
||||
List<HostTagVO> searchByIds(Long... hostTagIds);
|
||||
|
||||
/**
|
||||
* List all host tags defined on hosts within a cluster
|
||||
*/
|
||||
List<String> listByClusterId(Long clusterId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.apache.cloudstack.api.response.HostTagResponse;
|
|||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -43,9 +44,12 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
|||
private final SearchBuilder<HostTagVO> stSearch;
|
||||
private final SearchBuilder<HostTagVO> tagIdsearch;
|
||||
private final SearchBuilder<HostTagVO> ImplicitTagsSearch;
|
||||
private final GenericSearchBuilder<HostTagVO, String> tagSearch;
|
||||
|
||||
@Inject
|
||||
private ConfigurationDao _configDao;
|
||||
@Inject
|
||||
private HostDao hostDao;
|
||||
|
||||
public HostTagsDaoImpl() {
|
||||
HostSearch = createSearchBuilder();
|
||||
|
|
@ -72,6 +76,11 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
|||
ImplicitTagsSearch.and("hostId", ImplicitTagsSearch.entity().getHostId(), SearchCriteria.Op.EQ);
|
||||
ImplicitTagsSearch.and("isImplicit", ImplicitTagsSearch.entity().getIsImplicit(), SearchCriteria.Op.EQ);
|
||||
ImplicitTagsSearch.done();
|
||||
|
||||
tagSearch = createSearchBuilder(String.class);
|
||||
tagSearch.selectFields(tagSearch.entity().getTag());
|
||||
tagSearch.and("hostIdIN", tagSearch.entity().getHostId(), SearchCriteria.Op.IN);
|
||||
tagSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -235,4 +244,15 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
|||
|
||||
return tagList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listByClusterId(Long clusterId) {
|
||||
List<Long> hostIds = hostDao.listIdsByClusterId(clusterId);
|
||||
if (CollectionUtils.isEmpty(hostIds)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
SearchCriteria<String> sc = tagSearch.create();
|
||||
sc.setParameters("hostIdIN", hostIds.toArray());
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import com.cloud.server.ManagementService;
|
|||
import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
|
||||
import com.cloud.cluster.ManagementServerHostPeerJoinVO;
|
||||
|
||||
import com.cloud.vm.UserVmManager;
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
|
||||
import org.apache.cloudstack.acl.SecurityChecker;
|
||||
|
|
@ -4330,6 +4331,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
List<String> hostTags = new ArrayList<>();
|
||||
if (currentVmOffering != null) {
|
||||
hostTags.addAll(com.cloud.utils.StringUtils.csvTagsToList(currentVmOffering.getHostTag()));
|
||||
if (UserVmManager.AllowDifferentHostTagsOfferingsForVmScale.value()) {
|
||||
addVmCurrentClusterHostTags(vmInstance, hostTags);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hostTags.isEmpty()) {
|
||||
|
|
@ -4341,7 +4345,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
flag = false;
|
||||
serviceOfferingSearch.op("hostTag" + tag, serviceOfferingSearch.entity().getHostTag(), Op.FIND_IN_SET);
|
||||
} else {
|
||||
serviceOfferingSearch.and("hostTag" + tag, serviceOfferingSearch.entity().getHostTag(), Op.FIND_IN_SET);
|
||||
serviceOfferingSearch.or("hostTag" + tag, serviceOfferingSearch.entity().getHostTag(), Op.FIND_IN_SET);
|
||||
}
|
||||
}
|
||||
serviceOfferingSearch.cp().cp();
|
||||
|
|
@ -4486,6 +4490,30 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
return new Pair<>(offeringIds, count);
|
||||
}
|
||||
|
||||
protected void addVmCurrentClusterHostTags(VMInstanceVO vmInstance, List<String> hostTags) {
|
||||
if (vmInstance == null) {
|
||||
return;
|
||||
}
|
||||
Long hostId = vmInstance.getHostId() == null ? vmInstance.getLastHostId() : vmInstance.getHostId();
|
||||
if (hostId == null) {
|
||||
return;
|
||||
}
|
||||
HostVO host = hostDao.findById(hostId);
|
||||
if (host == null) {
|
||||
logger.warn("Unable to find host with id " + hostId);
|
||||
return;
|
||||
}
|
||||
List<String> clusterTags = _hostTagDao.listByClusterId(host.getClusterId());
|
||||
if (CollectionUtils.isEmpty(clusterTags)) {
|
||||
logger.debug("No host tags defined for hosts in the cluster " + host.getClusterId());
|
||||
return;
|
||||
}
|
||||
Set<String> existingTagsSet = new HashSet<>(hostTags);
|
||||
clusterTags.stream()
|
||||
.filter(tag -> !existingTagsSet.contains(tag))
|
||||
.forEach(hostTags::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResponse<ZoneResponse> listDataCenters(ListZonesCmd cmd) {
|
||||
Pair<List<DataCenterJoinVO>, Integer> result = listDataCentersInternal(cmd);
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ public interface UserVmManager extends UserVmService {
|
|||
"Comma separated list of allowed additional VM settings if VM instance settings are read from OVA.",
|
||||
true, ConfigKey.Scope.Zone, null, null, null, null, null, ConfigKey.Kind.CSV, null);
|
||||
|
||||
ConfigKey<Boolean> AllowDifferentHostTagsOfferingsForVmScale = new ConfigKey<>("Advanced", Boolean.class, "allow.different.host.tags.offerings.for.vm.scale", "false",
|
||||
"Enables/Disable allowing to change a VM offering to offerings with different host tags", true);
|
||||
|
||||
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
|
||||
|
||||
public static final String CKS_NODE = "cksnode";
|
||||
|
|
|
|||
|
|
@ -9412,7 +9412,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
|
||||
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction,
|
||||
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope,
|
||||
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva};
|
||||
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package com.cloud.api.query;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
|
@ -33,6 +34,7 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.cloud.host.dao.HostTagsDao;
|
||||
import org.apache.cloudstack.acl.SecurityChecker;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ResponseObject;
|
||||
|
|
@ -156,6 +158,9 @@ public class QueryManagerImplTest {
|
|||
@Mock
|
||||
HostDao hostDao;
|
||||
|
||||
@Mock
|
||||
HostTagsDao hostTagsDao;
|
||||
|
||||
@Mock
|
||||
ClusterDao clusterDao;
|
||||
|
||||
|
|
@ -622,4 +627,39 @@ public class QueryManagerImplTest {
|
|||
verify(host1).setExtensionId("a");
|
||||
verify(host2).setExtensionId("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddVmCurrentClusterHostTags() {
|
||||
String tag1 = "tag1";
|
||||
String tag2 = "tag2";
|
||||
VMInstanceVO vmInstance = mock(VMInstanceVO.class);
|
||||
HostVO host = mock(HostVO.class);
|
||||
when(vmInstance.getHostId()).thenReturn(null);
|
||||
when(vmInstance.getLastHostId()).thenReturn(1L);
|
||||
when(hostDao.findById(1L)).thenReturn(host);
|
||||
when(host.getClusterId()).thenReturn(1L);
|
||||
when(hostTagsDao.listByClusterId(1L)).thenReturn(Arrays.asList(tag1, tag2));
|
||||
|
||||
List<String> hostTags = new ArrayList<>(Collections.singleton(tag1));
|
||||
queryManagerImplSpy.addVmCurrentClusterHostTags(vmInstance, hostTags);
|
||||
assertEquals(2, hostTags.size());
|
||||
assertTrue(hostTags.contains(tag2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddVmCurrentClusterHostTagsEmptyHostTagsInCluster() {
|
||||
String tag1 = "tag1";
|
||||
VMInstanceVO vmInstance = mock(VMInstanceVO.class);
|
||||
HostVO host = mock(HostVO.class);
|
||||
when(vmInstance.getHostId()).thenReturn(null);
|
||||
when(vmInstance.getLastHostId()).thenReturn(1L);
|
||||
when(hostDao.findById(1L)).thenReturn(host);
|
||||
when(host.getClusterId()).thenReturn(1L);
|
||||
when(hostTagsDao.listByClusterId(1L)).thenReturn(null);
|
||||
|
||||
List<String> hostTags = new ArrayList<>(Collections.singleton(tag1));
|
||||
queryManagerImplSpy.addVmCurrentClusterHostTags(vmInstance, hostTags);
|
||||
assertEquals(1, hostTags.size());
|
||||
assertTrue(hostTags.contains(tag1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@
|
|||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'cpu'"><appstore-outlined /> {{ $t('label.cpu') }}</template>
|
||||
<template v-if="column.key === 'ram'"><bulb-outlined /> {{ $t('label.memory') }}</template>
|
||||
<template v-if="column.key === 'hosttags'"><tag-outlined /> {{ $t('label.hosttags') }}</template>
|
||||
<template v-if="column.key === 'storagetags'"><tag-outlined /> {{ $t('label.storagetags') }}</template>
|
||||
<template v-if="column.key === 'gpu'"><font-awesome-icon
|
||||
:icon="['fa-solid', 'fa-microchip']"
|
||||
class="anticon"
|
||||
|
|
@ -197,6 +199,22 @@ export default {
|
|||
})
|
||||
}
|
||||
|
||||
if (this.computeItems.some(item => item.hosttags !== undefined && item.hosttags !== null)) {
|
||||
baseColumns.push({
|
||||
key: 'hosttags',
|
||||
dataIndex: 'hosttags',
|
||||
width: '30%'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.computeItems.some(item => item.storagetags !== undefined && item.storagetags !== null)) {
|
||||
baseColumns.push({
|
||||
key: 'storagetags',
|
||||
dataIndex: 'storagetags',
|
||||
width: '30%'
|
||||
})
|
||||
}
|
||||
|
||||
return baseColumns
|
||||
},
|
||||
tableSource () {
|
||||
|
|
@ -256,6 +274,7 @@ export default {
|
|||
}
|
||||
gpuValue = gpuCount + ' x ' + gpuType
|
||||
}
|
||||
|
||||
return {
|
||||
key: item.id,
|
||||
name: item.name,
|
||||
|
|
@ -267,7 +286,9 @@ export default {
|
|||
gpuCount: gpuCount,
|
||||
gpuType: gpuType,
|
||||
gpu: gpuValue,
|
||||
gpuDetails: this.getGpuDetails(item)
|
||||
gpuDetails: this.getGpuDetails(item),
|
||||
hosttags: item.hosttags !== undefined && item.hosttags !== null ? item.hosttags : undefined,
|
||||
storagetags: item.storagetags !== undefined && item.storagetags !== null ? item.storagetags : undefined
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue