Skip to content

Commit 8e485f3

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

File tree

2 files changed

+264
-90
lines changed

2 files changed

+264
-90
lines changed

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

Lines changed: 154 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,20 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
109112
@Inject
110113
MessageBus _messageBus;
111114

115+
static boolean rollBackState = false;
116+
117+
protected GlobalLock getGlobalLock(String name) {
118+
return GlobalLock.getInternLock(name);
119+
}
120+
121+
protected boolean getRollBackState() {
122+
return rollBackState;
123+
}
124+
125+
protected Account getCaller() {
126+
return CallContext.current().getCallingAccount();
127+
}
128+
112129
@Override
113130
public Domain getDomain(long domainId) {
114131
return _domainDao.findById(domainId);
@@ -151,7 +168,7 @@ public boolean isChildDomain(Long parentId, Long childId) {
151168
@Override
152169
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_CREATE, eventDescription = "creating Domain")
153170
public Domain createDomain(String name, Long parentId, String networkDomain, String domainUUID) {
154-
Account caller = CallContext.current().getCallingAccount();
171+
Account caller = getCaller();
155172

156173
if (parentId == null) {
157174
parentId = Long.valueOf(Domain.ROOT_DOMAIN);
@@ -256,7 +273,7 @@ public List<? extends Domain> findInactiveDomains() {
256273
@Override
257274
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_DELETE, eventDescription = "deleting Domain", async = true)
258275
public boolean deleteDomain(long domainId, Boolean cleanup) {
259-
Account caller = CallContext.current().getCallingAccount();
276+
Account caller = getCaller();
260277

261278
DomainVO domain = _domainDao.findById(domainId);
262279

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

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

283304
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);
305+
// mark domain as inactive
306+
s_logger.debug("Marking domain id=" + domain.getId() + " as " + Domain.State.Inactive + " before actually deleting it");
307+
domain.setState(Domain.State.Inactive);
308+
_domainDao.update(domain.getId(), domain);
309+
310+
try {
311+
long ownerId = domain.getAccountId();
312+
if (BooleanUtils.toBoolean(cleanup)) {
313+
tryCleanupDomain(domain, ownerId);
314314
} 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-
}
315+
removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain);
316+
}
324317

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

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-
}
343+
/**
344+
* Try cleaning up domain. If it couldn't, sets rollback state and throws CloudRuntimeException
345+
* @param domain domain
346+
* @param ownerId owner id
347+
* @throws ConcurrentOperationException
348+
* @throws ResourceUnavailableException
349+
* @throws CloudRuntimeException when cleanupDomain
350+
*/
351+
private void tryCleanupDomain(DomainVO domain, long ownerId) throws ConcurrentOperationException, ResourceUnavailableException, CloudRuntimeException {
352+
if (!cleanupDomain(domain.getId(), ownerId)) {
353+
rollBackState = true;
354+
CloudRuntimeException e =
355+
new CloudRuntimeException("Failed to clean up domain resources and sub domains, delete failed on domain " + domain.getName() + " (id: " +
356+
domain.getId() + ").");
357+
e.addProxyObject(domain.getUuid(), "domainId");
358+
throw e;
359+
}
360+
}
361+
362+
/**
363+
* First check domain resources before removing domain. There are 2 cases:
364+
* <ol>
365+
* <li>Domain doesn't have accounts for cleanup, non-removed networks, or dedicated resources</li>
366+
* <ul><li>Delete domain</li></ul>
367+
* <li>Domain has one of the following: accounts set for cleanup, non-removed networks, dedicated resources</li>
368+
* <ul><li>Dont' delete domain</li><li>Fail operation</li></ul>
369+
* </ol>
370+
* @param domain domain to remove
371+
* @throws CloudRuntimeException when case 2 or when domain cannot be deleted on case 1
372+
*/
373+
protected void removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(DomainVO domain) {
374+
boolean hasDedicatedResources = false;
375+
List<Long> networkIds = _networkDomainDao.listNetworkIdsByDomain(domain.getId());
376+
List<AccountVO> accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domain.getId());
377+
List<DedicatedResourceVO> dedicatedResources = _dedicatedDao.listByDomainId(domain.getId());
378+
if (CollectionUtils.isNotEmpty(dedicatedResources)) {
379+
s_logger.error("There are dedicated resources for the domain " + domain.getId());
380+
hasDedicatedResources = true;
381+
}
382+
if (accountsForCleanup.isEmpty() && networkIds.isEmpty() && !hasDedicatedResources) {
383+
publishRemoveEventsAndRemoveDomain(domain);
384+
} else {
385+
failRemoveOperation(domain, accountsForCleanup, networkIds, hasDedicatedResources);
348386
}
349387
}
350388

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

364-
private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
444+
protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOperationException, ResourceUnavailableException {
365445
s_logger.debug("Cleaning up domain id=" + domainId);
366446
boolean success = true;
367447
DomainVO domainHandle = _domainDao.findById(domainId);
@@ -399,15 +479,15 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
399479
for (AccountVO account : accounts) {
400480
if (account.getType() != Account.ACCOUNT_TYPE_PROJECT) {
401481
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());
482+
boolean deleteAccount = _accountMgr.deleteAccount(account, CallContext.current().getCallingUserId(), getCaller());
403483
if (!deleteAccount) {
404484
s_logger.warn("Failed to cleanup account id=" + account.getId() + " as a part of domain cleanup");
405485
}
406486
success = (success && deleteAccount);
407487
} else {
408488
ProjectVO project = _projectDao.findByProjectAccountId(account.getId());
409489
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);
490+
boolean deleteProject = _projectMgr.deleteProject(getCaller(), CallContext.current().getCallingUserId(), project);
411491
if (!deleteProject) {
412492
s_logger.warn("Failed to cleanup project " + project + " as a part of domain cleanup");
413493
}
@@ -470,7 +550,7 @@ private boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOper
470550

471551
@Override
472552
public Pair<List<? extends Domain>, Integer> searchForDomains(ListDomainsCmd cmd) {
473-
Account caller = CallContext.current().getCallingAccount();
553+
Account caller = getCaller();
474554
Long domainId = cmd.getId();
475555
boolean listAll = cmd.listAll();
476556
boolean isRecursive = false;
@@ -542,7 +622,7 @@ public Pair<List<? extends Domain>, Integer> searchForDomainChildren(ListDomainC
542622
boolean listAll = cmd.listAll();
543623
String path = null;
544624

545-
Account caller = CallContext.current().getCallingAccount();
625+
Account caller = getCaller();
546626
if (domainId != null) {
547627
_accountMgr.checkAccess(caller, getDomain(domainId));
548628
} else {
@@ -612,7 +692,7 @@ public DomainVO updateDomain(UpdateDomainCmd cmd) {
612692
}
613693

614694
// check permissions
615-
Account caller = CallContext.current().getCallingAccount();
695+
Account caller = getCaller();
616696
_accountMgr.checkAccess(caller, domain);
617697

618698
// domain name is unique in the cloud

0 commit comments

Comments
 (0)