diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index 4efc000fae5..f93b3c576ef 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -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 diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java index 171d51712ae..4e4a290f021 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java @@ -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); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 6d51c19e086..eb8c58b3060 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -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 userTemplates = _templateDao.listByAccountId(project.getProjectAccountId()); + List vmSnapshots = _vmSnapshotDao.listByAccountId(project.getProjectAccountId()); + List vms = _userVmDao.listByAccountId(project.getProjectAccountId()); + List volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId()); + List networks = _networkDao.listByOwner(project.getProjectAccountId()); + List vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId()); + + Optional 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 diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index f8af30f2eb2..988d675ec04 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -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; } diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index aeb8bfb9d10..b8c1138e846 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4100,6 +4100,7 @@ class Project: cmd = deleteProject.deleteProjectCmd() cmd.id = self.id + cmd.cleanup = True apiclient.deleteProject(cmd) def update(self, apiclient, **kwargs): diff --git a/ui/src/config/section/project.js b/ui/src/config/section/project.js index 531901c50b6..3a693c5f2c6 100644 --- a/ui/src/config/section/project.js +++ b/ui/src/config/section/project.js @@ -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 + } } ] }