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 e5c7a51b8d2..97db648e799 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 @@ -20,13 +20,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; @@ -86,6 +86,9 @@ public class CreateVolumeCmd extends BaseAsyncCreateCmd { @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 /////////////////////// ///////////////////////////////////////////////////// @@ -135,6 +138,10 @@ public class CreateVolumeCmd extends BaseAsyncCreateCmd { 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 a773ac42a3a..1bba479fa4f 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; @@ -62,7 +63,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 0f9e1e4a954..41a3f290736 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -265,7 +265,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(); @@ -278,17 +279,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 91131fe3a60..6e01893691b 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -572,6 +572,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); + } + } if (displayVolumeEnabled == null) { @@ -677,10 +697,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) { @@ -696,10 +734,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()); @@ -981,11 +1025,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