This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.15
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.15 by this push:
     new ab790c1  server: Allow to upgrade service offerings from local <> 
shared storage pools (#4915)
ab790c1 is described below

commit ab790c11d5663afb4e2da098aab5701a625fb3b1
Author: Gabriel Beims Bräscher <[email protected]>
AuthorDate: Fri Apr 30 03:29:50 2021 -0300

    server: Allow to upgrade service offerings from local <> shared storage 
pools (#4915)
    
    This PR addresses the issue raised at #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: #4545
---
 .../java/com/cloud/vm/VirtualMachineManager.java   |  6 +++
 .../com/cloud/vm/VirtualMachineManagerImpl.java    | 50 ++++++++++++-------
 .../cloud/vm/VirtualMachineManagerImplTest.java    | 56 ++++++++++++++++++++++
 .../java/com/cloud/api/query/QueryManagerImpl.java | 10 +++-
 4 files changed, 104 insertions(+), 18 deletions(-)

diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java 
b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java
index 40777b38..9670464 100644
--- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java
+++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java
@@ -257,5 +257,11 @@ public interface VirtualMachineManager extends Manager {
 
     UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws 
ResourceUnavailableException, InsufficientCapacityException;
 
+    /**
+     * Returns true if the VM's Root volume is allocated at a local storage 
pool
+     */
+    boolean isRootVolumeOnLocalStorage(long vmId);
+
     Pair<Long, Long> findClusterAndHostIdForVm(long vmId);
+
 }
diff --git 
a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
 
b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
index 765bb90..6967e74 100755
--- 
a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ 
b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -3632,22 +3632,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
 
         final ServiceOfferingVO currentServiceOffering = 
_offeringDao.findByIdIncludingRemoved(vmInstance.getId(), 
vmInstance.getServiceOfferingId());
 
-        // Check that the service offering being upgraded to has the same 
Guest IP type as the VM's current service offering
-        // NOTE: With the new network refactoring in 2.2, we shouldn't need 
the check for same guest IP type anymore.
-        /*
-         * if 
(!currentServiceOffering.getGuestIpType().equals(newServiceOffering.getGuestIpType()))
 { String errorMsg =
-         * "The service offering being upgraded to has a guest IP type: " + 
newServiceOffering.getGuestIpType(); errorMsg +=
-         * ". Please select a service offering with the same guest IP type as 
the VM's current service offering (" +
-         * currentServiceOffering.getGuestIpType() + ")."; throw new 
InvalidParameterValueException(errorMsg); }
-         */
-
-        // Check that the service offering being upgraded to has the same 
storage pool preference as the VM's current service
-        // offering
-        if (currentServiceOffering.isUseLocalStorage() != 
newServiceOffering.isUseLocalStorage()) {
-            throw new InvalidParameterValueException("Unable to upgrade 
virtual machine " + vmInstance.toString() +
-                    ", cannot switch between local storage and shared storage 
service offerings.  Current offering " + "useLocalStorage=" +
-                    currentServiceOffering.isUseLocalStorage() + ", target 
offering useLocalStorage=" + newServiceOffering.isUseLocalStorage());
-        }
+        checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, 
newServiceOffering);
 
         // 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
         if (currentServiceOffering.isSystemUse() != 
newServiceOffering.isSystemUse()) {
@@ -3669,6 +3654,39 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
         }
     }
 
+    /**
+     * Throws an InvalidParameterValueException in case the new service 
offerings does not match the storage scope (e.g. local or shared).
+     */
+    protected void 
checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, 
ServiceOffering newServiceOffering) {
+        boolean isRootVolumeOnLocalStorage = 
isRootVolumeOnLocalStorage(vmInstance.getId());
+
+        if (newServiceOffering.isUseLocalStorage() && 
!isRootVolumeOnLocalStorage) {
+            String message = String .format("Unable to upgrade virtual machine 
%s, target offering use local storage but the storage pool where "
+                    + "the volume is allocated is a shared storage.", 
vmInstance.toString());
+            throw new InvalidParameterValueException(message);
+        }
+
+        if (!newServiceOffering.isUseLocalStorage() && 
isRootVolumeOnLocalStorage) {
+            String message = String.format("Unable to upgrade virtual machine 
%s, target offering use shared storage but the storage pool where "
+                    + "the volume is allocated is a local storage.", 
vmInstance.toString());
+            throw new InvalidParameterValueException(message);
+        }
+    }
+
+    public boolean isRootVolumeOnLocalStorage(long vmId) {
+        ScopeType poolScope = ScopeType.ZONE;
+        List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vmId, 
Type.ROOT);
+        if(CollectionUtils.isNotEmpty(volumes)) {
+            VolumeVO rootDisk = volumes.get(0);
+            Long poolId = rootDisk.getPoolId();
+            if (poolId != null) {
+                StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId);
+                poolScope = storagePoolVO.getScope();
+            }
+        }
+        return ScopeType.HOST == poolScope;
+    }
+
     @Override
     public boolean upgradeVmDb(final long vmId, final ServiceOffering 
newServiceOffering, ServiceOffering currentServiceOffering) {
 
diff --git 
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
 
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
index 1725a41..d8df27d 100644
--- 
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
+++ 
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
@@ -31,6 +31,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.exception.InvalidParameterValueException;
 import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -623,4 +624,59 @@ public class VirtualMachineManagerImplTest {
         assertTrue(VirtualMachineManagerImpl.matches(tags,three));
         assertTrue(VirtualMachineManagerImpl.matches(others,three));
     }
+
+    @Test
+    public void isRootVolumeOnLocalStorageTestOnLocal() {
+        prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.HOST, true);
+    }
+
+    @Test
+    public void isRootVolumeOnLocalStorageTestCluster() {
+        prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.CLUSTER, false);
+    }
+
+    @Test
+    public void isRootVolumeOnLocalStorageTestZone() {
+        prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.ZONE, false);
+    }
+
+    private void prepareAndTestIsRootVolumeOnLocalStorage(ScopeType scope, 
boolean expected) {
+        StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class);
+        
Mockito.doReturn(storagePoolVoMock).when(storagePoolDaoMock).findById(Mockito.anyLong());
+        Mockito.doReturn(scope).when(storagePoolVoMock).getScope();
+        List<VolumeVO> mockedVolumes = new ArrayList<>();
+        mockedVolumes.add(volumeVoMock);
+        
Mockito.doReturn(mockedVolumes).when(volumeDaoMock).findByInstanceAndType(Mockito.anyLong(),
 Mockito.any());
