Skip to content

Commit

Permalink
server: fix attach uploaded volume (apache#10267)
Browse files Browse the repository at this point in the history
* server: fix attach uploaded volume

Fixes apache#10120

When an uploaded volume is attached to a VM for which no existing volume
can be found it was resulting in error. For such volumes, server needs
to find a suitable pool first and copy them to the pool from secondary
store.

Signed-off-by: Abhishek Kumar <[email protected]>

* fix

Signed-off-by: Abhishek Kumar <[email protected]>

* fix

Signed-off-by: Abhishek Kumar <[email protected]>

* add unit tests

Signed-off-by: Abhishek Kumar <[email protected]>

---------

Signed-off-by: Abhishek Kumar <[email protected]>
Co-authored-by: Boris Stoyanov - a.k.a Bobby <[email protected]>
  • Loading branch information
2 people authored and rg9975 committed Jan 30, 2025
1 parent c7decdb commit 73279ad
Show file tree
Hide file tree
Showing 2 changed files with 330 additions and 42 deletions.
123 changes: 81 additions & 42 deletions server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.Pod;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
Expand All @@ -153,6 +155,7 @@
import com.cloud.hypervisor.HypervisorCapabilitiesVO;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.offering.DiskOffering;
import com.cloud.org.Cluster;
import com.cloud.org.Grouping;
import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager;
Expand Down Expand Up @@ -323,6 +326,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject
private VmWorkJobDao _workJobDao;
@Inject
ClusterDao clusterDao;
@Inject
private ClusterDetailsDao _clusterDetailsDao;
@Inject
private StorageManager storageMgr;
Expand All @@ -346,6 +351,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
protected ProjectManager projectManager;
@Inject
protected StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
HostPodDao podDao;


protected Gson _gson;
Expand Down Expand Up @@ -2380,25 +2387,18 @@ public Volume attachVolumeToVM(AttachVolumeCmd command) {
return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId());
}

private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);

if (volumeToAttach.isAttachedVM()) {
throw new CloudRuntimeException("This volume is already attached to a VM.");
}

UserVmVO vm = _userVmDao.findById(vmId);
protected VolumeVO getVmExistingVolumeForVolumeAttach(UserVmVO vm, VolumeInfo volumeToAttach) {
VolumeVO existingVolumeOfVm = null;
VMTemplateVO template = _templateDao.findById(vm.getTemplateId());
List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.ROOT);
List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
if (rootVolumesOfVm.size() > 1 && template != null && !template.isDeployAsIs()) {
throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state.");
} else {
if (!rootVolumesOfVm.isEmpty()) {
existingVolumeOfVm = rootVolumesOfVm.get(0);
} else {
// locate data volume of the vm
List<VolumeVO> diskVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK);
List<VolumeVO> diskVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.DATADISK);
for (VolumeVO diskVolume : diskVolumesOfVm) {
if (diskVolume.getState() != Volume.State.Allocated) {
existingVolumeOfVm = diskVolume;
Expand All @@ -2407,45 +2407,89 @@ private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long device
}
}
}
if (s_logger.isTraceEnabled()) {
String msg = "attaching volume %s/%s to a VM (%s/%s) with an existing volume %s/%s on primary storage %s";
if (existingVolumeOfVm != null) {
s_logger.trace(String.format(msg,
volumeToAttach.getName(), volumeToAttach.getUuid(),
if (existingVolumeOfVm == null) {
if (s_logger.isTraceEnabled()) {
s_logger.trace(String.format("No existing volume found for VM (%s/%s) to attach volume %s/%s",
vm.getName(), vm.getUuid(),
existingVolumeOfVm.getName(), existingVolumeOfVm.getUuid(),
existingVolumeOfVm.getPoolId()));
volumeToAttach.getName(), volumeToAttach.getUuid()));
}
return null;
}

HypervisorType rootDiskHyperType = vm.getHypervisorType();
HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());

if (s_logger.isTraceEnabled()) {
String msg = "attaching volume %s/%s to a VM (%s/%s) with an existing volume %s/%s on primary storage %s";
s_logger.trace(String.format(msg,
volumeToAttach.getName(), volumeToAttach.getUuid(),
vm.getName(), vm.getUuid(),
existingVolumeOfVm.getName(), existingVolumeOfVm.getUuid(),
existingVolumeOfVm.getPoolId()));
}
return existingVolumeOfVm;
}

protected StoragePool getPoolForAllocatedOrUploadedVolumeForAttach(final VolumeInfo volumeToAttach, final UserVmVO vm) {
DataCenter zone = _dcDao.findById(vm.getDataCenterId());
Pair<Long, Long> clusterHostId = virtualMachineManager.findClusterAndHostIdForVm(vm, false);
long podId = vm.getPodIdToDeployIn();
if (clusterHostId.first() != null) {
Cluster cluster = clusterDao.findById(clusterHostId.first());
podId = cluster.getPodId();
}
Pod pod = podDao.findById(podId);
DiskOfferingVO offering = _diskOfferingDao.findById(volumeToAttach.getDiskOfferingId());
DiskProfile diskProfile = new DiskProfile(volumeToAttach.getId(), volumeToAttach.getVolumeType(),
volumeToAttach.getName(), volumeToAttach.getId(), volumeToAttach.getSize(), offering.getTagsArray(),
offering.isUseLocalStorage(), offering.isRecreatable(),
volumeToAttach.getTemplateId());
diskProfile.setHyperType(vm.getHypervisorType());
StoragePool pool = _volumeMgr.findStoragePool(diskProfile, zone, pod, clusterHostId.first(),
clusterHostId.second(), vm, Collections.emptySet());
if (pool == null) {
throw new CloudRuntimeException(String.format("Failed to find a primary storage for volume in state: %s", volumeToAttach.getState()));
}
return pool;
}

