rafaelweingartner closed pull request #2636: Fix limitation on tag matching in 
'migrateVolume' with disk offering replacement
URL: https://github.com/apache/cloudstack/pull/2636
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java 
b/api/src/main/java/com/cloud/storage/VolumeApiService.java
index cf13cd671a9..ec20c33e17e 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 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long 
snapshotId, Account acc
     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 9bff4a17295..3fcf7618ade 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 @@
 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 @@ private DiskOfferingVO 
retrieveAndValidateNewDiskOffering(MigrateVolumeCmd cmd)
      * 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 @@ protected void 
validateConditionsToReplaceDiskOfferingOfVolume(VolumeVO volume,
         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());
@@ -2189,6 +2189,58 @@ protected void 
validateConditionsToReplaceDiskOfferingOfVolume(VolumeVO volume,
         s_logger.info(String.format("Changing disk offering to [uuid=%s] while 
migrating volume [uuid=%s, name=%s].", newDiskOffering.getUuid(), 
volume.getUuid(), volume.getName()));
     }
 
+    /**
+     *  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.
      */
diff --git 
a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java 
b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java
index 8ae122d4c46..890b1eab526 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 void 
validateConditionsToReplaceDiskOfferingOfVolumeTestEverythingWorking
         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 void 
deleteVolumeTestVolumeStateReadyThrowingRuntimeException() throws In
 
         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);
+    }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to