Skip to content

Commit 215ac45

Browse files
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.
1 parent 1b10c18 commit 215ac45

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.net.MalformedURLException;
2020
import java.net.URL;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Date;
2324
import java.util.HashMap;
2425
import java.util.List;
@@ -2114,9 +2115,9 @@ private DiskOfferingVO retrieveAndValidateNewDiskOffering(MigrateVolumeCmd cmd)
21142115
* 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.
21152116
* If a disk offering is informed, we then proceed with the following checks.
21162117
* <ul>
2117-
* <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}.
2118-
* <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.
2119-
* <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.
2118+
* <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};
2119+
* <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;
2120+
* <li>We then proceed checking the target storage pool supports the new disk offering {@link #doesTargetStorageSupportNewDiskOffering(StoragePool, DiskOfferingVO)}.
21202121
* </ul>
21212122
*
21222123
* 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.
@@ -2128,10 +2129,9 @@ protected void validateConditionsToReplaceDiskOfferingOfVolume(VolumeVO volume,
21282129
if ((destPool.isShared() && newDiskOffering.getUseLocalStorage()) || destPool.isLocal() && newDiskOffering.isShared()) {
21292130
throw new InvalidParameterValueException("You cannot move the volume to a shared storage and assing a disk offering for local storage and vice versa.");
21302131
}
2131-
String storageTags = getStoragePoolTags(destPool);
2132-
if (!StringUtils.areTagsEqual(storageTags, newDiskOffering.getTags())) {
2133-
throw new InvalidParameterValueException(String.format("Target Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", destPool.getUuid(), storageTags,
2134-
newDiskOffering.getUuid(), newDiskOffering.getTags()));
2132+
if (!doesTargetStorageSupportNewDiskOffering(destPool, newDiskOffering)) {
2133+
throw new InvalidParameterValueException(String.format("Target Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", destPool.getUuid(),
2134+
getStoragePoolTags(destPool), newDiskOffering.getUuid(), newDiskOffering.getTags()));
21352135
}
21362136
if (volume.getSize() != newDiskOffering.getDiskSize()) {
21372137
DiskOfferingVO oldDiskOffering = this._diskOfferingDao.findById(volume.getDiskOfferingId());
@@ -2142,6 +2142,54 @@ protected void validateConditionsToReplaceDiskOfferingOfVolume(VolumeVO volume,
21422142
s_logger.info(String.format("Changing disk offering to [uuid=%s] while migrating volume [uuid=%s, name=%s].", newDiskOffering.getUuid(), volume.getUuid(), volume.getName()));
21432143
}
21442144

2145+
/**
2146+
* Checks if the target storage supports the new disk offering.
2147+
* 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.
2148+
*
2149+
* The scenarios when this method returns true or false is presented in the following table.
2150+
*
2151+
* <table border="1">
2152+
* <tr>
2153+
* <th>#</th><th>Disk offering tags</th><th>Storage tags</th><th>Does the storage support the disk offering?</th>
2154+
* </tr>
2155+
* <body>
2156+
* <tr>
2157+
* <td>1</td><td>A,B</td><td>A</td><td>NO</td>
2158+
* </tr>
2159+
* <tr>
2160+
* <td>2</td><td>A,B,C</td><td>A,B,C,D,X</td><td>YES</td>
2161+
* </tr>
2162+
* <tr>
2163+
* <td>3</td><td>A,B,C</td><td>X,Y,Z</td><td>NO</td>
2164+
* </tr>
2165+
* <tr>
2166+
* <td>4</td><td>null</td><td>A,S,D</td><td>YES</td>
2167+
* </tr>
2168+
* <tr>
2169+
* <td>5</td><td>A</td><td>null</td><td>NO</td>
2170+
* </tr>
2171+
* <tr>
2172+
* <td>6</td><td>null</td><td>null</td><td>YES</td>
2173+
* </tr>
2174+
* </body>
2175+
* </table>
2176+
*/
2177+
protected boolean doesTargetStorageSupportNewDiskOffering(StoragePool destPool, DiskOfferingVO newDiskOffering) {
2178+
String newDiskOfferingTags = newDiskOffering.getTags();
2179+
if (org.apache.commons.lang.StringUtils.isBlank(newDiskOfferingTags)) {
2180+
return true;
2181+
}
2182+
String storageTags = getStoragePoolTags(destPool);
2183+
boolean isStorageTagsBlank = org.apache.commons.lang.StringUtils.isBlank(storageTags);
2184+
if (isStorageTagsBlank) {
2185+
return false;
2186+
}
2187+
String[] storageTagsAsStringArray = org.apache.commons.lang.StringUtils.split(storageTags, ",");
2188+
String[] newDiskOfferingTagsAsStringArray = org.apache.commons.lang.StringUtils.split(newDiskOfferingTags, ",");
2189+
2190+
return CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray), Arrays.asList(storageTagsAsStringArray));
2191+
}
2192+
21452193
/**
21462194
* Retrieves the storage pool tags as a {@link String}. If the storage pool does not have tags we return a null value.
21472195
*/

server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,9 +580,86 @@ public void validateConditionsToReplaceDiskOfferingOfVolumeTestEverythingWorking
580580
inOrder.verify(storagePoolMock).isLocal();
581581
inOrder.verify(newDiskOfferingMock, times(0)).isShared();
582582
inOrder.verify(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
583-
inOrder.verify(newDiskOfferingMock).getTags();
584583

585584
inOrder.verify(volumeVOMock).getSize();
586585
inOrder.verify(newDiskOfferingMock).getDiskSize();
587586
}
587+
588+
@Test
589+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingMoreTagsThanStorageTags() {
590+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
591+
Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
592+
593+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
594+
Mockito.doReturn("A").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
595+
596+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
597+
598+
Assert.assertFalse(result);
599+
}
600+
601+
@Test
602+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingTagsIsSubSetOfStorageTags() {
603+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
604+
Mockito.doReturn("A,B,C").when(diskOfferingVoMock).getTags();
605+
606+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
607+
Mockito.doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
608+
609+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
610+
611+
Assert.assertTrue(result);
612+
}
613+
614+
@Test
615+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingTagsEmptyAndStorageTagsNotEmpty() {
616+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
617+
Mockito.doReturn("").when(diskOfferingVoMock).getTags();
618+
619+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
620+
Mockito.doReturn("A,B,C,D,X,Y").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
621+
622+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
623+
624+
Assert.assertTrue(result);
625+
}
626+
627+
@Test
628+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingTagsNotEmptyAndStorageTagsEmpty() {
629+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
630+
Mockito.doReturn("A").when(diskOfferingVoMock).getTags();
631+
632+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
633+
Mockito.doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
634+
635+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
636+
637+
Assert.assertFalse(result);
638+
}
639+
640+
@Test
641+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingTagsEmptyAndStorageTagsEmpty() {
642+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
643+
Mockito.doReturn("").when(diskOfferingVoMock).getTags();
644+
645+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
646+
Mockito.doReturn("").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
647+
648+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
649+
650+
Assert.assertTrue(result);
651+
}
652+
653+
@Test
654+
public void doesTargetStorageSupportNewDiskOfferingTestDiskOfferingTagsDifferentFromdStorageTags() {
655+
DiskOfferingVO diskOfferingVoMock = Mockito.mock(DiskOfferingVO.class);
656+
Mockito.doReturn("A,B").when(diskOfferingVoMock).getTags();
657+
658+
StoragePool storagePoolMock = Mockito.mock(StoragePool.class);
659+
Mockito.doReturn("C,D").when(volumeApiServiceImpl).getStoragePoolTags(storagePoolMock);
660+
661+
boolean result = volumeApiServiceImpl.doesTargetStorageSupportNewDiskOffering(storagePoolMock, diskOfferingVoMock);
662+
663+
Assert.assertFalse(result);
664+
}
588665
}

0 commit comments

Comments
 (0)