// 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.user; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import javax.inject.Inject; import com.cloud.api.query.dao.NetworkOfferingJoinDao; import com.cloud.api.query.dao.VpcOfferingJoinDao; import com.cloud.api.query.vo.NetworkOfferingJoinVO; import com.cloud.api.query.vo.VpcOfferingJoinVO; import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.network.vpc.dao.VpcOfferingDetailsDao; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd; import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.region.RegionManager; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.api.query.dao.DiskOfferingJoinDao; import com.cloud.api.query.dao.ServiceOfferingJoinDao; import com.cloud.api.query.vo.DiskOfferingJoinVO; import com.cloud.api.query.vo.ServiceOfferingJoinVO; import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.Resource.ResourceOwnerType; import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.dao.ResourceCountDao; import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.dao.NetworkDomainDao; import com.cloud.projects.ProjectManager; import com.cloud.projects.ProjectVO; import com.cloud.projects.dao.ProjectDao; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ReservationContext; import com.cloud.vm.ReservationContextImpl; import org.apache.commons.lang3.StringUtils; @Component public class DomainManagerImpl extends ManagerBase implements DomainManager, DomainService { public static final Logger s_logger = Logger.getLogger(DomainManagerImpl.class); @Inject private DomainDao _domainDao; @Inject private AccountManager _accountMgr; @Inject private ResourceCountDao _resourceCountDao; @Inject private AccountDao _accountDao; @Inject private DiskOfferingJoinDao diskOfferingJoinDao; @Inject private DiskOfferingDao diskOfferingDao; @Inject private DiskOfferingDetailsDao diskOfferingDetailsDao; @Inject private NetworkOfferingDao networkOfferingDao; @Inject private NetworkOfferingJoinDao networkOfferingJoinDao; @Inject private NetworkOfferingDetailsDao networkOfferingDetailsDao; @Inject private ServiceOfferingJoinDao serviceOfferingJoinDao; @Inject private ServiceOfferingDao serviceOfferingDao; @Inject private ServiceOfferingDetailsDao serviceOfferingDetailsDao; @Inject private VpcOfferingDao vpcOfferingDao; @Inject private VpcOfferingJoinDao vpcOfferingJoinDao; @Inject private VpcOfferingDetailsDao vpcOfferingDetailsDao; @Inject private ProjectDao _projectDao; @Inject private ProjectManager _projectMgr; @Inject private RegionManager _regionMgr; @Inject private ResourceLimitDao _resourceLimitDao; @Inject private DedicatedResourceDao _dedicatedDao; @Inject private NetworkOrchestrationService _networkMgr; @Inject private NetworkDomainDao _networkDomainDao; @Inject private ConfigurationManager _configMgr; @Inject private DomainDetailsDao _domainDetailsDao; @Inject private AnnotationDao annotationDao; @Inject MessageBus _messageBus; protected GlobalLock getGlobalLock(String name) { return GlobalLock.getInternLock(name); } protected Account getCaller() { return CallContext.current().getCallingAccount(); } @Override public Domain getDomain(long domainId) { return _domainDao.findById(domainId); } @Override public Domain getDomain(String domainUuid) { return _domainDao.findByUuid(domainUuid); } @Override public Domain getDomainByName(String name, long parentId) { SearchCriteria sc = _domainDao.createSearchCriteria(); sc.addAnd("name", SearchCriteria.Op.EQ, name); sc.addAnd("parent", SearchCriteria.Op.EQ, parentId); Domain domain = _domainDao.findOneBy(sc); return domain; } @Override public Set getDomainChildrenIds(String parentDomainPath) { Set childDomains = new HashSet(); SearchCriteria sc = _domainDao.createSearchCriteria(); sc.addAnd("path", SearchCriteria.Op.LIKE, parentDomainPath + "%"); List domains = _domainDao.search(sc, null); for (DomainVO domain : domains) { childDomains.add(domain.getId()); } return childDomains; } @Override public boolean isChildDomain(Long parentId, Long childId) { return _domainDao.isChildDomain(parentId, childId); } @Override @ActionEvent(eventType = EventTypes.EVENT_DOMAIN_CREATE, eventDescription = "creating Domain") public Domain createDomain(String name, Long parentId, String networkDomain, String domainUUID) { Account caller = getCaller(); if (parentId == null) { parentId = Long.valueOf(Domain.ROOT_DOMAIN); } DomainVO parentDomain = _domainDao.findById(parentId); if (parentDomain == null) { throw new InvalidParameterValueException("Unable to create domain " + name + ", parent domain " + parentId + " not found."); } if (parentDomain.getState().equals(Domain.State.Inactive)) { throw new CloudRuntimeException("The domain cannot be created as the parent domain " + parentDomain.getName() + " is being deleted"); } _accountMgr.checkAccess(caller, parentDomain); return createDomain(name, parentId, caller.getId(), networkDomain, domainUUID); } @Override @DB public Domain createDomain(final String name, final Long parentId, final Long ownerId, final String networkDomain, String domainUuid) { validateDomainNameAndNetworkDomain(name, parentId, networkDomain); DomainVO domainVO = createDomainVo(name, parentId, ownerId, networkDomain, domainUuid); DomainVO domain = Transaction.execute(new TransactionCallback() { @Override public DomainVO doInTransaction(TransactionStatus status) { DomainVO domain = _domainDao.create(domainVO); _resourceCountDao.createResourceCounts(domain.getId(), ResourceLimit.ResourceOwnerType.Domain); CallContext.current().putContextParameter(Domain.class, domain.getUuid()); return domain; } }); if (domain != null) { _messageBus.publish(_name, MESSAGE_ADD_DOMAIN_EVENT, PublishScope.LOCAL, domain.getId()); _messageBus.publish(_name, MESSAGE_CREATE_TUNGSTEN_DOMAIN_EVENT, PublishScope.LOCAL, domain); } return domain; } protected DomainVO createDomainVo(String name, Long parentId, Long ownerId, String networkDomain, String domainUuid) { if (StringUtils.isBlank(domainUuid)) { domainUuid = UUID.randomUUID().toString(); s_logger.info(String.format("Domain UUID [%s] generated for domain name [%s].", domainUuid, name)); } DomainVO domainVO = new DomainVO(name, ownerId, parentId, networkDomain, domainUuid); return domainVO; } protected void validateDomainNameAndNetworkDomain(String name, Long parentId, String networkDomain) { validateNetworkDomain(networkDomain); validateUniqueDomainName(name, parentId); } protected void validateUniqueDomainName(String name, Long parentId) { SearchCriteria sc = _domainDao.createSearchCriteria(); sc.addAnd("name", SearchCriteria.Op.EQ, name); sc.addAnd("parent", SearchCriteria.Op.EQ, parentId); List domains = _domainDao.search(sc, null); if (!domains.isEmpty()) { throw new InvalidParameterValueException("Domain with name " + name + " already exists for the parent id=" + parentId); } } protected void validateNetworkDomain(String networkDomain) { if (networkDomain != null && !NetUtils.verifyDomainName(networkDomain)) { throw new InvalidParameterValueException( "Invalid network domain. Total length should not exceed 190 chars. Each domain label must be between 1 and 63 characters long." + " It can contain ASCII letters 'a' through 'z', the digits '0' through '9', and the hyphen ('-'); it cannot start or end with \"-\"." ); } } @Override public DomainVO findDomainByPath(String domainPath) { return _domainDao.findDomainByPath(domainPath); } @Override public Domain findDomainByIdOrPath(final Long id, final String domainPath) { Long domainId = id; if (domainId == null || domainId < 1L) { if (StringUtils.isBlank(domainPath)) { domainId = Domain.ROOT_DOMAIN; } else { final Domain domainVO = findDomainByPath(domainPath.trim()); if (domainVO != null) { return domainVO; } } } if (domainId != null && domainId > 0L) { return _domainDao.findById(domainId); } return null; } @Override public Set getDomainParentIds(long domainId) { return _domainDao.getDomainParentIds(domainId); } @Override public boolean removeDomain(long domainId) { return _domainDao.remove(domainId); } @Override public List findInactiveDomains() { return _domainDao.findInactiveDomains(); } @Override @ActionEvent(eventType = EventTypes.EVENT_DOMAIN_DELETE, eventDescription = "deleting Domain", async = true) public boolean deleteDomain(long domainId, Boolean cleanup) { Account caller = getCaller(); DomainVO domain = _domainDao.findById(domainId); if (domain == null) { throw new InvalidParameterValueException("Failed to delete domain " + domainId + ", domain not found"); } else if (domainId == Domain.ROOT_DOMAIN) { throw new PermissionDeniedException("Can't delete ROOT domain"); } _accountMgr.checkAccess(caller, domain); return deleteDomain(domain, cleanup); } @Override public boolean deleteDomain(DomainVO domain, Boolean cleanup) { GlobalLock lock = getGlobalLock("AccountCleanup"); if (lock == null) { s_logger.debug("Couldn't get the global lock"); return false; } if (!lock.lock(30)) { s_logger.debug("Couldn't lock the db"); return false; } try { // mark domain as inactive s_logger.debug("Marking domain id=" + domain.getId() + " as " + Domain.State.Inactive + " before actually deleting it"); domain.setState(Domain.State.Inactive); _domainDao.update(domain.getId(), domain); try { long ownerId = domain.getAccountId(); if (BooleanUtils.toBoolean(cleanup)) { tryCleanupDomain(domain, ownerId); } else { removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain); } if (!_configMgr.releaseDomainSpecificVirtualRanges(domain.getId())) { CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because failed to release domain specific virtual ip ranges"); e.addProxyObject(domain.getUuid(), "domainId"); throw e; } else { s_logger.debug("Domain specific Virtual IP ranges " + " are successfully released as a part of domain id=" + domain.getId() + " cleanup."); } cleanupDomainDetails(domain.getId()); cleanupDomainOfferings(domain.getId()); annotationDao.removeByEntityType(AnnotationService.EntityType.DOMAIN.name(), domain.getUuid()); CallContext.current().putContextParameter(Domain.class, domain.getUuid()); return true; } catch (Exception ex) { s_logger.error("Exception deleting domain with id " + domain.getId(), ex); if (ex instanceof CloudRuntimeException) { rollbackDomainState(domain); throw (CloudRuntimeException)ex; } else return false; } } finally { lock.unlock(); } } /** * Roll back domain state to Active * @param domain domain */ protected void rollbackDomainState(DomainVO domain) { s_logger.debug("Changing domain id=" + domain.getId() + " state back to " + Domain.State.Active + " because it can't be removed due to resources referencing to it"); domain.setState(Domain.State.Active); _domainDao.update(domain.getId(), domain); } /** * Try cleaning up domain. If it couldn't throws CloudRuntimeException * @param domain domain * @param ownerId owner id * @throws ConcurrentOperationException * @throws ResourceUnavailableException * @throws CloudRuntimeException when cleanupDomain */ protected void tryCleanupDomain(DomainVO domain, long ownerId) throws ConcurrentOperationException, ResourceUnavailableException, CloudRuntimeException { if (!cleanupDomain(domain.getId(), ownerId)) { CloudRuntimeException e = new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " + domain.getId() + ")."); e.addProxyObject(domain.getUuid(), "domainId"); throw e; } } /** * First check domain resources before removing domain. There are 2 cases: *
    *
  1. Domain doesn't have accounts for cleanup, non-removed networks, or dedicated resources
  2. *
    • Delete domain
    *
  3. Domain has one of the following: accounts set for cleanup, non-removed networks, dedicated resources
  4. *
    • Dont' delete domain
    • Fail operation
    *
