From f846c0eda5e355bc34f6935c6fbe402311d88ca9 Mon Sep 17 00:00:00 2001 From: Rakesh Venkatesh Date: Thu, 1 Oct 2020 01:41:13 +0200 Subject: [PATCH 1/3] Match template tags with host tag for vm deployment 1. Match the template tags with host tags for vm deployment/migration 2. Allow updating templates/isos with template tag by admin 3. Allow adding template tag while registering/creating template/iso 4. Allow searching templats/isos using template tags If a template has template tag then deploy the vm which uses this template on hosts which matches the template tags. If no suitable hosts are found then throw exception Similarly match the template tag with host tags while starting or migrating a vm to a different host --- .../api/command/user/iso/ListIsosCmd.java | 12 +- .../api/command/user/iso/RegisterIsoCmd.java | 6 + .../api/command/user/iso/UpdateIsoCmd.java | 14 ++ .../user/template/CreateTemplateCmd.java | 3 +- .../user/template/ListTemplatesCmd.java | 12 +- .../user/template/RegisterTemplateCmd.java | 3 +- .../user/template/UpdateTemplateCmd.java | 11 +- .../allocator/impl/RandomAllocator.java | 30 ++- .../com/cloud/api/query/QueryManagerImpl.java | 18 +- .../api/query/dao/TemplateJoinDaoImpl.java | 11 +- .../deploy/DeploymentPlanningManagerImpl.java | 15 +- .../cloud/template/TemplateAdapterBase.java | 2 +- .../cloud/template/TemplateManagerImpl.java | 31 ++- test/integration/component/test_host_tags.py | 232 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 10 +- tools/marvin/marvin/lib/common.py | 4 +- ui/public/locales/en.json | 1 + ui/src/config/section/image.js | 27 +- ui/src/views/AutogenView.vue | 3 + ui/src/views/image/RegisterOrUploadIso.vue | 8 + .../views/image/RegisterOrUploadTemplate.vue | 10 + 21 files changed, 429 insertions(+), 34 deletions(-) create mode 100644 test/integration/component/test_host_tags.py diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java index 44bbb7168c1b..6c7ca925a30d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java @@ -16,8 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.iso; -import com.cloud.server.ResourceIcon; -import com.cloud.server.ResourceTag; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; @@ -33,6 +32,8 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; @@ -90,6 +91,9 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the isos") private Boolean showIcon; + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, since = "4.16", description = "show ISO's which matches the tag", authorized = {RoleType.Admin}) + private String templateTag; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -157,6 +161,10 @@ public boolean listInReadyState() { return onlyReady; } + public String getTemplateTag() { + return templateTag; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java index 1c1a767aa3bc..47e9b4aa4018 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java @@ -18,6 +18,7 @@ import java.util.List; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -119,6 +120,9 @@ public class RegisterIsoCmd extends BaseCmd implements UserCmd { description = "true if password reset feature is supported; default is false") private Boolean passwordEnabled; + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.", authorized = {RoleType.Admin}) + private String templateTag; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -223,6 +227,8 @@ public boolean isPasswordEnabled() { return passwordEnabled == null ? false : passwordEnabled; } + public String getTemplateTag() {return templateTag; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoCmd.java index 36e9b5329e17..629504eb73ab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoCmd.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.user.iso; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -35,6 +38,13 @@ public class UpdateIsoCmd extends BaseUpdateTemplateOrIsoCmd implements UserCmd public static final Logger s_logger = Logger.getLogger(UpdateIsoCmd.class.getName()); private static final String s_name = "updateisoresponse"; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this iso.", authorized = {RoleType.Admin}) + private String templateTag; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -49,6 +59,10 @@ public String getFormat() { return null; } + public String getTemplateTag() { + return templateTag; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java index 79936eecf87f..ba2a49ac5bf3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.GuestOSResponse; @@ -121,7 +122,7 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd { description = "Optional, only for baremetal hypervisor. The directory name where template stored on CIFS server") private String url; - @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.") + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.", authorized = {RoleType.Admin}) private String templateTag; @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Template details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].hypervisortoolsversion=xenserver61") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index bf3b2fbf5a40..8ee10c872d1a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandJobType; import org.apache.cloudstack.api.ApiConstants; @@ -96,6 +97,12 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User description = "comma separated list of template details requested, value can be a list of [ all, min]") private List viewDetails; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the templates") + private Boolean showIcon; + + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, since = "4.16", description = "show templates with matching templatetag", authorized = {RoleType.Admin}) + private String templateTag; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -164,8 +171,9 @@ public boolean listInReadyState() { return onlyReady; } - @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the templates") - private Boolean showIcon; + public String getTemplateTag() { + return templateTag; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java index bb7f7a441b68..fe0b6f5268c3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandJobType; import org.apache.cloudstack.api.ApiConstants; @@ -127,7 +128,7 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { @Parameter(name = ApiConstants.CHECKSUM, type = CommandType.STRING, description = "the checksum value of this template. " + ApiConstants.CHECKSUM_PARAMETER_PREFIX_DESCRIPTION) private String checksum; - @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.") + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.", authorized = {RoleType.Admin}) private String templateTag; @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Register template for the project") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java index 8396b078c157..4322594aec6d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java @@ -16,12 +16,14 @@ // under the License. package org.apache.cloudstack.api.command.user.template; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; -import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; @@ -43,6 +45,9 @@ public class UpdateTemplateCmd extends BaseUpdateTemplateOrIsoCmd implements Use @Parameter(name = "templatetype", type = CommandType.STRING, description = "the type of the template") private String templateType; + @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.", authorized = {RoleType.Admin}) + private String templateTag; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -56,6 +61,10 @@ public String getTemplateType() { return templateType; } + public String getTemplateTag() { + return templateTag; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java index 8a46d10a7b5a..5e465727e26f 100644 --- a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java +++ b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java @@ -38,6 +38,7 @@ import com.cloud.host.dao.HostDao; import com.cloud.offering.ServiceOffering; import com.cloud.resource.ResourceManager; +import com.cloud.storage.VMTemplateVO; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; import com.cloud.vm.VirtualMachine; @@ -71,19 +72,15 @@ private List findSuitableHosts(VirtualMachineProfile vmProfile, Deployment return suitableHosts; } String hostTag = offering.getHostTag(); + StringBuilder message = new StringBuilder(); + message.append("Looking for hosts in dc: ").append(dcId) + .append(" pod: ").append(podId) + .append(" cluster: ").append(clusterId); + if (hostTag != null) { - s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId + " having host tag:" + hostTag); - } else { - s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId); - } - if (hosts != null) { - // retain all computing hosts, regardless of whether they support routing...it's random after all - hostsCopy = new ArrayList(hosts); - if (hostTag != null) { - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag)); - } else { - hostsCopy.retainAll(_resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId)); - } + message.append(" having host tag:").append(hostTag); + s_logger.debug(message.toString()); + hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag)); } else { // list all computing hosts, regardless of whether they support routing...it's random after all hostsCopy = new ArrayList(); @@ -93,6 +90,15 @@ private List findSuitableHosts(VirtualMachineProfile vmProfile, Deployment hostsCopy = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); } } + + VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); + String templateTag = template.getTemplateTag(); + if (templateTag != null) { + message.append(" having template tag:").append(templateTag); + s_logger.debug(message.toString()); + hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, templateTag)); + } + s_logger.debug("Random Allocator found " + hostsCopy.size() + " hosts"); if (hostsCopy.size() == 0) { return suitableHosts; diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 3f481d84fc28..84a24db6faad 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -3564,6 +3564,10 @@ private Pair, Integer> searchForTemplatesInternal(ListTempl listAll = true; } + if (cmd.getTemplateTag() != null && !(caller.getType() == Account.ACCOUNT_TYPE_ADMIN)) { + throw new InvalidParameterValueException("Parameter templatetag can only be specified by admins"); + } + List permittedAccountIds = new ArrayList(); Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds, domainIdRecursiveListProject, listAll, false); @@ -3577,12 +3581,12 @@ private Pair, Integer> searchForTemplatesInternal(ListTempl HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor()); return searchForTemplatesInternal(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, - showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique()); + showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), cmd.getTemplateTag()); } private Pair, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, TemplateFilter templateFilter, boolean isIso, Boolean bootable, Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean showDomr, boolean onlyReady, List permittedAccounts, Account caller, - ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique) { + ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique, final String templateTag) { // check if zone is configured, if not, just return empty list List hypers = null; @@ -3826,6 +3830,10 @@ private Pair, Integer> templateChecks(boolean isIso, List, Integer> searchForIsosInternal(ListIsosCmd cm listAll = true; } + if (cmd.getTemplateTag() != null && !(caller.getType() == Account.ACCOUNT_TYPE_ADMIN)) { + throw new InvalidParameterValueException("Parameter templatetag can only be specified by admins"); + } + List permittedAccountIds = new ArrayList(); Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccountIds, domainIdRecursiveListProject, listAll, false); @@ -3918,7 +3930,7 @@ private Pair, Integer> searchForIsosInternal(ListIsosCmd cm HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor()); return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), - hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique()); + hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique(), cmd.getTemplateTag()); } @Override diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java index 3a71b3e2b3d2..cb67b3037fdc 100644 --- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java @@ -247,7 +247,9 @@ public TemplateResponse newTemplateResponse(EnumSet if (template.getSourceTemplateId() != null) { templateResponse.setSourceTemplateId(template.getSourceTemplateUuid()); } - templateResponse.setTemplateTag(template.getTemplateTag()); + if (view == ResponseView.Full) { + templateResponse.setTemplateTag(template.getTemplateTag()); + } if (template.getParentTemplateId() != null) { templateResponse.setParentTemplateId(template.getParentTemplateUuid()); @@ -416,7 +418,7 @@ public TemplateResponse newIsoResponse(TemplateJoinVO iso) { isAdmin = true; } - // If the user is an admin, add the template download status + // If the user is an admin, add the template download status and template tag if (isAdmin || caller.getId() == iso.getAccountId()) { // add download status if (iso.getDownloadState() != Status.DOWNLOADED) { @@ -464,6 +466,11 @@ public TemplateResponse newIsoResponse(TemplateJoinVO iso) { isoResponse.setDirectDownload(iso.isDirectDownload()); + // Update ISO template tag + if (_accountService.isRootAdmin(caller.getId())) { + isoResponse.setTemplateTag(iso.getTemplateTag()); + } + isoResponse.setObjectName("iso"); return isoResponse; diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 86907a19ce30..387d0d0f7342 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -245,6 +245,8 @@ public void setHostAllocators(List hostAllocators) { protected ResourceManager _resourceMgr; @Inject protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao; + @Inject + protected VMTemplateDao _templateDao; protected List _planners; @@ -667,13 +669,22 @@ public DeploymentPlanner getDeploymentPlannerByName(String plannerName) { private boolean checkVmProfileAndHost(final VirtualMachineProfile vmProfile, final HostVO host) { ServiceOffering offering = vmProfile.getServiceOffering(); - if (offering.getHostTag() != null) { + VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmProfile.getVirtualMachine().getTemplateId()); + if (offering.getHostTag() != null || template.getTemplateTag() != null) { _hostDao.loadHostTags(host); if (!host.checkHostServiceOfferingTags(offering)) { s_logger.debug("Service Offering host tag does not match the last host of this VM"); return false; } + + if (template.getTemplateTag() != null && (!(host.getHostTags() != null + && host.getHostTags().contains(template.getTemplateTag())))) { + s_logger.debug("Template tag does not match the last host tag of this VM"); + return false; + } } + + long guestOSId = vmProfile.getTemplate().getGuestOSId(); GuestOSVO guestOS = _guestOSDao.findById(guestOSId); if (guestOS != null) { @@ -681,7 +692,7 @@ private boolean checkVmProfileAndHost(final VirtualMachineProfile vmProfile, fin DetailVO hostDetail = _hostDetailsDao.findDetail(host.getId(), "guest.os.category.id"); if (hostDetail != null) { String guestOSCategoryIdString = hostDetail.getValue(); - if (String.valueOf(guestOSCategoryId) != guestOSCategoryIdString) { + if (!String.valueOf(guestOSCategoryId).equals(guestOSCategoryIdString)) { s_logger.debug("The last host has different guest.os.category.id than guest os category of VM, skipping"); return false; } diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java index a9cd9478c58b..1b2d95f3507f 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java @@ -388,7 +388,7 @@ public TemplateProfile prepare(RegisterIsoCmd cmd) throws ResourceAllocationExce } return prepare(true, CallContext.current().getCallingUserId(), cmd.getIsoName(), cmd.getDisplayText(), 64, cmd.isPasswordEnabled(), true, cmd.getUrl(), cmd.isPublic(), - cmd.isFeatured(), cmd.isExtractable(), ImageFormat.ISO.toString(), cmd.getOsTypeId(), zoneList, HypervisorType.None, cmd.getChecksum(), cmd.isBootable(), null, + cmd.isFeatured(), cmd.isExtractable(), ImageFormat.ISO.toString(), cmd.getOsTypeId(), zoneList, HypervisorType.None, cmd.getChecksum(), cmd.isBootable(), cmd.getTemplateTag(), owner, null, false, cmd.getImageStoreUuid(), cmd.isDynamicallyScalable(), TemplateType.USER, cmd.isDirectDownload(), false); } diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 527be5d8cd09..22173aae18ec 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -319,6 +319,13 @@ private TemplateAdapter getAdapter(HypervisorType type) { @Override @ActionEvent(eventType = EventTypes.EVENT_ISO_CREATE, eventDescription = "creating iso") public VirtualMachineTemplate registerIso(RegisterIsoCmd cmd) throws ResourceAllocationException { + Account account = CallContext.current().getCallingAccount(); + if (cmd.getTemplateTag() != null) { + if (!_accountService.isRootAdmin(account.getId())) { + throw new PermissionDeniedException("Parameter templatetag can only be specified by Root Admin, permission denied"); + } + } + TemplateAdapter adapter = getAdapter(HypervisorType.None); TemplateProfile profile = adapter.prepare(cmd); VMTemplateVO template = adapter.create(profile); @@ -1875,6 +1882,9 @@ public VMTemplateVO createPrivateTemplateRecord(CreateTemplateCmd cmd, Account t } String templateTag = cmd.getTemplateTag(); if (templateTag != null) { + if (!_accountService.isRootAdmin(caller.getId())) { + throw new PermissionDeniedException("Parameter templatetag can only be specified by a Root Admin, permission denied"); + } if (s_logger.isDebugEnabled()) { s_logger.debug("Adding template tag: " + templateTag); } @@ -2056,6 +2066,7 @@ private VMTemplateVO updateTemplateOrIso(BaseUpdateTemplateOrIsoCmd cmd) { Map details = cmd.getDetails(); Account account = CallContext.current().getCallingAccount(); boolean cleanupDetails = cmd.isCleanupDetails(); + boolean isRootAdmin = _accountService.isRootAdmin(account.getId()); // verify that template exists VMTemplateVO template = _tmpltDao.findById(id); @@ -2071,13 +2082,14 @@ private VMTemplateVO updateTemplateOrIso(BaseUpdateTemplateOrIsoCmd cmd) { // do a permission check _accountMgr.checkAccess(account, AccessType.OperateEntry, true, template); if (cmd.isRoutingType() != null) { - if (!_accountService.isRootAdmin(account.getId())) { + if (!isRootAdmin) { throw new PermissionDeniedException("Parameter isrouting can only be specified by a Root Admin, permission denied"); } } // update template type TemplateType templateType = null; + String templateTag = null; if (cmd instanceof UpdateTemplateCmd) { String newType = ((UpdateTemplateCmd)cmd).getTemplateType(); if (newType != null) { @@ -2096,6 +2108,14 @@ private VMTemplateVO updateTemplateOrIso(BaseUpdateTemplateOrIsoCmd cmd) { if (templateType != null && (templateType == TemplateType.SYSTEM || templateType == TemplateType.BUILTIN) && !template.isCrossZones()) { throw new InvalidParameterValueException("System and Builtin templates must be cross zone"); } + templateTag = ((UpdateTemplateCmd) cmd).getTemplateTag(); + if (templateTag != null) { + if (!isRootAdmin) { + throw new PermissionDeniedException("Parameter templatetag can only be specified by a Root Admin, permission denied"); + } + } + } else if (cmd instanceof UpdateIsoCmd) { + templateTag = ((UpdateIsoCmd)cmd).getTemplateTag(); } // update is needed if any of the fields below got filled by the user @@ -2112,6 +2132,7 @@ private VMTemplateVO updateTemplateOrIso(BaseUpdateTemplateOrIsoCmd cmd) { isDynamicallyScalable == null && isRoutingTemplate == null && templateType == null && + templateTag == null && (! cleanupDetails && details == null) //update details in every case except this one ); if (!updateNeeded) { @@ -2209,6 +2230,14 @@ else if (details != null && !details.isEmpty()) { _tmpltDao.saveDetails(template); } + if (templateTag != null) { + if (templateTag.isEmpty()) { + template.setTemplateTag(null); + } else { + template.setTemplateTag(templateTag); + } + } + _tmpltDao.update(id, template); return _tmpltDao.findById(id); diff --git a/test/integration/component/test_host_tags.py b/test/integration/component/test_host_tags.py new file mode 100644 index 000000000000..785af0d01df0 --- /dev/null +++ b/test/integration/component/test_host_tags.py @@ -0,0 +1,232 @@ +# 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 host tags (host, service offering, template) +""" +# Import Local Modules +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import cleanup_resources, validateList +from marvin.lib.base import (Account, + VirtualMachine, + Iso, + Host, + Template, + ServiceOffering, + Domain) +from marvin.lib.common import (get_zone, + get_domain, + get_template, + list_hosts) +from marvin.codes import FAILED, PASS +import time + +class TestHostTags(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestHostTags, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services = cls.testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + ) + cls.skip = False + if cls.template == FAILED: + cls.skip = True + return + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + ) + + # Create service offerings, disk offerings etc + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"] + ) + + cls.services["iso"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.hosts = list_hosts( + cls.apiclient, + zoneid=cls.zone.id, + type='Routing', + resourcestate='Enabled' + ) + cls._cleanup = [ + cls.account, + cls.service_offering + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + print("Cleanup resources used") + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + if self.skip: + self.skipTest("skip test") + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created accounts, domains etc + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + if not isinstance(self.template, Template): + self.template = Template(self.template.__dict__) + Template.update(self.template, self.apiclient, templatetag="") + for host in self.hosts: + Host.update(self.apiclient, id=host.id, hosttags="") + + return + + def set_host_tag(self, hostid, hosttag): + Host.update(self.apiclient, id=hostid, hosttags=hosttag) + + def set_template_tag(self, tag): + if not isinstance(self.template, Template): + self.template = Template(self.template.__dict__) + Template.update(self.template, self.apiclient, templatetag=tag) + + @attr(tags=["advanced", "advancedsg", "basic"], required_hardware="false") + def test_01_template_host_tag(cls): + """ Test template/host tag + """ + # Validate the following + # 1. Update template tag to 'template'. Deploy vm, it should fail + # 2. Update all hosts tag to 'host'. Deploy vm, it should fail + # 3. Update template tag to NULL, Deploy vm, it should succeed. stop/start/migrate should work + # 4. Update template tag to 'host', stop/start/migrate should work + # 5. Update template tag to 'template'. start/migrate should fail + # 6. Update a host tag to 'template'. + # 6.1 vm should be started on the host. migrate should fail + # 6.2 start vm on other hosts, it should fail + + # 1. Update template tag to 'template'. Deploy vm, it should fail + cls.set_template_tag("template") + try: + cls.vm_1 = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + ) + cls.fail("Deploy vm_1 should fail") + except Exception as e: + cls.debug("Failed to deploy vm_1 as expected") + + # 2. Update all hosts tag to 'host'. Deploy vm, it should fail + for host in cls.hosts: + cls.set_host_tag(host.id, "host") + + try: + cls.vm_2 = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + ) + cls.fail("Deploy vm_2 should fail") + except Exception as e: + cls.debug("Failed to deploy vm_2 as expected") + + # 3. Update template tag to NULL, Deploy vm, it should succeed. stop/start/migrate should work + cls.set_template_tag("") + cls.vm_3 = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + ) + cls.vm_3.stop(cls.apiclient, forced=True) + cls.vm_3.start(cls.apiclient) + if len(cls.hosts) > 1: + cls.vm_3.migrate(cls.apiclient) + + # 4. Update template tag to 'host', stop/start/migrate should work + cls.set_template_tag("host") + cls.vm_3.stop(cls.apiclient, forced=True) + cls.vm_3.start(cls.apiclient) + if len(cls.hosts) > 1: + cls.vm_3.migrate(cls.apiclient) + + # 5. Update template tag to 'template'. start/migrate should fail + cls.set_template_tag("template") + if len(cls.hosts) > 1: + try: + cls.vm_3.migrate(cls.apiclient) + cls.fail("migrate vm_3 should fail") + except Exception as e: + cls.debug("Failed to migrate vm_3 as expected") + cls.vm_3.stop(cls.apiclient, forced=True) + try: + cls.vm_3.start(cls.apiclient) + cls.fail("start vm_3 should fail") + except Exception as e: + cls.debug("Failed to start vm_3 as expected") + + # 6. Update a host tag to 'template'. + host = cls.hosts[0] + cls.set_host_tag(host.id, "template") + # 6.1 vm should be started on the host. migrate should fail + cls.vm_3.start(cls.apiclient, hostid = host.id) + list_vm_response = VirtualMachine.list( + cls.apiclient, + id=cls.vm_3.id + ) + vm_response = list_vm_response[0] + if vm_response.hostid != host.id: + cls.fail("vm_3 is not started on %s but on %s" % (host.name, vm_response.hostname)) + if len(cls.hosts) > 1: + try: + cls.vm_3.migrate(cls.apiclient) + cls.fail("migrate vm_3 should fail") + except Exception as e: + cls.debug("Failed to migrate vm_3 as expected") + # 6.2 start vm on other hosts, it should fail + if len(cls.hosts) > 1: + host_1 = cls.hosts[1] + try: + cls.vm_3.start(cls.apiclient, hostid = host_1.id) + cls.fail("start vm_3 on %s should fail" % host.name) + except Exception as e: + cls.debug("Failed to start vm_3 on %s as expected" % host.name) + diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 115c08b9264f..0f79606f8a78 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -713,10 +713,12 @@ def create(cls, apiclient, services, templateid=None, accountid=None, return VirtualMachine(virtual_machine.__dict__, services) - def start(self, apiclient): + def start(self, apiclient, hostid = None): """Start the instance""" cmd = startVirtualMachine.startVirtualMachineCmd() cmd.id = self.id + if hostid is not None: + cmd.hostid = hostid apiclient.startVirtualMachine(cmd) response = self.getState(apiclient, VirtualMachine.RUNNING) if response[0] == FAIL: @@ -1378,6 +1380,8 @@ def create(cls, apiclient, services, volumeid=None, "isextractable"] if "isextractable" in services else False cmd.passwordenabled = services[ "passwordenabled"] if "passwordenabled" in services else False + cmd.templatetag = services[ + "templatetag"] if "templatetag" in services else None if volumeid: cmd.volumeid = volumeid @@ -1445,6 +1449,8 @@ def register(cls, apiclient, services, zoneid=None, cmd.passwordenabled = services[ "passwordenabled"] if "passwordenabled" in services else False cmd.deployasis = services["deployasis"] if "deployasis" in services else False + cmd.templatetag = services[ + "templatetag"] if "templatetag" in services else None if account: cmd.account = account @@ -1667,6 +1673,8 @@ def create(cls, apiclient, services, account=None, domainid=None, cmd.isfeatured = services["isfeatured"] if "ispublic" in services: cmd.ispublic = services["ispublic"] + if "templatetag" in services: + cmd.templatetag = services["templatetag"] if account: cmd.account = account diff --git a/tools/marvin/marvin/lib/common.py b/tools/marvin/marvin/lib/common.py index 1c5fc32c3a7c..9c9d22664911 100644 --- a/tools/marvin/marvin/lib/common.py +++ b/tools/marvin/marvin/lib/common.py @@ -305,7 +305,7 @@ def get_pod(apiclient, zone_id=None, pod_id=None, pod_name=None): def get_template( apiclient, zone_id=None, ostype_desc=None, template_filter="featured", template_type='BUILTIN', template_id=None, template_name=None, account=None, domain_id=None, project_id=None, - hypervisor=None): + hypervisor=None, template_tag=None): ''' @Name : get_template @Desc : Retrieves the template Information based upon inputs provided @@ -331,6 +331,8 @@ def get_template( cmd.projectid = project_id if account is not None: cmd.account = account + if template_tag is not None: + cmd.templatetag = template_tag ''' Get the Templates pertaining to the inputs provided diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 458a0b738847..18fa78f6a26c 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2210,6 +2210,7 @@ "label.templatenames": "Template", "label.templates": "Templates", "label.templatesubject": "Subject", +"label.templatetag": "Template Tag", "label.templatetotal": "Template", "label.templatetype": "Template Type", "label.tftp.dir": "TFTP Directory", diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js index 7a4feaa7271a..cadc6c35fa88 100644 --- a/ui/src/config/section/image.js +++ b/ui/src/config/section/image.js @@ -46,7 +46,7 @@ export default { }, details: () => { var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'format', 'ostypename', 'size', 'isready', 'passwordenabled', - 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type', + 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type', 'templatetag', 'account', 'domain', 'created'] if (['Admin'].includes(store.getters.userInfo.roletype)) { fields.push('templatetype', 'url') @@ -104,7 +104,19 @@ export default { record.isready }, popup: true, - component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateTemplate.vue'))) + component: shallowRef(defineAsyncComponent(() => import('@/views/image/UpdateTemplate.vue'))), + args: (record, store) => { + var fields = ['name', 'displaytext', 'passwordenabled', 'ostypeid', 'isdynamicallyscalable'] + if (['Admin'].includes(store.userInfo.roletype)) { + fields.push('templatetype', 'templatetag') + } + return fields + }, + mapping: { + templatetype: { + options: ['BUILTIN', 'USER', 'SYSTEM', 'ROUTING'] + } + } }, { api: 'updateTemplatePermissions', @@ -189,7 +201,8 @@ export default { } return fields }, - details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'bootable', 'isready', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'crosszones', 'account', 'domain', 'created'], + details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'bootable', 'isready', 'directdownload', + 'isextractable', 'ispublic', 'isfeatured', 'crosszones', 'templatetag', 'account', 'domain', 'created'], searchFilters: ['name', 'zoneid', 'tags'], related: [{ name: 'vm', @@ -238,7 +251,13 @@ export default { !(record.account === 'system' && record.domainid === 1) && record.isready }, - args: ['name', 'displaytext', 'bootable', 'ostypeid'] + args: (record, store) => { + var fields = ['name', 'displaytext', 'bootable', 'ostypeid'] + if (['Admin'].includes(store.userInfo.roletype)) { + fields.push('templatetag') + } + return fields + } }, { api: 'updateIsoPermissions', diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index beac49fb9817..ac0ee7c08bed 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -1367,6 +1367,9 @@ export default { if (param.type === 'boolean') { params[key] = false } + if (param.name === 'templatetag') { + params[key] = '' + } break } if (input === '' && !['tags', 'hosttags', 'storagetags'].includes(key)) { diff --git a/ui/src/views/image/RegisterOrUploadIso.vue b/ui/src/views/image/RegisterOrUploadIso.vue index 155e61ee2a49..4fd0536c8638 100644 --- a/ui/src/views/image/RegisterOrUploadIso.vue +++ b/ui/src/views/image/RegisterOrUploadIso.vue @@ -137,6 +137,14 @@ + + + +
{{ $t('label.cancel') }} {{ $t('label.ok') }} diff --git a/ui/src/views/image/RegisterOrUploadTemplate.vue b/ui/src/views/image/RegisterOrUploadTemplate.vue index 64965a12af01..ff789e525ba5 100644 --- a/ui/src/views/image/RegisterOrUploadTemplate.vue +++ b/ui/src/views/image/RegisterOrUploadTemplate.vue @@ -302,6 +302,16 @@ + + + + + +
{{ $t('label.cancel') }} {{ $t('label.ok') }} From c62bfceba712521afa0c780f9923c106b23b0d74 Mon Sep 17 00:00:00 2001 From: Rakesh Venkatesh Date: Fri, 2 Oct 2020 11:12:24 +0200 Subject: [PATCH 2/3] Add test case for listing template with tags --- .../component/test_template_tags.py | 437 ++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 test/integration/component/test_template_tags.py diff --git a/test/integration/component/test_template_tags.py b/test/integration/component/test_template_tags.py new file mode 100644 index 000000000000..707480e7c809 --- /dev/null +++ b/test/integration/component/test_template_tags.py @@ -0,0 +1,437 @@ +# 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 template/iso tags +""" +from nose.plugins.attrib import attr +from marvin.cloudstackAPI import listZones, updateIso +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import cleanup_resources +from marvin.lib.base import (Account, + Iso, + Template, + Domain, + Zone) +from marvin.lib.common import (get_zone, + get_domain, + get_template, + list_isos) +import logging +import time + +class TestTemplateTags(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.testClient = super(TestTemplateTags, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.services["iso1"]["zoneid"] = cls.zone.id + cls.logger = logging.getLogger("TestTemplateTags") + cls.domain = get_domain(cls.apiclient) + + cls._cleanup = [] + cls.unsupportedHypervisor = False + cls.hypervisor = cls.testClient.getHypervisorInfo() + if cls.hypervisor.lower() in ['lxc']: + cls.unsupportedHypervisor = True + return + + # Create new domain1 + cls.domain1 = Domain.create( + cls.apiclient, + services=cls.services["acl"]["domain1"], + parentdomainid=cls.domain.id) + + # Create account1 + cls.account1 = Account.create( + cls.apiclient, + cls.services["acl"]["accountD1"], + domainid=cls.domain1.id + ) + + if cls.hypervisor.lower() in ['kvm', "simulator"]: + # register template + cls.template = Template.register(cls.apiclient, + cls.services["test_templates"]["kvm"], + zoneid=cls.zone.id, + domainid=cls.domain1.id, + account=cls.account1.name, + hypervisor=cls.hypervisor.lower()) + cls.services["test_templates"]["kvm"]["name"] = cls.account1.name + + cls.iso = Iso.create( + cls.apiclient, + cls.services["iso1"], + account=cls.account1.name, + domainid=cls.account1.domainid + ) + + cls._cleanup.append(cls.template) + cls._cleanup.append(cls.iso) + else: + return + + cls._cleanup.append(cls.account1) + cls._cleanup.append(cls.domain1) + + @classmethod + def tearDownClass(cls): + try: + cls.apiclient = super( + TestTemplateTags, + cls).getClsTestClient().getApiClient() + # Cleanup resources used + cleanup_resources(cls.apiclient, cls._cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + return + + def validate_uploaded_template(self, apiclient, template_id, retries=24, interval=5): + """Check if template download will finish in 2 minutes""" + while retries > -1: + time.sleep(interval) + template_response = Template.list( + apiclient, + id=template_id, + zoneid=self.zone.id, + templatefilter='self' + ) + + if isinstance(template_response, list): + template = template_response[0] + if not hasattr(template, 'status') or not template or not template.status: + retries = retries - 1 + continue + if 'Failed' in template.status: + raise Exception( + "Failed to download template: status - %s" % + template.status) + + elif template.status == 'Download Complete' and template.isready: + return + + elif 'Downloaded' in template.status: + retries = retries - 1 + continue + + elif 'Installing' not in template.status: + if retries >= 0: + retries = retries - 1 + continue + raise Exception( + "Error in downloading template: status - %s" % + template.status) + + else: + retries = retries - 1 + raise Exception("Template download failed exception.") + + def verify_upload_iso(self, iso): + try: + iso.download(self.apiclient) + except Exception as e: + self.fail("Exception while downloading ISO %s: %s" \ + % (iso.id, e)) + + def list_templates(self, tag): + return Template.list( + self.apiclient, + templatefilter='all', + zoneid=self.zone.id, + domainid=self.domain1.id, + account=self.account1.name, + templatetag=tag + ) + + def list_isos(self, tag): + return list_isos( + self.apiclient, + account=self.account1.name, + domainid=self.account1.domainid, + templatetag=tag + ) + + def update_iso(self, isoid, tag): + cmd = updateIso.updateIsoCmd() + # Assign new values to attributes + cmd.id = isoid + cmd.templatetag = tag + self.apiclient.updateIso(cmd) + + @attr(tags=["advanced", "basic", "sg"], required_hardware="false") + def test_01_update_template_tags(self): + """ + Verify that the template and iso can be updated with new template tags + and listing the templates/iso with tags should return the result + + Steps: + 1. Update the template tag of uploaded template as "TEST_TEMPLATE" + 2. List all the templates matching the tag "TEST_TEMPLATE" + 3. Make sure that the count is 1 + 4. Update the iso tag of uploaded iso as "TEST_ISO" + 5. List all the isos matching the tag "TEST_ISO" + 6. Make sure that the count is 1 + 7. Now list the template and iso with tag "dummy" + 8. Make sure that the count is 0 + :return: + """ + template_tag = "TEST_TEMPLATE" + iso_tag = "TEST_ISO" + + self.validate_uploaded_template(self.apiclient, self.template.id) + + list_template_response = self.list_templates(None) + + # Verify template response to check whether template added successfully + self.assertEqual( + isinstance(list_template_response, list), + True, + "No templates found" + ) + + template_response = list_template_response[0] + self.assertEqual( + template_response.isready, + True, + "Template state is not ready, it is %s" % template_response.isready + ) + + # step 1 + self.template.update(self.apiclient, + templatetag = template_tag) + + # Step 2 + list_template_response = self.list_templates(template_tag) + + # Step 3 + self.assertEqual( + len(list_template_response), + 1, + "Template tag not updated properly to %s" % template_tag + ) + + self.verify_upload_iso(self.iso) + list_iso_response = self.list_isos(None) + self.assertEqual( + isinstance(list_iso_response, list), + True, + "No iso found" + ) + + # Step 4 + self.update_iso(self.iso.id, iso_tag) + + # Step 5 + list_iso_response = self.list_isos(iso_tag) + + # Step 6 + self.assertEqual( + isinstance(list_iso_response, list), + True, + "Iso tag is not updated properly to %s" % iso_tag + ) + self.assertEqual( + len(list_iso_response), + 1, + "Check template available in List ISOs" + ) + + # Step 7 + list_template_response = self.list_templates("dummy") + self.assertEqual( + list_template_response, + None, + "Template tag should not be updated to dummy" + ) + + list_iso_response = self.list_isos("dummy") + + # Step 8 + self.assertEqual( + list_iso_response, + None, + "ISO tag should not be updated to dummy" + ) + + @attr(tags=["advanced", "basic", "sg"], required_hardware="false") + def test_02_register_template_with_tags(self): + """ + Register template and iso with new template tags + Verify that it can be registered with tags + Listing the templates/iso with tags should return the result + + Steps: + 1. Register the template with tag "REGISTER_TEMPLATE" + 2. Register the iso tag of uploaded iso as "REGISTER_ISO" + 3. List all the templates matching the tag "REGISTER_TEMPLATE" + 4. Make sure that the count is 1 + 5. List all the isos matching the tag "REGISTER_ISO" + 6. Make sure that the count is 1 + 7. Update the template tag to "dummy" + 8. List the templates matching tag "dummy" + 9. Make sure that the count is 1 + 10. List the iso matching tag "dummy" + 11. Make sure that the count is 1 + 12. List all the templates matching the tag "REGISTER_TEMPLATE" + 13. Make sure that the count is 0 + 14. List all the isos matching the tag "REGISTER_ISO" + 15. Make sure that the count is 0 + :return: + """ + template_tag = "REGISTER_TEMPLATE" + iso_tag = "REGISTER_ISO" + + # Step 1 + self.services["test_templates"]["kvm"]["templatetag"] = template_tag + template1 = Template.register(self.apiclient, + self.services["test_templates"]["kvm"], + zoneid=self.zone.id, + domainid=self.domain1.id, + account=self.account1.name, + hypervisor=self.hypervisor.lower() + ) + self.services["test_templates"]["kvm"]["name"] = self.account1.name + + # Step 2 + self.services["iso1"]["templatetag"] = iso_tag + iso1 = Iso.create(self.apiclient, + self.services["iso1"], + zoneid=self.zone.id, + account=self.account1.name, + domainid=self.account1.domainid + ) + + self.validate_uploaded_template(self.apiclient, template1.id) + + # Step 3 + list_template_response = self.list_templates(template_tag) + + # Verify template response to check whether template added successfully + self.assertEqual( + isinstance(list_template_response, list), + True, + "No templates found matching tag %s" % template_tag + ) + + # Step 4 + template_response = list_template_response[0] + self.assertEqual( + template_response.isready, + True, + "Template state is not ready, it is %s" % template_response.isready + ) + self.assertEqual( + len(list_template_response), + 1, + "No templates found" + ) + self.assertEqual( + template_response.templatetag, + template_tag, + "Template tag does not match" + ) + + self.verify_upload_iso(iso1) + + # Step 5 + list_iso_response = self.list_isos(iso_tag) + + # Step 6 + iso_response = list_iso_response[0] + self.assertEqual( + isinstance(list_iso_response, list), + True, + "Check list response returns a valid list" + ) + self.assertEqual( + len(list_iso_response), + 1, + "NO iso found with tag %s" % iso_tag + ) + self.assertEqual( + iso_response.templatetag, + iso_tag, + "ISO tag does not match" + ) + + # Step 7 + template1.update(self.apiclient, + templatetag = "dummy") + + # Step 8 + list_template_response = self.list_templates("dummy") + + # Step 9 + template_response = list_template_response[0] + self.assertEqual( + len(list_template_response), + 1, + "Check template available in List Templates" + ) + self.assertEqual( + template_response.templatetag, + "dummy", + "Template tag does not match" + ) + + # Step 10 + self.update_iso(iso1.id, "dummy") + + list_iso_response = self.list_isos("dummy") + + # Step 11 + iso_response = list_iso_response[0] + self.assertEqual( + isinstance(list_iso_response, list), + True, + "Check list response returns a valid list" + ) + self.assertEqual( + len(list_iso_response), + 1, + "ISO tag does not match" + ) + self.assertEqual( + iso_response.templatetag, + "dummy", + "ISO tag does not match" + ) + + # Step 12 + list_template_response = self.list_templates(template_tag) + + # Step 13 + self.assertEqual( + list_template_response, + None, + "Template tag not updated properly" + ) + + # Step 14 + list_iso_response = self.list_isos(iso_tag) + + # Step 15 + self.assertEqual( + list_iso_response, + None, + "ISO tag not updated properly" + ) From 73c3792225df787ef004e0206d842a727931acfa Mon Sep 17 00:00:00 2001 From: Rakesh Venkatesh Date: Wed, 15 Sep 2021 13:38:32 +0200 Subject: [PATCH 3/3] fix build error --- .../src/main/java/com/cloud/api/query/QueryManagerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 84a24db6faad..c3f95d32bc20 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -3746,14 +3746,14 @@ else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN) } return templateChecks(isIso, hypers, tags, name, keyword, hyperType, onlyReady, bootable, zoneId, showDomr, - showRemovedTmpl, parentTemplateId, showUnique, searchFilter, sc); + showRemovedTmpl, parentTemplateId, showUnique, searchFilter, sc, templateTag); } private Pair, Integer> templateChecks(boolean isIso, List hypers, Map tags, String name, String keyword, HypervisorType hyperType, boolean onlyReady, Boolean bootable, Long zoneId, boolean showDomr, boolean showRemovedTmpl, Long parentTemplateId, Boolean showUnique, - Filter searchFilter, SearchCriteria sc) { + Filter searchFilter, SearchCriteria sc, String templateTag) { if (!isIso) { // add hypervisor criteria for template case if (hypers != null && !hypers.isEmpty()) {