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

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


The following commit(s) were added to refs/heads/master by this push:
     new 756a7e8  Fix limitation on tag matching in 'migrateVolume' with disk 
offering replacement (#2636)
756a7e8 is described below

commit 756a7e89cbcec7a1aa98b3f470cbdf8181af75ad
Author: Rafael Weingärtner <[email protected]>
AuthorDate: Sat Jul 21 07:01:24 2018 -0300

    Fix limitation on tag matching in 'migrateVolume' with disk offering 
replacement (#2636)
    
    * Fix limitation on tag matching in 'migrateVolume' with disk offering 
replacement
    
    When the feature to enable disk offering replacement during volume 
migration was created, we were forcing the tags of the new disk offering to 
exact the same as the tags of the target storage poll. However, that is not how 
ACS manages volumes allocation. This change modifies this validation to make it 
consistent with volume allocation.
    
    * Address Nitin's suggestions
    
    * Apply Daan's suggestion regarding "doesTargetStorageSupportDiskOffering" 
method
    
    * fix problem
---
 .../java/com/cloud/storage/VolumeApiService.java   | 35 +++++++-
 .../com/cloud/storage/VolumeApiServiceImpl.java    | 66 ++++++++++++++--
 .../cloud/storage/VolumeApiServiceImplTest.java    | 92 +++++++++++++++++++++-
 3 files changed, 184 insertions(+), 9 deletions(-)

diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java 
b/api/src/main/java/com/cloud/storage/VolumeApiService.java
index cf13cd6..ec20c33 100644
--- a/api/src/main/java/com/cloud/storage/VolumeApiService.java
+++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java
@@ -102,4 +102,37 @@ public interface VolumeApiService {
     void updateDisplay(Volume volume, Boolean displayVolume);
 
     Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) 
throws ResourceAllocationException;
-}
+
+    /**
+     *  Checks if the target storage supports the disk offering.
+     *  This validation is consistent with the mechanism used to select a 
storage pool to deploy a volume when a virtual machine is deployed or when a 
data disk is allocated.
+     *
+     *  The scenarios when this method returns true or false is presented in 
the following table.
+     *   <table border="1">
+     *      <tr>
+     *          <th>#</th><th>Disk offering tags</th><th>Storage 
tags</th><th>Does the storage support the disk offering?</th>
+     *      </tr>
+     *      <body>
+     *      <tr>
+     *          <td>1</td><td>A,B</td><td>A</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>2</td><td>A,B,C</td><td>A,B,C,D,X</td><td>YES</td>
+     *      </tr>
+     *      <tr>
+     *          <td>3</td><td>A,B,C</td><td>X,Y,Z</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>4</td><td>null</td><td>A,S,D</td><td>YES</td>
+     *      </tr>
+     *      <tr>
+     *          <td>5</td><td>A</td><td>null</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>6</td><td>null</td><td>null</td><td>YES</td>
+     *      </tr>
+     *      </body>
+     *   </table>
+     */
+    boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, String 
diskOfferingTags);
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java 
b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index 9bff4a1..3fcf761 100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@ -19,6 +19,7 @@ package com.cloud.storage;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -2161,9 +2162,9 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
      * Performs the validations required for replacing the disk offering while 
migrating the volume of storage. If no new disk offering is provided, we do not 
execute any validation.
      * If a disk offering is informed, we then proceed with the following 
checks.
      * <ul>
-     *  <li>We check if the given volume is of ROOT type. We cannot change the 
disk offering of a ROOT volume. Therefore, we thrown an {@link 
InvalidParameterValueException}.
-     *  <li>We the disk is being migrated to shared storage and the new disk 
offering is for local storage (or vice versa), we throw an {@link 
InvalidParameterValueException}. Bear in mind that we are validating only the 
new disk offering. If none is provided we can override the current disk 
offering. This means, placing a volume with shared disk offering in local 
storage and vice versa.
-     *  <li>We then proceed checking if the tags of the new disk offerings 
match the tags of the target storage. If they do not match an {@link 
InvalidParameterValueException} is thrown.
+     *  <li>We check if the given volume is of ROOT type. We cannot change the 
disk offering of a ROOT volume. Therefore, we thrown an {@link 
InvalidParameterValueException};
+     *  <li>We the disk is being migrated to shared storage and the new disk 
offering is for local storage (or vice versa), we throw an {@link 
InvalidParameterValueException}. Bear in mind that we are validating only the 
new disk offering. If none is provided we can override the current disk 
offering. This means, placing a volume with shared disk offering in local 
storage and vice versa;
+     *  <li>We then proceed checking the target storage pool supports the new 
disk offering {@link #doesTargetStorageSupportNewDiskOffering(StoragePool, 
DiskOfferingVO)}.
      * </ul>
      *
      * If all of the above validations pass, we check if the size of the new 
