-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Prevent deletion of account and domain if either of them has deleted protected instance #12901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.22
Are you sure you want to change the base?
Changes from all commits
6569709
0096e21
4557b07
1644338
e1cbab1
0ca0734
12d61e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2097,6 +2097,7 @@ public boolean deleteUserAccount(long accountId) { | |||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| validateNoDeleteProtectedVmsForAccount(account); | ||||||||||||||||||||||||||||||
| checkIfAccountManagesProjects(accountId); | ||||||||||||||||||||||||||||||
| verifyCallerPrivilegeForUserOrAccountOperations(account); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
Comment on lines
+2100
to
2103
|
||||||||||||||||||||||||||||||
|
|
@@ -2138,6 +2139,23 @@ protected boolean isDeleteNeeded(AccountVO account, long accountId, Account call | |||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private void validateNoDeleteProtectedVmsForAccount(Account account) { | ||||||||||||||||||||||||||||||
| long accountId = account.getId(); | ||||||||||||||||||||||||||||||
| List<VMInstanceVO> deleteProtectedVms = _vmDao.listDeleteProtectedVmsByAccountId(accountId); | ||||||||||||||||||||||||||||||
| if (deleteProtectedVms.isEmpty()) { | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| if (deleteProtectedVms.isEmpty()) { | |
| if (CollectionUtils.isEmpty(deleteProtectedVms)) { |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation currently fetches all delete-protected VMs for the account and then builds a UUID list for debug logging. To avoid potentially large queries/allocations, consider changing the DAO call to an existence check or applying a Filter limit (e.g., 1 row) and only logging a bounded number of UUIDs.
| List<String> vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList()); | |
| logger.debug("Cannot delete Account {}, delete protection enabled for Instances: {}", account, vmUuids); | |
| final int maxLoggedVms = 10; | |
| List<String> vmUuids = deleteProtectedVms.stream() | |
| .limit(maxLoggedVms) | |
| .map(VMInstanceVO::getUuid) | |
| .collect(Collectors.toList()); | |
| boolean hasMore = deleteProtectedVms.size() > maxLoggedVms; | |
| if (hasMore) { | |
| logger.debug("Cannot delete Account {}, delete protection enabled for at least {} Instances (showing first {}): {}", | |
| account, deleteProtectedVms.size(), vmUuids.size(), vmUuids); | |
| } else { | |
| logger.debug("Cannot delete Account {}, delete protection enabled for Instances: {}", account, vmUuids); | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||
| import java.util.regex.Matcher; | ||||||||||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import javax.inject.Inject; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -104,6 +105,7 @@ | |||||||||||||||||||||
| import com.cloud.utils.net.NetUtils; | ||||||||||||||||||||||
| import com.cloud.vm.ReservationContext; | ||||||||||||||||||||||
| import com.cloud.vm.ReservationContextImpl; | ||||||||||||||||||||||
| import com.cloud.vm.VMInstanceVO; | ||||||||||||||||||||||
| import com.cloud.vm.dao.VMInstanceDao; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import org.apache.commons.lang3.StringUtils; | ||||||||||||||||||||||
|
|
@@ -622,9 +624,6 @@ protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOp | |||||||||||||||||||||
| DomainVO domainHandle = _domainDao.findById(domainId); | ||||||||||||||||||||||
| logger.debug("Cleaning up domain {}", domainHandle); | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| domainHandle.setState(Domain.State.Inactive); | ||||||||||||||||||||||
| _domainDao.update(domainId, domainHandle); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| SearchCriteria<DomainVO> sc = _domainDao.createSearchCriteria(); | ||||||||||||||||||||||
| sc.addAnd("parent", SearchCriteria.Op.EQ, domainId); | ||||||||||||||||||||||
| List<DomainVO> domains = _domainDao.search(sc, null); | ||||||||||||||||||||||
|
|
@@ -633,6 +632,12 @@ protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOp | |||||||||||||||||||||
| sc1.addAnd("path", SearchCriteria.Op.LIKE, "%" + "replace(" + domainHandle.getPath() + ", '%', '[%]')" + "%"); | ||||||||||||||||||||||
| List<DomainVO> domainsToBeInactivated = _domainDao.search(sc1, null); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Validate that no Instance in this domain or its subdomains has delete protection | ||||||||||||||||||||||
| validateNoDeleteProtectedVmsForDomain(domainHandle, domainsToBeInactivated); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+635
to
+637
|
||||||||||||||||||||||
| domainHandle.setState(Domain.State.Inactive); | ||||||||||||||||||||||
| _domainDao.update(domainId, domainHandle); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+635
to
+640
|
||||||||||||||||||||||
| // update all subdomains to inactive so no accounts/users can be created | ||||||||||||||||||||||
| for (DomainVO domain : domainsToBeInactivated) { | ||||||||||||||||||||||
| domain.setState(Domain.State.Inactive); | ||||||||||||||||||||||
|
|
@@ -724,6 +729,25 @@ protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOp | |||||||||||||||||||||
| return success && deleteDomainSuccess; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private void validateNoDeleteProtectedVmsForDomain(Domain domainHandle, List<DomainVO> subDomains) { | ||||||||||||||||||||||
| List<Long> allDomainIds = subDomains.stream().map(Domain::getId).collect(Collectors.toList()); | ||||||||||||||||||||||
| allDomainIds.add(domainHandle.getId()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| List<VMInstanceVO> deleteProtectedVms = vmInstanceDao.listDeleteProtectedVmsByDomainIds(allDomainIds); | ||||||||||||||||||||||
| if (deleteProtectedVms.isEmpty()) { | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (logger.isDebugEnabled()) { | ||||||||||||||||||||||
| List<String> vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList()); | ||||||||||||||||||||||
| logger.debug("Cannot delete Domain {}, it has delete protection enabled for Instances: {}", domainHandle, vmUuids); | ||||||||||||||||||||||
|
Comment on lines
+742
to
+743
|
||||||||||||||||||||||
| List<String> vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList()); | |
| logger.debug("Cannot delete Domain {}, it has delete protection enabled for Instances: {}", domainHandle, vmUuids); | |
| final int maxLoggedUuids = 10; | |
| List<String> vmUuids = deleteProtectedVms.stream() | |
| .limit(maxLoggedUuids) | |
| .map(VMInstanceVO::getUuid) | |
| .collect(Collectors.toList()); | |
| logger.debug("Cannot delete Domain {}, it has delete protection enabled for {} Instances. " | |
| + "Example Instance UUIDs (up to {}): {}", | |
| domainHandle, deleteProtectedVms.size(), maxLoggedUuids, vmUuids); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Op.INparameters in this codebase are typically set using an array (e.g.,ids.toArray()). Passing aListdirectly (sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds)) may not bind correctly and can break the query at runtime. ConvertdomainIdsto an array when setting the parameter (and consider handling empty lists defensively).