Skip to content

Commit 5475312

Browse files
author
Prachi Damle
committed
CLOUDSTACK-5399: Add option to createVolume API to specify a VM, to place the volume appropriately and attach immediately
Changes: - Added 'virtualmachineid' parameter to the createVolume API to specify a VM for the volume. The Vm should be in 'Running' or 'Stopped' state. - This parameter is used only when createVolume API is called using snapshotid parameter - When this parameter is set, the volume is created from the snapshot in the pod/cluster of the VM. Also the volume is then attached to the VM in the same request - If attach Volume fails but create has succeeded, the API errors out but the Volume created remains available. User may attach the same volume later - When Vm is provided, but if no storage pool is available in the VM's pod/cluster then the volume is not created and API fails.
1 parent 60cca0f commit 5475312

4 files changed

Lines changed: 115 additions & 12 deletions

File tree

api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
import org.apache.cloudstack.api.ApiCommandJobType;
2525
import org.apache.cloudstack.api.ApiConstants;
2626
import org.apache.cloudstack.api.ApiErrorCode;
27-
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
2827
import org.apache.cloudstack.api.Parameter;
2928
import org.apache.cloudstack.api.ServerApiException;
3029
import org.apache.cloudstack.api.response.DiskOfferingResponse;
3130
import org.apache.cloudstack.api.response.DomainResponse;
3231
import org.apache.cloudstack.api.response.ProjectResponse;
3332
import org.apache.cloudstack.api.response.SnapshotResponse;
33+
import org.apache.cloudstack.api.response.UserVmResponse;
3434
import org.apache.cloudstack.api.response.VolumeResponse;
3535
import org.apache.cloudstack.api.response.ZoneResponse;
3636
import org.apache.cloudstack.context.CallContext;
@@ -100,6 +100,9 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd {
100100
@Parameter(name = ApiConstants.DISPLAY_VOLUME, type = CommandType.BOOLEAN, description = "an optional field, whether to display the volume to the end user or not.")
101101
private Boolean displayVolume;
102102

103+
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "the ID of the virtual machine; to be used with snapshot Id, VM to which the volume gets attached after creation")
104+
private Long virtualMachineId;
105+
103106
/////////////////////////////////////////////////////
104107
/////////////////// Accessors ///////////////////////
105108
/////////////////////////////////////////////////////
@@ -148,6 +151,10 @@ public Boolean getDisplayVolume() {
148151
return displayVolume;
149152
}
150153

154+
public Long getVirtualMachineId() {
155+
return virtualMachineId;
156+
}
157+
151158
/////////////////////////////////////////////////////
152159
/////////////// API Implementation///////////////////
153160
/////////////////////////////////////////////////////

engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.cloud.storage.Volume.Type;
4040
import com.cloud.template.VirtualMachineTemplate;
4141
import com.cloud.user.Account;
42+
import com.cloud.uservm.UserVm;
4243
import com.cloud.utils.fsm.NoTransitionException;
4344
import com.cloud.vm.DiskProfile;
4445
import com.cloud.vm.VirtualMachine;
@@ -63,7 +64,7 @@ VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId,
6364

6465
String getVmNameOnVolume(Volume volume);
6566

66-
VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot) throws StorageUnavailableException;
67+
VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, UserVm vm) throws StorageUnavailableException;
6768

6869
Volume migrateVolume(Volume volume, StoragePool destPool) throws StorageUnavailableException;
6970

engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ public Pair<Pod, Long> findPod(VirtualMachineTemplate template, ServiceOffering
253253

254254
@DB
255255
@Override
256-
public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot) throws StorageUnavailableException {
256+
public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, UserVm vm)
257+
throws StorageUnavailableException {
257258
Account account = _entityMgr.findById(Account.class, volume.getAccountId());
258259

259260
final HashSet<StoragePool> poolsToAvoid = new HashSet<StoragePool>();
@@ -266,17 +267,62 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot) thr
266267
DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId());
267268
DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType());
268269

