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:
Prachi Damle 2013-12-06 01:04:26 -08:00
parent 60cca0f65e
commit 5475312612
4 changed files with 115 additions and 12 deletions

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -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;

View File

@ -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);
}

View File

@ -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