Skip to content

Commit 3066a10

Browse files
GabrielBrascherrp-
authored andcommitted
server: Allow to upgrade service offerings from local <> shared storage pools (apache#4915)
This PR addresses the issue raised at apache#4545 (Fail to change Service offering from local <> shared storage). When upgrading a VM service offering it is validated if the new offering has the same storage scope (local or shared) as the current offering. I think that the validation makes sense in a way of preventing running Root disks with an offering that does not match the current storage pool. However, the validation only compares both offerings and does not consider that it is possible to migrate Volumes between local <> shared storage pools. The idea behind this implementation is that CloudStack should check the scope of the current storage pool which the ROOT volume is allocated; this, it is possible to migrate the volume between storage pools and list/upgrade according to the offerings that are supported for such pool. This PR also fixes an issue where the API command that lists offerings for a VM should follow the same idea and list based on the storage pool that the volume is allocated and not the previous offering. Fixes: apache#4545
1 parent c7b9d6b commit 3066a10

File tree

4 files changed

+104
-18
lines changed

4 files changed

+104
-18
lines changed

engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,5 +262,11 @@ static String getHypervisorHostname(String name) {
262262

263263
UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException;
264264

265+
/**
266+
* Returns true if the VM's Root volume is allocated at a local storage pool
267+
*/
268+
boolean isRootVolumeOnLocalStorage(long vmId);
269+
265270
Pair<Long, Long> findClusterAndHostIdForVm(long vmId);
271+
266272
}

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3828,22 +3828,7 @@ public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffe
38283828

38293829
final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId());
38303830

3831-
// Check that the service offering being upgraded to has the same Guest IP type as the VM's current service offering
3832-
// NOTE: With the new network refactoring in 2.2, we shouldn't need the check for same guest IP type anymore.
3833-
/*
3834-
* if (!currentServiceOffering.getGuestIpType().equals(newServiceOffering.getGuestIpType())) { String errorMsg =
3835-
* "The service offering being upgraded to has a guest IP type: " + newServiceOffering.getGuestIpType(); errorMsg +=
3836-
* ". Please select a service offering with the same guest IP type as the VM's current service offering (" +
3837-
* currentServiceOffering.getGuestIpType() + ")."; throw new InvalidParameterValueException(errorMsg); }
3838-
*/
3839-
3840-
// Check that the service offering being upgraded to has the same storage pool preference as the VM's current service
3841-
// offering
3842-
if (currentServiceOffering.isUseLocalStorage() != newServiceOffering.isUseLocalStorage()) {
3843-
throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() +
3844-
", cannot switch between local storage and shared storage service offerings. Current offering " + "useLocalStorage=" +
3845-
currentServiceOffering.isUseLocalStorage() + ", target offering useLocalStorage=" + newServiceOffering.isUseLocalStorage());
3846-
}
3831+
checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newServiceOffering);
38473832

38483833
// if vm is a system vm, check if it is a system service offering, if yes return with error as it cannot be used for user vms
38493834
if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) {
@@ -3865,6 +3850,39 @@ public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffe
38653850
}
38663851
}
38673852

