From ff77308e06804c027a469e4337cb9f0ca241e30b Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 29 Mar 2019 17:22:25 +0530 Subject: [PATCH] server: compute offerings for specified domain(s) and zone(s) Service or compute offerings will be allowed to link with specified domain(s) and zone(s). Compute offering linked with multiple domains and zones can be created both with UI and API. Refactored createServiceOffering API to allow passing list of domain and zone IDs with domainids and zoneids parameter respectively. UI has been refactored to allow selecting multiple domains and zones while creating compute offering using multi-select elements. When list of passed domains contain both parent and child domain, offering will be created for parent domain. Compute offering details will now show list of linked domains and zones as a comma-separated list of names. Linked domains and zones will be stored in the cloud.service_offering_details table in database as comma-separated list of IDs with key domainids and zoneids respectively. Signed-off-by: Abhishek Kumar --- .../java/com/cloud/user/AccountService.java | 3 +- .../cloudstack/acl/SecurityChecker.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 3 + .../offering/CreateServiceOfferingCmd.java | 37 ++++++- .../offering/UpdateServiceOfferingCmd.java | 3 +- .../offering/ListServiceOfferingsCmd.java | 13 ++- .../api/response/ServiceOfferingResponse.java | 7 +- .../java/com/cloud/dc/dao/DataCenterDao.java | 2 + .../com/cloud/dc/dao/DataCenterDaoImpl.java | 8 ++ .../java/com/cloud/domain/dao/DomainDao.java | 2 + .../com/cloud/domain/dao/DomainDaoImpl.java | 7 ++ .../com/cloud/service/ServiceOfferingVO.java | 14 +-- .../service/dao/ServiceOfferingDaoImpl.java | 52 ++++++--- .../quota/vo/ServiceOfferingVO.java | 2 +- .../management/MockAccountManager.java | 3 +- .../java/com/cloud/acl/DomainChecker.java | 63 +++++++---- .../main/java/com/cloud/api/ApiDBUtils.java | 41 ++++++- .../com/cloud/api/query/QueryManagerImpl.java | 93 ++++++++++------ .../ConfigurationManagerImpl.java | 104 +++++++++++++++--- .../com/cloud/user/AccountManagerImpl.java | 5 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 8 +- .../cloud/user/MockAccountManagerImpl.java | 3 +- .../vm/DeploymentPlanningManagerImplTest.java | 30 +++-- ui/l10n/en.js | 1 + ui/scripts/configuration.js | 95 +++++++++++++++- ui/scripts/docs.js | 7 +- ui/scripts/instanceWizard.js | 4 + 27 files changed, 476 insertions(+), 136 deletions(-) diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 060861d18091..1188819c2ef3 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.api.command.admin.user.RegisterCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; +import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; import com.cloud.exception.PermissionDeniedException; import com.cloud.offering.DiskOffering; @@ -96,7 +97,7 @@ UserAccount createUserAccount(String userName, String password, String firstName void checkAccess(Account account, AccessType accessType, boolean sameOwner, ControlledEntity... entities) throws PermissionDeniedException; - void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException; + void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException; void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException; diff --git a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java index 41708717548a..bd6b529fa49b 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java @@ -136,7 +136,7 @@ boolean checkAccess(Account caller, AccessType accessType, String action, Contro boolean checkAccess(Account account, DataCenter zone) throws PermissionDeniedException; - public boolean checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException; + public boolean checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException; boolean checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 0c1f2b11f327..f0d67ec02184 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -111,6 +111,8 @@ public class ApiConstants { public static final String IP6_DNS2 = "ip6dns2"; public static final String DOMAIN = "domain"; public static final String DOMAIN_ID = "domainid"; + public static final String DOMAIN_ID_LIST = "domainids"; + public static final String DOMAIN_NAME_LIST = "domainnames"; public static final String DOMAIN__ID = "domainId"; public static final String DURATION = "duration"; public static final String ELIGIBLE = "eligible"; @@ -706,6 +708,7 @@ public class ApiConstants { public static final String NETSCALER_SERVICEPACKAGE_ID = "netscalerservicepackageid"; public static final String ZONE_ID_LIST = "zoneids"; + public static final String ZONE_NAME_LIST = "zonenames"; public static final String DESTINATION_ZONE_ID_LIST = "destzoneids"; public static final String ADMIN = "admin"; public static final String CHECKSUM_PARAMETER_PREFIX_DESCRIPTION = "The parameter containing the checksum will be considered a MD5sum if it is not prefixed\n" diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index ed87ad86c409..d260936c3a23 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -16,12 +16,13 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; -import com.cloud.storage.Storage; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -30,9 +31,11 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.log4j.Logger; import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Storage; import com.cloud.user.Account; @APICommand(name = "createServiceOffering", description = "Creates a service offering.", responseObject = ServiceOfferingResponse.class, @@ -86,6 +89,24 @@ public class CreateServiceOfferingCmd extends BaseCmd { description = "the ID of the containing domain, null for public offerings") private Long domainId; + @Parameter(name = ApiConstants.DOMAIN_ID_LIST, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = DomainResponse.class, + required = false, + description = "the ID of the domains offering is associated with, null for all domain offerings", + since = "4.13") + private List domainIds; + + @Parameter(name = ApiConstants.ZONE_ID_LIST, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = ZoneResponse.class, + required = false, + description = "the ID of the zones offering is associated with, null for all zone offerings", + since = "4.13") + private List zoneIds; + @Parameter(name = ApiConstants.HOST_TAGS, type = CommandType.STRING, description = "the host tag for this service offering.") private String hostTag; @@ -214,6 +235,20 @@ public Long getDomainId() { return domainId; } + public List getDomainIds() { + if (domainId != null) { + if (domainIds == null) { + domainIds = new ArrayList<>(); + } + domainIds.add(domainId); + } + return domainIds; + } + + public List getZoneIds() { + return zoneIds; + } + public String getHostTag() { return hostTag; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java index f4f4bdfac885..78f6c07b054c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -25,6 +23,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.log4j.Logger; import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java index 4b7481974f24..9b81066d4eae 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.offering; -import org.apache.log4j.Logger; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListDomainResourcesCmd; @@ -25,6 +23,8 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.log4j.Logger; @APICommand(name = "listServiceOfferings", description = "Lists all available service offerings.", responseObject = ServiceOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -57,6 +57,13 @@ public class ListServiceOfferingsCmd extends BaseListDomainResourcesCmd { description = "the system VM type. Possible types are \"consoleproxy\", \"secondarystoragevm\" or \"domainrouter\".") private String systemVmType; + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "id of zone disk offering is associated with", + since = "4.13") + private Long zoneId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -81,6 +88,8 @@ public String getSystemVmType() { return systemVmType; } + public Long getZoneId() { return zoneId; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index 51b5c1f6db8b..9ccc7d756efb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -19,14 +19,13 @@ import java.util.Date; import java.util.Map; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import com.cloud.offering.ServiceOffering; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = ServiceOffering.class) public class ServiceOfferingResponse extends BaseResponse { @@ -416,6 +415,10 @@ public void setIopsWriteRate(Long iopsWriteRate) { public void setIopsWriteRateMaxLength(Long iopsWriteRateMaxLength) { this.iopsWriteRateMaxLength = iopsWriteRateMaxLength; } + public Map getDetails() { + return details; + } + public void setDetails(Map details) { this.details = details; } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java index a6cd59f1cc35..e006b0e4573b 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java @@ -121,4 +121,6 @@ public Integer getVlan() { List findByKeyword(String keyword); List listAllZones(); + + List list(Object[] ids); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java index 385fb4061551..2db904f78c9f 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java @@ -437,4 +437,12 @@ public List listAllZones() { return dcs; } + + @Override + public List list(Object[] ids) { + SearchBuilder sb = createSearchBuilder(); + SearchCriteria sc = sb.create(); + sc.addAnd("id", SearchCriteria.Op.IN, ids); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java index 297fbfad6de9..45ff6b5df545 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java @@ -40,4 +40,6 @@ public interface DomainDao extends GenericDao { Set getDomainParentIds(long domainId); List getDomainChildrenIds(String path); + + List list(Object[] ids); } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java index 4316e3bc8ed1..45be8ea2af17 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java @@ -291,4 +291,11 @@ public Set getDomainParentIds(long domainId) { return parentDomains; } + @Override + public List list(Object[] ids) { + SearchBuilder sb = createSearchBuilder(); + SearchCriteria sc = sb.create(); + sc.addAnd("id", SearchCriteria.Op.IN, ids); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index efaadcfae106..4373a7ff1885 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -105,8 +105,8 @@ public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer spee } public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitCpuUse, - boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, Long domainId) { - super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true, domainId); + boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType) { + super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); this.cpu = cpu; this.ramSize = ramSize; this.speed = speed; @@ -120,7 +120,7 @@ public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer spee public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, Long domainId, String hostTag) { + VirtualMachine.Type vmType, String hostTag) { this(name, cpu, ramSize, @@ -136,14 +136,13 @@ public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer spee recreatable, tags, systemUse, - vmType, - domainId); + vmType); this.hostTag = hostTag; } public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, - VirtualMachine.Type vmType, Long domainId, String hostTag, String deploymentPlanner) { + VirtualMachine.Type vmType, String hostTag, String deploymentPlanner) { this(name, cpu, ramSize, @@ -160,7 +159,6 @@ public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer spee tags, systemUse, vmType, - domainId, hostTag); this.deploymentPlanner = deploymentPlanner; } @@ -177,7 +175,7 @@ public ServiceOfferingVO(ServiceOfferingVO offering) { offering.isSystemUse(), true, offering.isCustomizedIops()== null ? false:offering.isCustomizedIops(), - offering.getDomainId(), + null, offering.getMinIops(), offering.getMaxIops()); cpu = offering.getCpu(); diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 81ecc677e6fe..d3f9d6f0cffc 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -24,6 +24,7 @@ import javax.inject.Inject; import javax.persistence.EntityExistsException; +import org.apache.cloudstack.api.ApiConstants; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -38,6 +39,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.UserVmDetailsDao; +import com.google.common.base.Strings; @Component @DB() @@ -50,7 +52,6 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase UniqueNameSearch; - protected final SearchBuilder ServiceOfferingsByDomainIdSearch; protected final SearchBuilder SystemServiceOffering; protected final SearchBuilder ServiceOfferingsByKeywordSearch; protected final SearchBuilder PublicServiceOfferingSearch; @@ -63,19 +64,13 @@ public ServiceOfferingDaoImpl() { UniqueNameSearch.and("system", UniqueNameSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); UniqueNameSearch.done(); - ServiceOfferingsByDomainIdSearch = createSearchBuilder(); - ServiceOfferingsByDomainIdSearch.and("domainId", ServiceOfferingsByDomainIdSearch.entity().getDomainId(), SearchCriteria.Op.EQ); - ServiceOfferingsByDomainIdSearch.done(); - SystemServiceOffering = createSearchBuilder(); - SystemServiceOffering.and("domainId", SystemServiceOffering.entity().getDomainId(), SearchCriteria.Op.EQ); SystemServiceOffering.and("system", SystemServiceOffering.entity().isSystemUse(), SearchCriteria.Op.EQ); SystemServiceOffering.and("vm_type", SystemServiceOffering.entity().getSpeed(), SearchCriteria.Op.EQ); SystemServiceOffering.and("removed", SystemServiceOffering.entity().getRemoved(), SearchCriteria.Op.NULL); SystemServiceOffering.done(); PublicServiceOfferingSearch = createSearchBuilder(); - PublicServiceOfferingSearch.and("domainId", PublicServiceOfferingSearch.entity().getDomainId(), SearchCriteria.Op.NULL); PublicServiceOfferingSearch.and("system", PublicServiceOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); PublicServiceOfferingSearch.and("removed", PublicServiceOfferingSearch.entity().getRemoved(), SearchCriteria.Op.NULL); PublicServiceOfferingSearch.done(); @@ -129,25 +124,34 @@ public ServiceOfferingVO persistSystemServiceOffering(ServiceOfferingVO offering @Override public List findServiceOfferingByDomainId(Long domainId) { - SearchCriteria sc = ServiceOfferingsByDomainIdSearch.create(); - sc.setParameters("domainId", domainId); - return listBy(sc); + return filterOfferingsForDomain(listAll(), domainId); } @Override public List findSystemOffering(Long domainId, Boolean isSystem, String vmType) { SearchCriteria sc = SystemServiceOffering.create(); - sc.setParameters("domainId", domainId); sc.setParameters("system", isSystem); sc.setParameters("vm_type", vmType); - return listBy(sc); + return filterOfferingsForDomain(listBy(sc), domainId); } @Override public List findPublicServiceOfferings() { SearchCriteria sc = PublicServiceOfferingSearch.create(); sc.setParameters("system", false); - return listBy(sc); + List offerings = listBy(sc); + + if(offerings!=null) { + for (int i = offerings.size() - 1; i >= 0; i--) { + ServiceOfferingVO offering = offerings.get(i); + Map offeringDetails = detailsDao.listDetailsKeyPairs(offering.getId()); + if (!Strings.isNullOrEmpty(offeringDetails.get(ApiConstants.DOMAIN_ID_LIST))) { + // TODO: For ROOT domainId? + offerings.remove(i); + } + } + } + return offerings; } @Override @@ -289,4 +293,26 @@ public ServiceOfferingVO findDefaultSystemOffering(String offeringName, Boolean } return serviceOffering; } + + private List filterOfferingsForDomain(final List offerings, Long domainId) { + List filteredOfferings = null; + if (offerings != null && !offerings.isEmpty() && domainId != null) { + filteredOfferings = new ArrayList<>(offerings); + for (int i = filteredOfferings.size() - 1; i >= 0; i--) { + ServiceOfferingVO offering = offerings.get(i); + Map offeringDetails = detailsDao.listDetailsKeyPairs(offering.getId()); + if (!Strings.isNullOrEmpty(offeringDetails.get(ApiConstants.DOMAIN_ID_LIST))) { + String[] domainIdsArray = offeringDetails.get(ApiConstants.DOMAIN_ID_LIST).split(","); + List domainIds = new ArrayList<>(); + for (String dIdStr : domainIdsArray) { + domainIds.add(Long.valueOf(dIdStr.trim())); + } + if (!domainIds.contains(domainId)) { + filteredOfferings.remove(i); + } + } + } + } + return filteredOfferings; + } } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java index 00bdb82cdec6..57b596a6c32f 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java @@ -133,7 +133,7 @@ public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer spee public ServiceOfferingVO(ServiceOfferingVO offering) { super(offering.getId(), offering.getName(), offering.getDisplayText(), offering.getProvisioningType(), false, offering.getTags(), offering.isRecreatable(), - offering.isUseLocalStorage(), offering.isSystemUse(), true, offering.isCustomizedIops() == null ? false : offering.isCustomizedIops(), offering.getDomainId(), + offering.isUseLocalStorage(), offering.isSystemUse(), true, offering.isCustomizedIops() == null ? false : offering.isCustomizedIops(), null, offering.getMinIops(), offering.getMaxIops()); cpu = offering.getCpu(); ramSize = offering.getRamSize(); diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index 100f38060be4..888389865f62 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -41,6 +41,7 @@ import com.cloud.api.query.vo.ControlledViewEntity; import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.dao.ResourceCountDao; +import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.PermissionDeniedException; @@ -430,7 +431,7 @@ public Long finalyzeAccountId(String accountName, Long domainId, Long projectId, } @Override - public void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException { + public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { // TODO Auto-generated method stub } diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index c9e4087f6baf..4f3a73ce7331 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -16,13 +16,17 @@ // under the License. package com.cloud.acl; -import javax.inject.Inject; +import java.util.Arrays; +import java.util.List; +import java.util.Map; -import org.springframework.stereotype.Component; +import javax.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.api.ApiConstants; +import org.springframework.stereotype.Component; import com.cloud.dc.DataCenter; import com.cloud.dc.DedicatedResourceVO; @@ -36,6 +40,7 @@ import com.cloud.offering.ServiceOffering; import com.cloud.projects.ProjectManager; import com.cloud.projects.dao.ProjectAccountDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.LaunchPermissionVO; import com.cloud.storage.dao.LaunchPermissionDao; import com.cloud.template.VirtualMachineTemplate; @@ -44,6 +49,7 @@ import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.component.AdapterBase; +import com.google.common.base.Strings; @Component public class DomainChecker extends AdapterBase implements SecurityChecker { @@ -64,6 +70,8 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { private DedicatedResourceDao _dedicatedDao; @Inject AccountService _accountService; + @Inject + ServiceOfferingDetailsDao serviceOfferingDetailsDao; protected DomainChecker() { super(); @@ -206,42 +214,49 @@ else if (_accountService.isNormalUser(account.getId()) } @Override - public boolean checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException { - if (account == null || so.getDomainId() == null) {//public offering - return true; + public boolean checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { + boolean isAccess = false; + Map details = null; + if (account == null || so == null) {//public offering + isAccess = true; } else { //admin has all permissions if (_accountService.isRootAdmin(account.getId())) { - return true; + isAccess = true; } //if account is normal user or domain admin - //check if account's domain is a child of zone's domain (Note: This is made consistent with the list command for service offering) + //check if account's domain is a child of zone's domain (Note: This is made consistent with the list command for disk offering) else if (_accountService.isNormalUser(account.getId()) || account.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN || _accountService.isDomainAdmin(account.getId()) || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { - if (account.getDomainId() == so.getDomainId()) { - return true; //service offering and account at exact node - } else { - Domain domainRecord = _domainDao.findById(account.getDomainId()); - if (domainRecord != null) { - while (true) { - if (domainRecord.getId() == so.getDomainId()) { - //found as a child - return true; - } - if (domainRecord.getParent() != null) { - domainRecord = _domainDao.findById(domainRecord.getParent()); - } else { - break; - } + details = serviceOfferingDetailsDao.listDetailsKeyPairs(so.getId()); + isAccess = true; + if (details.containsKey(ApiConstants.DOMAIN_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.DOMAIN_ID_LIST))) { + List domainIds = Arrays.asList(details.get(ApiConstants.DOMAIN_ID_LIST).split(",")); + for (String domainId : domainIds) { + if (!_domainDao.isChildDomain(Long.valueOf(domainId), account.getDomainId())) { + isAccess = false; + break; } } } } } - //not found - return false; + + // Check for zones + if (isAccess && so != null && zone != null) { + if (details == null) + details = serviceOfferingDetailsDao.listDetailsKeyPairs(so.getId()); + if (details.containsKey(ApiConstants.ZONE_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.ZONE_ID_LIST))) { + List zoneIds = Arrays.asList(details.get(ApiConstants.ZONE_ID_LIST).split(",")); + isAccess = zoneIds.contains(String.valueOf(zone.getId())); + } + } + + return isAccess; } @Override diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 22ed2437d4de..7bcf66afd524 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.ApiCommandJobType; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.DomainDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; @@ -185,6 +186,7 @@ import com.cloud.network.dao.AccountGuestVlanMapVO; import com.cloud.network.dao.FirewallRulesCidrsDao; import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.FirewallRulesDcidrsDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerDao; @@ -277,6 +279,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountDetailsDao; +import com.cloud.user.AccountManager; import com.cloud.user.AccountService; import com.cloud.user.AccountVO; import com.cloud.user.ResourceLimitService; @@ -312,8 +315,7 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import com.cloud.user.AccountManager; -import com.cloud.network.dao.FirewallRulesDcidrsDao; +import com.google.common.base.Strings; public class ApiDBUtils { private static ManagementServer s_ms; @@ -1914,7 +1916,40 @@ public static DiskOfferingJoinVO newDiskOfferingView(DiskOffering offering) { } public static ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offering) { - return s_serviceOfferingJoinDao.newServiceOfferingResponse(offering); + ServiceOfferingResponse serviceOfferingResponse = s_serviceOfferingJoinDao.newServiceOfferingResponse(offering); + if(serviceOfferingResponse!=null) { + Map details = serviceOfferingResponse.getDetails(); + if (details != null && !details.isEmpty()) { + if(details.containsKey(ApiConstants.DOMAIN_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.DOMAIN_ID_LIST))) { + String[] domainIdsArray = details.get(ApiConstants.DOMAIN_ID_LIST).split(","); + List domains = s_domainDao.list(domainIdsArray); + List domainIdsList = new ArrayList<>(); + List domainNamesList = new ArrayList<>(); + for (DomainVO domain : domains) { + domainIdsList.add(domain.getUuid()); + domainNamesList.add(domain.getName()); + } + details.put(ApiConstants.DOMAIN_ID_LIST, String.join(",", domainIdsList)); + details.put(ApiConstants.DOMAIN_NAME_LIST, String.join(", ", domainNamesList)); + } + if(details.containsKey(ApiConstants.ZONE_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.ZONE_ID_LIST))) { + String[] zoneIdsArray = details.get(ApiConstants.ZONE_ID_LIST).split(","); + List zones = s_zoneDao.list(zoneIdsArray); + List zoneIdsList = new ArrayList<>(); + List zoneNamesList = new ArrayList<>(); + for (DataCenterVO zone : zones) { + zoneIdsList.add(zone.getUuid()); + zoneNamesList.add(zone.getName()); + } + details.put(ApiConstants.ZONE_ID_LIST, String.join(",", zoneIdsList)); + details.put(ApiConstants.ZONE_NAME_LIST, String.join(", ", zoneNamesList)); + } + serviceOfferingResponse.setDetails(details); + } + } + return serviceOfferingResponse; } public static ServiceOfferingJoinVO newServiceOfferingView(ServiceOffering offering) { 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 91e0466d9dbb..4e96ca83a020 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -25,14 +25,13 @@ import javax.inject.Inject; -import com.cloud.cluster.ManagementServerHostVO; -import com.cloud.cluster.dao.ManagementServerHostDao; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.ResourceDetail; import org.apache.cloudstack.api.ResponseObject.ResponseView; @@ -156,6 +155,8 @@ import com.cloud.api.query.vo.UserAccountJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.api.query.vo.VolumeJoinVO; +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; import com.cloud.domain.Domain; @@ -185,6 +186,7 @@ import com.cloud.server.TaggedResourceService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DataStoreRole; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; @@ -223,6 +225,7 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.base.Strings; @Component public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable { @@ -327,6 +330,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private ServiceOfferingDao _srvOfferingDao; + @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject private DataCenterJoinDao _dcJoinDao; @@ -2715,36 +2721,6 @@ private Pair, Integer> searchForServiceOfferingsInte if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL) { throw new InvalidParameterValueException("Only ROOT admins and Domain admins can list service offerings with isrecursive=true"); } - DomainVO domainRecord = _domainDao.findById(caller.getDomainId()); - sc.addAnd("domainPath", SearchCriteria.Op.LIKE, domainRecord.getPath() + "%"); - } else { // domain + all ancestors - // find all domain Id up to root domain for this account - List domainIds = new ArrayList(); - DomainVO domainRecord; - if (vmId != null) { - UserVmVO vmInstance = _userVmDao.findById(vmId); - domainRecord = _domainDao.findById(vmInstance.getDomainId()); - if (domainRecord == null) { - s_logger.error("Could not find the domainId for vmId:" + vmId); - throw new CloudAuthenticationException("Could not find the domainId for vmId:" + vmId); - } - } else { - domainRecord = _domainDao.findById(caller.getDomainId()); - if (domainRecord == null) { - s_logger.error("Could not find the domainId for account:" + caller.getAccountName()); - throw new CloudAuthenticationException("Could not find the domainId for account:" + caller.getAccountName()); - } - } - domainIds.add(domainRecord.getId()); - while (domainRecord.getParent() != null) { - domainRecord = _domainDao.findById(domainRecord.getParent()); - domainIds.add(domainRecord.getId()); - } - - SearchCriteria spc = _srvOfferingJoinDao.createSearchCriteria(); - spc.addOr("domainId", SearchCriteria.Op.IN, domainIds.toArray()); - spc.addOr("domainId", SearchCriteria.Op.NULL); // include public offering as well - sc.addAnd("domainId", SearchCriteria.Op.SC, spc); } } else { // for root users @@ -2786,7 +2762,58 @@ private Pair, Integer> searchForServiceOfferingsInte //Couldn't figure out a smart way to filter offerings based on tags in sql so doing it in Java. List filteredOfferings = filterOfferingsOnCurrentTags(result.first(), currentVmOffering); - return new Pair<>(filteredOfferings, result.second()); + result = new Pair<>(filteredOfferings, result.second()); + + // Remove offerings that are not associated with caller's domain and passed zone + // TODO: Better approach + if (result.first() != null && !result.first().isEmpty()) { + List offerings = result.first(); + for (int i = offerings.size() - 1; i >= 0; i--) { + ServiceOfferingJoinVO offering = offerings.get(i); + Map details = serviceOfferingDetailsDao.listDetailsKeyPairs(offering.getId()); + boolean toRemove = caller.getType() == Account.ACCOUNT_TYPE_ADMIN ? false : isRecursive; + if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN && + details.containsKey(ApiConstants.DOMAIN_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.DOMAIN_ID_LIST))) { + toRemove = true; + String[] domainIdsArray = details.get(ApiConstants.DOMAIN_ID_LIST).split(","); + for (String dIdStr : domainIdsArray) { + Long dId = Long.valueOf(dIdStr.trim()); + if(isRecursive) { + if (_domainDao.isChildDomain(caller.getDomainId(), dId)) { + toRemove = false; + break; + } + } else { + if (_domainDao.isChildDomain(dId, caller.getDomainId())) { + toRemove = false; + break; + } + } + } + } + if (toRemove) { + offerings.remove(i); + } else { + // If zoneid is passed remove offerings that are not associated with the zone + if (cmd.getZoneId() != null) { + final Long zoneId = cmd.getZoneId(); + if (details.containsKey(ApiConstants.ZONE_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.ZONE_ID_LIST))) { + String[] zoneIdsArray = details.get(ApiConstants.ZONE_ID_LIST).split(","); + List zoneIds = new ArrayList<>(); + for (String zId : zoneIdsArray) + zoneIds.add(Long.valueOf(zId.trim())); + if (!zoneIds.contains(zoneId)) { + offerings.remove(i); + } + } + } + } + } + } + + return result; } @Override diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 30f2f7cd1e22..8715384c379b 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -35,12 +35,11 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.google.common.collect.Sets; - import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; @@ -232,6 +231,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.common.collect.Sets; public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable { public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class); @@ -2329,7 +2329,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) } return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(), - cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), + cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainIds(), cmd.getZoneIds(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), cmd.getDetails(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), cmd.getBytesReadRate(), cmd.getBytesReadRateMax(), cmd.getBytesReadRateMaxLength(), cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(), @@ -2340,7 +2340,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) protected ServiceOfferingVO createServiceOffering(final long userId, final boolean isSystem, final VirtualMachine.Type vmType, final String name, final Integer cpu, final Integer ramSize, final Integer speed, final String displayText, final String provisioningType, final boolean localStorageRequired, - final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final Long domainId, final String hostTag, + final boolean offerHA, final boolean limitResourceUse, final boolean volatileVm, String tags, final List domainIds, final List zoneIds, final String hostTag, final Integer networkRate, final String deploymentPlanner, final Map details, final Boolean isCustomizedIops, Long minIops, Long maxIops, Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength, Long bytesWriteRate, Long bytesWriteRateMax, Long bytesWriteRateMaxLength, @@ -2353,28 +2353,38 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole if (user == null || user.getRemoved() != null) { throw new InvalidParameterValueException("Unable to find active user by id " + userId); } + // Filter child domains when both parent and child domains are present + List filteredDomainIds = filterChildSubDomains(domainIds); final Account account = _accountDao.findById(user.getAccountId()); if (account.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) { - if (domainId == null) { + if (filteredDomainIds.isEmpty()) { throw new InvalidParameterValueException("Unable to create public service offering by id " + userId + " because it is domain-admin"); } if (tags != null || hostTag != null) { throw new InvalidParameterValueException("Unable to create service offering with storage tags or host tags by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), domainId)) { - throw new InvalidParameterValueException("Unable to create service offering by another domain admin with id " + userId); + for (Long domainId : filteredDomainIds) { + if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to create service offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to create service offering by id " + userId + " because it is not root-admin or domain-admin"); } + if (zoneIds != null) { + for (Long zoneId : zoneIds) { + if (_zoneDao.findById(zoneId) == null) + throw new InvalidParameterValueException("Unable to create disk offering associated with invalid zone, " + zoneId); + } + } + final ProvisioningType typedProvisioningType = ProvisioningType.getProvisioningType(provisioningType); tags = StringUtils.cleanupTags(tags); ServiceOfferingVO offering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, - limitResourceUse, volatileVm, displayText, typedProvisioningType, localStorageRequired, false, tags, isSystem, vmType, - domainId, hostTag, deploymentPlanner); + limitResourceUse, volatileVm, displayText, typedProvisioningType, localStorageRequired, false, tags, isSystem, vmType, hostTag, deploymentPlanner); if (Boolean.TRUE.equals(isCustomizedIops)) { minIops = null; @@ -2470,6 +2480,22 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntry.getValue(), true)); } } + if(!filteredDomainIds.isEmpty()) { + List domainIdsStringList = new ArrayList<>(); + for(Long domainId : filteredDomainIds) + domainIdsStringList.add(String.valueOf(domainId)); + if(detailsVO==null) + detailsVO = new ArrayList(); + detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), ApiConstants.DOMAIN_ID_LIST, String.join(",", domainIdsStringList), true)); + } + if(zoneIds!=null && !zoneIds.isEmpty()) { + List zoneIdsStringList = new ArrayList<>(); + for(Long zoneId : zoneIds) + zoneIdsStringList.add(String.valueOf(zoneId)); + if(detailsVO==null) + detailsVO = new ArrayList(); + detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), ApiConstants.ZONE_ID_LIST, String.join(",", zoneIdsStringList), true)); + } if ((offering = _serviceOfferingDao.persist(offering)) != null) { if (detailsVO != null && !detailsVO.isEmpty()) { @@ -2500,6 +2526,15 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) // Verify input parameters final ServiceOffering offeringHandle = _entityMgr.findById(ServiceOffering.class, id); + List existingDomainIds = new ArrayList<>(); + Map details = _serviceOfferingDetailsDao.listDetailsKeyPairs(id); + if (details.containsKey(ApiConstants.DOMAIN_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.DOMAIN_ID_LIST))) { + String[] domainIdsArray = details.get(ApiConstants.DOMAIN_ID_LIST).split(","); + for (String dIdStr : domainIdsArray) { + existingDomainIds.add(Long.valueOf(dIdStr.trim())); + } + } if (offeringHandle == null) { throw new InvalidParameterValueException("unable to find service offering " + id); @@ -2510,12 +2545,16 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) throw new InvalidParameterValueException("Unable to find active user by id " + userId); } final Account account = _accountDao.findById(user.getAccountId()); + if (account.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) { - if (offeringHandle.getDomainId() == null) { + + if (existingDomainIds.isEmpty()) { throw new InvalidParameterValueException("Unable to update public service offering by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), offeringHandle.getDomainId() )) { - throw new InvalidParameterValueException("Unable to update service offering by another domain admin with id " + userId); + for (Long domainId : existingDomainIds) { + if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to update service offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to update service offering by id " + userId + " because it is not root-admin or domain-admin"); @@ -2919,11 +2958,22 @@ public boolean deleteServiceOffering(final DeleteServiceOfferingCmd cmd) { } final Account account = _accountDao.findById(user.getAccountId()); if (account.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) { - if (offering.getDomainId() == null) { + List existingDomainIds = new ArrayList<>(); + Map details = _serviceOfferingDetailsDao.listDetailsKeyPairs(offeringId); + if (details.containsKey(ApiConstants.DOMAIN_ID_LIST) && + !Strings.isNullOrEmpty(details.get(ApiConstants.DOMAIN_ID_LIST))) { + String[] domainIdsArray = details.get(ApiConstants.DOMAIN_ID_LIST).split(","); + for (String dIdStr : domainIdsArray) { + existingDomainIds.add(Long.valueOf(dIdStr.trim())); + } + } + if (existingDomainIds.isEmpty()) { throw new InvalidParameterValueException("Unable to delete public service offering by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), offering.getDomainId() )) { - throw new InvalidParameterValueException("Unable to delete service offering by another domain admin with id " + userId); + for (Long domainId : existingDomainIds) { + if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to delete service offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to delete service offering by id " + userId + " because it is not root-admin or domain-admin"); @@ -5723,6 +5773,30 @@ private boolean checkOverlapPortableIpRange(final int regionId, final String new return false; } + private List filterChildSubDomains(final List domainIds) { + List filteredDomainIds = new ArrayList<>(); + if (domainIds != null) { + filteredDomainIds.addAll(domainIds); + } + if (filteredDomainIds.size() > 1) { + for (int i = filteredDomainIds.size() - 1; i >= 1; i--) { + long first = filteredDomainIds.get(i); + for (int j = i - 1; j >= 0; j--) { + long second = filteredDomainIds.get(j); + if (_domainDao.isChildDomain(filteredDomainIds.get(i), filteredDomainIds.get(j))) { + filteredDomainIds.remove(j); + i--; + } + if (_domainDao.isChildDomain(filteredDomainIds.get(j), filteredDomainIds.get(i))) { + filteredDomainIds.remove(i); + break; + } + } + } + } + return filteredDomainIds; + } + public List getSecChecker() { return _secChecker; } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index bda4ccab4680..688914253dd4 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -77,6 +77,7 @@ import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.dao.ResourceCountDao; import com.cloud.configuration.dao.ResourceLimitDao; +import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DataCenterDao; @@ -2829,9 +2830,9 @@ public UserAccount getUserAccountById(Long userId) { } @Override - public void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException { + public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { for (SecurityChecker checker : _securityCheckers) { - if (checker.checkAccess(account, so)) { + if (checker.checkAccess(account, so, zone)) { if (s_logger.isDebugEnabled()) { s_logger.debug("Access granted to " + account + " to " + so + " by " + checker.getName()); } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 68b45e1af7c3..bc9f0bdaf201 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -1104,7 +1104,7 @@ private UserVm upgradeStoppedVirtualMachine(Long vmId, Long svcOffId, Map vpcSupportedHTypes = _vpcMgr.getSupportedVpcHypervisors(); diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java index 4fbf7526f312..29698971f468 100644 --- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java +++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java @@ -36,6 +36,7 @@ import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import com.cloud.api.query.vo.ControlledViewEntity; +import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.PermissionDeniedException; @@ -212,7 +213,7 @@ public void checkAccess(Account account, Domain domain) throws PermissionDeniedE } @Override - public void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException { + public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { // TODO Auto-generated method stub } diff --git a/server/src/test/java/com/cloud/vm/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/vm/DeploymentPlanningManagerImplTest.java index 5d8f9ad21593..c17f0a84f4e9 100644 --- a/server/src/test/java/com/cloud/vm/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/DeploymentPlanningManagerImplTest.java @@ -28,8 +28,17 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.host.Host; +import org.apache.cloudstack.affinity.AffinityGroupProcessor; +import org.apache.cloudstack.affinity.AffinityGroupService; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.test.utils.SpringUtils; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -50,17 +59,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; -import org.apache.cloudstack.affinity.AffinityGroupProcessor; -import org.apache.cloudstack.affinity.AffinityGroupService; -import org.apache.cloudstack.affinity.dao.AffinityGroupDao; -import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; -import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.messagebus.MessageBus; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.test.utils.SpringUtils; - import com.cloud.agent.AgentManager; import com.cloud.capacity.CapacityManager; import com.cloud.capacity.dao.CapacityDao; @@ -84,6 +82,7 @@ import com.cloud.exception.AffinityConflictException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.gpu.dao.HostGpuGroupsDao; +import com.cloud.host.Host; import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostTagsDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -143,7 +142,6 @@ public class DeploymentPlanningManagerImplTest { @Mock Host host; - private static long domainId = 5L; private static long dataCenterId = 1L; private static long hostId = 1l; @@ -186,7 +184,7 @@ public void testSetUp() { public void dataCenterAvoidTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, domainId, + ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, null, "FirstFitPlanner"); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); @@ -201,7 +199,7 @@ public void dataCenterAvoidTest() throws InsufficientServerCapacityException, Af public void plannerCannotHandleTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, domainId, + ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, null, "UserDispersingPlanner"); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); @@ -217,7 +215,7 @@ public void plannerCannotHandleTest() throws InsufficientServerCapacityException public void emptyClusterListTest() throws InsufficientServerCapacityException, AffinityConflictException { ServiceOfferingVO svcOffering = new ServiceOfferingVO("testOffering", 1, 512, 500, 1, 1, false, false, false, "test dpm", - ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, domainId, + ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.User, null, "FirstFitPlanner"); Mockito.when(vmProfile.getServiceOffering()).thenReturn(svcOffering); diff --git a/ui/l10n/en.js b/ui/l10n/en.js index eb7b82250891..cd0e01635f4d 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -705,6 +705,7 @@ var dictionary = { "label.dns.1":"DNS 1", "label.dns.2":"DNS 2", "label.domain":"Domain", +"label.domains":"Domains", "label.domain.admin":"Domain Admin", "label.domain.details":"Domain details", "label.domain.id":"Domain ID", diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index de8f4726e66d..1ba8a9806c54 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -73,8 +73,9 @@ preFilter: function(args) { if (isAdmin()) { } else { + args.$form.find('.form-item[rel=isPublic]').find('input[name=isPublic]').prop('checked', false); args.$form.find('.form-item[rel=isPublic]').hide(); - args.$form.find('.form-item[rel=domainId]').css('display', 'inline-block'); //shown + args.$form.find('.form-item[rel=domain]').css('display', 'inline-block'); //shown args.$form.find('.form-item[rel=deploymentPlanner]').hide(); args.$form.find('.form-item[rel=plannerMode]').hide(); args.$form.find('.form-item[rel=storageTags]').hide(); @@ -555,10 +556,11 @@ } }, - domainId: { + domain: { label: 'label.domain', docID: 'helpComputeOfferingDomain', dependsOn: 'isPublic', + isMultiple: true, select: function(args) { $.ajax({ url: createURL("listDomains&listAll=true"), @@ -583,6 +585,43 @@ }); }, isHidden: true + }, + + zone: { + label: 'label.zone', + docID: 'helpComputeOfferingZone', + isMultiple: true, + validation: { + allzonesonly: true + }, + select: function(args) { + $.ajax({ + url: createURL("listZones&available=true"), + dataType: "json", + async: true, + success: function(json) { + var zoneObjs = []; + var items = json.listzonesresponse.zone; + if (items != null) { + for (var i = 0; i < items.length; i++) { + zoneObjs.push({ + id: items[i].id, + description: items[i].name + }); + } + } + if (isAdmin()) { + zoneObjs.unshift({ + id: -1, + description: "All Zones" + }); + } + args.response.success({ + data: zoneObjs + }); + } + }); + } } } }, @@ -716,8 +755,41 @@ }); if (args.data.isPublic != "on") { + var domains = ""; + if (Object.prototype.toString.call(args.data.domain) === '[object Array]') { + domains = args.data.domain.join(","); + } else { + if (args.data.domain != null) { + domains = args.data.domain; + } + } + if (domains != "") { + $.extend(data, { + domainids: domains + }); + } + } + + var zones = ""; + if (Object.prototype.toString.call(args.data.zone) === '[object Array]') { + var allZonesSelected = false; + args.data.zone.forEach(function (zone) { + if (zone === null) { + allZonesSelected = true; + break; + } + }); + if(!allZonesSelected) { + zones = args.data.zone.join(","); + } + } else { + if (args.data.zone != null) { + zones = args.data.zone; + } + } + if (zones != "") { $.extend(data, { - domainid: args.data.domainId + zoneids: zones }); } @@ -952,8 +1024,11 @@ hosttags: { label: 'label.host.tag' }, - domain: { - label: 'label.domain' + domains: { + label: 'label.domains' + }, + zones: { + label: 'label.zones' }, created: { label: 'label.created', @@ -982,6 +1057,16 @@ if (item.serviceofferingdetails != null) { item.pciDevice = item.serviceofferingdetails.pciDevice; item.vgpuType = item.serviceofferingdetails.vgpuType; + if(item.serviceofferingdetails.domainnames) { + $.extend(item, { + domains: item.serviceofferingdetails.domainnames + }); + } + if(item.serviceofferingdetails.zonenames) { + $.extend(item, { + zones: item.serviceofferingdetails.zonenames + }); + } } args.response.success({ diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index bbe8f3e64b49..5bb7d9265f55 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -298,7 +298,12 @@ cloudStack.docs = { externalLink: '' }, helpComputeOfferingDomain: { - desc: 'The domain to associate this compute offering with' + desc: 'Select domain(s) to associate this compute offering with (Tip: Use Ctrl to choose multiple domains)', + externalLink: '' + }, + helpComputeOfferingZone: { + desc: 'Select zone(s) to associate this compute offering with (Tip: Use Ctrl to choose multiple zones)', + externalLink: '' }, // Create Instance Snapshot helpCreateInstanceSnapshotName: { diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index 351ca7b30208..97643ce71785 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -291,6 +291,7 @@ // Step 3: Service offering function(args) { selectedTemplateObj = null; //reset + var zoneid = args.currentData["zoneid"] if (args.currentData["select-template"] == "select-template") { if (featuredTemplateObjs != null && featuredTemplateObjs.length > 0) { for (var i = 0; i < featuredTemplateObjs.length; i++) { @@ -353,6 +354,9 @@ $.ajax({ url: createURL("listServiceOfferings&issystem=false"), dataType: "json", + data: { + zoneid: zoneid + }, async: false, success: function(json) { serviceOfferingObjs = json.listserviceofferingsresponse.serviceoffering;