This commit is contained in:
Abhishek Kumar 2026-01-22 15:13:13 +01:00 committed by GitHub
commit 67dc24df79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 223 additions and 2 deletions

View File

@ -99,4 +99,6 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
List<VMTemplateVO> listByIds(List<Long> ids);
List<Long> listIdsByTemplateTag(String tag);
List<Long> listByUserdataIdsNotAccount(List<Long> userdataIds, long accountId);
}

View File

@ -863,4 +863,22 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
}
return rows > 0;
}
@Override
public List<Long> listByUserdataIdsNotAccount(List<Long> userdataIds, long accountId) {
if (CollectionUtils.isEmpty(userdataIds)) {
return Collections.emptyList();
}
GenericSearchBuilder<VMTemplateVO, Long> sb = createSearchBuilder(Long.class);
sb.selectFields(sb.entity().getId());
sb.and("userDataId", sb.entity().getUserDataId(), SearchCriteria.Op.EQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.NEQ);
sb.done();
SearchCriteria<Long> sc = sb.create();
sc.setParameters("userDataId", userdataIds.toArray());
sc.setParameters("state", VirtualMachineTemplate.State.Active.toString());
sc.setParameters("accountId", accountId);
return customSearch(sc, null);
}
}

View File

@ -16,6 +16,8 @@
// under the License.
package com.cloud.user.dao;
import java.util.List;
import com.cloud.user.UserDataVO;
import com.cloud.utils.db.GenericDao;
@ -25,6 +27,8 @@ public interface UserDataDao extends GenericDao<UserDataVO, Long> {
public UserDataVO findByName(long accountId, long domainId, String name);
List<Long> listIdsByAccountId(long accountId);
int removeByAccountId(long accountId);
}

View File

@ -16,8 +16,11 @@
// under the License.
package com.cloud.user.dao;
import java.util.List;
import com.cloud.user.UserDataVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.springframework.stereotype.Component;
@ -64,6 +67,18 @@ public class UserDataDaoImpl extends GenericDaoBase<UserDataVO, Long> implements
return findOneBy(sc);
}
@Override
public List<Long> listIdsByAccountId(long accountId) {
GenericSearchBuilder<UserDataVO, Long> sb = createSearchBuilder(Long.class);
sb.selectFields(sb.entity().getId());
sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<Long> sc = sb.create();
sc.setParameters("accountId", accountId);
return customSearch(sc, null);
}
@Override
public int removeByAccountId(long accountId) {
SearchCriteria<UserDataVO> sc = userdataSearch.create();

View File

@ -19,6 +19,7 @@ package com.cloud.storage.dao;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@ -47,6 +48,7 @@ import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@ -207,4 +209,49 @@ public class VMTemplateDaoImplTest {
VMTemplateVO readyTemplate = templateDao.findSystemVMReadyTemplate(zoneId, Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64.getType());
Assert.assertEquals(CPU.CPUArch.arm64, readyTemplate.getArch());
}
@Test
public void listByUserdataIdsNotAccount_ReturnsEmptyListWhenUserdataIdsIsEmpty() {
List<Long> result = templateDao.listByUserdataIdsNotAccount(Collections.emptyList(), 1L);
assertNotNull(result);
assertTrue(result.isEmpty());
}
@Test
public void listByUserdataIdsNotAccount_ReturnsMatchingTemplates() {
List<Long> userdataIds = Arrays.asList(1L, 2L);
long accountId = 3L;
GenericSearchBuilder<VMTemplateVO, Long> sb = mock(GenericSearchBuilder.class);
when(sb.entity()).thenReturn(mock(VMTemplateVO.class));
SearchCriteria<Long> sc = mock(SearchCriteria.class);
doReturn(sb).when(templateDao).createSearchBuilder(Long.class);
doReturn(sc).when(sb).create();
doReturn(Arrays.asList(10L, 20L)).when(templateDao).customSearch(sc, null);
List<Long> result = templateDao.listByUserdataIdsNotAccount(userdataIds, accountId);
assertNotNull(result);
assertEquals(2, result.size());
assertTrue(result.contains(10L));
assertTrue(result.contains(20L));
}
@Test
public void listByUserdataIdsNotAccount_ReturnsEmptyListWhenNoMatchingTemplates() {
List<Long> userdataIds = Arrays.asList(1L, 2L);
long accountId = 3L;
GenericSearchBuilder<VMTemplateVO, Long> sb = mock(GenericSearchBuilder.class);
when(sb.entity()).thenReturn(mock(VMTemplateVO.class));
SearchCriteria<Long> sc = mock(SearchCriteria.class);
doReturn(sb).when(templateDao).createSearchBuilder(Long.class);
doReturn(sc).when(sb).create();
doReturn(Collections.emptyList()).when(templateDao).customSearch(sc, null);
List<Long> result = templateDao.listByUserdataIdsNotAccount(userdataIds, accountId);
assertNotNull(result);
assertTrue(result.isEmpty());
}
}

View File

