Provide option to force delete the project (#4617)

* Provide a cleanup flag so that the project will
be deleted only when there are no resources left
in the project. If users click on delete project
by mistake then everything is deleted.

* fix travis failures

Co-authored-by: Rakesh Venkatesh <rakeshv@apache.org>
This commit is contained in:
Rakesh 2021-09-26 06:02:29 +02:00 committed by GitHub
parent 9de5ef9f2b
commit a9c42fd0cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 6 deletions

View File

@ -55,7 +55,7 @@ public interface ProjectService {
* - project id
* @return true if the project was deleted successfully, false otherwise
*/
boolean deleteProject(long id);
boolean deleteProject(long id, Boolean cleanup);
/**
* Gets a project by id

View File

@ -48,6 +48,9 @@ public class DeleteProjectCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted")
private Long id;
@Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, since = "4.16.0", description = "true if all project resources have to be cleaned up, false otherwise")
private Boolean cleanup;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -56,6 +59,10 @@ public class DeleteProjectCmd extends BaseAsyncCmd {
return id;
}
public Boolean isCleanup() {
return cleanup;
}
@Override
public String getCommandName() {
return s_name;
@ -68,7 +75,7 @@ public class DeleteProjectCmd extends BaseAsyncCmd {
@Override
public void execute() {
CallContext.current().setEventDetails("Project Id: " + id);
boolean result = _projectService.deleteProject(id);
boolean result = _projectService.deleteProject(id, isCleanup());
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
this.setResponseObject(response);

View File

@ -27,11 +27,24 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.mail.MessagingException;
import javax.naming.ConfigurationException;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcManager;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.acl.ProjectRole;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.acl.dao.ProjectRoleDao;
@ -125,6 +138,18 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
private ProjectRoleDao projectRoleDao;
@Inject
private UserDao userDao;
@Inject
private VolumeDao _volumeDao;
@Inject
private UserVmDao _userVmDao;
@Inject
private VMTemplateDao _templateDao;
@Inject
private NetworkDao _networkDao;
@Inject
private VMSnapshotDao _vmSnapshotDao;
@Inject
private VpcManager _vpcMgr;
protected boolean _invitationRequired = false;
protected long _invitationTimeOut = 86400000;
@ -285,7 +310,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
@Override
@ActionEvent(eventType = EventTypes.EVENT_PROJECT_DELETE, eventDescription = "deleting project", async = true)
public boolean deleteProject(long projectId) {
public boolean deleteProject(long projectId, Boolean isCleanup) {
CallContext ctx = CallContext.current();
ProjectVO project = getProject(projectId);
@ -297,7 +322,29 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C
CallContext.current().setProject(project);
_accountMgr.checkAccess(ctx.getCallingAccount(), AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId()));
return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project);
if (isCleanup != null && isCleanup) {
return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project);
} else {
List<VMTemplateVO> userTemplates = _templateDao.listByAccountId(project.getProjectAccountId());
List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.listByAccountId(project.getProjectAccountId());
List<UserVmVO> vms = _userVmDao.listByAccountId(project.getProjectAccountId());
List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId());
List<NetworkVO> networks = _networkDao.listByOwner(project.getProjectAccountId());
List<? extends Vpc> vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId());
Optional<String> message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs)
.filter(entity -> !entity.isEmpty())
.map(entity -> entity.size() + " " + entity.get(0).getEntityType().getSimpleName() + " to clean up")
.findFirst();
if (message.isEmpty()) {
return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project);
}
CloudRuntimeException e = new CloudRuntimeException("Can't delete the project yet because it has " + message.get());
e.addProxyObject(project.getUuid(), "projectId");
throw e;
}
}
@DB

View File

@ -37,7 +37,7 @@ public class MockProjectManagerImpl extends ManagerBase implements ProjectManage
}
@Override
public boolean deleteProject(long id) {
public boolean deleteProject(long id, Boolean cleanup) {
// TODO Auto-generated method stub
return false;
}

View File

@ -4100,6 +4100,7 @@ class Project:
cmd = deleteProject.deleteProjectCmd()
cmd.id = self.id
cmd.cleanup = True
apiclient.deleteProject(cmd)
def update(self, apiclient, **kwargs):

View File

@ -148,7 +148,14 @@ export default {
},
groupAction: true,
popup: true,
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
args: (record, store) => {
const fields = []
if (store.apis.deleteProject.params.filter(x => x.name === 'cleanup').length > 0) {
fields.push('cleanup')
}
return fields
}
}
]
}