mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
New feature: Implicit host tags (#8929)
* Merge two HostTagVO and HostTagDaoImpl * Implicit host tags * PR8929: add since * Update variable names * Update 8929: add unit test in LibvirtComputingResourceTest * Update 8929: add explicithosttags in response * Update 8929 UI: Update explicit host tags * Update 8929: remove host tags and change labels on UI * Update 8929: update host_view to use explicit_host_tags.is_tag_a_rule * Update: ui polish for host tags * Update 8929: fix UI error if no host tags
This commit is contained in:
parent
f1c3d2c4be
commit
5433e775e5
@ -430,3 +430,6 @@ iscsi.session.cleanup.enabled=false
|
||||
# If set to "true", the agent will register for libvirt domain events, allowing for immediate updates on crashed or
|
||||
# unexpectedly stopped. Experimental, requires agent restart.
|
||||
# libvirt.events.enabled=false
|
||||
|
||||
# Implicit host tags managed by agent.properties
|
||||
# host.tags=
|
||||
|
||||
@ -803,6 +803,13 @@ public class AgentProperties{
|
||||
*/
|
||||
public static final Property<String> KEYSTORE_PASSPHRASE = new Property<>(KeyStoreUtils.KS_PASSPHRASE_PROPERTY, null, String.class);
|
||||
|
||||
/**
|
||||
* Implicit host tags
|
||||
* Data type: String.<br>
|
||||
* Default value: <code>null</code>
|
||||
*/
|
||||
public static final Property<String> HOST_TAGS = new Property<>("host.tags", null, String.class);
|
||||
|
||||
public static class Property <T>{
|
||||
private String name;
|
||||
private T defaultValue;
|
||||
|
||||
@ -265,6 +265,7 @@ public class ApiConstants {
|
||||
public static final String IS_EDGE = "isedge";
|
||||
public static final String IS_EXTRACTABLE = "isextractable";
|
||||
public static final String IS_FEATURED = "isfeatured";
|
||||
public static final String IS_IMPLICIT = "isimplicit";
|
||||
public static final String IS_PORTABLE = "isportable";
|
||||
public static final String IS_PUBLIC = "ispublic";
|
||||
public static final String IS_PERSISTENT = "ispersistent";
|
||||
|
||||
@ -208,6 +208,14 @@ public class HostForMigrationResponse extends BaseResponse {
|
||||
@Param(description = "comma-separated list of tags for the host")
|
||||
private String hostTags;
|
||||
|
||||
@SerializedName("explicithosttags")
|
||||
@Param(description = "comma-separated list of explicit host tags for the host", since = "4.20.0")
|
||||
private String explicitHostTags;
|
||||
|
||||
@SerializedName("implicithosttags")
|
||||
@Param(description = "comma-separated list of implicit host tags for the host", since = "4.20.0")
|
||||
private String implicitHostTags;
|
||||
|
||||
@SerializedName("hasenoughcapacity")
|
||||
@Param(description = "true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise")
|
||||
private Boolean hasEnoughCapacity;
|
||||
@ -414,6 +422,14 @@ public class HostForMigrationResponse extends BaseResponse {
|
||||
this.hostTags = hostTags;
|
||||
}
|
||||
|
||||
public void setExplicitHostTags(String explicitHostTags) {
|
||||
this.explicitHostTags = explicitHostTags;
|
||||
}
|
||||
|
||||
public void setImplicitHostTags(String implicitHostTags) {
|
||||
this.implicitHostTags = implicitHostTags;
|
||||
}
|
||||
|
||||
public void setHasEnoughCapacity(Boolean hasEnoughCapacity) {
|
||||
this.hasEnoughCapacity = hasEnoughCapacity;
|
||||
}
|
||||
|
||||
@ -221,6 +221,14 @@ public class HostResponse extends BaseResponseWithAnnotations {
|
||||
@Param(description = "comma-separated list of tags for the host")
|
||||
private String hostTags;
|
||||
|
||||
@SerializedName("explicithosttags")
|
||||
@Param(description = "comma-separated list of explicit host tags for the host", since = "4.20.0")
|
||||
private String explicitHostTags;
|
||||
|
||||
@SerializedName("implicithosttags")
|
||||
@Param(description = "comma-separated list of implicit host tags for the host", since = "4.20.0")
|
||||
private String implicitHostTags;
|
||||
|
||||
@SerializedName(ApiConstants.IS_TAG_A_RULE)
|
||||
@Param(description = ApiConstants.PARAMETER_DESCRIPTION_IS_TAG_A_RULE)
|
||||
private Boolean isTagARule;
|
||||
@ -458,6 +466,22 @@ public class HostResponse extends BaseResponseWithAnnotations {
|
||||
this.hostTags = hostTags;
|
||||
}
|
||||
|
||||
public String getExplicitHostTags() {
|
||||
return explicitHostTags;
|
||||
}
|
||||
|
||||
public void setExplicitHostTags(String explicitHostTags) {
|
||||
this.explicitHostTags = explicitHostTags;
|
||||
}
|
||||
|
||||
public String getImplicitHostTags() {
|
||||
return implicitHostTags;
|
||||
}
|
||||
|
||||
public void setImplicitHostTags(String implicitHostTags) {
|
||||
this.implicitHostTags = implicitHostTags;
|
||||
}
|
||||
|
||||
public void setHasEnoughCapacity(Boolean hasEnoughCapacity) {
|
||||
this.hasEnoughCapacity = hasEnoughCapacity;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package org.apache.cloudstack.api.response;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.cloud.serializer.Param;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
|
||||
public class HostTagResponse extends BaseResponse {
|
||||
@ -34,6 +35,10 @@ public class HostTagResponse extends BaseResponse {
|
||||
@Param(description = "the name of the host tag")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.IS_IMPLICIT)
|
||||
@Param(description = "true if the host tag is implicit", since = "4.20.0")
|
||||
private boolean isImplicit;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
@ -57,4 +62,12 @@ public class HostTagResponse extends BaseResponse {
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isImplicit() {
|
||||
return isImplicit;
|
||||
}
|
||||
|
||||
public void setImplicit(boolean implicit) {
|
||||
isImplicit = implicit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +174,10 @@ public class StartupRoutingCommand extends StartupCommand {
|
||||
this.hostTags.add(hostTag);
|
||||
}
|
||||
|
||||
public void setHostTags(List<String> hostTags) {
|
||||
this.hostTags = hostTags;
|
||||
}
|
||||
|
||||
public HashMap<String, HashMap<String, VgpuTypesInfo>> getGpuGroupDetails() {
|
||||
return groupDetails;
|
||||
}
|
||||
|
||||
@ -40,6 +40,9 @@ public class HostTagVO implements InternalIdentity {
|
||||
@Column(name = "tag")
|
||||
private String tag;
|
||||
|
||||
@Column(name = "is_implicit")
|
||||
private boolean isImplicit = false;
|
||||
|
||||
@Column(name = "is_tag_a_rule")
|
||||
private boolean isTagARule;
|
||||
|
||||
@ -74,6 +77,13 @@ public class HostTagVO implements InternalIdentity {
|
||||
return isTagARule;
|
||||
}
|
||||
|
||||
public void setIsImplicit(boolean isImplicit) {
|
||||
this.isImplicit = isImplicit;
|
||||
}
|
||||
|
||||
public boolean getIsImplicit() {
|
||||
return isImplicit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
|
||||
@ -20,6 +20,7 @@ import java.util.List;
|
||||
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.api.response.HostTagResponse;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
||||
public interface HostTagsDao extends GenericDao<HostTagVO, Long> {
|
||||
@ -35,6 +36,13 @@ public interface HostTagsDao extends GenericDao<HostTagVO, Long> {
|
||||
|
||||
void deleteTags(long hostId);
|
||||
|
||||
boolean updateImplicitTags(long hostId, List<String> hostTags);
|
||||
|
||||
List<HostTagVO> getExplicitHostTags(long hostId);
|
||||
|
||||
List<HostTagVO> findHostRuleTags();
|
||||
|
||||
HostTagResponse newHostTagResponse(HostTagVO hostTag);
|
||||
|
||||
List<HostTagVO> searchByIds(Long... hostTagIds);
|
||||
}
|
||||
|
||||
@ -16,10 +16,14 @@
|
||||
// under the License.
|
||||
package com.cloud.host.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.host.HostTagVO;
|
||||
@ -30,14 +34,23 @@ import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.SearchCriteria.Func;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Component
|
||||
public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements HostTagsDao, Configurable {
|
||||
protected final SearchBuilder<HostTagVO> HostSearch;
|
||||
protected final GenericSearchBuilder<HostTagVO, String> DistinctImplictTagsSearch;
|
||||
private final SearchBuilder<HostTagVO> stSearch;
|
||||
private final SearchBuilder<HostTagVO> tagIdsearch;
|
||||
private final SearchBuilder<HostTagVO> ImplicitTagsSearch;
|
||||
|
||||
@Inject
|
||||
private ConfigurationDao _configDao;
|
||||
|
||||
public HostTagsDaoImpl() {
|
||||
HostSearch = createSearchBuilder();
|
||||
HostSearch.and("hostId", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ);
|
||||
HostSearch.and("isImplicit", HostSearch.entity().getIsImplicit(), SearchCriteria.Op.EQ);
|
||||
HostSearch.and("isTagARule", HostSearch.entity().getIsTagARule(), SearchCriteria.Op.EQ);
|
||||
HostSearch.done();
|
||||
|
||||
@ -46,6 +59,19 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
||||
DistinctImplictTagsSearch.and("hostIds", DistinctImplictTagsSearch.entity().getHostId(), SearchCriteria.Op.IN);
|
||||
DistinctImplictTagsSearch.and("implicitTags", DistinctImplictTagsSearch.entity().getTag(), SearchCriteria.Op.IN);
|
||||
DistinctImplictTagsSearch.done();
|
||||
|
||||
stSearch = createSearchBuilder();
|
||||
stSearch.and("idIN", stSearch.entity().getId(), SearchCriteria.Op.IN);
|
||||
stSearch.done();
|
||||
|
||||
tagIdsearch = createSearchBuilder();
|
||||
tagIdsearch.and("id", tagIdsearch.entity().getId(), SearchCriteria.Op.EQ);
|
||||
tagIdsearch.done();
|
||||
|
||||
ImplicitTagsSearch = createSearchBuilder();
|
||||
ImplicitTagsSearch.and("hostId", ImplicitTagsSearch.entity().getHostId(), SearchCriteria.Op.EQ);
|
||||
ImplicitTagsSearch.and("isImplicit", ImplicitTagsSearch.entity().getIsImplicit(), SearchCriteria.Op.EQ);
|
||||
ImplicitTagsSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,6 +100,36 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateImplicitTags(long hostId, List<String> hostTags) {
|
||||
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
SearchCriteria<HostTagVO> sc = ImplicitTagsSearch.create();
|
||||
sc.setParameters("hostId", hostId);
|
||||
sc.setParameters("isImplicit", true);
|
||||
boolean expunged = expunge(sc) > 0;
|
||||
boolean persisted = false;
|
||||
for (String tag : hostTags) {
|
||||
if (StringUtils.isNotBlank(tag)) {
|
||||
HostTagVO vo = new HostTagVO(hostId, tag.trim());
|
||||
vo.setIsImplicit(true);
|
||||
persist(vo);
|
||||
persisted = true;
|
||||
}
|
||||
}
|
||||
txn.commit();
|
||||
return expunged || persisted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostTagVO> getExplicitHostTags(long hostId) {
|
||||
SearchCriteria<HostTagVO> sc = ImplicitTagsSearch.create();
|
||||
sc.setParameters("hostId", hostId);
|
||||
sc.setParameters("isImplicit", false);
|
||||
|
||||
return search(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostTagVO> findHostRuleTags() {
|
||||
SearchCriteria<HostTagVO> sc = HostSearch.create();
|
||||
@ -89,6 +145,7 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
||||
txn.start();
|
||||
SearchCriteria<HostTagVO> sc = HostSearch.create();
|
||||
sc.setParameters("hostId", hostId);
|
||||
sc.setParameters("isImplicit", false);
|
||||
expunge(sc);
|
||||
|
||||
for (String tag : hostTags) {
|
||||
@ -110,4 +167,72 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
|
||||
public String getConfigComponentName() {
|
||||
return HostTagsDaoImpl.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostTagResponse newHostTagResponse(HostTagVO tag) {
|
||||
HostTagResponse tagResponse = new HostTagResponse();
|
||||
|
||||
tagResponse.setName(tag.getTag());
|
||||
tagResponse.setHostId(tag.getHostId());
|
||||
tagResponse.setImplicit(tag.getIsImplicit());
|
||||
|
||||
tagResponse.setObjectName("hosttag");
|
||||
|
||||
return tagResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostTagVO> searchByIds(Long... tagIds) {
|
||||
String batchCfg = _configDao.getValue("detail.batch.query.size");
|
||||
|
||||
final int detailsBatchSize = batchCfg != null ? Integer.parseInt(batchCfg) : 2000;
|
||||
|
||||
// query details by batches
|
||||
List<HostTagVO> tagList = new ArrayList<>();
|
||||
int curr_index = 0;
|
||||
|
||||
if (tagIds.length > detailsBatchSize) {
|
||||
while ((curr_index + detailsBatchSize) <= tagIds.length) {
|
||||
Long[] ids = new Long[detailsBatchSize];
|
||||
|
||||
for (int k = 0, j = curr_index; j < curr_index + detailsBatchSize; j++, k++) {
|
||||
ids[k] = tagIds[j];
|
||||
}
|
||||
|
||||
SearchCriteria<HostTagVO> sc = stSearch.create();
|
||||
|
||||
sc.setParameters("idIN", (Object[])ids);
|
||||
|
||||
List<HostTagVO> vms = searchIncludingRemoved(sc, null, null, false);
|
||||
|
||||
if (vms != null) {
|
||||
tagList.addAll(vms);
|
||||
}
|
||||
|
||||
curr_index += detailsBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_index < tagIds.length) {
|
||||
int batch_size = (tagIds.length - curr_index);
|
||||
// set the ids value
|
||||
Long[] ids = new Long[batch_size];
|
||||
|
||||
for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
|
||||
ids[k] = tagIds[j];
|
||||
}
|
||||
|
||||
SearchCriteria<HostTagVO> sc = stSearch.create();
|
||||
|
||||
sc.setParameters("idIN", (Object[])ids);
|
||||
|
||||
List<HostTagVO> tags = searchIncludingRemoved(sc, null, null, false);
|
||||
|
||||
if (tags != null) {
|
||||
tagList.addAll(tags);
|
||||
}
|
||||
}
|
||||
|
||||
return tagList;
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +187,6 @@
|
||||
<bean id="storageNetworkIpAddressDaoImpl" class="com.cloud.dc.dao.StorageNetworkIpAddressDaoImpl" />
|
||||
<bean id="storageNetworkIpRangeDaoImpl" class="com.cloud.dc.dao.StorageNetworkIpRangeDaoImpl" />
|
||||
<bean id="storagePoolJoinDaoImpl" class="com.cloud.api.query.dao.StoragePoolJoinDaoImpl" />
|
||||
<bean id="hostTagDaoImpl" class="com.cloud.api.query.dao.HostTagDaoImpl" />
|
||||
<bean id="storagePoolWorkDaoImpl" class="com.cloud.storage.dao.StoragePoolWorkDaoImpl" />
|
||||
<bean id="uploadDaoImpl" class="com.cloud.storage.dao.UploadDaoImpl" />
|
||||
<bean id="usageDaoImpl" class="com.cloud.usage.dao.UsageDaoImpl" />
|
||||
|
||||
@ -79,3 +79,6 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`(
|
||||
PRIMARY KEY (`account_id`, `email_template_id`),
|
||||
CONSTRAINT `FK_quota_email_configuration_account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`quota_account`(`account_id`),
|
||||
CONSTRAINT `FK_quota_email_configuration_email_template_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`));
|
||||
|
||||
-- Add `is_implicit` column to `host_tags` table
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_tags', 'is_implicit', 'int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT "If host tag is implicit or explicit" ');
|
||||
|
||||
@ -53,7 +53,9 @@ SELECT
|
||||
host_pod_ref.uuid pod_uuid,
|
||||
host_pod_ref.name pod_name,
|
||||
GROUP_CONCAT(DISTINCT(host_tags.tag)) AS tag,
|
||||
`host_tags`.`is_tag_a_rule` AS `is_tag_a_rule`,
|
||||
GROUP_CONCAT(DISTINCT(explicit_host_tags.tag)) AS explicit_tag,
|
||||
GROUP_CONCAT(DISTINCT(implicit_host_tags.tag)) AS implicit_tag,
|
||||
`explicit_host_tags`.`is_tag_a_rule` AS `is_tag_a_rule`,
|
||||
guest_os_category.id guest_os_category_id,
|
||||
guest_os_category.uuid guest_os_category_uuid,
|
||||
guest_os_category.name guest_os_category_name,
|
||||
@ -89,6 +91,10 @@ FROM
|
||||
LEFT JOIN
|
||||
`cloud`.`host_tags` ON host_tags.host_id = host.id
|
||||
LEFT JOIN
|
||||
`cloud`.`host_tags` AS explicit_host_tags ON explicit_host_tags.host_id = host.id AND explicit_host_tags.is_implicit = 0
|
||||
LEFT JOIN
|
||||
`cloud`.`host_tags` AS implicit_host_tags ON implicit_host_tags.host_id = host.id AND implicit_host_tags.is_implicit = 1
|
||||
LEFT JOIN
|
||||
`cloud`.`op_host_capacity` mem_caps ON host.id = mem_caps.host_id
|
||||
AND mem_caps.capacity_type = 0
|
||||
LEFT JOIN
|
||||
|
||||
@ -3646,6 +3646,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
cmd.setGatewayIpAddress(localGateway);
|
||||
cmd.setIqn(getIqn());
|
||||
cmd.getHostDetails().put(HOST_VOLUME_ENCRYPTION, String.valueOf(hostSupportsVolumeEncryption()));
|
||||
cmd.setHostTags(getHostTags());
|
||||
HealthCheckResult healthCheckResult = getHostHealthCheckResult();
|
||||
if (healthCheckResult != HealthCheckResult.IGNORE) {
|
||||
cmd.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS);
|
||||
@ -3674,6 +3675,19 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
||||
return startupCommandsArray;
|
||||
}
|
||||
|
||||
protected List<String> getHostTags() {
|
||||
List<String> hostTagsList = new ArrayList<>();
|
||||
String hostTags = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_TAGS);
|
||||
if (StringUtils.isNotBlank(hostTags)) {
|
||||
for (String hostTag : hostTags.split(",")) {
|
||||
if (!hostTagsList.contains(hostTag.trim())) {
|
||||
hostTagsList.add(hostTag.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return hostTagsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and sets the host CPU max capacity according to the cgroup version of the host.
|
||||
* <ul>
|
||||
|
||||
@ -6295,4 +6295,40 @@ public class LibvirtComputingResourceTest {
|
||||
Assert.assertEquals(expectedShares, libvirtComputingResourceSpy.getHostCpuMaxCapacity());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHostTags() throws ConfigurationException {
|
||||
try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
|
||||
Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.HOST_TAGS)))
|
||||
.thenReturn("aa,bb,cc,dd");
|
||||
|
||||
List<String> hostTagsList = libvirtComputingResourceSpy.getHostTags();
|
||||
Assert.assertEquals(4, hostTagsList.size());
|
||||
Assert.assertEquals("aa,bb,cc,dd", StringUtils.join(hostTagsList, ","));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHostTagsWithSpace() throws ConfigurationException {
|
||||
try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
|
||||
Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.HOST_TAGS)))
|
||||
.thenReturn(" aa, bb , cc , dd ");
|
||||
|
||||
List<String> hostTagsList = libvirtComputingResourceSpy.getHostTags();
|
||||
Assert.assertEquals(4, hostTagsList.size());
|
||||
Assert.assertEquals("aa,bb,cc,dd", StringUtils.join(hostTagsList, ","));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHostTagsWithEmptyPropertyValue() throws ConfigurationException {
|
||||
try (MockedStatic<AgentPropertiesFileHandler> ignored = Mockito.mockStatic(AgentPropertiesFileHandler.class)) {
|
||||
Mockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.HOST_TAGS)))
|
||||
.thenReturn(" ");
|
||||
|
||||
List<String> hostTagsList = libvirtComputingResourceSpy.getHostTags();
|
||||
Assert.assertEquals(0, hostTagsList.size());
|
||||
Assert.assertEquals("", StringUtils.join(hostTagsList, ","));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +103,6 @@ import com.cloud.api.query.dao.DiskOfferingJoinDao;
|
||||
import com.cloud.api.query.dao.DomainJoinDao;
|
||||
import com.cloud.api.query.dao.DomainRouterJoinDao;
|
||||
import com.cloud.api.query.dao.HostJoinDao;
|
||||
import com.cloud.api.query.dao.HostTagDao;
|
||||
import com.cloud.api.query.dao.ImageStoreJoinDao;
|
||||
import com.cloud.api.query.dao.InstanceGroupJoinDao;
|
||||
import com.cloud.api.query.dao.NetworkOfferingJoinDao;
|
||||
@ -129,7 +128,6 @@ import com.cloud.api.query.vo.DomainJoinVO;
|
||||
import com.cloud.api.query.vo.DomainRouterJoinVO;
|
||||
import com.cloud.api.query.vo.EventJoinVO;
|
||||
import com.cloud.api.query.vo.HostJoinVO;
|
||||
import com.cloud.api.query.vo.HostTagVO;
|
||||
import com.cloud.api.query.vo.ImageStoreJoinVO;
|
||||
import com.cloud.api.query.vo.InstanceGroupJoinVO;
|
||||
import com.cloud.api.query.vo.NetworkOfferingJoinVO;
|
||||
@ -183,9 +181,11 @@ import com.cloud.gpu.dao.VGPUTypesDao;
|
||||
import com.cloud.ha.HighAvailabilityManager;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.HostStats;
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.host.dao.HostDetailsDao;
|
||||
import com.cloud.host.dao.HostTagsDao;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.network.IpAddress;
|
||||
import com.cloud.network.Network;
|
||||
@ -452,7 +452,7 @@ public class ApiDBUtils {
|
||||
static VolumeJoinDao s_volJoinDao;
|
||||
static StoragePoolJoinDao s_poolJoinDao;
|
||||
static StoragePoolTagsDao s_tagDao;
|
||||
static HostTagDao s_hostTagDao;
|
||||
static HostTagsDao s_hostTagDao;
|
||||
static ImageStoreJoinDao s_imageStoreJoinDao;
|
||||
static AccountJoinDao s_accountJoinDao;
|
||||
static AsyncJobJoinDao s_jobJoinDao;
|
||||
@ -675,7 +675,7 @@ public class ApiDBUtils {
|
||||
@Inject
|
||||
private StoragePoolTagsDao tagDao;
|
||||
@Inject
|
||||
private HostTagDao hosttagDao;
|
||||
private HostTagsDao hosttagDao;
|
||||
@Inject
|
||||
private ImageStoreJoinDao imageStoreJoinDao;
|
||||
@Inject
|
||||
|
||||
@ -172,7 +172,6 @@ import com.cloud.api.query.dao.DiskOfferingJoinDao;
|
||||
import com.cloud.api.query.dao.DomainJoinDao;
|
||||
import com.cloud.api.query.dao.DomainRouterJoinDao;
|
||||
import com.cloud.api.query.dao.HostJoinDao;
|
||||
import com.cloud.api.query.dao.HostTagDao;
|
||||
import com.cloud.api.query.dao.ImageStoreJoinDao;
|
||||
import com.cloud.api.query.dao.InstanceGroupJoinDao;
|
||||
import com.cloud.api.query.dao.ManagementServerJoinDao;
|
||||
@ -197,7 +196,6 @@ import com.cloud.api.query.vo.DomainJoinVO;
|
||||
import com.cloud.api.query.vo.DomainRouterJoinVO;
|
||||
import com.cloud.api.query.vo.EventJoinVO;
|
||||
import com.cloud.api.query.vo.HostJoinVO;
|
||||
import com.cloud.api.query.vo.HostTagVO;
|
||||
import com.cloud.api.query.vo.ImageStoreJoinVO;
|
||||
import com.cloud.api.query.vo.InstanceGroupJoinVO;
|
||||
import com.cloud.api.query.vo.ManagementServerJoinVO;
|
||||
@ -229,8 +227,10 @@ import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.ha.HighAvailabilityManager;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.host.dao.HostTagsDao;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.network.PublicIpQuarantine;
|
||||
@ -426,7 +426,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
private StoragePoolTagsDao _storageTagDao;
|
||||
|
||||
@Inject
|
||||
private HostTagDao _hostTagDao;
|
||||
private HostTagsDao _hostTagDao;
|
||||
|
||||
@Inject
|
||||
private ImageStoreJoinDao _imageStoreJoinDao;
|
||||
@ -2268,10 +2268,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
||||
if (haHosts != null && haTag != null && !haTag.isEmpty()) {
|
||||
SearchBuilder<HostTagVO> hostTagSearchBuilder = _hostTagDao.createSearchBuilder();
|
||||
if ((Boolean)haHosts) {
|
||||
hostTagSearchBuilder.and("tag", hostTagSearchBuilder.entity().getName(), SearchCriteria.Op.EQ);
|
||||
hostTagSearchBuilder.and("tag", hostTagSearchBuilder.entity().getTag(), SearchCriteria.Op.EQ);
|
||||
} else {
|
||||
hostTagSearchBuilder.and().op("tag", hostTagSearchBuilder.entity().getName(), Op.NEQ);
|
||||
hostTagSearchBuilder.or("tagNull", hostTagSearchBuilder.entity().getName(), Op.NULL);
|
||||
hostTagSearchBuilder.and().op("tag", hostTagSearchBuilder.entity().getTag(), Op.NEQ);
|
||||
hostTagSearchBuilder.or("tagNull", hostTagSearchBuilder.entity().getTag(), Op.NULL);
|
||||
hostTagSearchBuilder.cp();
|
||||
}
|
||||
hostSearchBuilder.join("hostTagSearch", hostTagSearchBuilder, hostSearchBuilder.entity().getId(), hostTagSearchBuilder.entity().getHostId(), JoinBuilder.JoinType.LEFT);
|
||||
|
||||
@ -74,7 +74,6 @@ import com.cloud.api.query.vo.DomainJoinVO;
|
||||
import com.cloud.api.query.vo.DomainRouterJoinVO;
|
||||
import com.cloud.api.query.vo.EventJoinVO;
|
||||
import com.cloud.api.query.vo.HostJoinVO;
|
||||
import com.cloud.api.query.vo.HostTagVO;
|
||||
import com.cloud.api.query.vo.ImageStoreJoinVO;
|
||||
import com.cloud.api.query.vo.InstanceGroupJoinVO;
|
||||
import com.cloud.api.query.vo.ProjectAccountJoinVO;
|
||||
@ -91,6 +90,7 @@ import com.cloud.api.query.vo.UserVmJoinVO;
|
||||
import com.cloud.api.query.vo.VolumeJoinVO;
|
||||
import com.cloud.configuration.Resource;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.StoragePoolTagVO;
|
||||
import com.cloud.storage.VolumeStats;
|
||||
|
||||
@ -205,6 +205,8 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
|
||||
hostResponse.setHostTags(hostTags);
|
||||
hostResponse.setIsTagARule(host.getIsTagARule());
|
||||
hostResponse.setHaHost(containsHostHATag(hostTags));
|
||||
hostResponse.setExplicitHostTags(host.getExplicitTag());
|
||||
hostResponse.setImplicitHostTags(host.getImplicitTag());
|
||||
|
||||
hostResponse.setHypervisorVersion(host.getHypervisorVersion());
|
||||
|
||||
@ -349,6 +351,7 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
|
||||
String hostTags = host.getTag();
|
||||
hostResponse.setHostTags(hostTags);
|
||||
hostResponse.setHaHost(containsHostHATag(hostTags));
|
||||
hostResponse.setImplicitHostTags(host.getImplicitTag());
|
||||
|
||||
hostResponse.setHypervisorVersion(host.getHypervisorVersion());
|
||||
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
// 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.
|
||||
package com.cloud.api.query.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.api.response.HostTagResponse;
|
||||
|
||||
import com.cloud.api.query.vo.HostTagVO;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
public interface HostTagDao extends GenericDao<HostTagVO, Long> {
|
||||
HostTagResponse newHostTagResponse(HostTagVO hostTag);
|
||||
|
||||
List<HostTagVO> searchByIds(Long... hostTagIds);
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
// 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.
|
||||
package com.cloud.api.query.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.response.HostTagResponse;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.api.query.vo.HostTagVO;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
|
||||
@Component
|
||||
public class HostTagDaoImpl extends GenericDaoBase<HostTagVO, Long> implements HostTagDao {
|
||||
|
||||
@Inject
|
||||
private ConfigurationDao _configDao;
|
||||
|
||||
private final SearchBuilder<HostTagVO> stSearch;
|
||||
private final SearchBuilder<HostTagVO> stIdSearch;
|
||||
|
||||
protected HostTagDaoImpl() {
|
||||
stSearch = createSearchBuilder();
|
||||
|
||||
stSearch.and("idIN", stSearch.entity().getId(), SearchCriteria.Op.IN);
|
||||
stSearch.done();
|
||||
|
||||
stIdSearch = createSearchBuilder();
|
||||
|
||||
stIdSearch.and("id", stIdSearch.entity().getId(), SearchCriteria.Op.EQ);
|
||||
stIdSearch.done();
|
||||
|
||||
_count = "select count(distinct id) from host_tags WHERE ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostTagResponse newHostTagResponse(HostTagVO tag) {
|
||||
HostTagResponse tagResponse = new HostTagResponse();
|
||||
|
||||
tagResponse.setName(tag.getName());
|
||||
tagResponse.setHostId(tag.getHostId());
|
||||
|
||||
tagResponse.setObjectName("hosttag");
|
||||
|
||||
return tagResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostTagVO> searchByIds(Long... stIds) {
|
||||
String batchCfg = _configDao.getValue("detail.batch.query.size");
|
||||
|
||||
final int detailsBatchSize = batchCfg != null ? Integer.parseInt(batchCfg) : 2000;
|
||||
|
||||
// query details by batches
|
||||
List<HostTagVO> uvList = new ArrayList<HostTagVO>();
|
||||
int curr_index = 0;
|
||||
|
||||
if (stIds.length > detailsBatchSize) {
|
||||
while ((curr_index + detailsBatchSize) <= stIds.length) {
|
||||
Long[] ids = new Long[detailsBatchSize];
|
||||
|
||||
for (int k = 0, j = curr_index; j < curr_index + detailsBatchSize; j++, k++) {
|
||||
ids[k] = stIds[j];
|
||||
}
|
||||
|
||||
SearchCriteria<HostTagVO> sc = stSearch.create();
|
||||
|
||||
sc.setParameters("idIN", (Object[])ids);
|
||||
|
||||
List<HostTagVO> vms = searchIncludingRemoved(sc, null, null, false);
|
||||
|
||||
if (vms != null) {
|
||||
uvList.addAll(vms);
|
||||
}
|
||||
|
||||
curr_index += detailsBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_index < stIds.length) {
|
||||
int batch_size = (stIds.length - curr_index);
|
||||
// set the ids value
|
||||
Long[] ids = new Long[batch_size];
|
||||
|
||||
for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
|
||||
ids[k] = stIds[j];
|
||||
}
|
||||
|
||||
SearchCriteria<HostTagVO> sc = stSearch.create();
|
||||
|
||||
sc.setParameters("idIN", (Object[])ids);
|
||||
|
||||
List<HostTagVO> vms = searchIncludingRemoved(sc, null, null, false);
|
||||
|
||||
if (vms != null) {
|
||||
uvList.addAll(vms);
|
||||
}
|
||||
}
|
||||
|
||||
return uvList;
|
||||
}
|
||||
}
|
||||
@ -174,6 +174,12 @@ public class HostJoinVO extends BaseViewVO implements InternalIdentity, Identity
|
||||
@Column(name = "tag")
|
||||
private String tag;
|
||||
|
||||
@Column(name = "explicit_tag")
|
||||
private String explicitTag;
|
||||
|
||||
@Column(name = "implicit_tag")
|
||||
private String implicitTag;
|
||||
|
||||
@Column(name = "is_tag_a_rule")
|
||||
private Boolean isTagARule;
|
||||
|
||||
@ -393,6 +399,14 @@ public class HostJoinVO extends BaseViewVO implements InternalIdentity, Identity
|
||||
return tag;
|
||||
}
|
||||
|
||||
public String getExplicitTag() {
|
||||
return explicitTag;
|
||||
}
|
||||
|
||||
public String getImplicitTag() {
|
||||
return implicitTag;
|
||||
}
|
||||
|
||||
public Boolean getIsTagARule() {
|
||||
return isTagARule;
|
||||
}
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
// 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.
|
||||
package com.cloud.api.query.vo;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
/**
|
||||
* Storage Tags DB view.
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "host_tags")
|
||||
public class HostTagVO extends BaseViewVO implements InternalIdentity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "tag")
|
||||
private String name;
|
||||
|
||||
@Column(name = "host_id")
|
||||
long hostId;
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getHostId() {
|
||||
return hostId;
|
||||
}
|
||||
|
||||
public void setHostId(long hostId) {
|
||||
this.hostId = hostId;
|
||||
}
|
||||
}
|
||||
@ -38,9 +38,9 @@ import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.exception.StorageConflictException;
|
||||
import com.cloud.exception.StorageUnavailableException;
|
||||
import com.cloud.host.HostTagVO;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
@ -2334,22 +2334,6 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
|
||||
}
|
||||
}
|
||||
|
||||
if (startup instanceof StartupRoutingCommand) {
|
||||
final StartupRoutingCommand ssCmd = (StartupRoutingCommand)startup;
|
||||
final List<String> implicitHostTags = ssCmd.getHostTags();
|
||||
if (!implicitHostTags.isEmpty()) {
|
||||
if (hostTags == null) {
|
||||
hostTags = _hostTagsDao.getHostTags(host.getId()).parallelStream().map(HostTagVO::getTag).collect(Collectors.toList());
|
||||
}
|
||||
if (hostTags != null) {
|
||||
implicitHostTags.removeAll(hostTags);
|
||||
hostTags.addAll(implicitHostTags);
|
||||
} else {
|
||||
hostTags = implicitHostTags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
host.setDataCenterId(dc.getId());
|
||||
host.setPodId(podId);
|
||||
host.setClusterId(clusterId);
|
||||
@ -2392,6 +2376,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
|
||||
|
||||
if (startup instanceof StartupRoutingCommand) {
|
||||
final StartupRoutingCommand ssCmd = (StartupRoutingCommand)startup;
|
||||
_hostTagsDao.updateImplicitTags(host.getId(), ssCmd.getHostTags());
|
||||
|
||||
updateSupportsClonedVolumes(host, ssCmd.getSupportsClonedVolumes());
|
||||
}
|
||||
|
||||
160
test/integration/smoke/test_host_tags.py
Normal file
160
test/integration/smoke/test_host_tags.py
Normal file
@ -0,0 +1,160 @@
|
||||
# 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.
|
||||
""" Tests for importVolume and unmanageVolume APIs
|
||||
"""
|
||||
# Import Local Modules
|
||||
from marvin.cloudstackAPI import updateHost
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase, unittest
|
||||
from marvin.lib.base import Host
|
||||
from marvin.lib.utils import is_server_ssh_ready, wait_until
|
||||
|
||||
# Import System modules
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
import logging
|
||||
|
||||
class TestHostTags(cloudstackTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
testClient = super(TestHostTags, cls).getClsTestClient()
|
||||
cls.apiclient = testClient.getApiClient()
|
||||
cls.hypervisor = testClient.getHypervisorInfo()
|
||||
if cls.testClient.getHypervisorInfo().lower() != "kvm":
|
||||
raise unittest.SkipTest("This is only available for KVM")
|
||||
|
||||
cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__
|
||||
|
||||
hosts = Host.list(
|
||||
cls.apiclient,
|
||||
type = "Routing",
|
||||
hypervisor = cls.hypervisor
|
||||
)
|
||||
if isinstance(hosts, list) and len(hosts) > 0:
|
||||
cls.host = hosts[0]
|
||||
else:
|
||||
raise unittest.SkipTest("No available host for this test")
|
||||
|
||||
cls.logger = logging.getLogger("TestHostTags")
|
||||
cls.stream_handler = logging.StreamHandler()
|
||||
cls.logger.setLevel(logging.DEBUG)
|
||||
cls.logger.addHandler(cls.stream_handler)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.update_host_tags_via_api(cls.host.hosttags)
|
||||
cls.update_implicit_host_tags_via_agent_properties(cls.host.implicithosttags)
|
||||
|
||||
@classmethod
|
||||
def update_host_tags_via_api(cls, hosttags):
|
||||
cmd = updateHost.updateHostCmd()
|
||||
cmd.id = cls.host.id
|
||||
cmd.hosttags = hosttags
|
||||
cls.apiclient.updateHost(cmd)
|
||||
|
||||
@classmethod
|
||||
def update_implicit_host_tags_via_agent_properties(cls, implicithosttags):
|
||||
ssh_client = is_server_ssh_ready(
|
||||
cls.host.ipaddress,
|
||||
22,
|
||||
cls.hostConfig["username"],
|
||||
cls.hostConfig["password"],
|
||||
)
|
||||
if implicithosttags:
|
||||
command = "sed -i '/host.tags=/d' /etc/cloudstack/agent/agent.properties \
|
||||
&& echo 'host.tags=%s' >> /etc/cloudstack/agent/agent.properties \
|
||||
&& systemctl restart cloudstack-agent" % implicithosttags
|
||||
else:
|
||||
command = "sed -i '/host.tags=/d' /etc/cloudstack/agent/agent.properties \
|
||||
&& systemctl restart cloudstack-agent"
|
||||
|
||||
ssh_client.execute(command)
|
||||
|
||||
def wait_until_host_is_up_and_verify_hosttags(self, explicithosttags, implicithosttags, interval=3, retries=20):
|
||||
def check_host_state():
|
||||
hosts = Host.list(
|
||||
self.apiclient,
|
||||
id=self.host.id
|
||||
)
|
||||
if isinstance(hosts, list) and len(hosts) > 0:
|
||||
host = hosts[0]
|
||||
if host.state == "Up":
|
||||
self.logger.debug("Host %s is in Up state" % host.name)
|
||||
self.logger.debug("Host explicithosttags is %s, implicit hosttags is %s" % (host.explicithosttags, host.implicithosttags))
|
||||
if explicithosttags:
|
||||
self.assertEquals(explicithosttags, host.explicithosttags)
|
||||
else:
|
||||
self.assertIsNone(host.explicithosttags)
|
||||
if implicithosttags:
|
||||
self.assertEquals(implicithosttags, host.implicithosttags)
|
||||
else:
|
||||
self.assertIsNone(host.implicithosttags)
|
||||
return True, None
|
||||
else:
|
||||
self.logger.debug("Waiting for host %s to be Up state, current state is %s" % (host.name, host.state))
|
||||
return False, None
|
||||
|
||||
done, _ = wait_until(interval, retries, check_host_state)
|
||||
if not done:
|
||||
raise Exception("Failed to wait for host %s to be Up" % self.host.name)
|
||||
return True
|
||||
|
||||
@attr(tags=['advanced', 'basic', 'sg'], required_hardware=False)
|
||||
def test_01_host_tags(self):
|
||||
"""Test implicit/explicit host tags
|
||||
"""
|
||||
|
||||
# update explicit host tags to "s1,s2"
|
||||
explicithosttags="s1,s2"
|
||||
implicithosttags=self.host.implicithosttags
|
||||
self.update_host_tags_via_api(explicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update implicit host tags to "d1,d2"
|
||||
implicithosttags="d1,d2"
|
||||
self.update_implicit_host_tags_via_agent_properties(implicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update explicit host tags to "s3,s4"
|
||||
explicithosttags="s3,s4"
|
||||
self.update_host_tags_via_api(explicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update implicit host tags to "d3,d4"
|
||||
implicithosttags="d3,d4"
|
||||
self.update_implicit_host_tags_via_agent_properties(implicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update hosttags to ""
|
||||
explicithosttags=""
|
||||
self.update_host_tags_via_api(explicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update implicit host tags to ""
|
||||
implicithosttags=""
|
||||
self.update_implicit_host_tags_via_agent_properties(implicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update explicit host tags to "s1,s2"
|
||||
explicithosttags="s1,s2"
|
||||
self.update_host_tags_via_api(explicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
|
||||
# update implicit host tags to "d1,d2"
|
||||
implicithosttags="d1,d2"
|
||||
self.update_implicit_host_tags_via_agent_properties(implicithosttags)
|
||||
self.wait_until_host_is_up_and_verify_hosttags(explicithosttags, implicithosttags)
|
||||
@ -198,6 +198,7 @@
|
||||
"label.action.unmanage.virtualmachine": "Unmanage Instance",
|
||||
"label.action.unmanage.volume": "Unmanage Volume",
|
||||
"label.action.unmanage.volumes": "Unmanage Volumes",
|
||||
"label.action.update.host": "Update host",
|
||||
"label.action.update.offering.access": "Update offering access",
|
||||
"label.action.update.resource.count": "Update resource count",
|
||||
"label.action.value": "Action/Value",
|
||||
@ -1006,6 +1007,12 @@
|
||||
"label.hostnamelabel": "Host name",
|
||||
"label.hosts": "Hosts",
|
||||
"label.hosttags": "Host tags",
|
||||
"label.hosttags.explicit": "API-defined Host tags",
|
||||
"label.hosttags.explicit.abbr": "api-defined",
|
||||
"label.hosttags.explicit.description": "The host tags defined by CloudStack APIs",
|
||||
"label.hosttags.implicit": "Agent-defined Host tags",
|
||||
"label.hosttags.implicit.abbr": "agent-defined",
|
||||
"label.hosttags.implicit.description": "The host tags defined by CloudStack Agent",
|
||||
"label.hourly": "Hourly",
|
||||
"label.hypervisor": "Hypervisor",
|
||||
"label.hypervisor.capabilities": "Hypervisor capabilities",
|
||||
|
||||
@ -74,12 +74,8 @@ export default {
|
||||
icon: 'edit-outlined',
|
||||
label: 'label.edit',
|
||||
dataView: true,
|
||||
args: ['name', 'hosttags', 'istagarule', 'oscategoryid'],
|
||||
mapping: {
|
||||
oscategoryid: {
|
||||
api: 'listOsCategories'
|
||||
}
|
||||
}
|
||||
popup: true,
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/HostUpdate')))
|
||||
},
|
||||
{
|
||||
api: 'provisionCertificate',
|
||||
|
||||
@ -51,8 +51,14 @@
|
||||
<a-list-item v-if="host.hosttags">
|
||||
<div>
|
||||
<strong>{{ $t('label.hosttags') }}</strong>
|
||||
<div>
|
||||
{{ host.hosttags }}
|
||||
<div v-for="hosttag in host.allhosttags" :key="hosttag.tag">
|
||||
{{ hosttag.tag }}
|
||||
<span v-if="hosttag.isexplicit">
|
||||
<a-tag color="blue">{{ $t('label.hosttags.explicit.abbr') }}</a-tag>
|
||||
</span>
|
||||
<span v-if="hosttag.isimplicit">
|
||||
<a-tag color="orange">{{ $t('label.hosttags.implicit.abbr') }}</a-tag>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
@ -158,6 +164,22 @@ export default {
|
||||
this.fetchLoading = true
|
||||
api('listHosts', { id: this.resource.id }).then(json => {
|
||||
this.host = json.listhostsresponse.host[0]
|
||||
const hosttags = this.host.hosttags?.split(',') || []
|
||||
const explicithosttags = this.host.explicithosttags?.split(',') || []
|
||||
const implicithosttags = this.host.implicithosttags?.split(',') || []
|
||||
const allHostTags = []
|
||||
for (const hosttag of hosttags) {
|
||||
var isexplicit = false
|
||||
var isimplicit = false
|
||||
if (explicithosttags.includes(hosttag)) {
|
||||
isexplicit = true
|
||||
}
|
||||
if (implicithosttags.includes(hosttag)) {
|
||||
isimplicit = true
|
||||
}
|
||||
allHostTags.push({ tag: hosttag, isexplicit: isexplicit, isimplicit: isimplicit })
|
||||
}
|
||||
this.host.allhosttags = allHostTags
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
|
||||
183
ui/src/views/infra/HostUpdate.vue
Normal file
183
ui/src/views/infra/HostUpdate.vue
Normal file
@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-form
|
||||
class="form-layout"
|
||||
layout="vertical"
|
||||
:ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
v-ctrl-enter="handleSubmit"
|
||||
@finish="handleSubmit">
|
||||
<a-form-item name="name" ref="name">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.name"
|
||||
v-focus="true" />
|
||||
</a-form-item>
|
||||
<a-form-item name="hosttags" ref="hosttags">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.hosttags')" :tooltip="$t('label.hosttags.explicit.description')"/>
|
||||
</template>
|
||||
<a-input v-model:value="form.hosttags" />
|
||||
</a-form-item>
|
||||
<a-form-item name="istagarule" ref="istagarule">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.istagarule')" :tooltip="apiParams.istagarule.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.istagarule" />
|
||||
</a-form-item>
|
||||
<a-form-item name="oscategoryid" ref="oscategoryid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.oscategoryid')" :tooltip="apiParams.oscategoryid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="osCategories.loading"
|
||||
v-model:value="form.oscategoryid">
|
||||
<a-select-option v-for="(osCategory) in osCategories.opts" :key="osCategory.id" :label="osCategory.name">
|
||||
{{ osCategory.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<div :span="24" class="action-button">
|
||||
<a-button :loading="loading" @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, toRaw } from 'vue'
|
||||
import { api } from '@/api'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
|
||||
export default {
|
||||
name: 'EditVM',
|
||||
components: {
|
||||
TooltipLabel
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
osCategories: {
|
||||
loading: false,
|
||||
opts: []
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.apiParams = this.$getApiParams('updateHost')
|
||||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
this.fetchOsCategories()
|
||||
},
|
||||
methods: {
|
||||
initForm () {
|
||||
this.formRef = ref()
|
||||
this.form = reactive({
|
||||
name: this.resource.name,
|
||||
hosttags: this.resource.explicithosttags,
|
||||
istagarule: this.resource.istagarule,
|
||||
oscategoryid: this.resource.oscategoryid
|
||||
})
|
||||
this.rules = reactive({})
|
||||
},
|
||||
fetchOsCategories () {
|
||||
this.osCategories.loading = true
|
||||
this.osCategories.opts = []
|
||||
api('listOsCategories').then(json => {
|
||||
this.osCategories.opts = json.listoscategoriesresponse.oscategory || []
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.osCategories.loading = false
|
||||
})
|
||||
},
|
||||
handleSubmit () {
|
||||
this.formRef.value.validate().then(() => {
|
||||
const values = toRaw(this.form)
|
||||
const params = {}
|
||||
params.id = this.resource.id
|
||||
params.name = values.name
|
||||
params.hosttags = values.hosttags
|
||||
params.oscategoryid = values.oscategoryid
|
||||
if (values.istagarule !== undefined) {
|
||||
params.istagarule = values.istagarule
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
api('updateHost', params).then(json => {
|
||||
this.$message.success({
|
||||
content: `${this.$t('label.action.update.host')} - ${values.name}`,
|
||||
duration: 2
|
||||
})
|
||||
this.$emit('refresh-data')
|
||||
this.onCloseAction()
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => { this.loading = false })
|
||||
}).catch(error => {
|
||||
this.formRef.value.scrollToField(error.errorFields[0].name)
|
||||
})
|
||||
},
|
||||
onCloseAction () {
|
||||
this.$emit('close-action')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form-layout {
|
||||
width: 80vw;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
text-align: right;
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user