@ -0,0 +1,82 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.user.dao;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.user.UserDataVO;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@RunWith(MockitoJUnitRunner.class)
public class UserDataDaoImplTest {
@Spy
@InjectMocks
UserDataDaoImpl userDataDaoImpl;
@Test
public void listIdsByAccountId_ReturnsEmptyListWhenNoIdsFound() {
long accountId = 1L;
GenericSearchBuilder<UserDataVO, Long> sb = mock(GenericSearchBuilder.class);
when(sb.entity()).thenReturn(mock(UserDataVO.class));
SearchCriteria<Long> sc = mock(SearchCriteria.class);
doReturn(sb).when(userDataDaoImpl).createSearchBuilder(Long.class);
doReturn(sc).when(sb).create();
doReturn(Collections.emptyList()).when(userDataDaoImpl).customSearch(sc, null);
List<Long> result = userDataDaoImpl.listIdsByAccountId(accountId);
assertNotNull(result);
assertTrue(result.isEmpty());
}
@Test
public void listIdsByAccountId_ReturnsListOfIdsWhenFound() {
long accountId = 1L;
GenericSearchBuilder<UserDataVO, Long> sb = mock(GenericSearchBuilder.class);
when(sb.entity()).thenReturn(mock(UserDataVO.class));
SearchCriteria<Long> sc = mock(SearchCriteria.class);
doReturn(sb).when(userDataDaoImpl).createSearchBuilder(Long.class);
doReturn(sc).when(sb).create();
doReturn(Arrays.asList(10L, 20L)).when(userDataDaoImpl).customSearch(sc, null);
List<Long> result = userDataDaoImpl.listIdsByAccountId(accountId);
assertNotNull(result);
assertEquals(2, result.size());
assertTrue(result.contains(10L));
assertTrue(result.contains(20L));
}
}

View File

@ -475,6 +475,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
_querySelectors = querySelectors;
}
protected void deleteUserDataForAccount(long accountId) {
List<Long> userdataIdsList = userDataDao.listIdsByAccountId(accountId);
if (CollectionUtils.isEmpty(userdataIdsList)) {
return;
}
List<Long> conflictingTemplateIds = _templateDao.listByUserdataIdsNotAccount(userdataIdsList, accountId);
if (CollectionUtils.isNotEmpty(conflictingTemplateIds)) {
logger.warn("User data IDs {} owned by account ID {} cannot be deleted as some of them are " +
"linked to templates {} not owned by the account.", userdataIdsList, accountId,
conflictingTemplateIds);
throw new CloudRuntimeException("User data owned by account linked to templates not owned by the account");
}
userDataDao.removeByAccountId(accountId);
}
protected void deleteWebhooksForAccount(long accountId) {
try {
WebhookHelper webhookService = ComponentContext.getDelegateComponentOfType(WebhookHelper.class);
@ -1200,7 +1215,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
// Delete registered UserData
userDataDao.removeByAccountId(accountId);
deleteUserDataForAccount(accountId);
// Delete Webhooks
deleteWebhooksForAccount(accountId);

View File

@ -23,6 +23,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -198,7 +199,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
sshkeyList.add(sshkey);
Mockito.when(_sshKeyPairDao.listKeyPairs(Mockito.anyLong(), Mockito.anyLong())).thenReturn(sshkeyList);
Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true);
Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222);
Mockito.doNothing().when(accountManagerImpl).deleteUserDataForAccount(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
@ -1589,4 +1590,41 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
@Test
public void deleteUserDataForAccountWhenNoUserDataExists() {
long accountId = 1L;
Mockito.when(userDataDao.listIdsByAccountId(accountId)).thenReturn(Collections.emptyList());
accountManagerImpl.deleteUserDataForAccount(accountId);
Mockito.verify(userDataDao, Mockito.times(1)).listIdsByAccountId(accountId);
Mockito.verify(userDataDao, Mockito.times(0)).removeByAccountId(accountId);
Mockito.verifyNoInteractions(_templateDao);
}
@Test
public void deleteUserDataForAccountWhenNoConflictingTemplatesExist() {
long accountId = 1L;
List<Long> userdataIds = List.of(101L, 102L);
Mockito.when(userDataDao.listIdsByAccountId(accountId)).thenReturn(userdataIds);
Mockito.when(_templateDao.listByUserdataIdsNotAccount(userdataIds, accountId)).thenReturn(Collections.emptyList());
accountManagerImpl.deleteUserDataForAccount(accountId);
Mockito.verify(userDataDao, Mockito.times(1)).listIdsByAccountId(accountId);
Mockito.verify(_templateDao, Mockito.times(1)).listByUserdataIdsNotAccount(userdataIds, accountId);
Mockito.verify(userDataDao, Mockito.times(1)).removeByAccountId(accountId);
}
@Test(expected = CloudRuntimeException.class)
public void deleteUserDataForAccountWhenConflictingTemplatesExist() {
long accountId = 1L;
List<Long> userdataIds = List.of(101L, 102L);
List<Long> conflictingTemplateIds = List.of(201L, 202L);
Mockito.when(userDataDao.listIdsByAccountId(accountId)).thenReturn(userdataIds);
Mockito.when(_templateDao.listByUserdataIdsNotAccount(userdataIds, accountId)).thenReturn(conflictingTemplateIds);
accountManagerImpl.deleteUserDataForAccount(accountId);
}
}