269-
// Determine what pod to store the volume in
270-
while ((pod = findPod(null, null, dc, account.getId(), podsToAvoid)) != null) {
271-
podsToAvoid.add(pod.first().getId());
270+
String msg = "There are no available storage pools to store the volume in";
271+
272+
if(vm != null){
273+
Pod podofVM = _entityMgr.findById(Pod.class, vm.getPodIdToDeployIn());
274+
if(podofVM != null){
275+
pod = new Pair<Pod, Long>(podofVM, podofVM.getId());
276+
}
277+
}
278+
279+
if(vm != null && pod != null){
280+
//if VM is running use the hostId to find the clusterID. If it is stopped, refer the cluster where the ROOT volume of the VM exists.
281+
Long hostId = null;
282+
Long clusterId = null;
283+
if(vm.getState() == State.Running){
284+
hostId = vm.getHostId();
285+
if(hostId != null){
286+
Host vmHost = _entityMgr.findById(Host.class, hostId);
287+
clusterId = vmHost.getClusterId();
288+
}
289+
}else{
290+
List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
291+
if (rootVolumesOfVm.size() != 1) {
292+
throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state. Please contact Cloud Support.");
293+
} else {
294+
VolumeVO rootVolumeOfVm = rootVolumesOfVm.get(0);
295+
StoragePoolVO rootDiskPool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId());
296+
clusterId = (rootDiskPool == null ? null : rootDiskPool.getClusterId());
297+
}
298+
}
272299
// Determine what storage pool to store the volume in
273-
while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) {
300+
while ((pool = findStoragePool(dskCh, dc, pod.first(), clusterId, hostId, vm, poolsToAvoid)) != null) {
274301
break;
275302
}
303+
304+
if (pool == null) {
305+
//pool could not be found in the VM's pod/cluster.
306+
if(s_logger.isDebugEnabled()){
307+
s_logger.debug("Could not find any storage pool to create Volume in the pod/cluster of the provided VM "+vm.getUuid());
308+
}
309+
StringBuilder addDetails = new StringBuilder(msg);
310+
addDetails.append(", Could not find any storage pool to create Volume in the pod/cluster of the VM ");
311+
addDetails.append(vm.getUuid());
312+
msg = addDetails.toString();
313+
}
314+
}else{
315+
// Determine what pod to store the volume in
316+
while ((pod = findPod(null, null, dc, account.getId(), podsToAvoid)) != null) {
317+
podsToAvoid.add(pod.first().getId());
318+
// Determine what storage pool to store the volume in
319+
while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) {
320+
break;
321+
}
322+
}
276323
}
277324

278325
if (pool == null) {
279-
String msg = "There are no available storage pools to store the volume in";
280326
s_logger.info(msg);
281327
throw new StorageUnavailableException(msg, -1);
282328
}

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,26 @@ public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationExcept
584584

585585
// check snapshot permissions
586586
_accountMgr.checkAccess(caller, null, true, snapshotCheck);
587+
588+
// one step operation - create volume in VM's cluster and attach it
589+
// to the VM
590+
Long vmId = cmd.getVirtualMachineId();
591+
if (vmId != null) {
592+
// Check that the virtual machine ID is valid and it's a user vm
593+
UserVmVO vm = _userVmDao.findById(vmId);
594+
if (vm == null || vm.getType() != VirtualMachine.Type.User) {
595+
throw new InvalidParameterValueException("Please specify a valid User VM.");
596+
}
597+
598+
// Check that the VM is in the correct state
599+
if (vm.getState() != State.Running && vm.getState() != State.Stopped) {
600+
throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
601+
}
602+
603+
// permission check
604+
_accountMgr.checkAccess(caller, null, false, vm);
605+
}
606+
587607
}
588608

589609
// Check that the resource limit for primary storage won't be exceeded
@@ -682,10 +702,28 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) {
682702

683703
try {
684704
if (cmd.getSnapshotId() != null) {
685-
volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId());
705+
volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId(), cmd.getVirtualMachineId());
686706
if (volume.getState() != Volume.State.Ready) {
687707
created = false;
688708
}
709+
710+
// if VM Id is provided, attach the volume to the VM
711+
if (cmd.getVirtualMachineId() != null) {
712+
try {
713+
attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId());
714+
} catch (Exception ex) {
715+
StringBuilder message = new StringBuilder("Volume: ");
716+
message.append(volume.getUuid());
717+
message.append(" created successfully, but failed to attach the newly created volume to VM: ");
718+
message.append(cmd.getVirtualMachineId());
719+
message.append(" due to error: ");
720+
message.append(ex.getMessage());
721+
if (s_logger.isDebugEnabled()) {
722+
s_logger.debug(message, ex);
723+
}
724+
throw new CloudRuntimeException(message.toString());
725+
}
726+
}
689727
}
690728
return volume;
691729
} catch (Exception e) {
@@ -701,10 +739,16 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) {
701739
}
702740
}
703741

704-
protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId) throws StorageUnavailableException {
742+
protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Long vmId)
743+
throws StorageUnavailableException {
705744
VolumeInfo createdVolume = null;
706745
SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
707-
createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot);
746+
747+
UserVmVO vm = null;
748+
if (vmId != null) {
749+
vm = _userVmDao.findById(vmId);
750+
}
751+
createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot, vm);
708752

709753
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(),
710754
createdVolume.getName(), createdVolume.getDiskOfferingId(), null, createdVolume.getSize(), Volume.class.getName(), createdVolume.getUuid());
@@ -986,11 +1030,16 @@ private boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransiti
9861030
}
9871031

9881032
@Override
989-
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
9901033
public Volume attachVolumeToVM(AttachVolumeCmd command) {
9911034
Long vmId = command.getVirtualMachineId();
9921035
Long volumeId = command.getId();
9931036
Long deviceId = command.getDeviceId();
1037+
return attachVolumeToVM(vmId, volumeId, deviceId);
1038+
}
1039+
1040+
1041+
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
1042+
public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
9941043
Account caller = CallContext.current().getCallingAccount();
9951044

9961045
// Check that the volume ID is valid

0 commit comments

Comments
 (0)