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,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