Skip to content

Commit 0cd40f4

Browse files
Allow upgrading offering with shared storage or local storage if the volume is already allocated on the storage that matches with new offering type.
This is done by changing the validation in a way that it checks if new offering matches the current volume's storage pool as a shared or local. Return Service Offerings with the same storage type (local vs shared) than the storage pool where the VM's root volume is allocated
1 parent 0302750 commit 0cd40f4

File tree

4 files changed

+98
-18
lines changed

4 files changed

+98
-18
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,9 @@ static String getHypervisorHostname(String name) {
255255
boolean unmanage(String vmUuid);
256256

257257
UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException;
258+
259+
/**
260+
* Returns true if the VM's Root volume is allocated at a local storage pool
261+
*/
262+
boolean isRootVolumeOnLocalStorage(long vmId);
258263
}

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

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

36703670
final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId());
36713671

3672-
// Check that the service offering being upgraded to has the same Guest IP type as the VM's current service offering
3673-
// NOTE: With the new network refactoring in 2.2, we shouldn't need the check for same guest IP type anymore.
3674-
/*
3675-
* if (!currentServiceOffering.getGuestIpType().equals(newServiceOffering.getGuestIpType())) { String errorMsg =
3676-
* "The service offering being upgraded to has a guest IP type: " + newServiceOffering.getGuestIpType(); errorMsg +=
3677-
* ". Please select a service offering with the same guest IP type as the VM's current service offering (" +
3678-
* currentServiceOffering.getGuestIpType() + ")."; throw new InvalidParameterValueException(errorMsg); }
3679-
*/
3680-
3681-
// Check that the service offering being upgraded to has the same storage pool preference as the VM's current service
3682-
// offering
3683-
if (currentServiceOffering.isUseLocalStorage() != newServiceOffering.isUseLocalStorage()) {
3684-
throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() +
3685-
", cannot switch between local storage and shared storage service offerings. Current offering " + "useLocalStorage=" +
3686-
currentServiceOffering.isUseLocalStorage() + ", target offering useLocalStorage=" + newServiceOffering.isUseLocalStorage());
3687-
}
3672+
checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newServiceOffering);
36883673

36893674
// 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
36903675
if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) {
@@ -3706,6 +3691,34 @@ public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffe
37063691
}
37073692
}
37083693

3694+
protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, ServiceOffering newServiceOffering) {
3695+
boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId());
3696+
3697+
if (newServiceOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) {
3698+
String message = String .format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where "
3699+
+ "the volume is allocated is a shared storage.", vmInstance.toString());
3700+
throw new InvalidParameterValueException(message);
3701+
} else if (!newServiceOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) {
3702+
String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where "
3703+
+ "the volume is allocated is a local storage.", vmInstance.toString());
3704+
throw new InvalidParameterValueException(message);
3705+
}
3706+
}
3707+
3708+
public boolean isRootVolumeOnLocalStorage(long vmId) {
3709+
ScopeType poolScope = ScopeType.ZONE;
3710+
List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vmId, Type.ROOT);
3711+
if(CollectionUtils.isNotEmpty(volumes)) {
3712+
VolumeVO rootDisk = volumes.get(0);
3713+
Long poolId = rootDisk.getPoolId();
3714+
if (poolId != null) {
3715+
StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId);
3716+
poolScope = storagePoolVO.getScope();
3717+
}
3718+
}
3719+
return ScopeType.HOST == poolScope;
3720+
}
3721+
37093722
@Override
37103723
public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) {
37113724

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

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
*
@@ -2959,8 +2963,10 @@ private Pair<List<ServiceOfferingJoinVO>, Integer> searchForServiceOfferingsInte
29592963
sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId());
29602964
}
29612965

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

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

0 commit comments

Comments
 (0)