// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package com.cloud.resourcelimit; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceOwnerType; import com.cloud.configuration.Resource.ResourceType; import com.cloud.configuration.ResourceCount; import com.cloud.configuration.ResourceCountVO; import com.cloud.configuration.ResourceLimitVO; import com.cloud.configuration.dao.ResourceCountDao; import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.VlanDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount.Role; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DataStoreRole; import com.cloud.storage.SnapshotVO; import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDaoImpl.SumCount; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; import com.cloud.user.ResourceLimitService; import com.cloud.user.dao.AccountDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; @Component public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLimitService { public static final Logger s_logger = Logger.getLogger(ResourceLimitManagerImpl.class); @Inject private DomainDao _domainDao; @Inject private AccountManager _accountMgr; @Inject private AlertManager _alertMgr; @Inject private ResourceCountDao _resourceCountDao; @Inject private ResourceLimitDao _resourceLimitDao; @Inject private UserVmDao _userVmDao; @Inject private AccountDao _accountDao; @Inject protected SnapshotDao _snapshotDao; @Inject protected VMTemplateDao _vmTemplateDao; @Inject private VolumeDao _volumeDao; @Inject private IPAddressDao _ipAddressDao; @Inject private VMInstanceDao _vmDao; @Inject private ConfigurationDao _configDao; @Inject private EntityManager _entityMgr; @Inject private ProjectDao _projectDao; @Inject private ProjectAccountDao _projectAccountDao; @Inject private NetworkDao _networkDao; @Inject private VpcDao _vpcDao; @Inject private ServiceOfferingDao _serviceOfferingDao; @Inject private TemplateDataStoreDao _vmTemplateStoreDao; @Inject private VlanDao _vlanDao; @Inject private SnapshotDataStoreDao _snapshotDataStoreDao; protected GenericSearchBuilder<TemplateDataStoreVO, SumCount> templateSizeSearch; protected GenericSearchBuilder<SnapshotDataStoreVO, SumCount> snapshotSizeSearch; protected SearchBuilder<ResourceCountVO> ResourceCountSearch; ScheduledExecutorService _rcExecutor; long _resourceCountCheckInterval = 0; Map<ResourceType, Long> accountResourceLimitMap = new EnumMap<ResourceType, Long>(ResourceType.class); Map<ResourceType, Long> domainResourceLimitMap = new EnumMap<ResourceType, Long>(ResourceType.class); Map<ResourceType, Long> projectResourceLimitMap = new EnumMap<ResourceType, Long>(ResourceType.class); @Override public boolean start() { if (_resourceCountCheckInterval > 0) { _rcExecutor.scheduleAtFixedRate(new ResourceCountCheckTask(), _resourceCountCheckInterval, _resourceCountCheckInterval, TimeUnit.SECONDS); } return true; } @Override public boolean stop() { return true; } @Override public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { ResourceCountSearch = _resourceCountDao.createSearchBuilder(); ResourceCountSearch.and("id", ResourceCountSearch.entity().getId(), SearchCriteria.Op.IN); ResourceCountSearch.and("accountId", ResourceCountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); ResourceCountSearch.and("domainId", ResourceCountSearch.entity().getDomainId(), SearchCriteria.Op.EQ); ResourceCountSearch.done(); templateSizeSearch = _vmTemplateStoreDao.createSearchBuilder(SumCount.class); templateSizeSearch.select("sum", Func.SUM, templateSizeSearch.entity().getSize()); templateSizeSearch.and("downloadState", templateSizeSearch.entity().getDownloadState(), Op.EQ); templateSizeSearch.and("destroyed", templateSizeSearch.entity().getDestroyed(), Op.EQ); SearchBuilder<VMTemplateVO> join1 = _vmTemplateDao.createSearchBuilder(); join1.and("accountId", join1.entity().getAccountId(), Op.EQ); templateSizeSearch.join("templates", join1, templateSizeSearch.entity().getTemplateId(), join1.entity().getId(), JoinBuilder.JoinType.INNER); templateSizeSearch.done(); snapshotSizeSearch = _snapshotDataStoreDao.createSearchBuilder(SumCount.class); snapshotSizeSearch.select("sum", Func.SUM, snapshotSizeSearch.entity().getPhysicalSize()); snapshotSizeSearch.and("state", snapshotSizeSearch.entity().getState(), Op.EQ); snapshotSizeSearch.and("storeRole", snapshotSizeSearch.entity().getRole(), Op.EQ); SearchBuilder<SnapshotVO> join2 = _snapshotDao.createSearchBuilder(); join2.and("accountId", join2.entity().getAccountId(), Op.EQ); snapshotSizeSearch.join("snapshots", join2, snapshotSizeSearch.entity().getSnapshotId(), join2.entity().getId(), JoinBuilder.JoinType.INNER); snapshotSizeSearch.done(); _resourceCountCheckInterval = NumbersUtil.parseInt(_configDao.getValue(Config.ResourceCountCheckInterval.key()), 0); if (_resourceCountCheckInterval > 0) { _rcExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("ResourceCountChecker")); } try { projectResourceLimitMap.put(Resource.ResourceType.public_ip, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectPublicIPs.key()))); projectResourceLimitMap.put(Resource.ResourceType.snapshot, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectSnapshots.key()))); projectResourceLimitMap.put(Resource.ResourceType.template, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectTemplates.key()))); projectResourceLimitMap.put(Resource.ResourceType.user_vm, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectUserVms.key()))); projectResourceLimitMap.put(Resource.ResourceType.volume, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectVolumes.key()))); projectResourceLimitMap.put(Resource.ResourceType.network, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectNetworks.key()))); projectResourceLimitMap.put(Resource.ResourceType.vpc, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectVpcs.key()))); projectResourceLimitMap.put(Resource.ResourceType.cpu, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectCpus.key()))); projectResourceLimitMap.put(Resource.ResourceType.memory, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectMemory.key()))); projectResourceLimitMap.put(Resource.ResourceType.primary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectPrimaryStorage.key()))); projectResourceLimitMap.put(Resource.ResourceType.secondary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectSecondaryStorage.key()))); accountResourceLimitMap.put(Resource.ResourceType.public_ip, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPublicIPs.key()))); accountResourceLimitMap.put(Resource.ResourceType.snapshot, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountSnapshots.key()))); accountResourceLimitMap.put(Resource.ResourceType.template, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountTemplates.key()))); accountResourceLimitMap.put(Resource.ResourceType.user_vm, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountUserVms.key()))); accountResourceLimitMap.put(Resource.ResourceType.volume, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountVolumes.key()))); accountResourceLimitMap.put(Resource.ResourceType.network, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountNetworks.key()))); accountResourceLimitMap.put(Resource.ResourceType.vpc, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountVpcs.key()))); accountResourceLimitMap.put(Resource.ResourceType.cpu, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountCpus.key()))); accountResourceLimitMap.put(Resource.ResourceType.memory, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountMemory.key()))); accountResourceLimitMap.put(Resource.ResourceType.primary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPrimaryStorage.key()))); accountResourceLimitMap.put(Resource.ResourceType.secondary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountSecondaryStorage.key()))); domainResourceLimitMap.put(Resource.ResourceType.public_ip, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainPublicIPs.key()))); domainResourceLimitMap.put(Resource.ResourceType.snapshot, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainSnapshots.key()))); domainResourceLimitMap.put(Resource.ResourceType.template, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainTemplates.key()))); domainResourceLimitMap.put(Resource.ResourceType.user_vm, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainUserVms.key()))); domainResourceLimitMap.put(Resource.ResourceType.volume, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainVolumes.key()))); domainResourceLimitMap.put(Resource.ResourceType.network, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainNetworks.key()))); domainResourceLimitMap.put(Resource.ResourceType.vpc, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainVpcs.key()))); domainResourceLimitMap.put(Resource.ResourceType.cpu, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainCpus.key()))); domainResourceLimitMap.put(Resource.ResourceType.memory, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainMemory.key()))); domainResourceLimitMap.put(Resource.ResourceType.primary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainPrimaryStorage.key()))); domainResourceLimitMap.put(Resource.ResourceType.secondary_storage, Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainSecondaryStorage.key()))); } catch (NumberFormatException e) { s_logger.error("NumberFormatException during configuration", e); throw new ConfigurationException("Configuration failed due to NumberFormatException, see log for the stacktrace"); } return true; } @Override public void incrementResourceCount(long accountId, ResourceType type, Long... delta) { // don't upgrade resource count for system account if (accountId == Account.ACCOUNT_ID_SYSTEM) { s_logger.trace("Not incrementing resource count for system accounts, returning"); return; } long numToIncrement = (delta.length == 0) ? 1 : delta[0].longValue(); if (!updateResourceCountForAccount(accountId, type, true, numToIncrement)) { // we should fail the operation (resource creation) when failed to update the resource count throw new CloudRuntimeException("Failed to increment resource count of type " + type + " for account id=" + accountId); } } @Override public void decrementResourceCount(long accountId, ResourceType type, Long... delta) { // don't upgrade resource count for system account if (accountId == Account.ACCOUNT_ID_SYSTEM) { s_logger.trace("Not decrementing resource count for system accounts, returning"); return; } long numToDecrement = (delta.length == 0) ? 1 : delta[0].longValue(); if (!updateResourceCountForAccount(accountId, type, false, numToDecrement)) { _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, "Failed to decrement resource count of type " + type + " for account id=" + accountId, "Failed to decrement resource count of type " + type + " for account id=" + accountId + "; use updateResourceCount API to recalculate/fix the problem"); } } @Override public long findCorrectResourceLimitForAccount(Account account, ResourceType type) { long max = Resource.RESOURCE_UNLIMITED; // if resource limit is not found, then we treat it as unlimited // No limits for Root Admin accounts if (_accountMgr.isRootAdmin(account.getId())) { return max; } ResourceLimitVO limit = _resourceLimitDao.findByOwnerIdAndType(account.getId(), ResourceOwnerType.Account, type); // Check if limit is configured for account if (limit != null) { max = limit.getMax().longValue(); } else { // If the account has an no limit set, then return global default account limits Long value = null; if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { value = projectResourceLimitMap.get(type); } else { value = accountResourceLimitMap.get(type); } if (value != null) { if (value < 0) { // return unlimit if value is set to negative return max; } // convert the value from GiB to bytes in case of primary or secondary storage. if (type == ResourceType.primary_storage || type == ResourceType.secondary_storage) { value = value * ResourceType.bytesToGiB; } return value; } } return max; } @Override public long findCorrectResourceLimitForAccount(long accountId, Long limit, ResourceType type) { long max = Resource.RESOURCE_UNLIMITED; // if resource limit is not found, then we treat it as unlimited // No limits for Root Admin accounts if (_accountMgr.isRootAdmin(accountId)) { return max; } Account account = _accountDao.findById(accountId); if (account == null) { return max; } // Check if limit is configured for account if (limit != null) { max = limit.longValue(); } else { // If the account has an no limit set, then return global default account limits Long value = null; if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { value = projectResourceLimitMap.get(type); } else { value = accountResourceLimitMap.get(type); } if (value != null) { if (value < 0) { // return unlimit if value is set to negative return max; } if (type == ResourceType.primary_storage || type == ResourceType.secondary_storage) { value = value * ResourceType.bytesToGiB; } return value; } } return max; } @Override public long findCorrectResourceLimitForDomain(Domain domain, ResourceType type) { long max = Resource.RESOURCE_UNLIMITED; // no limits on ROOT domain if (domain.getId() == Domain.ROOT_DOMAIN) { return Resource.RESOURCE_UNLIMITED; } // Check account ResourceLimitVO limit = _resourceLimitDao.findByOwnerIdAndType(domain.getId(), ResourceOwnerType.Domain, type); if (limit != null) { max = limit.getMax().longValue(); } else { // check domain hierarchy Long domainId = domain.getParent(); while ((domainId != null) && (limit == null)) { if (domainId == Domain.ROOT_DOMAIN) { break; } limit = _resourceLimitDao.findByOwnerIdAndType(domainId, ResourceOwnerType.Domain, type); DomainVO tmpDomain = _domainDao.findById(domainId); domainId = tmpDomain.getParent(); } if (limit != null) { max = limit.getMax().longValue(); } else { Long value = null; value = domainResourceLimitMap.get(type); if (value != null) { if (value < 0) { // return unlimit if value is set to negative return max; } if (type == ResourceType.primary_storage || type == ResourceType.secondary_storage) { value = value * ResourceType.bytesToGiB; } return value; } } } return max; } @Override @DB public void checkResourceLimit(final Account account, final ResourceType type, long... count) throws ResourceAllocationException { final long numResources = ((count.length == 0) ? 1 : count[0]); Project project = null; // Don't place any limits on system or root admin accounts if (_accountMgr.isRootAdmin(account.getId())) { return; } if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { project = _projectDao.findByProjectAccountId(account.getId()); } final Project projectFinal = project; Transaction.execute(new TransactionCallbackWithExceptionNoReturn<ResourceAllocationException>() { @Override public void doInTransactionWithoutResult(TransactionStatus status) throws ResourceAllocationException { // Lock all rows first so nobody else can read it Set<Long> rowIdsToLock = _resourceCountDao.listAllRowsToUpdate(account.getId(), ResourceOwnerType.Account, type); SearchCriteria<ResourceCountVO> sc = ResourceCountSearch.create(); sc.setParameters("id", rowIdsToLock.toArray()); _resourceCountDao.lockRows(sc, null, true); // Check account limits long accountLimit = findCorrectResourceLimitForAccount(account, type); long potentialCount = _resourceCountDao.getResourceCount(account.getId(), ResourceOwnerType.Account, type) + numResources; if (accountLimit != Resource.RESOURCE_UNLIMITED && potentialCount > accountLimit) { String message = "Maximum number of resources of type '" + type + "' for account name=" + account.getAccountName() + " in domain id=" + account.getDomainId() + " has been exceeded."; if (projectFinal != null) { message = "Maximum number of resources of type '" + type + "' for project name=" + projectFinal.getName() + " in domain id=" + account.getDomainId() + " has been exceeded."; } ResourceAllocationException e= new ResourceAllocationException(message, type);; s_logger.error(message, e); throw e; } // check all domains in the account's domain hierarchy Long domainId = null; if (projectFinal != null) { domainId = projectFinal.getDomainId(); } else { domainId = account.getDomainId(); } while (domainId != null) { DomainVO domain = _domainDao.findById(domainId); // no limit check if it is ROOT domain if (domainId != Domain.ROOT_DOMAIN) { long domainLimit = findCorrectResourceLimitForDomain(domain, type); long domainCount = _resourceCountDao.getResourceCount(domainId, ResourceOwnerType.Domain, type) + numResources; if (domainLimit != Resource.RESOURCE_UNLIMITED && domainCount > domainLimit) { throw new ResourceAllocationException("Maximum number of resources of type '" + type + "' for domain id=" + domainId + " has been exceeded.", type); } } domainId = domain.getParent(); } } }); } @Override public List<ResourceLimitVO> searchForLimits(Long id, Long accountId, Long domainId, Integer type, Long startIndex, Long pageSizeVal) { Account caller = CallContext.current().getCallingAccount(); List<ResourceLimitVO> limits = new ArrayList<ResourceLimitVO>(); boolean isAccount = true; if (!_accountMgr.isAdmin(caller.getId())) { accountId = caller.getId(); domainId = null; } else { if (domainId != null) { // verify domain information and permissions Domain domain = _domainDao.findById(domainId); if (domain == null) { // return empty set return limits; } _accountMgr.checkAccess(caller, domain); if (accountId != null) { // Verify account information and permissions Account account = _accountDao.findById(accountId); if (account == null) { // return empty set return limits; } _accountMgr.checkAccess(caller, null, true, account); domainId = null; } } } // Map resource type ResourceType resourceType = null; if (type != null) { try { resourceType = ResourceType.values()[type]; } catch (ArrayIndexOutOfBoundsException e) { throw new InvalidParameterValueException("Please specify a valid resource type."); } } // If id is passed in, get the record and return it if permission check has passed if (id != null) { ResourceLimitVO vo = _resourceLimitDao.findById(id); if (vo.getAccountId() != null) { _accountMgr.checkAccess(caller, null, true, _accountDao.findById(vo.getAccountId())); limits.add(vo); } else if (vo.getDomainId() != null) { _accountMgr.checkAccess(caller, _domainDao.findById(vo.getDomainId())); limits.add(vo); } return limits; } // If account is not specified, default it to caller account if (accountId == null) { if (domainId == null) { accountId = caller.getId(); isAccount = true; } else { isAccount = false; } } else { isAccount = true; } SearchBuilder<ResourceLimitVO> sb = _resourceLimitDao.createSearchBuilder(); sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ); sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ); sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ); SearchCriteria<ResourceLimitVO> sc = sb.create(); Filter filter = new Filter(ResourceLimitVO.class, "id", true, startIndex, pageSizeVal); if (accountId != null) { sc.setParameters("accountId", accountId); } if (domainId != null) { sc.setParameters("domainId", domainId); sc.setParameters("accountId", (Object[])null); } if (resourceType != null) { sc.setParameters("type", resourceType); } List<ResourceLimitVO> foundLimits = _resourceLimitDao.search(sc, filter); if (resourceType != null) { if (foundLimits.isEmpty()) { if (isAccount) { limits.add(new ResourceLimitVO(resourceType, findCorrectResourceLimitForAccount(_accountMgr.getAccount(accountId), resourceType), accountId, ResourceOwnerType.Account)); } else { limits.add(new ResourceLimitVO(resourceType, findCorrectResourceLimitForDomain(_domainDao.findById(domainId), resourceType), domainId, ResourceOwnerType.Domain)); } } else { limits.addAll(foundLimits); } } else { limits.addAll(foundLimits); // see if any limits are missing from the table, and if yes - get it from the config table and add ResourceType[] resourceTypes = ResourceCount.ResourceType.values(); if (foundLimits.size() != resourceTypes.length) { List<String> accountLimitStr = new ArrayList<String>(); List<String> domainLimitStr = new ArrayList<String>(); for (ResourceLimitVO foundLimit : foundLimits) { if (foundLimit.getAccountId() != null) { accountLimitStr.add(foundLimit.getType().toString()); } else { domainLimitStr.add(foundLimit.getType().toString()); } } // get default from config values if (isAccount) { if (accountLimitStr.size() < resourceTypes.length) { for (ResourceType rt : resourceTypes) { if (!accountLimitStr.contains(rt.toString()) && rt.supportsOwner(ResourceOwnerType.Account)) { limits.add(new ResourceLimitVO(rt, findCorrectResourceLimitForAccount(_accountMgr.getAccount(accountId), rt), accountId, ResourceOwnerType.Account)); } } } } else { if (domainLimitStr.size() < resourceTypes.length) { for (ResourceType rt : resourceTypes) { if (!domainLimitStr.contains(rt.toString()) && rt.supportsOwner(ResourceOwnerType.Domain)) { limits.add(new ResourceLimitVO(rt, findCorrectResourceLimitForDomain(_domainDao.findById(domainId), rt), domainId, ResourceOwnerType.Domain)); } } } } } } return limits; } @Override public ResourceLimitVO updateResourceLimit(Long accountId, Long domainId, Integer typeId, Long max) { Account caller = CallContext.current().getCallingAccount(); if (max == null) { max = new Long(Resource.RESOURCE_UNLIMITED); } else if (max.longValue() < Resource.RESOURCE_UNLIMITED) { throw new InvalidParameterValueException("Please specify either '-1' for an infinite limit, or a limit that is at least '0'."); } // Map resource type ResourceType resourceType = null; if (typeId != null) { for (ResourceType type : Resource.ResourceType.values()) { if (type.getOrdinal() == typeId.intValue()) { resourceType = type; } } if (resourceType == null) { throw new InvalidParameterValueException("Please specify valid resource type"); } } //Convert max storage size from GiB to bytes if ((resourceType == ResourceType.primary_storage || resourceType == ResourceType.secondary_storage) && max >= 0) { max = max * ResourceType.bytesToGiB; } ResourceOwnerType ownerType = null; Long ownerId = null; if (accountId != null) { Account account = _entityMgr.findById(Account.class, accountId); if (account == null) { throw new InvalidParameterValueException("Unable to find account " + accountId); } if (account.getId() == Account.ACCOUNT_ID_SYSTEM) { throw new InvalidParameterValueException("Can't update system account"); } //only Unlimited value is accepted if account is Root Admin if (_accountMgr.isRootAdmin(account.getId()) && max.shortValue() != Resource.RESOURCE_UNLIMITED) { throw new InvalidParameterValueException("Only " + Resource.RESOURCE_UNLIMITED + " limit is supported for Root Admin accounts"); } if ((caller.getAccountId() == accountId.longValue()) && (_accountMgr.isDomainAdmin(caller.getId()) || caller.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN)) { // If the admin is trying to update his own account, disallow. throw new PermissionDeniedException("Unable to update resource limit for his own account " + accountId + ", permission denied"); } if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) { _accountMgr.checkAccess(caller, AccessType.ModifyProject, true, account); } else { _accountMgr.checkAccess(caller, null, true, account); } ownerType = ResourceOwnerType.Account; ownerId = accountId; } else if (domainId != null) { Domain domain = _entityMgr.findById(Domain.class, domainId); _accountMgr.checkAccess(caller, domain); if (Domain.ROOT_DOMAIN == domainId.longValue()) { // no one can add limits on ROOT domain, disallow... throw new PermissionDeniedException("Cannot update resource limit for ROOT domain " + domainId + ", permission denied"); } if ((caller.getDomainId() == domainId.longValue()) && caller.getType() == Account.ACCOUNT_TYPE_DOMAIN_ADMIN || caller.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) { // if the admin is trying to update their own domain, disallow... throw new PermissionDeniedException("Unable to update resource limit for domain " + domainId + ", permission denied"); } Long parentDomainId = domain.getParent(); if (parentDomainId != null) { DomainVO parentDomain = _domainDao.findById(parentDomainId); long parentMaximum = findCorrectResourceLimitForDomain(parentDomain, resourceType); if ((parentMaximum >= 0) && (max.longValue() > parentMaximum)) { throw new InvalidParameterValueException("Domain " + domain.getName() + "(id: " + parentDomain.getId() + ") has maximum allowed resource limit " + parentMaximum + " for " + resourceType + ", please specify a value less that or equal to " + parentMaximum); } } ownerType = ResourceOwnerType.Domain; ownerId = domainId; } if (ownerId == null) { throw new InvalidParameterValueException("AccountId or domainId have to be specified in order to update resource limit"); } ResourceLimitVO limit = _resourceLimitDao.findByOwnerIdAndType(ownerId, ownerType, resourceType); if (limit != null) { // Update the existing limit _resourceLimitDao.update(limit.getId(), max); return _resourceLimitDao.findById(limit.getId()); } else { return _resourceLimitDao.persist(new ResourceLimitVO(resourceType, max, ownerId, ownerType)); } } @Override public List<ResourceCountVO> recalculateResourceCount(Long accountId, Long domainId, Integer typeId) throws InvalidParameterValueException, CloudRuntimeException, PermissionDeniedException { Account callerAccount = CallContext.current().getCallingAccount(); long count = 0; List<ResourceCountVO> counts = new ArrayList<ResourceCountVO>(); List<ResourceType> resourceTypes = new ArrayList<ResourceType>(); ResourceType resourceType = null; if (typeId != null) { for (ResourceType type : Resource.ResourceType.values()) { if (type.getOrdinal() == typeId.intValue()) { resourceType = type; } } if (resourceType == null) { throw new InvalidParameterValueException("Please specify valid resource type"); } } DomainVO domain = _domainDao.findById(domainId); if (domain == null) { throw new InvalidParameterValueException("Please specify a valid domain ID."); } _accountMgr.checkAccess(callerAccount, domain); if (resourceType != null) { resourceTypes.add(resourceType); } else { resourceTypes = Arrays.asList(Resource.ResourceType.values()); } for (ResourceType type : resourceTypes) { if (accountId != null) { if (type.supportsOwner(ResourceOwnerType.Account)) { count = recalculateAccountResourceCount(accountId, type); counts.add(new ResourceCountVO(type, count, accountId, ResourceOwnerType.Account)); } } else { if (type.supportsOwner(ResourceOwnerType.Domain)) { count = recalculateDomainResourceCount(domainId, type); counts.add(new ResourceCountVO(type, count, domainId, ResourceOwnerType.Domain)); } } } return counts; } @DB protected boolean updateResourceCountForAccount(final long accountId, final ResourceType type, final boolean increment, final long delta) { try { return Transaction.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus status) { boolean result = true; Set<Long> rowsToLock = _resourceCountDao.listAllRowsToUpdate(accountId, ResourceOwnerType.Account, type); // Lock rows first SearchCriteria<ResourceCountVO> sc = ResourceCountSearch.create(); sc.setParameters("id", rowsToLock.toArray()); List<ResourceCountVO> rowsToUpdate = _resourceCountDao.lockRows(sc, null, true); for (ResourceCountVO rowToUpdate : rowsToUpdate) { if (!_resourceCountDao.updateById(rowToUpdate.getId(), increment, delta)) { s_logger.trace("Unable to update resource count for the row " + rowToUpdate); result = false; } } return result; } }); } catch (Exception ex) { s_logger.error("Failed to update resource count for account id=" + accountId); return false; } } @DB protected long recalculateDomainResourceCount(final long domainId, final ResourceType type) { return Transaction.execute(new TransactionCallback<Long>() { @Override public Long doInTransaction(TransactionStatus status) { long newCount = 0; // Lock all rows first so nobody else can read it Set<Long> rowIdsToLock = _resourceCountDao.listAllRowsToUpdate(domainId, ResourceOwnerType.Domain, type); SearchCriteria<ResourceCountVO> sc = ResourceCountSearch.create(); sc.setParameters("id", rowIdsToLock.toArray()); _resourceCountDao.lockRows(sc, null, true); ResourceCountVO domainRC = _resourceCountDao.findByOwnerAndType(domainId, ResourceOwnerType.Domain, type); long oldCount = domainRC.getCount(); List<DomainVO> domainChildren = _domainDao.findImmediateChildrenForParent(domainId); // for each child domain update the resource count if (type.supportsOwner(ResourceOwnerType.Domain)) { // calculate project count here if (type == ResourceType.project) { newCount = newCount + _projectDao.countProjectsForDomain(domainId); } for (DomainVO domainChild : domainChildren) { long domainCount = recalculateDomainResourceCount(domainChild.getId(), type); newCount = newCount + domainCount; // add the child domain count to parent domain count } } if (type.supportsOwner(ResourceOwnerType.Account)) { List<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(domainId); for (AccountVO account : accounts) { long accountCount = recalculateAccountResourceCount(account.getId(), type); newCount = newCount + accountCount; // add account's resource count to parent domain count } } _resourceCountDao.setResourceCount(domainId, ResourceOwnerType.Domain, type, newCount); if (oldCount != newCount) { s_logger.info("Discrepency in the resource count " + "(original count=" + oldCount + " correct count = " + newCount + ") for type " + type + " for domain ID " + domainId + " is fixed during resource count recalculation."); } return newCount; } }); } @DB protected long recalculateAccountResourceCount(final long accountId, final ResourceType type) { Long newCount = Transaction.execute(new TransactionCallback<Long>() { @Override public Long doInTransaction(TransactionStatus status) { Long newCount = null; // this lock guards against the updates to user_vm, volume, snapshot, public _ip and template table // as any resource creation precedes with the resourceLimitExceeded check which needs this lock too Set rowIdsToLock = _resourceCountDao.listAllRowsToUpdate(accountId, Resource.ResourceOwnerType.Account, type); SearchCriteria<ResourceCountVO> sc = ResourceCountSearch.create(); sc.setParameters("id", rowIdsToLock.toArray()); _resourceCountDao.lockRows(sc, null, true); ResourceCountVO accountRC = _resourceCountDao.findByOwnerAndType(accountId, ResourceOwnerType.Account, type); long oldCount = 0; if (accountRC != null) oldCount = accountRC.getCount(); if (type == Resource.ResourceType.user_vm) { newCount = _userVmDao.countAllocatedVMsForAccount(accountId); } else if (type == Resource.ResourceType.volume) { newCount = _volumeDao.countAllocatedVolumesForAccount(accountId); long virtualRouterCount = _vmDao.findIdsOfAllocatedVirtualRoutersForAccount(accountId).size(); newCount = newCount - virtualRouterCount; // don't count the volumes of virtual router } else if (type == Resource.ResourceType.snapshot) { newCount = _snapshotDao.countSnapshotsForAccount(accountId); } else if (type == Resource.ResourceType.public_ip) { newCount = calculatePublicIpForAccount(accountId); } else if (type == Resource.ResourceType.template) { newCount = _vmTemplateDao.countTemplatesForAccount(accountId); } else if (type == Resource.ResourceType.project) { newCount = _projectAccountDao.countByAccountIdAndRole(accountId, Role.Admin); } else if (type == Resource.ResourceType.network) { newCount = _networkDao.countNetworksUserCanCreate(accountId); } else if (type == Resource.ResourceType.vpc) { newCount = _vpcDao.countByAccountId(accountId); } else if (type == Resource.ResourceType.cpu) { newCount = countCpusForAccount(accountId); } else if (type == Resource.ResourceType.memory) { newCount = calculateMemoryForAccount(accountId); } else if (type == Resource.ResourceType.primary_storage) { List<Long> virtualRouters = _vmDao.findIdsOfAllocatedVirtualRoutersForAccount(accountId); newCount = _volumeDao.primaryStorageUsedForAccount(accountId, virtualRouters); } else if (type == Resource.ResourceType.secondary_storage) { newCount = calculateSecondaryStorageForAccount(accountId); } else { throw new InvalidParameterValueException("Unsupported resource type " + type); } _resourceCountDao.setResourceCount(accountId, ResourceOwnerType.Account, type, (newCount == null) ? 0 : newCount.longValue()); // No need to log message for primary and secondary storage because both are recalculating the resource count which will not lead to any discrepancy. if (!Long.valueOf(oldCount).equals(newCount) && (type != Resource.ResourceType.primary_storage && type != Resource.ResourceType.secondary_storage)) { s_logger.info("Discrepency in the resource count " + "(original count=" + oldCount + " correct count = " + newCount + ") for type " + type + " for account ID " + accountId + " is fixed during resource count recalculation."); } return newCount; } }); return (newCount == null) ? 0 : newCount.longValue(); } public long countCpusForAccount(long accountId) { GenericSearchBuilder<ServiceOfferingVO, SumCount> cpuSearch = _serviceOfferingDao.createSearchBuilder(SumCount.class); cpuSearch.select("sum", Func.SUM, cpuSearch.entity().getCpu()); SearchBuilder<UserVmVO> join1 = _userVmDao.createSearchBuilder(); join1.and("accountId", join1.entity().getAccountId(), Op.EQ); join1.and("type", join1.entity().getType(), Op.EQ); join1.and("state", join1.entity().getState(), SearchCriteria.Op.NIN); join1.and("displayVm", join1.entity().isDisplayVm(), Op.EQ); cpuSearch.join("offerings", join1, cpuSearch.entity().getId(), join1.entity().getServiceOfferingId(), JoinBuilder.JoinType.INNER); cpuSearch.done(); SearchCriteria<SumCount> sc = cpuSearch.create(); sc.setJoinParameters("offerings", "accountId", accountId); sc.setJoinParameters("offerings", "type", VirtualMachine.Type.User); sc.setJoinParameters("offerings", "state", new Object[] {State.Destroyed, State.Error, State.Expunging}); sc.setJoinParameters("offerings", "displayVm", 1); List<SumCount> cpus = _serviceOfferingDao.customSearch(sc, null); if (cpus != null) { return cpus.get(0).sum; } else { return 0; } } public long calculateMemoryForAccount(long accountId) { GenericSearchBuilder<ServiceOfferingVO, SumCount> memorySearch = _serviceOfferingDao.createSearchBuilder(SumCount.class); memorySearch.select("sum", Func.SUM, memorySearch.entity().getRamSize()); SearchBuilder<UserVmVO> join1 = _userVmDao.createSearchBuilder(); join1.and("accountId", join1.entity().getAccountId(), Op.EQ); join1.and("type", join1.entity().getType(), Op.EQ); join1.and("state", join1.entity().getState(), SearchCriteria.Op.NIN); join1.and("displayVm", join1.entity().isDisplayVm(), Op.EQ); memorySearch.join("offerings", join1, memorySearch.entity().getId(), join1.entity().getServiceOfferingId(), JoinBuilder.JoinType.INNER); memorySearch.done(); SearchCriteria<SumCount> sc = memorySearch.create(); sc.setJoinParameters("offerings", "accountId", accountId); sc.setJoinParameters("offerings", "type", VirtualMachine.Type.User); sc.setJoinParameters("offerings", "state", new Object[] {State.Destroyed, State.Error, State.Expunging}); sc.setJoinParameters("offerings", "displayVm", 1); List<SumCount> memory = _serviceOfferingDao.customSearch(sc, null); if (memory != null) { return memory.get(0).sum; } else { return 0; } } public long calculateSecondaryStorageForAccount(long accountId) { long totalVolumesSize = _volumeDao.secondaryStorageUsedForAccount(accountId); long totalSnapshotsSize = 0; long totalTemplatesSize = 0; SearchCriteria<SumCount> sc = templateSizeSearch.create(); sc.setParameters("downloadState", Status.DOWNLOADED); sc.setParameters("destroyed", false); sc.setJoinParameters("templates", "accountId", accountId); List<SumCount> templates = _vmTemplateStoreDao.customSearch(sc, null); if (templates != null) { totalTemplatesSize = templates.get(0).sum; } SearchCriteria<SumCount> sc2 = snapshotSizeSearch.create(); sc2.setParameters("state", ObjectInDataStoreStateMachine.State.Ready); sc2.setParameters("storeRole", DataStoreRole.Image); sc2.setJoinParameters("snapshots", "accountId", accountId); List<SumCount> snapshots = _snapshotDataStoreDao.customSearch(sc2, null); if (snapshots != null) { totalSnapshotsSize = snapshots.get(0).sum; } return totalVolumesSize + totalSnapshotsSize + totalTemplatesSize; } private long calculatePublicIpForAccount(long accountId) { Long dedicatedCount = 0L; Long allocatedCount = 0L; List<VlanVO> dedicatedVlans = _vlanDao.listDedicatedVlans(accountId); for (VlanVO dedicatedVlan : dedicatedVlans) { List<IPAddressVO> ips = _ipAddressDao.listByVlanId(dedicatedVlan.getId()); dedicatedCount += new Long(ips.size()); } allocatedCount = _ipAddressDao.countAllocatedIPsForAccount(accountId); if (dedicatedCount > allocatedCount) return dedicatedCount; else return allocatedCount; } @Override public long getResourceCount(Account account, ResourceType type) { return _resourceCountDao.getResourceCount(account.getId(), ResourceOwnerType.Account, type); } private boolean isDisplayFlagOn(Boolean displayResource){ // 1. If its null assume displayResource = 1 // 2. If its not null then send true if displayResource = 1 return (displayResource == null) || (displayResource != null && displayResource); } @Override public void checkResourceLimit(Account account, ResourceType type, Boolean displayResource, long... count) throws ResourceAllocationException { if (isDisplayFlagOn(displayResource)) { checkResourceLimit(account, type, count); } } @Override public void incrementResourceCount(long accountId, ResourceType type, Boolean displayResource, Long... delta) { if (isDisplayFlagOn(displayResource)) { incrementResourceCount(accountId, type, delta); } } @Override public void decrementResourceCount(long accountId, ResourceType type, Boolean displayResource, Long... delta) { if (isDisplayFlagOn(displayResource)) { decrementResourceCount(accountId, type, delta); } } @Override public void changeResourceCount(long accountId, ResourceType type, Boolean displayResource, Long... delta) { // meaning that the display flag is not changed so neither increment or decrement if (displayResource == null) return; // Increment because the display is turned on. if (displayResource) { incrementResourceCount(accountId, type, delta); } else { decrementResourceCount(accountId, type, delta); } } protected class ResourceCountCheckTask extends ManagedContextRunnable { public ResourceCountCheckTask() { } @Override protected void runInContext() { s_logger.info("Running resource count check periodic task"); List<DomainVO> domains = _domainDao.findImmediateChildrenForParent(Domain.ROOT_DOMAIN); // recalculateDomainResourceCount will take care of re-calculation of resource counts for sub-domains // and accounts of the sub-domains also. so just loop through immediate children of root domain for (Domain domain : domains) { for (ResourceType type : ResourceCount.ResourceType.values()) { if (type.supportsOwner(ResourceOwnerType.Domain)) { recalculateDomainResourceCount(domain.getId(), type); } } } // run through the accounts in the root domain List<AccountVO> accounts = _accountDao.findActiveAccountsForDomain(Domain.ROOT_DOMAIN); for (AccountVO account : accounts) { for (ResourceType type : ResourceCount.ResourceType.values()) { if (type.supportsOwner(ResourceOwnerType.Account)) { recalculateAccountResourceCount(account.getId(), type); } } } } } }