Skip to content

Commit ffe42bb

Browse files
committed
CLOUDSTACK-9764: Delete domain failure due to Account Cleanup task
1 parent 50147a4 commit ffe42bb

File tree

2 files changed

+245
-85
lines changed

2 files changed

+245
-85
lines changed

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

Lines changed: 136 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
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.lang.BooleanUtils;
3738

3839
import com.cloud.configuration.Resource.ResourceOwnerType;
3940
import com.cloud.configuration.ResourceLimit;
@@ -63,6 +64,7 @@
6364
import com.cloud.utils.component.ManagerBase;
6465
import com.cloud.utils.db.DB;
6566
import com.cloud.utils.db.Filter;
67+
import com.cloud.utils.db.GlobalLock;
6668
import com.cloud.utils.db.SearchBuilder;
6769
import com.cloud.utils.db.SearchCriteria;
6870
import com.cloud.utils.db.Transaction;
@@ -109,6 +111,20 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
109111
@Inject
110112
MessageBus _messageBus;
111113

114+
static boolean rollBackState = false;
115+
116+
protected GlobalLock getGlobalLock(String name) {
117+
return GlobalLock.getInternLock(name);
118+
}
119+
120+
protected boolean getRollBackState() {
121+
return rollBackState;
122+
}
123+
124+
protected Account getCaller() {
125+
return CallContext.current().getCallingAccount();
126+
}
127+
112128
@Override
113129
public Domain getDomain(long domainId) {
114130
return _domainDao.findById(domainId);
@@ -151,7 +167,7 @@ public boolean isChildDomain(Long parentId, Long childId) {
151167
@Override
152168
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_CREATE, eventDescription = "creating Domain")
153169
public Domain createDomain(String name, Long parentId, String networkDomain, String domainUUID) {
154-
Account caller = CallContext.current().getCallingAccount();
170+
Account caller = getCaller();
155171

156172
if (parentId == null) {
157173
parentId = Long.valueOf(Domain.ROOT_DOMAIN);
@@ -256,7 +272,7 @@ public List<? extends Domain> findInactiveDomains() {
256272
@Override
257273
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_DELETE, eventDescription = "deleting Domain", async = true)
258274
public boolean deleteDomain(long domainId, Boolean cleanup) {
259-
Account caller = CallContext.current().getCallingAccount();
275+
Account caller = getCaller();
260276

261277
DomainVO domain = _domainDao.findById(domainId);
262278

@@ -273,82 +289,133 @@ public boolean deleteDomain(long domainId, Boolean cleanup) {
273289

274290
@Override
275291
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;
292+
GlobalLock lock = getGlobalLock("AccountCleanup");
293+
if (lock == null) {
294+
s_logger.debug("Couldn't get the global lock");
295+
return false;
296+
}
297+
298+
if (!lock.lock(30)) {
299+
s_logger.debug("Couldn't lock the db");
300+
return false;
301+
}
282302

283303
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())) {
304+
// mark domain as inactive
305+
s_logger.debug("Marking domain id=" + domain.getId() + " as " + Domain.State.Inactive + " before actually deleting it");
306+
domain.setState(Domain.State.Inactive);
307+
_domainDao.update(domain.getId(), domain);
308+
309+
try {
310+
long ownerId = domain.getAccountId();
311+
if (BooleanUtils.toBoolean(cleanup)) {
312+
if (!cleanupDomain(domain.getId(), ownerId)) {
306313
rollBackState = true;
307314
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");
315+
new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " +
316+
domain.getId() + ").");
310317
e.addProxyObject(domain.getUuid(), "domainId");
311318
throw e;
312319
}
313-
_messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
314320
} 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-
}
321+
checkDomainAccountsNetworksAndResourcesBeforeRemoving(domain);
322+
}
324323

325-
CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg);
326-
e.addProxyObject(domain.getUuid(), "domainId");
327-
throw e;
324+
cleanupDomainOfferings(domain.getId());
325+
CallContext.current().putContextParameter(Domain.class, domain.getUuid());
326+
return true;
327+
} catch (Exception ex) {
328+
s_logger.error("Exception deleting domain with id " + domain.getId(), ex);
329+
if (ex instanceof CloudRuntimeException)
330+
throw (CloudRuntimeException)ex;
331+
else
332+
return false;
333+
} finally {
334+
//when success is false
335+
if (rollBackState) {
336+
s_logger.debug("Changing domain id=" + domain.getId() + " state back to " + Domain.State.Active +
337+
" because it can't be removed due to resources referencing to it");
338+
domain.setState(Domain.State.Active);
339+
_domainDao.update(domain.getId(), domain);
328340
}
329341
}
342+
}
343+
finally {
344+
lock.unlock();
345+
rollBackState = false;
346+
}
347+
}
330348

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-
}
349+
/**
350+
* Check domain resources before removing domain. There are 2 cases:
351+
* <ol>
352+
* <li>Domain doesn't have accounts for cleanup, non-removed networks, or dedicated resources</li>
353+
* <ul><li>Delete domain</li></ul>
354+
* <li>Domain has one of the following: accounts set for cleanup, non-removed networks, dedicated resources</li>
355+
* <ul><li>Dont' delete domain</li><li>Fail operation</li></ul>
356+
* </ol>
357+
* @param domain domain to remove
358+
* @throws CloudRuntimeException when case 2 or when domain cannot be deleted on case 1
359+
*/
360+
protected void checkDomainAccountsNetworksAndResourcesBeforeRemoving(DomainVO domain) {
361+
boolean hasDedicatedResources = false;
362+
List<Long> networkIds = _networkDomainDao.listNetworkIdsByDomain(domain.getId());
363+
List<AccountVO> accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domain.getId());
364+
List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByDomainId(domain.getId());
365+
if (dedicatedResources != null && !dedicatedResources.isEmpty()) {
366+
s_logger.error("There are dedicated resources for the domain " + domain.getId());
367+
hasDedicatedResources = true;
368+
}
369+
if (accountsForCleanup.isEmpty() && networkIds.isEmpty() && !hasDedicatedResources) {
370+
publishRemoveEventsAndRemoveDomain(domain);
371+
} else {
372+
failRemoveOperation(domain, accountsForCleanup, networkIds, hasDedicatedResources);
348373
}
349374
}
350375

