diff --git a/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index 437638c5e41..cb7b13a6a7e 100644 --- a/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -24,13 +24,13 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandJobType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; @@ -100,6 +100,9 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd { @Parameter(name = ApiConstants.DISPLAY_VOLUME, type = CommandType.BOOLEAN, description = "an optional field, whether to display the volume to the end user or not.") private Boolean displayVolume; + @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") + private Long virtualMachineId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -148,6 +151,10 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd { return displayVolume; } + public Long getVirtualMachineId() { + return virtualMachineId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index 7379ed690a6..a9ccc06617a 100644 --- a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -39,6 +39,7 @@ import com.cloud.storage.Volume; import com.cloud.storage.Volume.Type; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.uservm.UserVm; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.DiskProfile; import com.cloud.vm.VirtualMachine; @@ -63,7 +64,7 @@ public interface VolumeOrchestrationService { String getVmNameOnVolume(Volume volume); - VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot) throws StorageUnavailableException; + VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, UserVm vm) throws StorageUnavailableException; Volume migrateVolume(Volume volume, StoragePool destPool) throws StorageUnavailableException; diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index a10dc66650e..7b0d968ab07 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -253,7 +253,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati @DB @Override - public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot) throws StorageUnavailableException { + public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, UserVm vm) + throws StorageUnavailableException { Account account = _entityMgr.findById(Account.class, volume.getAccountId()); final HashSet poolsToAvoid = new HashSet(); @@ -266,17 +267,62 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId()); DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType()); - // Determine what pod to store the volume in - while ((pod = findPod(null, null, dc, account.getId(), podsToAvoid)) != null) { - podsToAvoid.add(pod.first().getId()); + String msg = "There are no available storage pools to store the volume in"; + + if(vm != null){ + Pod podofVM = _entityMgr.findById(Pod.class, vm.getPodIdToDeployIn()); + if(podofVM != null){ + pod = new Pair(podofVM, podofVM.getId()); + } + } + + if(vm != null && pod != null){ + //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. + Long hostId = null; + Long clusterId = null; + if(vm.getState() == State.Running){ + hostId = vm.getHostId(); + if(hostId != null){ + Host vmHost = _entityMgr.findById(Host.class, hostId); + clusterId = vmHost.getClusterId(); + } + }else{ + List rootVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); + if (rootVolumesOfVm.size() != 1) { + throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state. Please contact Cloud Support."); + } else { + VolumeVO rootVolumeOfVm = rootVolumesOfVm.get(0); + StoragePoolVO rootDiskPool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); + clusterId = (rootDiskPool == null ? null : rootDiskPool.getClusterId()); + } + } // Determine what storage pool to store the volume in - while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) { + while ((pool = findStoragePool(dskCh, dc, pod.first(), clusterId, hostId, vm, poolsToAvoid)) != null) { break; } + + if (pool == null) { + //pool could not be found in the VM's pod/cluster. + if(s_logger.isDebugEnabled()){ + s_logger.debug("Could not find any storage pool to create Volume in the pod/cluster of the provided VM "+vm.getUuid()); + } + StringBuilder addDetails = new StringBuilder(msg); + addDetails.append(", Could not find any storage pool to create Volume in the pod/cluster of the VM "); + addDetails.append(vm.getUuid()); + msg = addDetails.toString(); + } + }else{ + // Determine what pod to store the volume in + while ((pod = findPod(null, null, dc, account.getId(), podsToAvoid)) != null) { + podsToAvoid.add(pod.first().getId()); + // Determine what storage pool to store the volume in + while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) { + break; + } + } } if (pool == null) { - String msg = "There are no available storage pools to store the volume in"; s_logger.info(msg); throw new StorageUnavailableException(msg, -1); } diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index fdd5db5707f..18c64ddd531 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -584,6 +584,26 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // check snapshot permissions _accountMgr.checkAccess(caller, null, true, snapshotCheck); + + // one step operation - create volume in VM's cluster and attach it + // to the VM + Long vmId = cmd.getVirtualMachineId(); + if (vmId != null) { + // Check that the virtual machine ID is valid and it's a user vm + UserVmVO vm = _userVmDao.findById(vmId); + if (vm == null || vm.getType() != VirtualMachine.Type.User) { + throw new InvalidParameterValueException("Please specify a valid User VM."); + } + + // Check that the VM is in the correct state + if (vm.getState() != State.Running && vm.getState() != State.Stopped) { + throw new InvalidParameterValueException("Please specify a VM that is either running or stopped."); + } + + // permission check + _accountMgr.checkAccess(caller, null, false, vm); + } + } // Check that the resource limit for primary storage won't be exceeded @@ -682,10 +702,28 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic try { if (cmd.getSnapshotId() != null) { - volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId()); + volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId(), cmd.getVirtualMachineId()); if (volume.getState() != Volume.State.Ready) { created = false; } + + // if VM Id is provided, attach the volume to the VM + if (cmd.getVirtualMachineId() != null) { + try { + attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId()); + } catch (Exception ex) { + StringBuilder message = new StringBuilder("Volume: "); + message.append(volume.getUuid()); + message.append(" created successfully, but failed to attach the newly created volume to VM: "); + message.append(cmd.getVirtualMachineId()); + message.append(" due to error: "); + message.append(ex.getMessage()); + if (s_logger.isDebugEnabled()) { + s_logger.debug(message, ex); + } + throw new CloudRuntimeException(message.toString()); + } + } } return volume; } catch (Exception e) { @@ -701,10 +739,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } - protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId) throws StorageUnavailableException { + protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Long vmId) + throws StorageUnavailableException { VolumeInfo createdVolume = null; SnapshotVO snapshot = _snapshotDao.findById(snapshotId); - createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot); + + UserVmVO vm = null; + if (vmId != null) { + vm = _userVmDao.findById(vmId); + } + createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot, vm); UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(), createdVolume.getName(), createdVolume.getDiskOfferingId(), null, createdVolume.getSize(), Volume.class.getName(), createdVolume.getUuid()); @@ -986,11 +1030,16 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } @Override - @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true) public Volume attachVolumeToVM(AttachVolumeCmd command) { Long vmId = command.getVirtualMachineId(); Long volumeId = command.getId(); Long deviceId = command.getDeviceId(); + return attachVolumeToVM(vmId, volumeId, deviceId); + } + + + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true) + public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { Account caller = CallContext.current().getCallingAccount(); // Check that the volume ID is valid