/** * Copyright (C) 2011 Citrix Systems, Inc. All rights reserved * * This software is licensed under the GNU General Public License v3 or later. * * It is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ package com.cloud.user; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.ejb.Local; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.dao.ResourceCountDao; 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.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.user.dao.AccountDao; import com.cloud.utils.component.Inject; import com.cloud.utils.component.Manager; import com.cloud.utils.db.DB; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @Local(value = { DomainManager.class, DomainService.class }) public class DomainManagerImpl implements DomainManager, DomainService, Manager{ public static final Logger s_logger = Logger.getLogger(DomainManagerImpl.class); private String _name; @Inject private DomainDao _domainDao; @Inject private AccountManager _accountMgr; @Inject private ResourceCountDao _resourceCountDao; @Inject private AccountDao _accountDao; @Inject private DiskOfferingDao _diskOfferingDao; @Inject private ServiceOfferingDao _offeringsDao; @Override public Domain getDomain(long domainId) { return _domainDao.findById(domainId); } @Override public String getName() { return _name; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } @Override public boolean configure(final String name, final Map params) throws ConfigurationException { _name = name; return true; } @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 @DB public Domain createDomain(String name, Long parentId, String networkDomain) { Account caller = UserContext.current().getCaller(); if (parentId == null) { parentId = Long.valueOf(DomainVO.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, null); return createDomain(name, parentId, caller.getId(), networkDomain, null); } @Override @ActionEvent(eventType = EventTypes.EVENT_DOMAIN_CREATE, eventDescription = "creating Domain") @DB public Domain createDomain(String name, Long parentId, Long ownerId, String networkDomain, Domain.Type domainType) { //Verify network domain if (networkDomain != null) { 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 \"-\""); } } //verify domainType if (domainType != null && !(domainType == Domain.Type.Project || domainType == Domain.Type.Normal)) { throw new InvalidParameterValueException("Invalid domain type; following values are supported: " + Domain.Type.Normal + ", " + Domain.Type.Project); } 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); } Transaction txn = Transaction.currentTxn(); txn.start(); DomainVO domain = _domainDao.create(new DomainVO(name, ownerId, parentId, networkDomain, domainType)); _resourceCountDao.createResourceCounts(domain.getId(), ResourceLimit.ResourceOwnerType.Domain); txn.commit(); return domain; } @Override public DomainVO findDomainByPath(String domainPath) { return _domainDao.findDomainByPath(domainPath); } @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 = UserContext.current().getCaller(); DomainVO domain = _domainDao.findById(domainId); if (domain == null) { throw new InvalidParameterValueException("Failed to delete domain " + domainId + ", domain not found"); } else if (domainId == DomainVO.ROOT_DOMAIN) { throw new PermissionDeniedException("Can't delete ROOT domain"); } _accountMgr.checkAccess(caller, domain, null); //mark domain as inactive s_logger.debug("Marking domain id=" + domainId + " as " + Domain.State.Inactive + " before actually deleting it"); domain.setState(Domain.State.Inactive); _domainDao.update(domainId, domain); try { long ownerId = domain.getAccountId(); if ((cleanup != null) && cleanup.booleanValue()) { if (!cleanupDomain(domainId, ownerId)) { s_logger.error("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " + domainId + ")."); return false; } } else { List accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domainId); if (accountsForCleanup.isEmpty()) { if (!_domainDao.remove(domainId)) { s_logger.error("Delete failed on domain " + domain.getName() + " (id: " + domainId + "); please make sure all users and sub domains have been removed from the domain before deleting"); return false; } } else { s_logger.warn("Can't delete the domain yet because it has " + accountsForCleanup.size() + "accounts that need a cleanup"); return false; } } cleanupDomainOfferings(domainId); return true; } catch (Exception ex) { s_logger.error("Exception deleting domain with id " + domainId, ex); return false; } } private void cleanupDomainOfferings(Long domainId) { // delete the service and disk offerings associated with this domain List diskOfferingsForThisDomain = _diskOfferingDao.listByDomainId(domainId); for (DiskOfferingVO diskOffering : diskOfferingsForThisDomain) { _diskOfferingDao.remove(diskOffering.getId()); } List serviceOfferingsForThisDomain = _offeringsDao.findServiceOfferingByDomainId(domainId); for (ServiceOfferingVO serviceOffering : serviceOfferingsForThisDomain) { _offeringsDao.remove(serviceOffering.getId()); } } private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException { 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, "%" + 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) { success = (success && _accountMgr.deleteAccount(account, UserContext.current().getCallerUserId(), UserContext.current().getCaller())); if (!success) { s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup"); } } //don't remove the domain if there are accounts required cleanup boolean deleteDomainSuccess = true; List accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domainId); if (accountsForCleanup.isEmpty()) { deleteDomainSuccess = _domainDao.remove(domainId); } else { s_logger.debug("Can't delete the domain yet because it has " + accountsForCleanup.size() + "accounts that need a cleanup"); } return success && deleteDomainSuccess; } }