Skip to content

Commit 42759ed

Browse files
authored
AC-84 assign volume to account (#57)
* Adding the ability to re-assign a volume to a new account * Missed a UI file. * Fixing bug where account drop down had all accounts, not just the accounts for the selected domain.
1 parent 6defcc8 commit 42759ed

File tree

24 files changed

+592
-0
lines changed

24 files changed

+592
-0
lines changed

api/src/com/cloud/event/EventTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ public class EventTypes {
234234
public static final String EVENT_VOLUME_DETAIL_ADD = "VOLUME.DETAIL.ADD";
235235
public static final String EVENT_VOLUME_DETAIL_REMOVE = "VOLUME.DETAIL.REMOVE";
236236
public static final String EVENT_VOLUME_UPDATE = "VOLUME.UPDATE";
237+
public static final String EVENT_VOLUME_ASSIGN = "VOLUME.ASSIGN";
237238

238239
// Domains
239240
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";

api/src/com/cloud/storage/VolumeApiService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.net.MalformedURLException;
2222

23+
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
2324
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
2425
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
2526
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
@@ -54,6 +55,14 @@ public interface VolumeApiService {
5455
*/
5556
Volume createVolume(CreateVolumeCmd cmd);
5657

58+
/**
59+
* Re-assigns a volume from one account to another
60+
* @param cmd
61+
* the API command wrapping the criteria
62+
* @return the volume object
63+
*/
64+
Volume assignVolume(AssignVolumeCmd cmd) throws ResourceAllocationException;
65+
5766
/**
5867
* Resizes the volume based on the given criteria
5968
*
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.apache.cloudstack.api.command.user.volume;
2+
3+
import com.cloud.exception.ConcurrentOperationException;
4+
import com.cloud.exception.InsufficientCapacityException;
5+
import com.cloud.exception.InvalidParameterValueException;
6+
import com.cloud.exception.NetworkRuleConflictException;
7+
import com.cloud.exception.ResourceAllocationException;
8+
import com.cloud.exception.ResourceUnavailableException;
9+
import com.cloud.storage.Volume;
10+
import com.cloud.user.Account;
11+
import org.apache.cloudstack.acl.RoleType;
12+
import org.apache.cloudstack.api.APICommand;
13+
import org.apache.cloudstack.api.ApiConstants;
14+
import org.apache.cloudstack.api.ApiErrorCode;
15+
import org.apache.cloudstack.api.BaseCmd;
16+
import org.apache.cloudstack.api.Parameter;
17+
import org.apache.cloudstack.api.ResponseObject;
18+
import org.apache.cloudstack.api.ServerApiException;
19+
import org.apache.cloudstack.api.response.DomainResponse;
20+
import org.apache.cloudstack.api.response.ProjectResponse;
21+
import org.apache.cloudstack.api.response.VolumeResponse;
22+
import org.apache.log4j.Logger;
23+
24+
25+
@APICommand(name="assignVolume",
26+
description="Change ownership of a Volume from one account to another. A root administrator can reassign a VM from any account to any other account in any domain. A domain administrator can reassign a VM to any account in the same domain.",
27+
responseObject = VolumeResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11.3.0", entityType = {Volume.class},
28+
authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin})
29+
public class AssignVolumeCmd extends BaseCmd {
30+
public static final Logger s_logger = Logger.getLogger(AssignVolumeCmd.class.getName());
31+
32+
@Parameter(name= ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "id of the volume to be moved")
33+
private Long volumeId;
34+
35+
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "account name of the new VM owner.")
36+
private String accountName;
37+
38+
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "domain id of the new VM owner.")
39+
private Long domainId;
40+
41+
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "an optional project for the new VM owner.")
42+
private Long projectId;
43+
44+
/////////////////////////////////////////////////////
45+
/////////////////// Accessors ///////////////////////
46+
/////////////////////////////////////////////////////
47+
48+
public Long getVolumeId() {
49+
return volumeId;
50+
}
51+
52+
public String getAccountName() {
53+
return accountName;
54+
}
55+
56+
public Long getDomainId() {
57+
return domainId;
58+
}
59+
60+
public Long getProjectId() {
61+
return projectId;
62+
}
63+
64+
/////////////////////////////////////////////////////
65+
/////////////// API Implementation///////////////////
66+
/////////////////////////////////////////////////////
67+
68+
@Override
69+
public String getCommandName() {
70+
return "assignvolumeresponse";
71+
}
72+
73+
@Override
74+
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
75+
try {
76+
Volume volume = _volumeService.assignVolume(this);
77+
if (volume == null) {
78+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign volume");
79+
}
80+
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseObject.ResponseView.Restricted, volume);
81+
response.setResponseName(getCommandName());
82+
setResponseObject(response);
83+
} catch (InvalidParameterValueException e) {
84+
e.printStackTrace();
85+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
86+
} catch (Exception e) {
87+
e.printStackTrace();
88+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move volume: " + e.getMessage());
89+
}
90+
}
91+
92+
@Override
93+
public long getEntityOwnerId() {
94+
Volume volume = _responseGenerator.findVolumeById(getVolumeId());
95+
if (volume != null) {
96+
return volume.getAccountId();
97+
}
98+
return Account.ACCOUNT_ID_SYSTEM;
99+
}
100+
}

server/src/com/cloud/server/ManagementServerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@
472472
import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd;
473473
import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToVMSnapshotCmd;
474474
import org.apache.cloudstack.api.command.user.volume.AddResourceDetailCmd;
475+
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
475476
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
476477
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
477478
import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
@@ -2889,6 +2890,7 @@ public List<Class<?>> getCommands() {
28892890
cmdList.add(MigrateVolumeCmd.class);
28902891
cmdList.add(ResizeVolumeCmd.class);
28912892
cmdList.add(UploadVolumeCmd.class);
2893+
cmdList.add(AssignVolumeCmd.class);
28922894
cmdList.add(CreateStaticRouteCmd.class);
28932895
cmdList.add(CreateVPCCmd.class);
28942896
cmdList.add(DeleteStaticRouteCmd.class);

server/src/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929

3030
import javax.inject.Inject;
3131

32+
import com.cloud.user.AccountService;
33+
import com.cloud.utils.db.TransactionCallbackNoReturn;
34+
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
3235
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
3336
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
3437
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
@@ -197,6 +200,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
197200
@Inject
198201
AccountManager _accountMgr;
199202
@Inject
203+
private AccountService _accountService;
204+
@Inject
200205
ConfigurationManager _configMgr;
201206
@Inject
202207
VolumeDao _volsDao;
@@ -846,6 +851,85 @@ protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Lo
846851
return volumeVo;
847852
}
848853

854+
@Override
855+
@DB
856+
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_ASSIGN, eventDescription = "assigning volume", async = false)
857+
public VolumeVO assignVolume(AssignVolumeCmd cmd) throws ResourceAllocationException {
858+
// VERIFICATIONS and VALIDATIONS
859+
860+
// Verify the two users
861+
Account caller = CallContext.current().getCallingAccount();
862+
if (!_accountMgr.isRootAdmin(caller.getId())
863+
&& !_accountMgr.isDomainAdmin(caller.getId())) { // only root and domain admins can assign Volumes
864+
throw new InvalidParameterValueException("Only domain admins are allowed to assign VMs and not " + caller.getType());
865+
}
866+
867+
VolumeVO volume = _volsDao.findById(cmd.getVolumeId());
868+
if (volume == null) {
869+
throw new InvalidParameterValueException("There is no volume by that id " + cmd.getVolumeId());
870+
} else if (volume.getInstanceId() != null) {
871+
throw new InvalidParameterValueException("Volume is currently attached to a vm, detatch first");
872+
} else if (volume.getVolumeType() != Volume.Type.DATADISK) {
873+
throw new InvalidParameterValueException("Only able to assign data volumes");
874+
} else if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) {
875+
throw new InvalidParameterValueException("Can't assign a volume that is not ready or allocated");
876+
}
877+
878+
final Account oldAccount = _accountService.getActiveAccountById(volume.getAccountId());
879+
if (oldAccount == null) {
880+
throw new InvalidParameterValueException("Invalid account for Volume " + volume.getAccountId() + " in domain.");
881+
}
882+
final Account newAccount = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId());
883+
if (newAccount == null) {
884+
throw new InvalidParameterValueException("Invalid accountid=" + cmd.getAccountName() + " in domain " + cmd.getDomainId());
885+
}
886+
887+
if (newAccount.getState() == Account.State.disabled) {
888+
throw new InvalidParameterValueException("The new account owner " + cmd.getAccountName() + " is disabled.");
889+
}
890+
891+
// Check caller has access to both the old and new account
892+
_accountMgr.checkAccess(caller, null, true, oldAccount);
893+
_accountMgr.checkAccess(caller, null, true, newAccount);
894+
895+
// Make sure the accounts are not same
896+
if (oldAccount.getAccountId() == newAccount.getAccountId()) {
897+
throw new InvalidParameterValueException("The new account is the same as the old account. Account id =" + oldAccount.getAccountId());
898+
}
899+
900+
List<SnapshotVO> snapshots = _snapshotDao.listByStatusNotIn(volume.getId(), Snapshot.State.Destroyed,Snapshot.State.Error);
901+
if (snapshots != null && snapshots.size() > 0) {
902+
throw new InvalidParameterValueException(
903+
"Snapshots exists for volume: "+ volume.getName()+ ", remove snapshots for volume before assigning to another user.");
904+
}
905+
906+
// Check if volume and primary storage space are with in resource limits
907+
_resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.volume, 1);
908+
_resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.primary_storage, volume.getSize());
909+
910+
Transaction.execute(new TransactionCallbackNoReturn() {
911+
@Override
912+
public void doInTransactionWithoutResult(TransactionStatus status) {
913+
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
914+
Volume.class.getName(), volume.getUuid(), volume.isDisplayVolume());
915+
_resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.volume);
916+
_resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.primary_storage, volume.getSize());
917+
volume.setAccountId(newAccount.getAccountId());
918+
volume.setDomainId(newAccount.getDomainId());
919+
_volsDao.persist(volume);
920+
_resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.volume);
921+
_resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.primary_storage, volume.getSize());
922+
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(),
923+
volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(),
924+
volume.getUuid(), volume.isDisplayVolume());
925+
}
926+
});
927+
928+
s_logger.info("AssignVolume: volume " + volume.getName() + " now belongs to account " + newAccount.getAccountName());
929+
930+
return volume;
931+
}
932+
849933
@Override
850934
@DB
851935
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true)

0 commit comments

Comments
 (0)