3853+
/**
3854+
* Throws an InvalidParameterValueException in case the new service offerings does not match the storage scope (e.g. local or shared).
3855+
*/
3856+
protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, ServiceOffering newServiceOffering) {
3857+
boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId());
3858+
3859+
if (newServiceOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) {
3860+
String message = String .format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where "
3861+
+ "the volume is allocated is a shared storage.", vmInstance.toString());
3862+
throw new InvalidParameterValueException(message);
3863+
}
3864+
3865+
if (!newServiceOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) {
3866+
String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where "
3867+
+ "the volume is allocated is a local storage.", vmInstance.toString());
3868+
throw new InvalidParameterValueException(message);
3869+
}
3870+
}
3871+
3872+
public boolean isRootVolumeOnLocalStorage(long vmId) {
3873+
ScopeType poolScope = ScopeType.ZONE;
3874+
List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vmId, Type.ROOT);
3875+
if(CollectionUtils.isNotEmpty(volumes)) {
3876+
VolumeVO rootDisk = volumes.get(0);
3877+
Long poolId = rootDisk.getPoolId();
3878+
if (poolId != null) {
3879+
StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId);
3880+
poolScope = storagePoolVO.getScope();
3881+
}
3882+
}
3883+
return ScopeType.HOST == poolScope;
3884+
}
3885+
38683886
@Override
38693887
public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) {
38703888

engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.List;
3232
import java.util.Map;
3333

34+
import com.cloud.exception.InvalidParameterValueException;
3435
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
3536
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
3637
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -624,4 +625,59 @@ public void matchesOfSorts() {
624625
assertTrue(VirtualMachineManagerImpl.matches(tags,three));
625626
assertTrue(VirtualMachineManagerImpl.matches(others,three));
626627
}
628+
629+
@Test
630+
public void isRootVolumeOnLocalStorageTestOnLocal() {
631+
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.HOST, true);
632+
}
633+
634+
@Test
635+
public void isRootVolumeOnLocalStorageTestCluster() {
636+
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.CLUSTER, false);
637+
}
638+
639+
@Test
640+
public void isRootVolumeOnLocalStorageTestZone() {
641+
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.ZONE, false);
642+
}
643+
644+
private void prepareAndTestIsRootVolumeOnLocalStorage(ScopeType scope, boolean expected) {
645+
StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class);
646+
Mockito.doReturn(storagePoolVoMock).when(storagePoolDaoMock).findById(Mockito.anyLong());
647+
Mockito.doReturn(scope).when(storagePoolVoMock).getScope();
648+
List<VolumeVO> mockedVolumes = new ArrayList<>();
649+
mockedVolumes.add(volumeVoMock);
650+
Mockito.doReturn(mockedVolumes).when(volumeDaoMock).findByInstanceAndType(Mockito.anyLong(), Mockito.any());
651+
652+
boolean result = virtualMachineManagerImpl.isRootVolumeOnLocalStorage(0l);
653+
654+
Assert.assertEquals(expected, result);
655+
}
656+
657+
@Test
658+
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalLocal() {
659+
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, true);
660+
}
661+
662+
@Test
663+
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedShared() {
664+
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, false);
665+
}
666+
667+
@Test (expected = InvalidParameterValueException.class)
668+
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalShared() {
669+
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, false);
670+
}
671+
672+
@Test (expected = InvalidParameterValueException.class)
673+
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedLocal() {
674+
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, true);
675+
}
676+
677+
private void prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(boolean isRootOnLocal, boolean isOfferingUsingLocal) {
678+
Mockito.doReturn(isRootOnLocal).when(virtualMachineManagerImpl).isRootVolumeOnLocalStorage(Mockito.anyLong());
679+
Mockito.doReturn("vmInstanceMockedToString").when(vmInstanceMock).toString();
680+
Mockito.doReturn(isOfferingUsingLocal).when(serviceOfferingMock).isUseLocalStorage();
681+
virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, serviceOfferingMock);
682+
}
627683
}

server/src/main/java/com/cloud/api/query/QueryManagerImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import javax.inject.Inject;
3333

3434
import com.cloud.storage.dao.VMTemplateDetailsDao;
35+
import com.cloud.vm.VirtualMachineManager;
3536
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
3637
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
3738
import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -424,6 +425,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
424425
@Inject
425426
private UserDao userDao;
426427

428+
@Inject
429+
private VirtualMachineManager virtualMachineManager;
430+
427431
/*
428432
* (non-Javadoc)
429433
*
@@ -2962,8 +2966,10 @@ private Pair<List<ServiceOfferingJoinVO>, Integer> searchForServiceOfferingsInte
29622966
sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId());
29632967
}
29642968

2965-
// 1. Only return offerings with the same storage type
2966-
sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, currentVmOffering.isUseLocalStorage());
2969+
boolean isRootVolumeUsingLocalStorage = virtualMachineManager.isRootVolumeOnLocalStorage(vmId);
2970+
2971+
// 1. Only return offerings with the same storage type than the storage pool where the VM's root volume is allocated
2972+
sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, isRootVolumeUsingLocalStorage);
29672973

29682974
// 2.In case vm is running return only offerings greater than equal to current offering compute.
29692975
if (vmInstance.getState() == VirtualMachine.State.Running) {

0 commit comments

Comments
 (0)