Skip to content

Commit b196e97

Browse files
authored
Prevent deletion of account and domain if either of them has deleted protected instance (#12901)
1 parent df7ff97 commit b196e97

File tree

5 files changed

+84
-1
lines changed

5 files changed

+84
-1
lines changed

engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Set;
2425

2526
import com.cloud.hypervisor.Hypervisor;
2627
import com.cloud.utils.Pair;
@@ -192,4 +193,8 @@ List<VMInstanceVO> searchRemovedByRemoveDate(final Date startDate, final Date en
192193
int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List<Long> domainIds);
193194

194195
List<VMInstanceVO> listByIdsIncludingRemoved(List<Long> ids);
196+
197+
List<VMInstanceVO> listDeleteProtectedVmsByAccountId(long accountId);
198+
199+
List<VMInstanceVO> listDeleteProtectedVmsByDomainIds(Set<Long> domainIds);
195200
}

engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
import java.util.HashMap;
2626
import java.util.List;
2727
import java.util.Map;
28+
import java.util.Set;
2829
import java.util.stream.Collectors;
2930

3031
import javax.annotation.PostConstruct;
3132
import javax.inject.Inject;
3233

34+
import org.apache.cloudstack.api.ApiConstants;
3335
import org.apache.commons.collections.CollectionUtils;
3436
import org.springframework.stereotype.Component;
3537

@@ -106,6 +108,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
106108
protected SearchBuilder<VMInstanceVO> IdsPowerStateSelectSearch;
107109
GenericSearchBuilder<VMInstanceVO, Integer> CountByOfferingId;
108110
GenericSearchBuilder<VMInstanceVO, Integer> CountUserVmNotInDomain;
111+
SearchBuilder<VMInstanceVO> DeleteProtectedVmSearchByAccount;
112+
SearchBuilder<VMInstanceVO> DeleteProtectedVmSearchByDomainIds;
109113

110114
@Inject
111115
ResourceTagDao tagsDao;
@@ -368,6 +372,19 @@ protected void init() {
368372
CountUserVmNotInDomain.and("domainIdsNotIn", CountUserVmNotInDomain.entity().getDomainId(), Op.NIN);
369373
CountUserVmNotInDomain.done();
370374

375+
DeleteProtectedVmSearchByAccount = createSearchBuilder();
376+
DeleteProtectedVmSearchByAccount.selectFields(DeleteProtectedVmSearchByAccount.entity().getUuid());
377+
DeleteProtectedVmSearchByAccount.and(ApiConstants.ACCOUNT_ID, DeleteProtectedVmSearchByAccount.entity().getAccountId(), Op.EQ);
378+
DeleteProtectedVmSearchByAccount.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByAccount.entity().isDeleteProtection(), Op.EQ);
379+
DeleteProtectedVmSearchByAccount.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByAccount.entity().getRemoved(), Op.NULL);
380+
DeleteProtectedVmSearchByAccount.done();
381+
382+
DeleteProtectedVmSearchByDomainIds = createSearchBuilder();
383+
DeleteProtectedVmSearchByDomainIds.selectFields(DeleteProtectedVmSearchByDomainIds.entity().getUuid());
384+
DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DOMAIN_IDS, DeleteProtectedVmSearchByDomainIds.entity().getDomainId(), Op.IN);
385+
DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByDomainIds.entity().isDeleteProtection(), Op.EQ);
386+
DeleteProtectedVmSearchByDomainIds.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByDomainIds.entity().getRemoved(), Op.NULL);
387+
DeleteProtectedVmSearchByDomainIds.done();
371388
}
372389

373390
@Override
@@ -1296,4 +1313,22 @@ public List<VMInstanceVO> listByIdsIncludingRemoved(List<Long> ids) {
12961313
sc.setParameters("ids", ids.toArray());
12971314
return listIncludingRemovedBy(sc);
12981315
}
1316+
1317+
@Override
1318+
public List<VMInstanceVO> listDeleteProtectedVmsByAccountId(long accountId) {
1319+
SearchCriteria<VMInstanceVO> sc = DeleteProtectedVmSearchByAccount.create();
1320+
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
1321+
sc.setParameters(ApiConstants.DELETE_PROTECTION, true);
1322+
Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L);
1323+
return listBy(sc, filter);
1324+
}
1325+
1326+
@Override
1327+
public List<VMInstanceVO> listDeleteProtectedVmsByDomainIds(Set<Long> domainIds) {
1328+
SearchCriteria<VMInstanceVO> sc = DeleteProtectedVmSearchByDomainIds.create();
1329+
sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds.toArray());
1330+
sc.setParameters(ApiConstants.DELETE_PROTECTION, true);
1331+
Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L);
1332+
return listBy(sc, filter);
1333+
}
12991334
}

