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..c3f95d32bc20 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; @@ -3742,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()) { @@ -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/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" + ) 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') }}