351-
private void cleanupDomainOfferings(Long domainId) {
376+
/**
377+
* Enable rollback state and fail domain remove operation including proper message
378+
* @param domain domain
379+
* @param accountsForCleanup domain accounts for cleanup
380+
* @param networkIds domain network ids
381+
* @param hasDedicatedResources indicates if domain has dedicated resources
382+
* @throws CloudRuntimeException including descriptive message indicating the reason for failure
383+
*/
384+
protected void failRemoveOperation(DomainVO domain, List<AccountVO> accountsForCleanup, List<Long> networkIds, boolean hasDedicatedResources) {
385+
rollBackState = true;
386+
String msg = null;
387+
if (!accountsForCleanup.isEmpty()) {
388+
msg = accountsForCleanup.size() + " accounts to cleanup";
389+
} else if (!networkIds.isEmpty()) {
390+
msg = networkIds.size() + " non-removed networks";
391+
} else if (hasDedicatedResources) {
392+
msg = "dedicated resources.";
393+
}
394+
395+
CloudRuntimeException e = new CloudRuntimeException("Can't delete the domain yet because it has " + msg);
396+
e.addProxyObject(domain.getUuid(), "domainId");
397+
throw e;
398+
}
399+
400+
/**
401+
* Publish pre-remove and remove domain events and remove domain
402+
* @param domain domain to remove
403+
* @throws CloudRuntimeException when domain cannot be removed
404+
*/
405+
protected void publishRemoveEventsAndRemoveDomain(DomainVO domain) {
406+
_messageBus.publish(_name, MESSAGE_PRE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
407+
if (!_domainDao.remove(domain.getId())) {
408+
rollBackState = true;
409+
CloudRuntimeException e =
410+
new CloudRuntimeException("Delete failed on domain " + domain.getName() + " (id: " + domain.getId() +
411+
"); Please make sure all users and sub domains have been removed from the domain before deleting");
412+
e.addProxyObject(domain.getUuid(), "domainId");
413+
throw e;
414+
}
415+
_messageBus.publish(_name, MESSAGE_REMOVE_DOMAIN_EVENT, PublishScope.LOCAL, domain);
416+
}
417+
418+
protected void cleanupDomainOfferings(Long domainId) {
352419
// delete the service and disk offerings associated with this domain
353420
List<DiskOfferingVO> diskOfferingsForThisDomain = _diskOfferingDao.listByDomainId(domainId);
354421
for (DiskOfferingVO diskOffering : diskOfferingsForThisDomain) {
@@ -361,7 +428,7 @@ private void cleanupDomainOfferings(Long domainId) {
361428
}
362429
}
363430

364-
private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
431+
protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
365432
s_logger.debug("Cleaning up domain id=" + domainId);
366433
boolean success = true;
367434
DomainVO domainHandle = _domainDao.findById(domainId);
@@ -399,15 +466,15 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
399466
for (AccountVO account : accounts) {
400467
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
401468
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());
469+
boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), getCaller());
403470
if (!deleteAccount) {
404471
s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup");
405472
}
406473
success = (success && deleteAccount);
407474
} else {
408475
ProjectVO project = _projectDao.findByProjectAccountId(account.getId());
409476
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);
477+
boolean deleteProject = _projectMgr.deleteProject(getCaller(), CallContext.current().getCallingUserId(), project);
411478
if (!deleteProject) {
412479
s_logger.warn("Failed to cleanup project " + project + " as a part of domain cleanup");
413480
}
@@ -470,7 +537,7 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
470537

471538
@Override
472539
public Pair<List<? extends Domain>, Integer> searchForDomains(ListDomainsCmd cmd) {
473-
Account caller = CallContext.current().getCallingAccount();
540+
Account caller = getCaller();
474541
Long domainId = cmd.getId();
475542
boolean listAll = cmd.listAll();
476543
boolean isRecursive = false;
@@ -542,7 +609,7 @@ public Pair<List<? extends Domain>, Integer> searchForDomainChildren(ListDomainC
542609
boolean listAll = cmd.listAll();
543610
String path = null;
544611

545-
Account caller = CallContext.current().getCallingAccount();
612+
Account caller = getCaller();
546613
if (domainId != null) {
547614
_accountMgr.checkAccess(caller, getDomain(domainId));
548615
} else {
@@ -612,7 +679,7 @@ public DomainVO updateDomain(UpdateDomainCmd cmd) {
612679
}
613680

614681
// check permissions
615-
Account caller = CallContext.current().getCallingAccount();
682+
Account caller = getCaller();
616683
_accountMgr.checkAccess(caller, domain);
617684

618685
// domain name is unique in the cloud

0 commit comments

Comments
 (0)