server/src/main/java/com/cloud/user/AccountManagerImpl.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,7 @@ public boolean deleteUserAccount(long accountId) {
20992099

21002100
checkIfAccountManagesProjects(accountId);
21012101
verifyCallerPrivilegeForUserOrAccountOperations(account);
2102+
validateNoDeleteProtectedVmsForAccount(account);
21022103

21032104
CallContext.current().putContextParameter(Account.class, account.getUuid());
21042105

@@ -2138,6 +2139,23 @@ protected boolean isDeleteNeeded(AccountVO account, long accountId, Account call
21382139
return true;
21392140
}
21402141

2142+
private void validateNoDeleteProtectedVmsForAccount(Account account) {
2143+
long accountId = account.getId();
2144+
List<VMInstanceVO> deleteProtectedVms = _vmDao.listDeleteProtectedVmsByAccountId(accountId);
2145+
if (CollectionUtils.isEmpty(deleteProtectedVms)) {
2146+
return;
2147+
}
2148+
2149+
if (logger.isDebugEnabled()) {
2150+
List<String> vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList());
2151+
logger.debug("Cannot delete Account {}, delete protection enabled for Instances: {}", account, vmUuids);
2152+
}
2153+
2154+
throw new InvalidParameterValueException(
2155+
String.format("Cannot delete Account '%s'. One or more Instances have delete protection enabled.",
2156+
account.getAccountName()));
2157+
}
2158+
21412159
@Override
21422160
@ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_ENABLE, eventDescription = "enabling account", async = true)
21432161
public AccountVO enableAccount(String accountName, Long domainId, Long accountId) {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.UUID;
2626
import java.util.regex.Matcher;
2727
import java.util.regex.Pattern;
28+
import java.util.stream.Collectors;
2829

2930
import javax.inject.Inject;
3031

@@ -104,6 +105,7 @@
104105
import com.cloud.utils.net.NetUtils;
105106
import com.cloud.vm.ReservationContext;
106107
import com.cloud.vm.ReservationContextImpl;
108+
import com.cloud.vm.VMInstanceVO;
107109
import com.cloud.vm.dao.VMInstanceDao;
108110

109111
import org.apache.commons.lang3.StringUtils;
@@ -364,6 +366,8 @@ public boolean deleteDomain(long domainId, Boolean cleanup) {
364366
}
365367

366368
_accountMgr.checkAccess(caller, domain);
369+
// Check across the domain hierarchy (current + children) for any delete-protected instances
370+
validateNoDeleteProtectedVmsForDomain(domain);
367371

368372
return deleteDomain(domain, cleanup);
369373
}
@@ -724,6 +728,22 @@ protected boolean cleanupDomain(Long domainId, Long ownerId) throws ConcurrentOp
724728
return success && deleteDomainSuccess;
725729
}
726730

731+
private void validateNoDeleteProtectedVmsForDomain(Domain parentDomain) {
732+
Set<Long> allDomainIds = getDomainChildrenIds(parentDomain.getPath());
733+
List<VMInstanceVO> deleteProtectedVms = vmInstanceDao.listDeleteProtectedVmsByDomainIds(allDomainIds);
734+
if (CollectionUtils.isEmpty(deleteProtectedVms)) {
735+
return;
736+
}
737+
if (logger.isDebugEnabled()) {
738+
List<String> vmUuids = deleteProtectedVms.stream().map(VMInstanceVO::getUuid).collect(Collectors.toList());
739+
logger.debug("Cannot delete Domain {}, it has delete protection enabled for Instances: {}", parentDomain, vmUuids);
740+
}
741+
742+
throw new InvalidParameterValueException(
743+
String.format("Cannot delete Domain '%s'. One or more Instances have delete protection enabled.",
744+
parentDomain.getName()));
745+
}
746+
727747
@Override
728748
public Pair<List<? extends Domain>, Integer> searchForDomains(ListDomainsCmd cmd) {
729749
Account caller = getCaller();

server/src/test/java/com/cloud/user/DomainManagerImplTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import com.cloud.utils.db.SearchCriteria;
6969
import com.cloud.utils.exception.CloudRuntimeException;
7070
import com.cloud.utils.net.NetUtils;
71+
import com.cloud.vm.dao.VMInstanceDao;
7172

7273

7374
@RunWith(MockitoJUnitRunner.class)
@@ -123,6 +124,8 @@ public class DomainManagerImplTest {
123124
Account adminAccount;
124125
@Mock
125126
GlobalLock lock;
127+
@Mock
128+
VMInstanceDao vmInstanceDao;
126129

127130
List<AccountVO> domainAccountsForCleanup;
128131
List<Long> domainNetworkIds;
@@ -213,6 +216,7 @@ public void testDeleteDomainRootDomain() {
213216
@Test
214217
public void testDeleteDomainNoCleanup() {
215218
Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true);
219+
Mockito.doReturn(Collections.emptySet()).when(domainManager).getDomainChildrenIds(Mockito.any());
216220
domainManager.deleteDomain(DOMAIN_ID, testDomainCleanup);
217221
Mockito.verify(domainManager).deleteDomain(domain, testDomainCleanup);
218222
Mockito.verify(domainManager).removeDomainWithNoAccountsForCleanupNetworksOrDedicatedResources(domain);
@@ -278,6 +282,7 @@ public void deleteDomain() {
278282
Mockito.when(_dedicatedDao.listByDomainId(Mockito.anyLong())).thenReturn(new ArrayList<DedicatedResourceVO>());
279283
Mockito.when(domainDaoMock.remove(Mockito.anyLong())).thenReturn(true);
280284
Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true);
285+
Mockito.doReturn(Collections.emptySet()).when(domainManager).getDomainChildrenIds(Mockito.any());
281286

282287
try {
283288
Assert.assertTrue(domainManager.deleteDomain(20l, false));
@@ -309,7 +314,7 @@ public void deleteDomainCleanup() {
309314
Mockito.when(_resourceCountDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l);
310315
Mockito.when(_resourceLimitDao.removeEntriesByOwner(Mockito.anyLong(), Mockito.eq(ResourceOwnerType.Domain))).thenReturn(1l);
311316
Mockito.when(_configMgr.releaseDomainSpecificVirtualRanges(Mockito.any())).thenReturn(true);
312-
317+
Mockito.when(vmInstanceDao.listDeleteProtectedVmsByDomainIds(Mockito.any())).thenReturn(Collections.emptyList());
313318
try {
314319
Assert.assertTrue(domainManager.deleteDomain(20l, true));
315320
} finally {

0 commit comments

Comments
 (0)