server: Failed to scale between Service Offerings with the same root disk size (#5095)

* Cover a case where resizing root disk failed; add isNotPossibleToResize method.

* remove format from resize validation

* Revert if-conditional changes that removed ImageFormat.ISO validation

* Add JUnit tests for VolumeApiServiceImpl.isNotPossibleToResize

* Fix checkstyle of test Class

* Use _templateDao.findByIdIncludingRemoved instead of _templateDao.findById

* Prevent null serviceOfferingView and Mock findByIdIncludingRemoved instead of findById
This commit is contained in:
Gabriel Beims Bräscher 2021-06-14 04:19:55 -03:00 committed by GitHub
parent 9dd0acf8c9
commit bc12833ccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 15 deletions

View File

@ -31,6 +31,8 @@ import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
@ -218,6 +220,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject
private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@Inject
private ServiceOfferingJoinDao serviceOfferingJoinDao;
@Inject
private UserVmDao _userVmDao;
@Inject
private UserVmDetailsDao userVmDetailsDao;
@ -920,20 +924,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
// if we are to use the existing disk offering
ImageFormat format = null;
if (newDiskOffering == null) {
Long templateId = volume.getTemplateId();
if (templateId != null) {
VMTemplateVO template = _templateDao.findById(templateId);
format = template.getFormat();
}
if (volume.getVolumeType().equals(Volume.Type.ROOT) && diskOffering.getDiskSize() > 0 && format != null && format != ImageFormat.ISO) {
throw new InvalidParameterValueException(
"Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; "
+ "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. "
+ "For more details please check out the Official Resizing Volumes documentation.");
}
newSize = cmd.getSize();
newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve();
@ -944,6 +935,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
+ "customizable or it must be a root volume (if providing a disk offering, make sure it is different from the current disk offering).");
}
if (isNotPossibleToResize(volume, diskOffering)) {
throw new InvalidParameterValueException(
"Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; "
+ "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. "
+ "For more details please check out the Official Resizing Volumes documentation.");
}
// convert from bytes to GiB
newSize = newSize << 30;
} else {
@ -1167,6 +1165,27 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
shrinkOk);
}
/**
* A volume should not be resized if it covers ALL the following scenarios: <br>
* 1 - Root volume <br>
* 2 - && Current Disk Offering enforces a root disk size (in this case one can resize only by changing the Service Offering)
*/
protected boolean isNotPossibleToResize(VolumeVO volume, DiskOfferingVO diskOffering) {
Long templateId = volume.getTemplateId();
ImageFormat format = null;
if (templateId != null) {
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(templateId);
format = template.getFormat();
}
boolean isNotIso = format != null && format != ImageFormat.ISO;
boolean isRoot = Volume.Type.ROOT.equals(volume.getVolumeType());
ServiceOfferingJoinVO serviceOfferingView = serviceOfferingJoinDao.findById(diskOffering.getId());
boolean isOfferingEnforcingRootDiskSize = serviceOfferingView != null && serviceOfferingView.getRootDiskSize() > 0;
return isOfferingEnforcingRootDiskSize && isRoot && isNotIso;
}
private void checkIfVolumeIsRootAndVmIsRunning(Long newSize, VolumeVO volume, VMInstanceVO vmInstanceVO) {
if (!volume.getSize().equals(newSize) && volume.getVolumeType().equals(Volume.Type.ROOT) && !State.Stopped.equals(vmInstanceVO.getState())) {
throw new InvalidParameterValueException(String.format("Cannot resize ROOT volume [%s] when VM is not on Stopped State. VM %s is in state %s", volume.getName(), vmInstanceVO

View File

@ -35,6 +35,9 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import com.cloud.storage.dao.VMTemplateDao;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
@ -78,7 +81,6 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.org.Grouping;
import com.cloud.serializer.GsonHelper;
import com.cloud.server.TaggedResourceService;
@ -153,7 +155,9 @@ public class VolumeApiServiceImplTest {
@Mock
private StoragePoolTagsDao storagePoolTagsDao;
@Mock
private HypervisorCapabilitiesDao hypervisorCapabilitiesDao;
private VMTemplateDao templateDao;
@Mock
private ServiceOfferingJoinDao serviceOfferingJoinDao;
private DetachVolumeCmd detachCmd = new DetachVolumeCmd();
private Class<?> _detachCmdClass = detachCmd.getClass();
@ -1079,4 +1083,52 @@ public class VolumeApiServiceImplTest {
Assert.assertTrue(result);
}
@Test
public void isNotPossibleToResizeTestAllFormats() {
Storage.ImageFormat[] imageFormat = Storage.ImageFormat.values();
for (int i = 0; i < imageFormat.length - 1; i++) {
if (imageFormat[i] != Storage.ImageFormat.ISO) {
prepareAndRunTestOfIsNotPossibleToResize(Type.ROOT, 10l, imageFormat[i], true);
} else {
prepareAndRunTestOfIsNotPossibleToResize(Type.ROOT, 10l, imageFormat[i], false);
}
}
}
@Test
public void isNotPossibleToResizeTestAllTypes() {
Type[] types = Type.values();
for (int i = 0; i < types.length - 1; i++) {
if (types[i] != Type.ROOT) {
prepareAndRunTestOfIsNotPossibleToResize(types[i], 10l, Storage.ImageFormat.QCOW2, false);
} else {
prepareAndRunTestOfIsNotPossibleToResize(types[i], 10l, Storage.ImageFormat.QCOW2, true);
}
}
}
@Test
public void isNotPossibleToResizeTestNoRootDiskSize() {
prepareAndRunTestOfIsNotPossibleToResize(Type.ROOT, 0l, Storage.ImageFormat.QCOW2, false);
}
private void prepareAndRunTestOfIsNotPossibleToResize(Type volumeType, Long rootDisk, Storage.ImageFormat imageFormat, boolean expectedIsNotPossibleToResize) {
VolumeVO volume = Mockito.mock(VolumeVO.class);
when(volume.getVolumeType()).thenReturn(volumeType);
when(volume.getTemplateId()).thenReturn(1l);
DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
ServiceOfferingJoinVO serviceOfferingJoinVO = Mockito.mock(ServiceOfferingJoinVO.class);
when(serviceOfferingJoinVO.getRootDiskSize()).thenReturn(rootDisk);
when(serviceOfferingJoinDao.findById(Mockito.anyLong())).thenReturn(serviceOfferingJoinVO);
VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
when(template.getFormat()).thenReturn(imageFormat);
when(templateDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(template);
boolean result = volumeApiServiceImpl.isNotPossibleToResize(volume, diskOffering);
Assert.assertEquals(expectedIsNotPossibleToResize, result);
}
}