protected VolumeInfo createVolumeOnPrimaryForAttachIfNeeded(final VolumeInfo volumeToAttach, final UserVmVO vm, VolumeVO existingVolumeOfVm) {
VolumeInfo newVolumeOnPrimaryStorage = volumeToAttach;

boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded;
if (!Arrays.asList(Volume.State.Allocated, Volume.State.Uploaded).contains(volumeToAttach.getState())) {
return newVolumeOnPrimaryStorage;
}
//don't create volume on primary storage if its being attached to the vm which Root's volume hasn't been created yet
StoragePoolVO destPrimaryStorage = null;
StoragePool destPrimaryStorage = null;
if (existingVolumeOfVm != null && !existingVolumeOfVm.getState().equals(Volume.State.Allocated)) {
destPrimaryStorage = _storagePoolDao.findById(existingVolumeOfVm.getPoolId());
if (s_logger.isTraceEnabled() && destPrimaryStorage != null) {
s_logger.trace(String.format("decided on target storage: %s/%s", destPrimaryStorage.getName(), destPrimaryStorage.getUuid()));
}
}
if (destPrimaryStorage == null) {
destPrimaryStorage = getPoolForAllocatedOrUploadedVolumeForAttach(volumeToAttach, vm);
}
try {
if (volumeOnSecondary && Storage.StoragePoolType.PowerFlex.equals(destPrimaryStorage.getPoolType())) {
throw new InvalidParameterValueException("Cannot attach uploaded volume, this operation is unsupported on storage pool type " + destPrimaryStorage.getPoolType());
}
newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach,
vm.getHypervisorType(), destPrimaryStorage);
} catch (NoTransitionException e) {
s_logger.debug("Failed to create volume on primary storage", e);
throw new CloudRuntimeException("Failed to create volume on primary storage", e);
}
return newVolumeOnPrimaryStorage;
}

boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded;
private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);

if (destPrimaryStorage != null && (volumeToAttach.getState() == Volume.State.Allocated || volumeOnSecondary)) {
try {
if (volumeOnSecondary && destPrimaryStorage.getPoolType() == Storage.StoragePoolType.PowerFlex) {
throw new InvalidParameterValueException("Cannot attach uploaded volume, this operation is unsupported on storage pool type " + destPrimaryStorage.getPoolType());
}
newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, destPrimaryStorage);
} catch (NoTransitionException e) {
s_logger.debug("Failed to create volume on primary storage", e);
throw new CloudRuntimeException("Failed to create volume on primary storage", e);
}
if (volumeToAttach.isAttachedVM()) {
throw new CloudRuntimeException("This volume is already attached to a VM.");
}

UserVmVO vm = _userVmDao.findById(vmId);
VolumeVO existingVolumeOfVm = getVmExistingVolumeForVolumeAttach(vm, volumeToAttach);
VolumeInfo newVolumeOnPrimaryStorage = createVolumeOnPrimaryForAttachIfNeeded(volumeToAttach, vm, existingVolumeOfVm);

// reload the volume from db
newVolumeOnPrimaryStorage = volFactory.getVolume(newVolumeOnPrimaryStorage.getId());
boolean moveVolumeNeeded = needMoveVolume(existingVolumeOfVm, newVolumeOnPrimaryStorage);
Expand All @@ -2463,19 +2507,17 @@ private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long device
StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(existingVolumeOfVm.getPoolId());

try {
HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());
newVolumeOnPrimaryStorage = _volumeMgr.moveVolume(newVolumeOnPrimaryStorage, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(),
volumeToAttachHyperType);
} catch (ConcurrentOperationException e) {
s_logger.debug("move volume failed", e);
throw new CloudRuntimeException("move volume failed", e);
} catch (StorageUnavailableException e) {
} catch (ConcurrentOperationException | StorageUnavailableException e) {
s_logger.debug("move volume failed", e);
throw new CloudRuntimeException("move volume failed", e);
}
}
VolumeVO newVol = _volsDao.findById(newVolumeOnPrimaryStorage.getId());
// Getting the fresh vm object in case of volume migration to check the current state of VM
if (moveVolumeNeeded || volumeOnSecondary) {
if (moveVolumeNeeded) {
vm = _userVmDao.findById(vmId);
if (vm == null) {
throw new InvalidParameterValueException("VM not found.");
Expand Down Expand Up @@ -2659,9 +2701,6 @@ private void checkDeviceId(Long deviceId, VolumeInfo volumeToAttach, UserVmVO vm
if (!_volsDao.findByInstanceAndDeviceId(vm.getId(), 0).isEmpty()) {
throw new InvalidParameterValueException("Vm already has root volume attached to it");
}
if (volumeToAttach.getState() == Volume.State.Uploaded) {
throw new InvalidParameterValueException("No support for Root volume attach in state " + Volume.State.Uploaded);
}
}
}

Expand Down
Loading

0 comments on commit 73279ad

Please sign in to comment.