disk offering is different from the volume. If it is, we log a warning message.
@@ -2175,10 +2176,9 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
         if ((destPool.isShared() && newDiskOffering.getUseLocalStorage()) || 
destPool.isLocal() && newDiskOffering.isShared()) {
             throw new InvalidParameterValueException("You cannot move the 
volume to a shared storage and assing a disk offering for local storage and 
vice versa.");
         }
-        String storageTags = getStoragePoolTags(destPool);
-        if (!StringUtils.areTagsEqual(storageTags, newDiskOffering.getTags())) 
{
-            throw new InvalidParameterValueException(String.format("Target 
Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", 
destPool.getUuid(), storageTags,
-                    newDiskOffering.getUuid(), newDiskOffering.getTags()));
+        if (!doesTargetStorageSupportNewDiskOffering(destPool, 
newDiskOffering)) {
+            throw new InvalidParameterValueException(String.format("Target 
Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", 
destPool.getUuid(),
+                    getStoragePoolTags(destPool), newDiskOffering.getUuid(), 
newDiskOffering.getTags()));
         }
         if (volume.getSize() != newDiskOffering.getDiskSize()) {
             DiskOfferingVO oldDiskOffering = 
this._diskOfferingDao.findById(volume.getDiskOfferingId());
@@ -2190,6 +2190,58 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
     }
 
     /**
+     *  Checks if the target storage supports the new disk offering.
+     *  This validation is consistent with the mechanism used to select a 
storage pool to deploy a volume when a virtual machine is deployed or when a 
new data disk is allocated.
+     *
+     *  The scenarios when this method returns true or false is presented in 
the following table.
+     *
+     *   <table border="1">
+     *      <tr>
+     *          <th>#</th><th>Disk offering tags</th><th>Storage 
tags</th><th>Does the storage support the disk offering?</th>
+     *      </tr>
+     *      <body>
+     *      <tr>
+     *          <td>1</td><td>A,B</td><td>A</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>2</td><td>A,B,C</td><td>A,B,C,D,X</td><td>YES</td>
+     *      </tr>
+     *      <tr>
+     *          <td>3</td><td>A,B,C</td><td>X,Y,Z</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>4</td><td>null</td><td>A,S,D</td><td>YES</td>
+     *      </tr>
+     *      <tr>
+     *          <td>5</td><td>A</td><td>null</td><td>NO</td>
+     *      </tr>
+     *      <tr>
+     *          <td>6</td><td>null</td><td>null</td><td>YES</td>
+     *      </tr>
+     *      </body>
+     *   </table>
+     */
+    protected boolean doesTargetStorageSupportNewDiskOffering(StoragePool 
destPool, DiskOfferingVO newDiskOffering) {
+        String newDiskOfferingTags = newDiskOffering.getTags();
+        return doesTargetStorageSupportDiskOffering(destPool, 
newDiskOfferingTags);
+    }
+
+    @Override
+    public boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, 
String diskOfferingTags) {
+        if (org.apache.commons.lang.StringUtils.isBlank(diskOfferingTags)) {
+            return true;
+        }
+        String storagePoolTags = getStoragePoolTags(destPool);
+        if (org.apache.commons.lang.StringUtils.isBlank(storagePoolTags)) {
+            return false;
+        }
+        String[] storageTagsAsStringArray = 
org.apache.commons.lang.StringUtils.split(storagePoolTags, ",");
+        String[] newDiskOfferingTagsAsStringArray = 
org.apache.commons.lang.StringUtils.split(diskOfferingTags, ",");
+
+        return 
CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray),
 Arrays.asList(storageTagsAsStringArray));
+    }
+
+    /**
      *  Retrieves the storage pool tags as a {@link String}. If the storage 
pool does not have tags we return a null value.
      */
     protected String getStoragePoolTags(StoragePool destPool) {
diff --git 
a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java 
b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
index 8ae122d..890b1ea 100644
--- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
+++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
@@ -613,7 +613,6 @@ public class VolumeApiServiceImplTest {
         inOrder.verify(storagePoolMock).isLocal();
         inOrder.verify(newDiskOfferingMock, times(0)).isShared();
         
inOrder.verify(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
-        inOrder.verify(newDiskOfferingMock).getTags();
 
         inOrder.verify(volumeVoMock).getSize();
         inOrder.verify(newDiskOfferingMock).getDiskSize();
@@ -996,4 +995,95 @@ public class VolumeApiServiceImplTest {
 
         volumeApiServiceImpl.deleteVolume(volumeMockId, accountMock);
     }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingMoreTagsThanStorageTags() {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("A").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsIsSubSetOfStorageTags() 
{
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsEmptyAndStorageTagsNotEmpty()
 {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsNotEmptyAndStorageTagsEmpty()
 {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsEmptyAndStorageTagsEmpty()
 {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsDifferentFromdStorageTags()
 {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A,B").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("C,D").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void 
doesTargetStorageSupportDiskOfferingTestDiskOfferingTagsEqualsStorageTags() {
+        DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
+        Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
+
+        StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
+        
Mockito.doReturn("A").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
+
+        boolean result = 
volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, 
diskOfferingVoMock);
+
+        Assert.assertTrue(result);
+    }
 }

Reply via email to