Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/src/main/java/com/cloud/projects/ProjectService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -56,6 +59,10 @@ public Long geId() {
return id;
}

public Boolean isCleanup() {
return cleanup;
}

@Override
public String getCommandName() {
return s_name;
Expand All @@ -68,7 +75,7 @@ public String getCommandName() {
@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);
Expand Down
51 changes: 49 additions & 2 deletions server/src/main/java/com/cloud/projects/ProjectManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,24 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -285,7 +310,7 @@ public Project enableProject(long projectId) {

@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);
Expand All @@ -297,7 +322,29 @@ public boolean deleteProject(long projectId) {
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, LGTM. But would it be better, if we could inform the user about all the resources that are tied to the project as opposed to the first one that's found - by doing something like this:

 List<String> message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs)
                    .filter(entity -> !entity.isEmpty())
                    .map(entity -> entity.size() + " " +  entity.get(0).getEntityType().getSimpleName())
                    .collect(Collectors.toList());
 ...                   
 String msg = String.join(",", message) + " to clean up";
 CloudRuntimeException e = new CloudRuntimeException("Can't delete the project yet because it has " + msg);
 ...

Such that the end error would look like:

image

Or is this too much info??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pearl1594 I can do that

.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public Project createProject(String name, String displayText, String accountName
}

@Override
public boolean deleteProject(long id) {
public boolean deleteProject(long id, Boolean cleanup) {
// TODO Auto-generated method stub
return false;
}
Expand Down
1 change: 1 addition & 0 deletions tools/marvin/marvin/lib/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4100,6 +4100,7 @@ def delete(self, apiclient):

cmd = deleteProject.deleteProjectCmd()
cmd.id = self.id
cmd.cleanup = True
apiclient.deleteProject(cmd)

def update(self, apiclient, **kwargs):
Expand Down
9 changes: 8 additions & 1 deletion ui/src/config/section/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
]
}