+
+        boolean result = 
virtualMachineManagerImpl.isRootVolumeOnLocalStorage(0l);
+
+        Assert.assertEquals(expected, result);
+    }
+
+    @Test
+    public void 
checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalLocal() {
+        prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, 
true);
+    }
+
+    @Test
+    public void 
checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedShared() {
+        prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, 
false);
+    }
+
+    @Test (expected = InvalidParameterValueException.class)
+    public void 
checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalShared() {
+        prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, 
false);
+    }
+
+    @Test (expected = InvalidParameterValueException.class)
+    public void 
checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedLocal() {
+        prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, 
true);
+    }
+
+    private void 
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(boolean 
isRootOnLocal, boolean isOfferingUsingLocal) {
+        
Mockito.doReturn(isRootOnLocal).when(virtualMachineManagerImpl).isRootVolumeOnLocalStorage(Mockito.anyLong());
+        
Mockito.doReturn("vmInstanceMockedToString").when(vmInstanceMock).toString();
+        
Mockito.doReturn(isOfferingUsingLocal).when(serviceOfferingMock).isUseLocalStorage();
+        
virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock,
 serviceOfferingMock);
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java 
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index cf01b8d..2e6bc8a 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -32,6 +32,7 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 
 import com.cloud.storage.dao.VMTemplateDetailsDao;
+import com.cloud.vm.VirtualMachineManager;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -424,6 +425,9 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
     @Inject
     private UserDao userDao;
 
+    @Inject
+    private VirtualMachineManager virtualMachineManager;
+
     /*
      * (non-Javadoc)
      *
@@ -2959,8 +2963,10 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
                 sc.addAnd("id", SearchCriteria.Op.NEQ, 
currentVmOffering.getId());
             }
 
-            // 1. Only return offerings with the same storage type
-            sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, 
currentVmOffering.isUseLocalStorage());
+            boolean isRootVolumeUsingLocalStorage = 
virtualMachineManager.isRootVolumeOnLocalStorage(vmId);
+
+            // 1. Only return offerings with the same storage type than the 
storage pool where the VM's root volume is allocated
+            sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, 
isRootVolumeUsingLocalStorage);
 
             // 2.In case vm is running return only offerings greater than 
equal to current offering compute.
             if (vmInstance.getState() == VirtualMachine.State.Running) {

Reply via email to