Skip to content

Commit e3e3ec7

Browse files
committed
CLOUDSTACK-9764: Delete domain failure due to Account Cleanup task
1 parent 85d073b commit e3e3ec7

File tree

2 files changed

+259
-90
lines changed

2 files changed

+259
-90
lines changed

server/src/com/cloud/user/DomainManagerImpl.java

Lines changed: 148 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.apache.cloudstack.framework.messagebus.MessageBus;
3535
import org.apache.cloudstack.framework.messagebus.PublishScope;
3636
import org.apache.cloudstack.region.RegionManager;
37+
import org.apache.commons.collections.CollectionUtils;
38+
import org.apache.commons.lang.BooleanUtils;
3739

3840
import com.cloud.configuration.Resource.ResourceOwnerType;
3941
import com.cloud.configuration.ResourceLimit;
@@ -63,6 +65,7 @@
6365
import com.cloud.utils.component.ManagerBase;
6466
import com.cloud.utils.db.DB;
6567
import com.cloud.utils.db.Filter;
68+
import com.cloud.utils.db.GlobalLock;
6669
import com.cloud.utils.db.SearchBuilder;
6770
import com.cloud.utils.db.SearchCriteria;
6871
import com.cloud.utils.db.Transaction;
@@ -109,6 +112,14 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
109112
@Inject
110113
MessageBus _messageBus;
111114