* @param domain domain to remove * @throws CloudRuntimeException when case 2 or when domain cannot be deleted on case 1 */ protected void removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(DomainVO domain) { boolean hasDedicatedResources = false; List networkIds = _networkDomainDao.listNetworkIdsByDomain(domain.getId()); List accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domain.getId()); List dedicatedResources = _dedicatedDao.listByDomainId(domain.getId()); if (CollectionUtils.isNotEmpty(dedicatedResources)) { s_logger.error("There are dedicated resources for the domain " + domain.getId()); hasDedicatedResources = true; } if (accountsForCleanup.isEmpty() && networkIds.isEmpty() && !hasDedicatedResources) { publishRemoveEventsAndRemoveDomain(domain); } else { failRemoveOperation(domain, accountsForCleanup, networkIds, hasDedicatedResources); } } /** * Fail domain remove operation including proper message * @param domain domain * @param accountsForCleanup domain accounts for cleanup * @param networkIds domain network ids * @param hasDedicatedResources indicates if domain has dedicated resources * @throws CloudRuntimeException including descriptive message indicating the reason for failure */ protected void failRemoveOperation(DomainVO domain, List accountsForCleanup, List networkIds, boolean hasDedicatedResources) { String msg = null; if (!accountsForCleanup.isEmpty()) { msg = accountsForCleanup.size() + " accounts to cleanup"; } else if (!networkIds.isEmpty()) { msg = networkIds.size() + " non-removed networks"; } else if (hasDedicatedResources) { msg = "dedicated resources."; } CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg); e.addProxyObject(domain.getUuid(), "domainId"); throw e; } /** * Publish pre-remove and remove domain events and remove domain * @param domain domain to remove * @throws CloudRuntimeException when domain cannot be removed */ protected void publishRemoveEventsAndRemoveDomain(DomainVO domain) { _messageBus.publish(_name, MESSAGE_PRE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain); if (!_domainDao.remove(domain.getId())) { CloudRuntimeException e = new CloudRuntimeException("Delete failed on domain " + domain.getName() + " (id: " + domain.getId() + "); Please make sure all users and sub domains have been removed from the domain before deleting"); e.addProxyObject(domain.getUuid(), "domainId"); throw e; } _messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain); } protected void cleanupDomainDetails(Long domainId) { _domainDetailsDao.deleteDetails(domainId); } protected void cleanupDomainOfferings(Long domainId) { if (domainId == null) { return; } String domainIdString = String.valueOf(domainId); removeDiskOfferings(domainId, domainIdString); removeServiceOfferings(domainId, domainIdString); removeNetworkOfferings(domainId, domainIdString); removeVpcOfferings(domainId, domainIdString); } private void removeVpcOfferings(Long domainId, String domainIdString) { List vpcOfferingsDetailsToRemove = new ArrayList<>(); List vpcOfferingsForThisDomain = vpcOfferingJoinDao.findByDomainId(domainId); for (VpcOfferingJoinVO vpcOffering : vpcOfferingsForThisDomain) { if (domainIdString.equals(vpcOffering.getDomainId())) { vpcOfferingDao.remove(vpcOffering.getId()); } else { vpcOfferingsDetailsToRemove.add(vpcOffering.getId()); } } for (final Long vpcOfferingId : vpcOfferingsDetailsToRemove) { vpcOfferingDetailsDao.removeDetail(vpcOfferingId, ApiConstants.DOMAIN_ID, domainIdString); } } private void removeNetworkOfferings(Long domainId, String domainIdString) { List networkOfferingsDetailsToRemove = new ArrayList<>(); List networkOfferingsForThisDomain = networkOfferingJoinDao.findByDomainId(domainId, false); for (NetworkOfferingJoinVO networkOffering : networkOfferingsForThisDomain) { if (domainIdString.equals(networkOffering.getDomainId())) { networkOfferingDao.remove(networkOffering.getId()); } else { networkOfferingsDetailsToRemove.add(networkOffering.getId()); } } for (final Long networkOfferingId : networkOfferingsDetailsToRemove) { networkOfferingDetailsDao.removeDetail(networkOfferingId, ApiConstants.DOMAIN_ID, domainIdString); } } private void removeServiceOfferings(Long domainId, String domainIdString) { List serviceOfferingsDetailsToRemove = new ArrayList<>(); List serviceOfferingsForThisDomain = serviceOfferingJoinDao.findByDomainId(domainId); for (ServiceOfferingJoinVO serviceOffering : serviceOfferingsForThisDomain) { if (domainIdString.equals(serviceOffering.getDomainId())) { serviceOfferingDao.remove(serviceOffering.getId()); } else { serviceOfferingsDetailsToRemove.add(serviceOffering.getId()); } } for (final Long serviceOfferingId : serviceOfferingsDetailsToRemove) { serviceOfferingDetailsDao.removeDetail(serviceOfferingId, ApiConstants.DOMAIN_ID, domainIdString); } } private void removeDiskOfferings(Long domainId, String domainIdString) { List diskOfferingsDetailsToRemove = new ArrayList<>(); List diskOfferingsForThisDomain = diskOfferingJoinDao.findByDomainId(domainId); for (DiskOfferingJoinVO diskOffering : diskOfferingsForThisDomain) { if (domainIdString.equals(diskOffering.getDomainId())) { diskOfferingDao.remove(diskOffering.getId()); } else { diskOfferingsDetailsToRemove.add(diskOffering.getId()); } } // Remove domain IDs for offerings which may be multi-domain for (final Long diskOfferingId : diskOfferingsDetailsToRemove) { diskOfferingDetailsDao.removeDetail(diskOfferingId, ApiConstants.DOMAIN_ID, domainIdString); } } protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException { s_logger.debug("Cleaning up domain id=" + domainId); boolean success = true; DomainVO domainHandle = _domainDao.findById(domainId); { domainHandle.setState(Domain.State.Inactive); _domainDao.update(domainId, domainHandle); SearchCriteria sc = _domainDao.createSearchCriteria(); sc.addAnd("parent", SearchCriteria.Op.EQ, domainId); List domains = _domainDao.search(sc, null); SearchCriteria sc1 = _domainDao.createSearchCriteria(); sc1.addAnd("path", SearchCriteria.Op.LIKE, "%" + "replace(" + domainHandle.getPath() + ", '%', '[%]')" + "%"); List domainsToBeInactivated = _domainDao.search(sc1, null); // update all subdomains to inactive so no accounts/users can be created for (DomainVO domain : domainsToBeInactivated) { domain.setState(Domain.State.Inactive); _domainDao.update(domain.getId(), domain); } // cleanup sub-domains first for (DomainVO domain : domains) { success = (success && cleanupDomain(domain.getId(), domain.getAccountId())); if (!success) { s_logger.warn("Failed to cleanup domain id=" + domain.getId()); } } } // delete users which will also delete accounts and release resources for those accounts SearchCriteria sc = _accountDao.createSearchCriteria(); sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); List accounts = _accountDao.search(sc, null); for (AccountVO account : accounts) { if (account.getType() != Account.Type.PROJECT) { s_logger.debug("Deleting account " + account + " as a part of domain id=" + domainId + " cleanup"); boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), getCaller()); if (!deleteAccount) { s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup"); } success = (success && deleteAccount); } else { ProjectVO project = _projectDao.findByProjectAccountId(account.getId()); s_logger.debug("Deleting project " + project + " as a part of domain id=" + domainId + " cleanup"); boolean deleteProject = _projectMgr.deleteProject(getCaller(), CallContext.current().getCallingUserId(), project); if (!deleteProject) { s_logger.warn("Failed to cleanup project " + project + " as a part of domain cleanup"); } success = (success && deleteProject); } } //delete the domain shared networks boolean networksDeleted = true; s_logger.debug("Deleting networks for domain id=" + domainId); List networkIds = _networkDomainDao.listNetworkIdsByDomain(domainId); CallContext ctx = CallContext.current(); ReservationContext context = new ReservationContextImpl(null, null, _accountMgr.getActiveUser(ctx.getCallingUserId()), ctx.getCallingAccount()); for (Long networkId : networkIds) { s_logger.debug("Deleting network id=" + networkId + " as a part of domain id=" + domainId + " cleanup"); if (!_networkMgr.destroyNetwork(networkId, context, false)) { s_logger.warn("Unable to destroy network id=" + networkId + " as a part of domain id=" + domainId + " cleanup."); networksDeleted = false; } else { s_logger.debug("Network " + networkId + " successfully deleted as a part of domain id=" + domainId + " cleanup."); } } //don't proceed if networks failed to cleanup. The cleanup will be performed for inactive domain once again if (!networksDeleted) { s_logger.debug("Failed to delete the shared networks as a part of domain id=" + domainId + " clenaup"); return false; } // don't remove the domain if there are accounts required cleanup boolean deleteDomainSuccess = true; List accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domainId); if (accountsForCleanup.isEmpty()) { //release dedication if any, before deleting the domain List dedicatedResources = _dedicatedDao.listByDomainId(domainId); if (dedicatedResources != null && !dedicatedResources.isEmpty()) { s_logger.debug("Releasing dedicated resources for domain" + domainId); for (DedicatedResourceVO dr : dedicatedResources) { if (!_dedicatedDao.remove(dr.getId())) { s_logger.warn("Fail to release dedicated resources for domain " + domainId); return false; } } } //delete domain _messageBus.publish(_name, MESSAGE_PRE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domainHandle); deleteDomainSuccess = _domainDao.remove(domainId); _messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domainHandle); // Delete resource count and resource limits entries set for this domain (if there are any). _resourceCountDao.removeEntriesByOwner(domainId, ResourceOwnerType.Domain); _resourceLimitDao.removeEntriesByOwner(domainId, ResourceOwnerType.Domain); } else { s_logger.debug("Can't delete the domain yet because it has " + accountsForCleanup.size() + "accounts that need a cleanup"); return false; } return success && deleteDomainSuccess; } @Override public Pair, Integer> searchForDomains(ListDomainsCmd cmd) { Account caller = getCaller(); Long domainId = cmd.getId(); boolean listAll = cmd.listAll(); boolean isRecursive = false; if (domainId != null) { Domain domain = getDomain(domainId); if (domain == null) { throw new InvalidParameterValueException("Domain id=" + domainId + " doesn't exist"); } _accountMgr.checkAccess(caller, domain); } else { if (!_accountMgr.isRootAdmin(caller.getId())) { domainId = caller.getDomainId(); } if (listAll) { isRecursive = true; } } Filter searchFilter = new Filter(DomainVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); String domainName = cmd.getDomainName(); Integer level = cmd.getLevel(); Object keyword = cmd.getKeyword(); SearchBuilder sb = _domainDao.createSearchBuilder(); sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); sb.and("level", sb.entity().getLevel(), SearchCriteria.Op.EQ); sb.and("path", sb.entity().getPath(), SearchCriteria.Op.LIKE); sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); SearchCriteria sc = sb.create(); if (keyword != null) { SearchCriteria ssc = _domainDao.createSearchCriteria(); ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); sc.addAnd("name", SearchCriteria.Op.SC, ssc); } if (domainName != null) { sc.setParameters("name", domainName); } if (level != null) { sc.setParameters("level", level); } if (domainId != null) { if (isRecursive) { sc.setParameters("path", getDomain(domainId).getPath() + "%"); } else { sc.setParameters("id", domainId); } } // return only Active domains to the API sc.setParameters("state", Domain.State.Active); Pair, Integer> result = _domainDao.searchAndCount(sc, searchFilter); return new Pair, Integer>(result.first(), result.second()); } @Override public Pair, Integer> searchForDomainChildren(ListDomainChildrenCmd cmd) throws PermissionDeniedException { Long domainId = cmd.getId(); String domainName = cmd.getDomainName(); Boolean isRecursive = cmd.isRecursive(); Object keyword = cmd.getKeyword(); boolean listAll = cmd.listAll(); String path = null; Account caller = getCaller(); if (domainId != null) { _accountMgr.checkAccess(caller, getDomain(domainId)); } else { domainId = caller.getDomainId(); } DomainVO domain = _domainDao.findById(domainId); if (domain != null && isRecursive && !listAll) { path = domain.getPath(); domainId = null; } Filter searchFilter = new Filter(DomainVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); Pair, Integer> result = searchForDomainChildren(searchFilter, domainId, domainName, keyword, path, true); return new Pair, Integer>(result.first(), result.second()); } private Pair, Integer> searchForDomainChildren(Filter searchFilter, Long domainId, String domainName, Object keyword, String path, boolean listActiveOnly) { SearchCriteria sc = _domainDao.createSearchCriteria(); if (keyword != null) { SearchCriteria ssc = _domainDao.createSearchCriteria(); ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); sc.addAnd("name", SearchCriteria.Op.SC, ssc); } if (domainId != null) { sc.addAnd("parent", SearchCriteria.Op.EQ, domainId); } if (domainName != null) { sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + domainName + "%"); } if (path != null) { sc.addAnd("path", SearchCriteria.Op.NEQ, path); sc.addAnd("path", SearchCriteria.Op.LIKE, path + "%"); } if (listActiveOnly) { sc.addAnd("state", SearchCriteria.Op.EQ, Domain.State.Active); } return _domainDao.searchAndCount(sc, searchFilter); } @Override @ActionEvent(eventType = EventTypes.EVENT_DOMAIN_UPDATE, eventDescription = "updating Domain") @DB public DomainVO updateDomain(UpdateDomainCmd cmd) { final Long domainId = cmd.getId(); final String domainName = cmd.getDomainName(); final String networkDomain = cmd.getNetworkDomain(); // check if domain exists in the system final DomainVO domain = _domainDao.findById(domainId); if (domain == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find domain with specified domain id"); ex.addProxyObject(domainId.toString(), "domainId"); throw ex; } else if (domain.getParent() == null && domainName != null) { // check if domain is ROOT domain - and deny to edit it with the new name throw new InvalidParameterValueException("ROOT domain can not be edited with a new name"); } // check permissions Account caller = getCaller(); _accountMgr.checkAccess(caller, domain); // domain name is unique in the cloud if (domainName != null) { SearchCriteria sc = _domainDao.createSearchCriteria(); sc.addAnd("name", SearchCriteria.Op.EQ, domainName); sc.addAnd("parent", SearchCriteria.Op.EQ, domain.getParent()); List domains = _domainDao.search(sc, null); boolean sameDomain = (domains.size() == 1 && domains.get(0).getId() == domainId); if (!domains.isEmpty() && !sameDomain) { InvalidParameterValueException ex = new InvalidParameterValueException("Failed to update specified domain id with name '" + domainName + "' since it already exists in the system"); ex.addProxyObject(domain.getUuid(), "domainId"); throw ex; } } // validate network domain if (networkDomain != null && !networkDomain.isEmpty()) { if (!NetUtils.verifyDomainName(networkDomain)) { throw new InvalidParameterValueException( "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + "and the hyphen ('-'); can't start or end with \"-\""); } } Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { if (domainName != null) { String updatedDomainPath = getUpdatedDomainPath(domain.getPath(), domainName); updateDomainChildren(domain, updatedDomainPath); domain.setName(domainName); domain.setPath(updatedDomainPath); } if (networkDomain != null) { if (networkDomain.isEmpty()) { domain.setNetworkDomain(null); } else { domain.setNetworkDomain(networkDomain); } } _domainDao.update(domainId, domain); CallContext.current().putContextParameter(Domain.class, domain.getUuid()); } }); return _domainDao.findById(domainId); } private String getUpdatedDomainPath(String oldPath, String newName) { String[] tokenizedPath = oldPath.split("/"); tokenizedPath[tokenizedPath.length - 1] = newName; StringBuilder finalPath = new StringBuilder(); for (String token : tokenizedPath) { finalPath.append(token); finalPath.append("/"); } return finalPath.toString(); } private void updateDomainChildren(DomainVO domain, String updatedDomainPrefix) { List domainChildren = _domainDao.findAllChildren(domain.getPath(), domain.getId()); // for each child, update the path for (DomainVO dom : domainChildren) { dom.setPath(dom.getPath().replaceFirst(domain.getPath(), updatedDomainPrefix)); _domainDao.update(dom.getId(), dom); } } }