From c85b3e597a334fb09611499797f06f6d7cf5cb3f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 19 Mar 2019 18:08:47 +0530 Subject: [PATCH] server: ability to create disk offerings for domain(s) and zone(s) Allows creating storage offerings associated with particular domain(s) and zone(s). In create disk/storage offfering form UI, a mult-select control has been addded to select desired zone(s) and domain select element has been made multi-select. createDiskOffering API has been modified to allow passing list of domain and zone IDs with keys domainids and zoneids respectively. These lists are stored in DB in cloud.disk_offering_details table with 'domainids' and 'zoneids' key as string of comma separated list of IDs. Response for create, update and list disk offering APIs will return domainids, domainnames, zoneids and zonenames in details object of offering. listDiskOfferings API has been modified to allow passing zoneid to return only offerings which are associated with the zone. Signed-off-by: Abhishek Kumar --- .../java/com/cloud/user/AccountService.java | 3 +- .../cloudstack/acl/SecurityChecker.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 3 + .../admin/offering/CreateDiskOfferingCmd.java | 39 +++++- .../admin/offering/UpdateDiskOfferingCmd.java | 3 +- .../user/offering/ListDiskOfferingsCmd.java | 12 ++ .../api/response/DiskOfferingResponse.java | 16 ++- .../configuration/ConfigurationManager.java | 2 +- .../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/storage/DiskOfferingVO.java | 20 ++- .../storage/dao/DiskOfferingDaoImpl.java | 61 +++++++--- .../management/MockAccountManager.java | 3 +- .../java/com/cloud/acl/DomainChecker.java | 59 +++++---- .../main/java/com/cloud/api/ApiDBUtils.java | 42 ++++++- .../com/cloud/api/query/QueryManagerImpl.java | 82 +++++++++---- .../ConfigurationManagerImpl.java | 115 ++++++++++++++---- .../cloud/storage/VolumeApiServiceImpl.java | 27 ++-- .../com/cloud/user/AccountManagerImpl.java | 7 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 6 +- .../cloud/user/MockAccountManagerImpl.java | 3 +- .../vpc/MockConfigurationManagerImpl.java | 2 +- .../test/resources/createNetworkOffering.xml | 3 +- ui/l10n/en.js | 1 + ui/scripts/configuration.js | 97 ++++++++++++++- ui/scripts/docs.js | 6 +- ui/scripts/instanceWizard.js | 4 + ui/scripts/storage.js | 20 ++- 30 files changed, 532 insertions(+), 125 deletions(-) diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 060861d1809..cda82f52a2a 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.GetUserKeysCmd; 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; @@ -98,7 +99,7 @@ public interface AccountService { void checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException; - void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException; + void checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException; void checkAccess(User user, ControlledEntity entity); 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 41708717548..e4f3ca8075e 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java @@ -138,5 +138,5 @@ public interface SecurityChecker extends Adapter { public boolean checkAccess(Account account, ServiceOffering so) throws PermissionDeniedException; - boolean checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException; + boolean checkAccess(Account account, DiskOffering dof, DataCenter zone) 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 0d8cb03cd3e..e09abb9c408 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -113,6 +113,8 @@ public class ApiConstants { public static final String DOMAIN_PATH = "domainpath"; public static final String DOMAIN_ID = "domainid"; 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 DURATION = "duration"; public static final String ELIGIBLE = "eligible"; public static final String EMAIL = "email"; @@ -711,6 +713,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/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index 469f714d028..3976a7584ea 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -16,7 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import org.apache.log4j.Logger; +import java.util.ArrayList; +import java.util.List; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -26,10 +27,12 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.log4j.Logger; -import com.cloud.storage.Storage.ProvisioningType; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Storage.ProvisioningType; import com.cloud.user.Account; @APICommand(name = "createDiskOffering", description = "Creates a disk offering.", responseObject = DiskOfferingResponse.class, @@ -64,6 +67,24 @@ public class CreateDiskOfferingCmd 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.STORAGE_TYPE, type = CommandType.STRING, description = "the storage type of the disk offering. Values are local and shared.") private String storageType = ServiceOffering.StorageType.shared.toString(); @@ -170,6 +191,20 @@ public class CreateDiskOfferingCmd extends BaseCmd { 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 Long getBytesReadRate() { return bytesReadRate; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java index 6e1fde55a30..55d1add370c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.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.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.log4j.Logger; import com.cloud.offering.DiskOffering; import com.cloud.user.Account; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java index e5f92c9e0de..92b8676b469 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.offering; +import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -42,6 +43,13 @@ public class ListDiskOfferingsCmd extends BaseListDomainResourcesCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the disk offering") private String diskOfferingName; + @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 /////////////////////// ///////////////////////////////////////////////////// @@ -54,6 +62,10 @@ public class ListDiskOfferingsCmd extends BaseListDomainResourcesCmd { return diskOfferingName; } + public Long getZoneId() { + return zoneId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java index 5f22c91488e..2f86629043d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -17,8 +17,7 @@ package org.apache.cloudstack.api.response; import java.util.Date; - -import com.google.gson.annotations.SerializedName; +import java.util.Map; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -26,6 +25,7 @@ import org.apache.cloudstack.api.EntityReference; import com.cloud.offering.DiskOffering; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = DiskOffering.class) public class DiskOfferingResponse extends BaseResponse { @@ -144,6 +144,10 @@ public class DiskOfferingResponse extends BaseResponse { @Param(description = "whether to display the offering to the end user or not.") private Boolean displayOffering; + @SerializedName(ApiConstants.DETAILS) + @Param(description = "the details of the disk offering", since = "4.13.0") + private Map details; + public Boolean getDisplayOffering() { return displayOffering; } @@ -328,4 +332,12 @@ public class DiskOfferingResponse extends BaseResponse { 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/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 235b241264c..58d43ae2596 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -177,7 +177,7 @@ public interface ConfigurationManager { void checkZoneAccess(Account caller, DataCenter zone); - void checkDiskOfferingAccess(Account caller, DiskOffering dof); + void checkDiskOfferingAccess(Account caller, DiskOffering dof, DataCenter zone); /** * Creates a new network offering 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 a6cd59f1cc3..e006b0e4573 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 interface DataCenterDao extends GenericDao { 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 385fb406155..2db904f78c9 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 class DataCenterDaoImpl extends GenericDaoBase implem 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 297fbfad6de..45ff6b5df54 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 4316e3bc8ed..45be8ea2af1 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 class DomainDaoImpl extends GenericDaoBase implements Dom 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/storage/DiskOfferingVO.java b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java index e8e91ea3781..2293553f22e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java @@ -51,7 +51,7 @@ public class DiskOfferingVO implements DiskOffering { long id; @Column(name = "domain_id") - Long domainId; + Long domainId = null; @Column(name = "unique_name") private String uniqueName; @@ -182,6 +182,24 @@ public class DiskOfferingVO implements DiskOffering { this.cacheMode = cacheMode; } + public DiskOfferingVO(String name, String displayText, Storage.ProvisioningType provisioningType, long diskSize, String tags, boolean isCustomized, Boolean isCustomizedIops, + Long minIops, Long maxIops) { + this.name = name; + this.displayText = displayText; + this.provisioningType = provisioningType; + this.diskSize = diskSize; + this.tags = tags; + recreatable = false; + type = Type.Disk; + useLocalStorage = false; + customized = isCustomized; + uuid = UUID.randomUUID().toString(); + customizedIops = isCustomizedIops; + this.minIops = minIops; + this.maxIops = maxIops; + state = State.Active; + } + public DiskOfferingVO(Long domainId, String name, String displayText, Storage.ProvisioningType provisioningType, long diskSize, String tags, boolean isCustomized, Boolean isCustomizedIops, Long minIops, Long maxIops) { this.domainId = domainId; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java index 2957b68e6e9..fdfdcc07ad6 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java @@ -16,42 +16,45 @@ // under the License. package com.cloud.storage.dao; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; +import javax.inject.Inject; import javax.persistence.EntityExistsException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.springframework.stereotype.Component; -import com.cloud.storage.DiskOfferingVO; import com.cloud.offering.DiskOffering.Type; +import com.cloud.storage.DiskOfferingVO; import com.cloud.utils.db.Attribute; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; +import com.google.common.base.Strings; @Component public class DiskOfferingDaoImpl extends GenericDaoBase implements DiskOfferingDao { - private final SearchBuilder DomainIdSearch; + + @Inject + protected DiskOfferingDetailsDao detailsDao; + private final SearchBuilder PrivateDiskOfferingSearch; private final SearchBuilder PublicDiskOfferingSearch; protected final SearchBuilder UniqueNameSearch; private final Attribute _typeAttr; protected DiskOfferingDaoImpl() { - DomainIdSearch = createSearchBuilder(); - DomainIdSearch.and("domainId", DomainIdSearch.entity().getDomainId(), SearchCriteria.Op.EQ); - DomainIdSearch.and("removed", DomainIdSearch.entity().getRemoved(), SearchCriteria.Op.NULL); - DomainIdSearch.done(); - PrivateDiskOfferingSearch = createSearchBuilder(); PrivateDiskOfferingSearch.and("diskSize", PrivateDiskOfferingSearch.entity().getDiskSize(), SearchCriteria.Op.EQ); PrivateDiskOfferingSearch.done(); PublicDiskOfferingSearch = createSearchBuilder(); - PublicDiskOfferingSearch.and("domainId", PublicDiskOfferingSearch.entity().getDomainId(), SearchCriteria.Op.NULL); PublicDiskOfferingSearch.and("system", PublicDiskOfferingSearch.entity().isSystemUse(), SearchCriteria.Op.EQ); PublicDiskOfferingSearch.and("removed", PublicDiskOfferingSearch.entity().getRemoved(), SearchCriteria.Op.NULL); PublicDiskOfferingSearch.done(); @@ -65,11 +68,7 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im @Override public List listByDomainId(long domainId) { - SearchCriteria sc = DomainIdSearch.create(); - sc.setParameters("domainId", domainId); - // FIXME: this should not be exact match, but instead should find all - // available disk offerings from parent domains - return listBy(sc); + return filterOfferingsForDomain(listAll(), domainId); } @Override @@ -108,7 +107,19 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im public List findPublicDiskOfferings() { SearchCriteria sc = PublicDiskOfferingSearch.create(); sc.setParameters("system", false); - return listBy(sc); + List offerings = listBy(sc); + + if(offerings!=null) { + for (int i = offerings.size() - 1; i >= 0; i--) { + DiskOfferingVO 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 @@ -145,4 +156,26 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im return update(id, diskOffering); } + + 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--) { + DiskOfferingVO 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/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 100f38060be..7320773b858 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 org.apache.cloudstack.context.CallContext; 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; @@ -435,7 +436,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager { } @Override - public void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException { + public void checkAccess(Account account, DiskOffering dof, 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 c9e4087f6ba..2fba4eaa53d 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -16,13 +16,18 @@ // 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.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.springframework.stereotype.Component; import com.cloud.dc.DataCenter; import com.cloud.dc.DedicatedResourceVO; @@ -44,6 +49,7 @@ import com.cloud.user.AccountService; 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 + DiskOfferingDetailsDao diskOfferingDetailsDao; protected DomainChecker() { super(); @@ -167,13 +175,15 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { } @Override - public boolean checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException { - if (account == null || dof == null || dof.getDomainId() == null) {//public offering - return true; + public boolean checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException { + boolean isAccess = false; + Map details = null; + if (account == null || dof == 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 disk offering) @@ -181,28 +191,33 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { || account.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN || _accountService.isDomainAdmin(account.getId()) || account.getType() == Account.ACCOUNT_TYPE_PROJECT) { - if (account.getDomainId() == dof.getDomainId()) { - return true; //disk offering and account at exact node - } else { - Domain domainRecord = _domainDao.findById(account.getDomainId()); - if (domainRecord != null) { - while (true) { - if (domainRecord.getId() == dof.getDomainId()) { - //found as a child - return true; - } - if (domainRecord.getParent() != null) { - domainRecord = _domainDao.findById(domainRecord.getParent()); - } else { - break; - } + details = diskOfferingDetailsDao.listDetailsKeyPairs(dof.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; } } } } } + + // Check for zones + if (isAccess && dof != null && zone != null) { + if (details == null) + details = diskOfferingDetailsDao.listDetailsKeyPairs(dof.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())); + } + } //not found - return false; + 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 22ed2437d4d..89958673bd8 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.AffinityGroup; 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; @@ -68,6 +69,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -314,6 +316,7 @@ 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; @@ -335,6 +338,7 @@ public class ApiDBUtils { static CapacityDao s_capacityDao; static DiskOfferingDao s_diskOfferingDao; static DiskOfferingJoinDao s_diskOfferingJoinDao; + static DiskOfferingDetailsDao s_diskOfferingDetailsDao; static DataCenterJoinDao s_dcJoinDao; static DomainDao s_domainDao; static DomainJoinDao s_domainJoinDao; @@ -471,6 +475,8 @@ public class ApiDBUtils { @Inject private DiskOfferingJoinDao diskOfferingJoinDao; @Inject + private DiskOfferingDetailsDao diskOfferingDetailsDao; + @Inject private DomainDao domainDao; @Inject private DomainJoinDao domainJoinDao; @@ -688,6 +694,7 @@ public class ApiDBUtils { s_dcJoinDao = dcJoinDao; s_diskOfferingDao = diskOfferingDao; s_diskOfferingJoinDao = diskOfferingJoinDao; + s_diskOfferingDetailsDao = diskOfferingDetailsDao; s_domainDao = domainDao; s_domainJoinDao = domainJoinDao; s_domainRouterDao = domainRouterDao; @@ -1906,7 +1913,40 @@ public class ApiDBUtils { } public static DiskOfferingResponse newDiskOfferingResponse(DiskOfferingJoinVO offering) { - return s_diskOfferingJoinDao.newDiskOfferingResponse(offering); + DiskOfferingResponse diskOfferingResponse = s_diskOfferingJoinDao.newDiskOfferingResponse(offering); + if(diskOfferingResponse!=null) { + Map details = s_diskOfferingDetailsDao.listDetailsKeyPairs(offering.getId()); + 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)); + } + diskOfferingResponse.setDetails(details); + } + } + return diskOfferingResponse; } public static DiskOfferingJoinVO newDiskOfferingView(DiskOffering 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 e1652773399..2fb337025d6 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 java.util.Set; 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; @@ -108,6 +107,7 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.query.QueryService; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -156,6 +156,8 @@ import com.cloud.api.query.vo.TemplateJoinVO; 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; @@ -223,6 +225,7 @@ import com.cloud.vm.dao.DomainRouterDao; 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 { @@ -321,6 +324,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private DiskOfferingJoinDao _diskOfferingJoinDao; + @Inject + private DiskOfferingDetailsDao diskOfferingDetailsDao; + @Inject private ServiceOfferingJoinDao _srvOfferingJoinDao; @@ -2538,7 +2544,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q } } - List domainIds = null; // For non-root users, only return all offerings for the user's domain, // and everything above till root if ((_accountMgr.isNormalUser(account.getId()) || _accountMgr.isDomainAdmin(account.getId())) || account.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) { @@ -2546,27 +2551,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q if (account.getType() == Account.ACCOUNT_TYPE_NORMAL) { throw new InvalidParameterValueException("Only ROOT admins and Domain admins can list disk offerings with isrecursive=true"); } - DomainVO domainRecord = _domainDao.findById(account.getDomainId()); - sc.addAnd("domainPath", SearchCriteria.Op.LIKE, domainRecord.getPath() + "%"); } else { // domain + all ancestors - // find all domain Id up to root domain for this account - domainIds = new ArrayList(); - DomainVO domainRecord = _domainDao.findById(account.getDomainId()); - if (domainRecord == null) { - s_logger.error("Could not find the domainId for account:" + account.getAccountName()); - throw new CloudAuthenticationException("Could not find the domainId for account:" + account.getAccountName()); - } - domainIds.add(domainRecord.getId()); - while (domainRecord.getParent() != null) { - domainRecord = _domainDao.findById(domainRecord.getParent()); - domainIds.add(domainRecord.getId()); - } - - SearchCriteria spc = _diskOfferingJoinDao.createSearchCriteria(); - - spc.addOr("domainId", SearchCriteria.Op.IN, domainIds.toArray()); - spc.addOr("domainId", SearchCriteria.Op.NULL); // include public offering as where - sc.addAnd("domainId", SearchCriteria.Op.SC, spc); sc.addAnd("systemUse", SearchCriteria.Op.EQ, false); // non-root users should not see system offering at all } @@ -2610,7 +2595,56 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q * domain.getPath() + "%"); // } */ - return _diskOfferingJoinDao.searchAndCount(sc, searchFilter); + Pair, Integer> result = _diskOfferingJoinDao.searchAndCount(sc, searchFilter); + // 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--) { + DiskOfferingJoinVO offering = offerings.get(i); + Map details = diskOfferingDetailsDao.listDetailsKeyPairs(offering.getId()); + boolean toRemove = account.getType() == Account.ACCOUNT_TYPE_ADMIN ? false : isRecursive; + if (account.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(account.getDomainId(), dId)) { + toRemove = false; + break; + } + } else { + if (_domainDao.isChildDomain(dId, account.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; } private List filterOfferingsOnCurrentTags(List offerings, ServiceOfferingVO currentVmOffering) { diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index cebc9e1f769..c4be75b17d5 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -86,6 +86,7 @@ import org.apache.cloudstack.region.PortableIpVO; import org.apache.cloudstack.region.Region; import org.apache.cloudstack.region.RegionVO; import org.apache.cloudstack.region.dao.RegionDao; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; @@ -267,6 +268,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject DiskOfferingDao _diskOfferingDao; @Inject + DiskOfferingDetailsDao diskOfferingDetailsDao; + @Inject NetworkOfferingDao _networkOfferingDao; @Inject VlanDao _vlanDao; @@ -2608,7 +2611,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - protected DiskOfferingVO createDiskOffering(final Long userId, final Long domainId, final String name, final String description, final String provisioningType, + protected DiskOfferingVO createDiskOffering(final Long userId, final List domainIds, final List zoneIds, final String name, final String description, final String provisioningType, final Long numGibibytes, String tags, boolean isCustomized, final boolean localStorageRequired, final boolean isDisplayOfferingEnabled, final Boolean isCustomizedIops, Long minIops, Long maxIops, Long bytesReadRate, Long bytesReadRateMax, Long bytesReadRateMaxLength, @@ -2654,6 +2657,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + // Filter child domains when both parent and child domains are present + List filteredDomainIds = filterChildSubDomains(domainIds); // Check if user exists in the system final User user = _userDao.findById(userId); @@ -2662,21 +2667,30 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } 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 disk offering by id " + userId + " because it is domain-admin"); } if (tags != null) { throw new InvalidParameterValueException("Unable to create disk offering with storage tags by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), domainId)) { - throw new InvalidParameterValueException("Unable to create disk offering by another domain admin with id " + userId); + for (Long domainId : filteredDomainIds) { + if (domainId == null || !_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to create disk offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to create disk 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); + } + } + tags = StringUtils.cleanupTags(tags); - final DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, typedProvisioningType, diskSize, tags, isCustomized, + final DiskOfferingVO newDiskOffering = new DiskOfferingVO(name, description, typedProvisioningType, diskSize, tags, isCustomized, isCustomizedIops, minIops, maxIops); newDiskOffering.setUseLocalStorage(localStorageRequired); newDiskOffering.setDisplayOffering(isDisplayOfferingEnabled); @@ -2727,11 +2741,22 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati CallContext.current().setEventDetails("Disk offering id=" + newDiskOffering.getId()); final DiskOfferingVO offering = _diskOfferingDao.persist(newDiskOffering); if (offering != null) { + if(!filteredDomainIds.isEmpty()) { + List domainIdsStringList = new ArrayList<>(); + for(Long domainId : filteredDomainIds) + domainIdsStringList.add(String.valueOf(domainId)); + diskOfferingDetailsDao.addDetail(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)); + diskOfferingDetailsDao.addDetail(offering.getId(), ApiConstants.ZONE_ID_LIST, String.join(",", zoneIdsStringList), true); + } CallContext.current().setEventDetails("Disk offering id=" + newDiskOffering.getId()); return offering; - } else { - return null; } + return null; } @Override @@ -2746,11 +2771,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // by // default final String tags = cmd.getTags(); - // Long domainId = cmd.getDomainId() != null ? cmd.getDomainId() : - // Long.valueOf(DomainVO.ROOT_DOMAIN); // disk offering - // always gets created under the root domain.Bug # 6055 if not passed in - // cmd - final Long domainId = cmd.getDomainId(); + + final List domainIds = cmd.getDomainIds(); + + final List zoneIds = cmd.getZoneIds(); if (!isCustomized && numGibibytes == null) { throw new InvalidParameterValueException("Disksize is required for a non-customized disk offering"); @@ -2788,7 +2812,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Integer hypervisorSnapshotReserve = cmd.getHypervisorSnapshotReserve(); final Long userId = CallContext.current().getCallingUserId(); - return createDiskOffering(userId, domainId, name, description, provisioningType, numGibibytes, tags, isCustomized, + return createDiskOffering(userId, domainIds, zoneIds, name, description, provisioningType, numGibibytes, tags, isCustomized, localStorageRequired, isDisplayOfferingEnabled, isCustomizedIops, minIops, maxIops, bytesReadRate, bytesReadRateMax, bytesReadRateMaxLength, bytesWriteRate, bytesWriteRateMax, bytesWriteRateMaxLength, iopsReadRate, iopsReadRateMax, iopsReadRateMaxLength, iopsWriteRate, iopsWriteRateMax, iopsWriteRateMaxLength, @@ -2806,6 +2830,15 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // Check if diskOffering exists final DiskOffering diskOfferingHandle = _entityMgr.findById(DiskOffering.class, diskOfferingId); + List existingDomainIds = new ArrayList<>(); + Map details = diskOfferingDetailsDao.listDetailsKeyPairs(diskOfferingId); + 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 (diskOfferingHandle == null) { throw new InvalidParameterValueException("Unable to find disk offering by id " + diskOfferingId); @@ -2820,12 +2853,15 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati 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 (diskOfferingHandle.getDomainId() == null) { + if (existingDomainIds.isEmpty()) { throw new InvalidParameterValueException("Unable to update public disk offering by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), diskOfferingHandle.getDomainId() )) { - throw new InvalidParameterValueException("Unable to update disk offering by another domain admin with id " + userId); + for (Long domainId : existingDomainIds) { + if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to update disk offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to update disk offering by id " + userId + " because it is not root-admin or domain-admin"); @@ -2908,11 +2944,22 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } 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 = diskOfferingDetailsDao.listDetailsKeyPairs(diskOfferingId); + 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 disk offering by id " + userId + " because it is domain-admin"); } - if (! _domainDao.isChildDomain(account.getDomainId(), offering.getDomainId() )) { - throw new InvalidParameterValueException("Unable to delete disk offering by another domain admin with id " + userId); + for (Long domainId : existingDomainIds) { + if (!_domainDao.isChildDomain(account.getDomainId(), domainId)) { + throw new InvalidParameterValueException("Unable to delete disk offering by another domain admin with id " + userId); + } } } else if (account.getType() != Account.ACCOUNT_TYPE_ADMIN) { throw new InvalidParameterValueException("Unable to delete disk offering by id " + userId + " because it is not root-admin or domain-admin"); @@ -4229,15 +4276,15 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } @Override - public void checkDiskOfferingAccess(final Account caller, final DiskOffering dof) { + public void checkDiskOfferingAccess(final Account caller, final DiskOffering dof, DataCenter zone) { for (final SecurityChecker checker : _secChecker) { - if (checker.checkAccess(caller, dof)) { + if (checker.checkAccess(caller, dof, zone)) { if (s_logger.isDebugEnabled()) { s_logger.debug("Access granted to " + caller + " to disk offering:" + dof.getId() + " by " + checker.getName()); } return; } else { - throw new PermissionDeniedException("Access denied to " + caller + " by " + checker.getName()); + throw new PermissionDeniedException(String.format("Access denied to %s for disk offering: %s, zone: %s by %s", caller, dof.getUuid(), zone.getUuid(), checker.getName())); } } @@ -5758,6 +5805,30 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati 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/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index b2a395b4ef8..7917b868c60 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -436,11 +436,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("Please specify a custom sized disk offering."); } - if (diskOffering.getDomainId() == null) { - // do nothing as offering is public - } else { - _configMgr.checkDiskOfferingAccess(volumeOwner, diskOffering); - } + _configMgr.checkDiskOfferingAccess(volumeOwner, diskOffering, zone); } return false; @@ -603,11 +599,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("This disk offering does not allow custom size"); } - if (diskOffering.getDomainId() == null) { - // do nothing as offering is public - } else { - _configMgr.checkDiskOfferingAccess(caller, diskOffering); - } + _configMgr.checkDiskOfferingAccess(caller, diskOffering, _dcDao.findById(zoneId)); if (diskOffering.getDiskSize() > 0) { size = diskOffering.getDiskSize(); @@ -666,6 +658,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // if zoneId is not provided, we default to create volume in the same zone as the snapshot zone. zoneId = snapshotCheck.getDataCenterId(); } + + _configMgr.checkDiskOfferingAccess(null, diskOffering, _dcDao.findById(zoneId)); + size = snapshotCheck.getSize(); // ; disk offering is used for tags // purposes @@ -949,10 +944,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("There are no tags on the current disk offering. The new disk offering needs to have no tags, as well."); } - if (newDiskOffering.getDomainId() != null) { - // not a public offering; check access - _configMgr.checkDiskOfferingAccess(CallContext.current().getCallingAccount(), newDiskOffering); - } + _configMgr.checkDiskOfferingAccess(CallContext.current().getCallingAccount(), newDiskOffering, _dcDao.findById(volume.getDataCenterId())); if (newDiskOffering.isCustomized()) { newSize = cmd.getSize(); @@ -2187,7 +2179,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException(String.format("We cannot assign a removed disk offering [id=%s] to a volume. ", newDiskOffering.getUuid())); } Account caller = CallContext.current().getCallingAccount(); - _accountMgr.checkAccess(caller, newDiskOffering); + DataCenter zone = null; + Volume volume = _volsDao.findById(cmd.getId()); + if (volume != null) { + zone = _dcDao.findById(volume.getDataCenterId()); + } + _accountMgr.checkAccess(caller, newDiskOffering, zone); return newDiskOffering; } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index bda4ccab468..5783e4b65f9 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.ResourceCountVO; 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; @@ -2844,13 +2845,15 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M } @Override - public void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException { + public void checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException { for (SecurityChecker checker : _securityCheckers) { - if (checker.checkAccess(account, dof)) { + if (checker.checkAccess(account, dof, zone)) { if (s_logger.isDebugEnabled()) { s_logger.debug("Access granted to " + account + " to " + dof + " by " + checker.getName()); } return; + } else { + throw new PermissionDeniedException(String.format("Access denied to %s for disk offering: %s, zone: %s by %s", account, dof.getUuid(), zone.getUuid(), 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 3727ea69960..47c370deeb1 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -3015,7 +3015,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Verify that owner can use the service offering _accountMgr.checkAccess(owner, serviceOffering); - _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId)); + _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId), zone); // Get default guest network in Basic zone Network defaultNetwork = _networkModel.getExclusiveGuestNetwork(zone.getId()); @@ -3074,7 +3074,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Verify that owner can use the service offering _accountMgr.checkAccess(owner, serviceOffering); - _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId)); + _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId), zone); // If no network is specified, find system security group enabled network if (networkIdList == null || networkIdList.isEmpty()) { @@ -3182,7 +3182,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Verify that owner can use the service offering _accountMgr.checkAccess(owner, serviceOffering); - _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId)); + _accountMgr.checkAccess(owner, _diskOfferingDao.findById(diskOfferingId), zone); List vpcSupportedHTypes = _vpcMgr.getSupportedVpcHypervisors(); if (networkIdList == null || networkIdList.isEmpty()) { diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java index 4fbf7526f31..c6088521360 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.RegisterCmd; 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; @@ -217,7 +218,7 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco } @Override - public void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException { + public void checkAccess(Account account, DiskOffering dof, DataCenter zone) throws PermissionDeniedException { // TODO Auto-generated method stub } diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index 9057241cc78..4c40f53afdc 100644 --- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -438,7 +438,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu * @see com.cloud.configuration.ConfigurationManager#checkDiskOfferingAccess(com.cloud.user.Account, com.cloud.offering.DiskOffering) */ @Override - public void checkDiskOfferingAccess(Account caller, DiskOffering dof) { + public void checkDiskOfferingAccess(Account caller, DiskOffering dof, DataCenter zone) { // TODO Auto-generated method stub } diff --git a/server/src/test/resources/createNetworkOffering.xml b/server/src/test/resources/createNetworkOffering.xml index 68ff007cdbf..aa92bd9f012 100644 --- a/server/src/test/resources/createNetworkOffering.xml +++ b/server/src/test/resources/createNetworkOffering.xml @@ -54,5 +54,6 @@ - + + diff --git a/ui/l10n/en.js b/ui/l10n/en.js index c16a955695c..43cec9db5db 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -707,6 +707,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 4003154710a..a81baa39d48 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1825,8 +1825,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=tags]').hide(); } }, @@ -2105,10 +2106,11 @@ isChecked: false, docID: 'helpDiskOfferingPublic' }, - domainId: { + domain: { label: 'label.domain', docID: 'helpDiskOfferingDomain', dependsOn: 'isPublic', + isMultiple: true, select: function(args) { $.ajax({ url: createURL('listDomains'), @@ -2137,6 +2139,42 @@ }); }, isHidden: true + }, + zone: { + label: 'label.zone', + docID: 'helpDiskOfferingZone', + 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 + }); + } + }); + } } } }, @@ -2217,8 +2255,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 }); } @@ -2390,8 +2461,11 @@ tags: { label: 'label.storage.tags' }, - domain: { - label: 'label.domain' + domains: { + label: 'label.domains' + }, + zones: { + label: 'label.zones' }, storagetype: { label: 'label.storage.type' @@ -2410,6 +2484,19 @@ data: data }; var diskOfferings = cloudStack.listDiskOfferings(listDiskOfferingsOptions); + var diskOffering = diskOfferings[0] + if(diskOffering.details) { + if(diskOffering.details.domainnames) { + $.extend(diskOffering, { + domains: diskOffering.details.domainnames + }); + } + if(diskOffering.details.zonenames) { + $.extend(diskOffering, { + zones: diskOffering.details.zonenames + }); + } + } args.response.success({ actionFilter: diskOfferingActionfilter, data: diskOfferings[0] diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index 9a737469ced..436c4eb8465 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -383,7 +383,11 @@ cloudStack.docs = { externalLink: '' }, helpDiskOfferingDomain: { - desc: 'Select the subdomain in which this offering is available', + desc: 'Select the domains in which this offering is available (Tip: Use Ctrl to choose multiple domains)', + externalLink: '' + }, + helpDiskOfferingZone: { + desc: 'Select the zones in which this offering is available (Tip: Use Ctrl to choose multiple zones)', externalLink: '' }, // Add domain diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index d175f1f8dfd..1134016865f 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -377,6 +377,7 @@ // Step 4: Data disk offering function(args) { var isRequired = (args.currentData["select-template"] == "select-iso" ? true : false); + var zoneid = args.currentData["zoneid"] var templateFilter = 'executable' if (isAdmin()) { templateFilter = 'all' @@ -384,6 +385,9 @@ $.ajax({ url: createURL("listDiskOfferings"), dataType: "json", + data: { + zoneid: zoneid + }, async: true, success: function(json) { diskOfferingObjs = json.listdiskofferingsresponse.diskoffering; diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 9fda923fe47..fb76c39fe39 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -168,7 +168,7 @@ }); } - var selectedDiskOfferingObj = null; + var diskOfferingsObjList, selectedDiskOfferingObj = null; cloudStack.sections.storage = { title: 'label.storage', @@ -274,6 +274,21 @@ }); } }); + args.$select.change(function() { + var diskOfferingSelect = $(this).closest('form').find('select[name=diskOffering]'); + if(diskOfferingSelect) { + $(diskOfferingSelect).find('option').remove().end(); + var data = { + zoneid: $(this).val(), + }; + console.log(data); + var diskOfferings = cloudStack.listDiskOfferings({ data: data }); + diskOfferingsObjList = diskOfferings; + $(diskOfferings).each(function() { + $(diskOfferingSelect).append(new Option(this.displaytext, this.id)); + }); + } + }); } }, diskOffering: { @@ -281,6 +296,7 @@ docID: 'helpVolumeDiskOffering', select: function(args) { var diskOfferings = cloudStack.listDiskOfferings({}); + diskOfferingsObjList = diskOfferings; var items = []; $(diskOfferings).each(function() { items.push({ @@ -293,7 +309,7 @@ }); args.$select.change(function() { var diskOfferingId = $(this).val(); - $(diskOfferings).each(function() { + $(diskOfferingsObjList).each(function() { if (this.id == diskOfferingId) { selectedDiskOfferingObj = this; return false; //break the $.each() loop