Add missing project cleanups while deleting/moving user (#13817)

Co-authored-by: Bernardo De Marco Gonçalves <bernardomg2004@gmail.com>
This commit is contained in:
Fabricio Duarte 2026-08-18 08:39:29 -03:00 committed by GitHub
parent 121d93164c
commit 64178ead02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 400 additions and 72 deletions

View File

@ -23,6 +23,7 @@ import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.projects.ProjectAccount.Role;
import com.cloud.user.Account;
import com.cloud.user.User;
public interface ProjectService {
/**
@ -102,4 +103,5 @@ public interface ProjectService {
boolean addUserToProject(Long projectId, String username, String email, Long projectRoleId, Role projectRole) throws ResourceAllocationException;
void moveProjectAssociationsToUser(User oldUser, User newUser) throws ResourceAllocationException;
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.admin.user;
import javax.inject.Inject;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
@ -112,7 +113,7 @@ public class MoveUserCmd extends BaseCmd {
}
@Override
public void execute() {
public void execute() throws ResourceAllocationException {
Preconditions.checkNotNull(getId(),"I have to have an user to move!");
Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!");

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.region;
import java.util.List;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.api.command.admin.account.DeleteAccountCmd;
import org.apache.cloudstack.api.command.admin.account.DisableAccountCmd;
import org.apache.cloudstack.api.command.admin.account.EnableAccountCmd;
@ -116,7 +117,7 @@ public interface RegionService {
* @param moveUserCmd
* @return true if delete was successful, false otherwise
*/
boolean moveUser(MoveUserCmd moveUserCmd);
boolean moveUser(MoveUserCmd moveUserCmd) throws ResourceAllocationException;
/**
* update an existing domain

View File

@ -110,6 +110,10 @@ public class ProjectAccountVO implements ProjectAccount, InternalIdentity {
return projectAccountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
public void setProjectRoleId(Long projectRoleId) {
this.projectRoleId = projectRoleId;
}

View File

@ -102,6 +102,10 @@ public class ProjectInvitationVO implements ProjectInvitation {
return forAccountId;
}
public void setForAccountId(Long forAccountId) {
this.forAccountId = forAccountId;
}
@Override
public String getToken() {
return token;

View File

@ -20,6 +20,7 @@ import java.util.List;
import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectAccountVO;
import com.cloud.user.User;
import com.cloud.utils.db.GenericDao;
public interface ProjectAccountDao extends GenericDao<ProjectAccountVO, Long> {
@ -47,9 +48,11 @@ public interface ProjectAccountDao extends GenericDao<ProjectAccountVO, Long> {
void removeAccountFromProjects(long accountId);
void removeUserFromProjects(long userId);
boolean canUserModifyProject(long projectId, long accountId, long userId);
List<ProjectAccountVO> listUsersOrAccountsByRole(long id);
List<ProjectAccountVO> listBy(Long projectId, Long accountId, Long userId);
void move(User oldUser, User newUser);
}

View File

@ -18,6 +18,7 @@ package com.cloud.projects.dao;
import java.util.List;
import com.cloud.user.User;
import org.springframework.stereotype.Component;
import com.cloud.projects.ProjectAccount;
@ -192,17 +193,6 @@ public class ProjectAccountDaoImpl extends GenericDaoBase<ProjectAccountVO, Long
}
}
@Override
public void removeUserFromProjects(long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
sc.setParameters("userId", userId);
int removedCount = remove(sc);
if (removedCount > 0) {
logger.debug(String.format("Removed user [%s] from %s project(s).", userId, removedCount));
}
}
@Override
public boolean canUserModifyProject(long projectId, long accountId, long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
@ -222,4 +212,23 @@ public class ProjectAccountDaoImpl extends GenericDaoBase<ProjectAccountVO, Long
sc.setParameters("projectRoleId", id);
return listBy(sc);
}
@Override
public List<ProjectAccountVO> listBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectAccountVO> sc = AllFieldsSearch.create();
sc.setParametersIfNotNull("projectId", projectId);
sc.setParametersIfNotNull("userId", userId);
sc.setParametersIfNotNull("accountId", accountId);
return listBy(sc);
}
@Override
public void move(User oldUser, User newUser) {
List<ProjectAccountVO> projectAccounts = listBy(null, oldUser.getAccountId(), oldUser.getId());
for (ProjectAccountVO projectAccount : projectAccounts) {
projectAccount.setAccountId(newUser.getAccountId());
projectAccount.setUserId(newUser.getId());
update(projectAccount.getId(), projectAccount);
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.List;
import com.cloud.projects.ProjectInvitation.State;
import com.cloud.projects.ProjectInvitationVO;
import com.cloud.user.User;
import com.cloud.utils.db.GenericDao;
public interface ProjectInvitationDao extends GenericDao<ProjectInvitationVO, Long> {
@ -43,4 +44,9 @@ public interface ProjectInvitationDao extends GenericDao<ProjectInvitationVO, Lo
List<ProjectInvitationVO> listInvitationsToExpire(long timeOut);
int removeBy(Long projectId, Long accountId, Long userId);
List<ProjectInvitationVO> listBy(Long projectId, Long accountId, Long userId);
void move(User oldUser, User newUser);
}

View File

@ -19,6 +19,7 @@ package com.cloud.projects.dao;
import java.sql.Date;
import java.util.List;
import com.cloud.user.User;
import org.springframework.stereotype.Component;
import com.cloud.projects.ProjectInvitation.State;
@ -124,6 +125,40 @@ public class ProjectInvitationDaoImpl extends GenericDaoBase<ProjectInvitationVO
return listBy(sc);
}
@Override
public int removeBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = prepareAllFieldsSearchCriteria(projectId, accountId, userId);
return remove(sc);
}
@Override
public List<ProjectInvitationVO> listBy(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = prepareAllFieldsSearchCriteria(projectId, accountId, userId);
return listBy(sc);
}
@Override
public void move(User oldUser, User newUser) {
List<ProjectInvitationVO> projectInvitations = listBy(null, oldUser.getAccountId(), oldUser.getId());
for (ProjectInvitationVO projectInvitation : projectInvitations) {
projectInvitation.setForAccountId(newUser.getAccountId());
projectInvitation.setForUserId(newUser.getId());
update(projectInvitation.getId(), projectInvitation);
}
}
private SearchCriteria<ProjectInvitationVO> prepareAllFieldsSearchCriteria(Long projectId, Long accountId, Long userId) {
SearchCriteria<ProjectInvitationVO> sc = AllFieldsSearch.create();
sc.setParametersIfNotNull("userId", userId);
sc.setParametersIfNotNull("accountId", accountId);
if (projectId != null && projectId != -1) {
sc.setParameters("projectId", projectId);
}
return sc;
}
@Override
public boolean isActive(long id, long timeout) {
SearchCriteria<ProjectInvitationVO> sc = InactiveSearch.create();

View File

@ -18,3 +18,7 @@
--;
-- Schema upgrade cleanup from 4.22.1.0 to 4.23.0.0
--;
-- Delete stale project association entries for users that were removed
DELETE FROM `cloud`.`project_account` WHERE `user_id` IN (SELECT `id` FROM `cloud`.`user` WHERE `removed` IS NOT NULL);
DELETE FROM `cloud`.`project_invitations` WHERE `user_id` IN (SELECT `id` FROM `cloud`.`user` WHERE `removed` IS NOT NULL);

View File

@ -23,6 +23,7 @@ import java.util.UUID;
import javax.inject.Inject;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.auth.UserAuthenticator;
import org.apache.commons.collections.CollectionUtils;
@ -170,7 +171,12 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
if (mappedAccount == null || mappedAccount.getRemoved() != null) {
throw new CloudRuntimeException("Mapped account for users does not exist. Please contact your administrator.");
}
_accountManager.moveUser(userAccount.getId(), userAccount.getDomainId(), mappedAccount);
try {
_accountManager.moveUser(userAccount.getId(), userAccount.getDomainId(), mappedAccount);
} catch (ResourceAllocationException e) {
throw new CloudRuntimeException(String.format("Failed to move User [%s] to mapped Account [%s] due to insufficient Project limits.",
userAccount.getUsername(), mappedAccount.getAccountName()), e);
}
}
// else { the user hasn't changed in ldap, the ldap group stayed the same, hurray, pass, fun thou self a lot of fun }
}

View File

@ -19,6 +19,7 @@ package com.cloud.projects;
import java.util.List;
import com.cloud.user.Account;
import com.cloud.user.User;
import org.apache.cloudstack.framework.config.ConfigKey;
public interface ProjectManager extends ProjectService {
@ -47,6 +48,8 @@ public interface ProjectManager extends ProjectService {
long getInvitationTimeout();
boolean cleanupProjectsForUser(Project project, User user);
public static final String MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT = "Message.CreateTungstenProject.Event";
public static final String MESSAGE_DELETE_TUNGSTEN_PROJECT_EVENT = "Message.DeleteTungstenProject.Event";

View File

@ -617,6 +617,37 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
}
}
/**
* Transfers all project associations and project invitations from one user to another.
*
* @param oldUser the user whose project associations are being transferred
* @param newUser the user to whom the project associations are being transferred
* @throws ResourceAllocationException if there is an issue with allocating the required project resources to the new user
*/
@Override
public void moveProjectAssociationsToUser(User oldUser, User newUser) throws ResourceAllocationException {
_projectInvitationDao.move(oldUser, newUser);
List<ProjectAccountVO> projectAccounts = _projectAccountDao.listBy(null, oldUser.getAccountId(), oldUser.getId());
if (projectAccounts.isEmpty()) {
return;
}
Account oldAccount = _accountDao.findById(oldUser.getAccountId());
Account newAccount = _accountDao.findById(newUser.getAccountId());
long requiredProjectsAmount = oldAccount.getId() != newAccount.getId()
? projectAccounts.stream().filter(pa -> pa.getAccountRole() == ProjectAccount.Role.Admin).count()
: 0L;
try (CheckedReservation projectReservation = new CheckedReservation(newAccount, ResourceType.project, null, null, requiredProjectsAmount, reservationDao, _resourceLimitMgr)) {
_projectAccountDao.move(oldUser, newUser);
if (requiredProjectsAmount > 0) {
_resourceLimitMgr.incrementResourceCount(newAccount.getId(), ResourceType.project, requiredProjectsAmount);
_resourceLimitMgr.decrementResourceCount(oldAccount.getId(), ResourceType.project, requiredProjectsAmount);
}
}
}
@Override
public Project findByNameAndDomainId(String name, long domainId) {
return _projectDao.findByNameAndDomain(name, domainId);
@ -1033,50 +1064,41 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
//verify permissions
_accountMgr.checkAccess(caller, AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId()));
//Check if the user exists in the project
ProjectAccount projectUser = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId());
if (projectUser == null) {
deletePendingInvite(projectId, user);
boolean success = cleanupProjectsForUser(project, user);
if (!success) {
InvalidParameterValueException ex = new InvalidParameterValueException("User " + user.getUsername() + " is not assigned to the project with specified id");
// Use the projectVO object and not the projectAccount object to inject the projectId.
ex.addProxyObject(project.getUuid(), "projectId");
throw ex;
}
return deleteUserFromProject(projectId, user);
return true;
}
private void deletePendingInvite(Long projectId, User user) {
ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId);
if (invite != null) {
boolean success = _projectInvitationDao.remove(invite.getId());
if (success){
logger.info("Successfully deleted invite pending for the user : {}", user);
} else {
logger.info("Failed to delete project invite for user: {}", user);
}
}
}
/**
* Cleans up project associations and invitations for a specified user in a given project.
*
* @param project the project from which the user is being cleaned up; if null, cleanup applies to all projects associated with the user
* @param user the user whose project associations and invitations are being cleaned up
* @return true if any project accounts associated with the user were removed, false otherwise
*/
@Override
public boolean cleanupProjectsForUser(Project project, User user) {
return Transaction.execute((TransactionCallback<Boolean>) status -> {
Long projectId = project != null ? project.getId() : null;
long userId = user.getId();
long accountId = user.getAccountId();
@DB
private boolean deleteUserFromProject(Long projectId, User user) {
return Transaction.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
boolean success = true;
ProjectAccountVO projectAccount = _projectAccountDao.findByProjectIdUserId(projectId, user.getAccountId(), user.getId());
success = _projectAccountDao.remove(projectAccount.getId());
_projectInvitationDao.removeBy(projectId, accountId, userId);
List<ProjectAccountVO> projectAccounts = _projectAccountDao.listBy(projectId, accountId, userId);
for (ProjectAccountVO projectAccount : projectAccounts) {
_projectAccountDao.remove(projectAccount.getId());
if (projectAccount.getAccountRole() == Role.Admin) {
_resourceLimitMgr.decrementResourceCount(user.getAccountId(), ResourceType.project);
_resourceLimitMgr.decrementResourceCount(accountId, ResourceType.project);
}
if (success) {
logger.debug("Removed user {} from project. Removing any invite sent to the user", user);
ProjectInvitation invite = _projectInvitationDao.findByUserIdProjectId(user.getId(), user.getAccountId(), projectId);
if (invite != null) {
success = success && _projectInvitationDao.remove(invite.getId());
}
}
return success;
logger.debug("Removed user [{}] from project [{}].", user, projectAccount.getProjectId());
}
return !projectAccounts.isEmpty();
});
}

View File

@ -20,6 +20,7 @@ import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
@ -148,7 +149,7 @@ public interface AccountManager extends AccountService, Configurable {
* moves a user to another account within the same domain
* @return true if the user was successfully moved
*/
boolean moveUser(MoveUserCmd moveUserCmd);
boolean moveUser(MoveUserCmd moveUserCmd) throws ResourceAllocationException;
@Override
UserAccount updateUser(UpdateUserCmd cmd);
@ -190,7 +191,7 @@ public interface AccountManager extends AccountService, Configurable {
ConfigKey<Boolean> UseSecretKeyInResponse = new ConfigKey<Boolean>("Advanced", Boolean.class, "use.secret.key.in.response", "false",
"This parameter allows the users to enable or disable of showing secret key as a part of response for various APIs. By default it is set to false.", true);
boolean moveUser(long id, Long domainId, Account newAccount);
boolean moveUser(long id, Long domainId, Account newAccount) throws ResourceAllocationException;
UserTwoFactorAuthenticator getUserTwoFactorAuthenticator(final Long domainId, final Long userAccountId);

View File

@ -45,10 +45,13 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.projects.dao.ProjectInvitationDao;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.SSHKeyPairDao;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.db.TransactionCallbackWithException;
import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
@ -315,6 +318,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Inject
private ProjectAccountDao _projectAccountDao;
@Inject
private ProjectInvitationDao projectInvitationDao;
@Inject
private IPAddressDao _ipAddressDao;
@Inject
private HostDao hostDao;
@ -2521,14 +2526,29 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
checkAccountAndAccess(user, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
removeUserApiKeys(id);
return deleteAndCleanupUser(user);
}
return _userDao.remove(id);
/**
* Removes the specified user and performs cleanup operations associated with the user.
*
* @param user the user to be deleted and cleaned up
* @return true if the user was successfully marked as removed, false otherwise
*/
protected boolean deleteAndCleanupUser(User user) {
return Transaction.execute((TransactionCallback<Boolean>) status -> {
long userId = user.getId();
removeUserApiKeys(userId);
_projectMgr.cleanupProjectsForUser(null, user);
return _userDao.remove(userId);
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account")
public boolean moveUser(MoveUserCmd cmd) {
public boolean moveUser(MoveUserCmd cmd) throws ResourceAllocationException {
final Long id = cmd.getId();
UserVO user = getValidUserVO(id);
Account oldAccount = _accountDao.findById(user.getAccountId());
@ -2542,7 +2562,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
@Override
public boolean moveUser(long id, Long domainId, Account newAccount) {
public boolean moveUser(long id, Long domainId, Account newAccount) throws ResourceAllocationException {
UserVO user = getValidUserVO(id);
Account oldAccount = _accountDao.findById(user.getAccountId());
checkAccountAndAccess(user, oldAccount);
@ -2550,24 +2570,22 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return moveUser(user, newAccount.getId());
}
private boolean moveUser(UserVO user, long newAccountId) {
private boolean moveUser(UserVO user, long newAccountId) throws ResourceAllocationException {
if (newAccountId == user.getAccountId()) {
// could do a not silent fail but the objective of the user is reached
return true; // no need to create a new user object for this user
}
return Transaction.execute(new TransactionCallback<>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
UserVO newUser = new UserVO(user);
user.setExternalEntity(user.getUuid());
user.setUuid(UUID.randomUUID().toString());
_userDao.update(user.getId(), user);
newUser.setAccountId(newAccountId);
boolean success = _userDao.remove(user.getId());
UserVO persisted = _userDao.persist(newUser);
return success && persisted.getUuid().equals(user.getExternalEntity());
}
return Transaction.execute((TransactionCallbackWithException<Boolean, ResourceAllocationException>) status -> {
UserVO newUser = new UserVO(user);
user.setExternalEntity(user.getUuid());
user.setUuid(UUID.randomUUID().toString());
_userDao.update(user.getId(), user);
newUser.setAccountId(newAccountId);
UserVO persisted = _userDao.persist(newUser);
_projectMgr.moveProjectAssociationsToUser(user, persisted);
boolean success = _userDao.remove(user.getId());
return success && persisted.getUuid().equals(user.getExternalEntity());
});
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.region;
import java.util.List;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
@ -128,7 +129,7 @@ public interface RegionManager {
* @param moveUserCmd
* @return
*/
boolean moveUser(MoveUserCmd moveUserCmd);
boolean moveUser(MoveUserCmd moveUserCmd) throws ResourceAllocationException;
/**
* update an existing domain

View File

@ -24,6 +24,7 @@ import java.util.Properties;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.springframework.stereotype.Component;
@ -227,7 +228,7 @@ public class RegionManagerImpl extends ManagerBase implements RegionManager, Man
* {@inheritDoc}
*/
@Override
public boolean moveUser(MoveUserCmd cmd) {
public boolean moveUser(MoveUserCmd cmd) throws ResourceAllocationException {
return _accountMgr.moveUser(cmd);
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.ResourceAllocationException;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.api.command.admin.account.DeleteAccountCmd;
@ -154,7 +155,7 @@ public class RegionServiceImpl extends ManagerBase implements RegionService, Man
* {@inheritDoc}
*/
@Override
public boolean moveUser(MoveUserCmd cmd) {
public boolean moveUser(MoveUserCmd cmd) throws ResourceAllocationException {
return _regionMgr.moveUser(cmd);
}

View File

@ -21,6 +21,7 @@ import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.projects.ProjectAccount.Role;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.component.ManagerBase;
import javax.naming.ConfigurationException;
@ -214,6 +215,17 @@ public class MockProjectManagerImpl extends ManagerBase implements ProjectManage
return 0;
}
@Override
public boolean cleanupProjectsForUser(Project project, User user) {
// TODO Auto-generated method stub
return false;
}
@Override
public void moveProjectAssociationsToUser(User oldUser, User newUser) throws ResourceAllocationException {
// TODO Auto-generated method stub
}
@Override
public Project findByProjectAccountIdIncludingRemoved(long projectAccountId) {
return null;

View File

@ -17,9 +17,11 @@
package com.cloud.projects;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.reservation.dao.ReservationDao;
import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Assert;
@ -28,6 +30,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
@ -35,7 +38,17 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.projects.ProjectAccount.Role;
import com.cloud.projects.dao.ProjectAccountDao;
import com.cloud.projects.dao.ProjectDao;
import com.cloud.projects.dao.ProjectInvitationDao;
import com.cloud.resourcelimit.CheckedReservation;
import com.cloud.user.AccountVO;
import com.cloud.user.ResourceLimitService;
import com.cloud.user.User;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.component.ComponentContext;
@ -49,6 +62,21 @@ public class ProjectManagerImplTest {
@Mock
ProjectDao projectDao;
@Mock
ProjectInvitationDao projectInvitationDao;
@Mock
ProjectAccountDao projectAccountDao;
@Mock
AccountDao accountDao;
@Mock
ResourceLimitService resourceLimitMgr;
@Mock
ReservationDao reservationDao;
List<ProjectVO> updateProjects;
@Before
@ -128,4 +156,158 @@ public class ProjectManagerImplTest {
Assert.assertTrue(CollectionUtils.isEmpty(result));
}
}
@Test
public void cleanupProjectsForUserTestNoAssociationsReturnsFalse() {
Project project = Mockito.mock(Project.class);
Mockito.when(project.getId()).thenReturn(100L);
User user = mockUser(1L, 10L);
Mockito.when(projectAccountDao.listBy(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()))
.thenReturn(Collections.emptyList());
boolean result = projectManager.cleanupProjectsForUser(project, user);
Assert.assertFalse(result);
Mockito.verify(projectInvitationDao).removeBy(100L, 10L, 1L);
Mockito.verify(projectAccountDao, Mockito.never()).remove(Mockito.anyLong());
Mockito.verify(resourceLimitMgr, Mockito.never()).decrementResourceCount(Mockito.anyLong(), Mockito.any(ResourceType.class));
}
@Test
public void cleanupProjectsForUserTestRemovesAdminAndRegularAssociations() {
Project project = Mockito.mock(Project.class);
Mockito.when(project.getId()).thenReturn(100L);
User user = mockUser(1L, 10L);
ProjectAccountVO admin = mockProjectAccount(1L, Role.Admin);
ProjectAccountVO regular = mockProjectAccount(2L, Role.Regular);
Mockito.when(projectAccountDao.listBy(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()))
.thenReturn(List.of(admin, regular));
boolean result = projectManager.cleanupProjectsForUser(project, user);
Assert.assertTrue(result);
Mockito.verify(projectInvitationDao).removeBy(100L, 10L, 1L);
Mockito.verify(projectAccountDao).remove(1L);
Mockito.verify(projectAccountDao).remove(2L);
Mockito.verify(resourceLimitMgr).decrementResourceCount(10L, ResourceType.project);
}
@Test
public void cleanupProjectsForUserTestNullProject() {
User user = mockUser(1L, 10L);
ProjectAccountVO admin = mockProjectAccount(1L, Role.Admin);
Mockito.when(projectAccountDao.listBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L)))
.thenReturn(List.of(admin));
boolean result = projectManager.cleanupProjectsForUser(null, user);
Assert.assertTrue(result);
Mockito.verify(projectInvitationDao).removeBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L));
Mockito.verify(projectAccountDao).remove(1L);
Mockito.verify(resourceLimitMgr).decrementResourceCount(10L, ResourceType.project);
}
@Test
public void moveProjectAssociationsToUserTestNoProjectAccounts() throws ResourceAllocationException {
User oldUser = mockUser(1L, 10L);
User newUser = mockUser(2L, 20L);
Mockito.when(projectAccountDao.listBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L)))
.thenReturn(Collections.emptyList());
projectManager.moveProjectAssociationsToUser(oldUser, newUser);
Mockito.verify(projectInvitationDao).move(oldUser, newUser);
Mockito.verify(projectAccountDao, Mockito.never()).move(Mockito.any(), Mockito.any());
Mockito.verifyNoInteractions(accountDao);
Mockito.verifyNoInteractions(resourceLimitMgr);
}
@Test
public void moveProjectAssociationsToUserTestSameAccount() throws ResourceAllocationException {
User oldUser = mockUser(1L, 10L);
User newUser = mockUser(2L, 10L);
ProjectAccountVO regular = mockProjectAccount(1L, Role.Regular);
Mockito.when(projectAccountDao.listBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L)))
.thenReturn(List.of(regular));
AccountVO oldAccount = mockAccount(10L);
AccountVO newAccount = mockAccount(10L);
Mockito.when(accountDao.findById(10L)).thenReturn(oldAccount).thenReturn(newAccount);
try (MockedConstruction<CheckedReservation> ignored = Mockito.mockConstruction(CheckedReservation.class)) {
projectManager.moveProjectAssociationsToUser(oldUser, newUser);
}
Mockito.verify(projectInvitationDao).move(oldUser, newUser);
Mockito.verify(projectAccountDao).move(oldUser, newUser);
Mockito.verify(resourceLimitMgr, Mockito.never())
.incrementResourceCount(Mockito.anyLong(), Mockito.any(ResourceType.class), Mockito.anyLong());
Mockito.verify(resourceLimitMgr, Mockito.never())
.decrementResourceCount(Mockito.anyLong(), Mockito.any(ResourceType.class), Mockito.anyLong());
}
@Test
public void moveProjectAssociationsToUserTestDifferentAccountsWithAdminRole() throws ResourceAllocationException {
User oldUser = mockUser(1L, 10L);
User newUser = mockUser(2L, 20L);
ProjectAccountVO admin = mockProjectAccount(1L, Role.Admin);
Mockito.when(projectAccountDao.listBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L)))
.thenReturn(List.of(admin));
AccountVO oldAccount = mockAccount(10L);
AccountVO newAccount = mockAccount(20L);
Mockito.when(accountDao.findById(10L)).thenReturn(oldAccount);
Mockito.when(accountDao.findById(20L)).thenReturn(newAccount);
try (MockedConstruction<CheckedReservation> ignored = Mockito.mockConstruction(CheckedReservation.class)) {
projectManager.moveProjectAssociationsToUser(oldUser, newUser);
}
Mockito.verify(projectInvitationDao).move(oldUser, newUser);
Mockito.verify(projectAccountDao).move(oldUser, newUser);
Mockito.verify(resourceLimitMgr).incrementResourceCount(20L, ResourceType.project, 1L);
Mockito.verify(resourceLimitMgr).decrementResourceCount(10L, ResourceType.project, 1L);
}
@Test
public void moveProjectAssociationsToUserTestDifferentAccountsWithoutAdminRole() throws ResourceAllocationException {
User oldUser = mockUser(1L, 10L);
User newUser = mockUser(2L, 20L);
ProjectAccountVO regular = mockProjectAccount(1L, Role.Regular);
Mockito.when(projectAccountDao.listBy(Mockito.isNull(), Mockito.eq(10L), Mockito.eq(1L)))
.thenReturn(List.of(regular));
AccountVO oldAccount = mockAccount(10L);
AccountVO newAccount = mockAccount(20L);
Mockito.when(accountDao.findById(10L)).thenReturn(oldAccount);
Mockito.when(accountDao.findById(20L)).thenReturn(newAccount);
try (MockedConstruction<CheckedReservation> ignored = Mockito.mockConstruction(CheckedReservation.class)) {
projectManager.moveProjectAssociationsToUser(oldUser, newUser);
}
Mockito.verify(projectInvitationDao).move(oldUser, newUser);
Mockito.verify(projectAccountDao).move(oldUser, newUser);
Mockito.verify(resourceLimitMgr, Mockito.never())
.incrementResourceCount(Mockito.anyLong(), Mockito.any(ResourceType.class), Mockito.anyLong());
Mockito.verify(resourceLimitMgr, Mockito.never())
.decrementResourceCount(Mockito.anyLong(), Mockito.any(ResourceType.class), Mockito.anyLong());
}
private User mockUser(long id, long accountId) {
User user = Mockito.mock(User.class);
Mockito.when(user.getId()).thenReturn(id);
Mockito.when(user.getAccountId()).thenReturn(accountId);
return user;
}
private AccountVO mockAccount(long id) {
AccountVO account = Mockito.mock(AccountVO.class);
Mockito.when(account.getId()).thenReturn(id);
return account;
}
private ProjectAccountVO mockProjectAccount(long id, Role role) {
ProjectAccountVO projectAccount = Mockito.mock(ProjectAccountVO.class);
Mockito.when(projectAccount.getId()).thenReturn(id);
Mockito.when(projectAccount.getAccountRole()).thenReturn(role);
return projectAccount;
}
}

View File

@ -2131,4 +2131,16 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
);
Assert.assertNotNull(userResultVO);
}
@Test
public void deleteAndCleanupUserTestUserCleanup() {
long userId = userVoMock.getId();
Mockito.doNothing().when(accountManagerImpl).removeUserApiKeys(userId);
Mockito.doReturn(true).when(_projectMgr).cleanupProjectsForUser(null, userVoMock);
accountManagerImpl.deleteAndCleanupUser(userVoMock);
Mockito.verify(accountManagerImpl).removeUserApiKeys(userId);
Mockito.verify(_projectMgr).cleanupProjectsForUser(null, userVoMock);
}
}