mirror of https://github.com/apache/cloudstack.git
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.
This commit is contained in:
parent
60cca0f65e
commit
5475312612
|
|
@ -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///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<StoragePool> poolsToAvoid = new HashSet<StoragePool>();
|
||||
|
|
@ -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<Pod, Long>(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<VolumeVO> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue