Skip to content

Commit 6e93ca1

Browse files
authored
Merge pull request #1935 from nvazquez/deleteDomainFix
CLOUDSTACK-9764: Delete domain failure due to Account Cleanup task
2 parents e27ee22 + 60ff09d commit 6e93ca1

File tree

2 files changed

+255
-90
lines changed

2 files changed

+255
-90
lines changed

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

Lines changed: 149 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,146 @@ 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+
try {
305+
long ownerId = domain.getAccountId();
306+
if (BooleanUtils.toBoolean(cleanup)) {
307+
tryCleanupDomain(domain, ownerId);
314308
} 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-
}
309+
removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain);
310+
}
324311

325-
CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg);
326-
e.addProxyObject(domain.getUuid(), "domainId");
327-
throw e;
312+
cleanupDomainOfferings(domain.getId());
313+
CallContext.current().putContextParameter(Domain.class, domain.getUuid());
314+
return true;
315+
} catch (Exception ex) {
316+
s_logger.error("Exception deleting domain with id " + domain.getId(), ex);
317+
if (ex instanceof CloudRuntimeException) {
318+
rollbackDomainState(domain);
319+
throw (CloudRuntimeException)ex;
328320
}
321+
else
322+
return false;
329323
}
324+
}
325+
finally {
326+
lock.unlock();
327+
}
328+
}
330329

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-
}
330+
/**
331+
* Roll back domain state to Active
332+
* @param domain domain
333+
*/
334+
protected void rollbackDomainState(DomainVO domain) {
335+
s_logger.debug("Changing domain id=" + domain.getId() + " state back to " + Domain.State.Active +
336+
" because it can't be removed due to resources referencing to it");
337+
domain.setState(Domain.State.Active);
338+
_domainDao.update(domain.getId(), domain);
339+
}
340+
341+
/**
342+
* Try cleaning up domain. If it couldn't throws CloudRuntimeException
343+
* @param domain domain
344+
* @param ownerId owner id
345+
* @throws ConcurrentOperationException
346+
* @throws ResourceUnavailableException
347+
* @throws CloudRuntimeException when cleanupDomain
348+
*/
349+
protected void tryCleanupDomain(DomainVO domain, long ownerId) throws ConcurrentOperationException, ResourceUnavailableException, CloudRuntimeException {
350+
if (!cleanupDomain(domain.getId(), ownerId)) {
351+
CloudRuntimeException e =
352+
new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " +
353+
domain.getId() + ").");
354+
e.addProxyObject(domain.getUuid(), "domainId");
355+
throw e;
348356
}
349357
}
350358

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

364-
private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
439+
protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
365440
s_logger.debug("Cleaning up domain id=" + domainId);
366441
boolean success = true;
367442
DomainVO domainHandle = _domainDao.findById(domainId);
@@ -399,15 +474,15 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
399474
for (AccountVO account : accounts) {
400475
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
401476
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());
477+
boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), getCaller());
403478
if (!deleteAccount) {
404479
s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup");
405480
}
406481
success = (success && deleteAccount);
407482
} else {
408483
ProjectVO project = _projectDao.findByProjectAccountId(account.getId());
409484
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);
485+
boolean deleteProject = _projectMgr.deleteProject(getCaller(), CallContext.current().getCallingUserId(), project);
411486
if (!deleteProject) {
412487
s_logger.warn("Failed to cleanup project " + project + " as a part of domain cleanup");
413488
}
@@ -470,7 +545,7 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
470545

471546
@Override
472547
public Pair<List<? extends Domain>, Integer> searchForDomains(ListDomainsCmd cmd) {
473-
Account caller = CallContext.current().getCallingAccount();
548+
Account caller = getCaller();
474549
Long domainId = cmd.getId();
475550
boolean listAll = cmd.listAll();
476551
boolean isRecursive = false;
@@ -542,7 +617,7 @@ public Pair<List<? extends Domain>, Integer> searchForDomainChildren(ListDomainC
542617
boolean listAll = cmd.listAll();
543618
String path = null;
544619

545-
Account caller = CallContext.current().getCallingAccount();
620+
Account caller = getCaller();
546621
if (domainId != null) {
547622
_accountMgr.checkAccess(caller, getDomain(domainId));
548623
} else {
@@ -612,7 +687,7 @@ public DomainVO updateDomain(UpdateDomainCmd cmd) {
612687
}
613688

614689
// check permissions
615-
Account caller = CallContext.current().getCallingAccount();
690+
Account caller = getCaller();
616691
_accountMgr.checkAccess(caller, domain);
617692

618693
// domain name is unique in the cloud

0 commit comments

Comments
 (0)