115+
protected GlobalLock getGlobalLock(String name) {
116+
return GlobalLock.getInternLock(name);
117+
}
118+
119+
protected Account getCaller() {
120+
return CallContext.current().getCallingAccount();
121+
}
122+
112123
@Override
113124
public Domain getDomain(long domainId) {
114125
return _domainDao.findById(domainId);
@@ -151,7 +162,7 @@ public boolean isChildDomain(Long parentId, Long childId) {
151162
@Override
152163
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_CREATE, eventDescription = "creating Domain")
153164
public Domain createDomain(String name, Long parentId, String networkDomain, String domainUUID) {
154-
Account caller = CallContext.current().getCallingAccount();
165+
Account caller = getCaller();
155166

156167
if (parentId == null) {
157168
parentId = Long.valueOf(Domain.ROOT_DOMAIN);
@@ -256,7 +267,7 @@ public List<? extends Domain> findInactiveDomains() {
256267
@Override
257268
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_DELETE, eventDescription = "deleting Domain", async = true)
258269
public boolean deleteDomain(long domainId, Boolean cleanup) {
259-
Account caller = CallContext.current().getCallingAccount();
270+
Account caller = getCaller();
260271

261272
DomainVO domain = _domainDao.findById(domainId);
262273

@@ -273,82 +284,145 @@ public boolean deleteDomain(long domainId, Boolean cleanup) {
273284

274285
@Override
275286
public boolean deleteDomain(DomainVO domain, Boolean cleanup) {
276-
// mark domain as inactive
277-
s_logger.debug("Marking domain id=" + domain.getId() + " as " + Domain.State.Inactive + " before actually deleting it");
278-
domain.setState(Domain.State.Inactive);
279-
_domainDao.update(domain.getId(), domain);
280-
boolean rollBackState = false;
281-
boolean hasDedicatedResources = false;
287+
GlobalLock lock = getGlobalLock("AccountCleanup");
288+
if (lock == null) {
289+
s_logger.debug("Couldn't get the global lock");
290+
return false;
291+
}
292+
293+
if (!lock.lock(30)) {
294+
s_logger.debug("Couldn't lock the db");
295+
return false;
296+
}
282297

283298
try {
284-
long ownerId = domain.getAccountId();
285-
if ((cleanup != null) && cleanup.booleanValue()) {
286-
if (!cleanupDomain(domain.getId(), ownerId)) {
287-
rollBackState = true;
288-
CloudRuntimeException e =
289-
new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " +
290-
domain.getId() + ").");
291-
e.addProxyObject(domain.getUuid(), "domainId");
292-
throw e;
293-
}
294-
} else {
295-
//don't delete the domain if there are accounts set for cleanup, or non-removed networks exist, or domain has dedicated resources
296-
List<Long> networkIds = _networkDomainDao.listNetworkIdsByDomain(domain.getId());
297-
List<AccountVO> accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domain.getId());
298-
List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByDomainId(domain.getId());
299-
if (dedicatedResources != null && !dedicatedResources.isEmpty()) {
300-
s_logger.error("There are dedicated resources for the domain " + domain.getId());
301-
hasDedicatedResources = true;
302-
}
303-
if (accountsForCleanup.isEmpty() && networkIds.isEmpty() && !hasDedicatedResources) {
304-
_messageBus.publish(_name, MESSAGE_PRE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
305-
if (!_domainDao.remove(domain.getId())) {
306-
rollBackState = true;
307-
CloudRuntimeException e =
308-
new CloudRuntimeException("Delete failed on domain " + domain.getName() + " (id: " + domain.getId() +
309-
"); Please make sure all users and sub domains have been removed from the domain before deleting");
310-
e.addProxyObject(domain.getUuid(), "domainId");
311-
throw e;
312-
}
313-
_messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
299+
// mark domain as inactive
300+
s_logger.debug("Marking domain id=" + domain.getId() + " as " + Domain.State.Inactive + " before actually deleting it");
301+
domain.setState(Domain.State.Inactive);
302+
_domainDao.update(domain.getId(), domain);
303+
304+
boolean rollBackState = false;
305+
306+
try {
307+
long ownerId = domain.getAccountId();
308+
if (BooleanUtils.toBoolean(cleanup)) {
309+
tryCleanupDomain(domain, ownerId);
314310
} else {
315-
rollBackState = true;
316-
String msg = null;
317-
if (!accountsForCleanup.isEmpty()) {
318-
msg = accountsForCleanup.size() + " accounts to cleanup";
319-
} else if (!networkIds.isEmpty()) {
320-
msg = networkIds.size() + " non-removed networks";
321-
} else if (hasDedicatedResources) {
322-
msg = "dedicated resources.";
323-
}
311+
removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain);
312+
}
324313

325-
CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg);
326-
e.addProxyObject(domain.getUuid(), "domainId");
327-
throw e;
314+
cleanupDomainOfferings(domain.getId());
315+
CallContext.current().putContextParameter(Domain.class, domain.getUuid());
316+
return true;
317+
} catch (Exception ex) {
318+
s_logger.error("Exception deleting domain with id " + domain.getId(), ex);
319+
if (ex instanceof CloudRuntimeException) {
320+
rollBackState = true;
321+
throw (CloudRuntimeException)ex;
322+
}
323+
else
324+
return false;
325+
} finally {
326+
//when success is false
327+
if (rollBackState) {
328+
s_logger.debug("Changing domain id=" + domain.getId() + " state back to " + Domain.State.Active +
329+
" because it can't be removed due to resources referencing to it");
330+
domain.setState(Domain.State.Active);
331+
_domainDao.update(domain.getId(), domain);
328332
}
329333
}
334+
}
335+
finally {
336+
lock.unlock();
337+
}
338+
}
330339

331-
cleanupDomainOfferings(domain.getId());
332-
CallContext.current().putContextParameter(Domain.class, domain.getUuid());
333-
return true;
334-
} catch (Exception ex) {
335-
s_logger.error("Exception deleting domain with id " + domain.getId(), ex);
336-
if (ex instanceof CloudRuntimeException)
337-
throw (CloudRuntimeException)ex;
338-
else
339-
return false;
340-
} finally {
341-
//when success is false
342-
if (rollBackState) {
343-
s_logger.debug("Changing domain id=" + domain.getId() + " state back to " + Domain.State.Active +
344-
" because it can't be removed due to resources referencing to it");
345-
domain.setState(Domain.State.Active);
346-
_domainDao.update(domain.getId(), domain);
347-
}
340+
/**
341+
* Try cleaning up domain. If it couldn't throws CloudRuntimeException
342+
* @param domain domain
343+
* @param ownerId owner id
344+
* @throws ConcurrentOperationException
345+
* @throws ResourceUnavailableException
346+
* @throws CloudRuntimeException when cleanupDomain
347+
*/
348+
protected void tryCleanupDomain(DomainVO domain, long ownerId) throws ConcurrentOperationException, ResourceUnavailableException, CloudRuntimeException {
349+
if (!cleanupDomain(domain.getId(), ownerId)) {
350+
CloudRuntimeException e =
351+
new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " +
352+
domain.getId() + ").");
353+
e.addProxyObject(domain.getUuid(), "domainId");
354+
throw e;
348355
}
349356
}
350357

351-
private void cleanupDomainOfferings(Long domainId) {
358+
/**
359+
* First check domain resources before removing domain. There are 2 cases:
360+
* <ol>
361+
* <li>Domain doesn't have accounts for cleanup, non-removed networks, or dedicated resources</li>
362+
* <ul><li>Delete domain</li></ul>
363+
* <li>Domain has one of the following: accounts set for cleanup, non-removed networks, dedicated resources</li>
364+
* <ul><li>Dont' delete domain</li><li>Fail operation</li></ul>
365+
* </ol>
366+
* @param domain domain to remove
367+
* @throws CloudRuntimeException when case 2 or when domain cannot be deleted on case 1
368+
*/
369+
protected void removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(DomainVO domain) {
370+
boolean hasDedicatedResources = false;
371+
List<Long> networkIds = _networkDomainDao.listNetworkIdsByDomain(domain.getId());
372+
List<AccountVO> accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domain.getId());
373+
List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByDomainId(domain.getId());
374+
if (CollectionUtils.isNotEmpty(dedicatedResources)) {
375+
s_logger.error("There are dedicated resources for the domain " + domain.getId());
376+
hasDedicatedResources = true;
377+
}
378+
if (accountsForCleanup.isEmpty() && networkIds.isEmpty() && !hasDedicatedResources) {
379+
publishRemoveEventsAndRemoveDomain(domain);
380+
} else {
381+
failRemoveOperation(domain, accountsForCleanup, networkIds, hasDedicatedResources);
382+
}
383+
}
384+
385+
/**
386+
* Enable rollback state and fail domain remove operation including proper message
387+
* @param domain domain
388+
* @param accountsForCleanup domain accounts for cleanup
389+
* @param networkIds domain network ids
390+
* @param hasDedicatedResources indicates if domain has dedicated resources
391+
* @throws CloudRuntimeException including descriptive message indicating the reason for failure
392+
*/
393+
protected void failRemoveOperation(DomainVO domain, List<AccountVO> accountsForCleanup, List<Long> networkIds, boolean hasDedicatedResources) {
394+
String msg = null;
395+
if (!accountsForCleanup.isEmpty()) {
396+
msg = accountsForCleanup.size() + " accounts to cleanup";
397+
} else if (!networkIds.isEmpty()) {
398+
msg = networkIds.size() + " non-removed networks";
399+
} else if (hasDedicatedResources) {
400+
msg = "dedicated resources.";
401+
}
402+
403+
CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg);
404+
e.addProxyObject(domain.getUuid(), "domainId");
405+
throw e;
406+
}
407+
408+
/**
409+
* Publish pre-remove and remove domain events and remove domain
410+
* @param domain domain to remove
411+
* @throws CloudRuntimeException when domain cannot be removed
412+
*/
413+
protected void publishRemoveEventsAndRemoveDomain(DomainVO domain) {
414+
_messageBus.publish(_name, MESSAGE_PRE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
415+
if (!_domainDao.remove(domain.getId())) {
416+
CloudRuntimeException e =
417+
new CloudRuntimeException("Delete failed on domain " + domain.getName() + " (id: " + domain.getId() +
418+
"); Please make sure all users and sub domains have been removed from the domain before deleting");
419+
e.addProxyObject(domain.getUuid(), "domainId");
420+
throw e;
421+
}
422+
_messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
423+
}
424+
425+
protected void cleanupDomainOfferings(Long domainId) {
352426
// delete the service and disk offerings associated with this domain
353427
List<DiskOfferingVO> diskOfferingsForThisDomain = _diskOfferingDao.listByDomainId(domainId);
354428
for (DiskOfferingVO diskOffering : diskOfferingsForThisDomain) {
@@ -361,7 +435,7 @@ private void cleanupDomainOfferings(Long domainId) {
361435
}
362436
}
363437

364-
private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
438+
protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
365439
s_logger.debug("Cleaning up domain id=" + domainId);
366440
boolean success = true;
367441
DomainVO domainHandle = _domainDao.findById(domainId);
@@ -399,15 +473,15 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
399473
for (AccountVO account : accounts) {
400474
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
401475
s_logger.debug("Deleting account " + account + " as a part of domain id=" + domainId + " cleanup");
402-
boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), CallContext.current().getCallingAccount());
476+
boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), getCaller());
403477
if (!deleteAccount) {
404478
s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup");
405479
}
406480
success = (success && deleteAccount);
407481
} else {
408482
ProjectVO project = _projectDao.findByProjectAccountId(account.getId());
409483
s_logger.debug("Deleting project " + project + " as a part of domain id=" + domainId + " cleanup");
410-
boolean deleteProject = _projectMgr.deleteProject(CallContext.current().getCallingAccount(), CallContext.current().getCallingUserId(), project);
484+
boolean deleteProject = _projectMgr.deleteProject(getCaller(), CallContext.current().getCallingUserId(), project);
411485
if (!deleteProject) {
412486
s_logger.warn("Failed to cleanup project " + project + " as a part of domain cleanup");
413487
}
@@ -470,7 +544,7 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
470544

471545
@Override
472546
public Pair<List<? extends Domain>, Integer> searchForDomains(ListDomainsCmd cmd) {
473-
Account caller = CallContext.current().getCallingAccount();
547+
Account caller = getCaller();
474548
Long domainId = cmd.getId();
475549
boolean listAll = cmd.listAll();
476550
boolean isRecursive = false;
@@ -542,7 +616,7 @@ public Pair<List<? extends Domain>, Integer> searchForDomainChildren(ListDomainC
542616
boolean listAll = cmd.listAll();
543617
String path = null;
544618

545-
Account caller = CallContext.current().getCallingAccount();
619+
Account caller = getCaller();
546620
if (domainId != null) {
547621
_accountMgr.checkAccess(caller, getDomain(domainId));
548622
} else {
@@ -612,7 +686,7 @@ public DomainVO updateDomain(UpdateDomainCmd cmd) {
612686
}
613687

614688
// check permissions
615-
Account caller = CallContext.current().getCallingAccount();
689+
Account caller = getCaller();
616690
_accountMgr.checkAccess(caller, domain);
617691

618692
// domain name is unique in the cloud

0 commit comments

Comments
 (0)