3434import org .apache .cloudstack .framework .messagebus .MessageBus ;
3535import org .apache .cloudstack .framework .messagebus .PublishScope ;
3636import org .apache .cloudstack .region .RegionManager ;
37+ import org .apache .commons .collections .CollectionUtils ;
38+ import org .apache .commons .lang .BooleanUtils ;
3739
3840import com .cloud .configuration .Resource .ResourceOwnerType ;
3941import com .cloud .configuration .ResourceLimit ;
6365import com .cloud .utils .component .ManagerBase ;
6466import com .cloud .utils .db .DB ;
6567import com .cloud .utils .db .Filter ;
68+ import com .cloud .utils .db .GlobalLock ;
6669import com .cloud .utils .db .SearchBuilder ;
6770import com .cloud .utils .db .SearchCriteria ;
6871import 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