Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import static com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER;
import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.lang.reflect.Field;
import java.net.URI;
Expand Down Expand Up @@ -319,6 +320,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
public static final String VM_WORK_JOB_HANDLER = VirtualMachineManagerImpl.class.getSimpleName();

private static final String VM_SYNC_ALERT_SUBJECT = "VM state sync alert";
private static final String ONTAP_SVM_NAME_DETAIL = "svmName";
private static final String ONTAP_SVM_UUID_DETAIL = "svmUUID";
private static final String ONTAP_STORAGE_IP_DETAIL = "storageIP";
private static final String ONTAP_PROTOCOL_DETAIL = "protocol";

@Inject
private UserVmManager _userVmMgr;
Expand Down Expand Up @@ -3512,6 +3517,9 @@ protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StorageP
if (currentPool.getId() == targetPool.getId()) {
return;
}
if (isSupportedOntapLiveStorageMigration(currentPool, targetPool)) {
return;
}

Map<String, String> details = _storagePoolDao.getDetails(currentPool.getId());
if (details != null && Boolean.parseBoolean(details.get(Storage.Capability.ALLOW_MIGRATE_OTHER_POOLS.toString()))) {
Expand All @@ -3521,6 +3529,38 @@ protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StorageP
volume.getUuid(), currentPool.getUuid(), targetPool.getUuid()));
}

protected boolean isSupportedOntapLiveStorageMigration(StoragePoolVO currentPool, StoragePoolVO targetPool) {
if (!DataStoreProvider.ONTAP_PLUGIN_NAME.equals(currentPool.getStorageProviderName())
|| !DataStoreProvider.ONTAP_PLUGIN_NAME.equals(targetPool.getStorageProviderName())) {
return false;
}

Map<String, String> currentDetails = _storagePoolDao.getDetails(currentPool.getId());
Map<String, String> targetDetails = _storagePoolDao.getDetails(targetPool.getId());
if (currentDetails == null || targetDetails == null) {
return false;
}

String currentSvmName = currentDetails.get(ONTAP_SVM_NAME_DETAIL);
String targetSvmName = targetDetails.get(ONTAP_SVM_NAME_DETAIL);
String currentSvmUuid = currentDetails.get(ONTAP_SVM_UUID_DETAIL);
String targetSvmUuid = targetDetails.get(ONTAP_SVM_UUID_DETAIL);
String currentStorageIp = currentDetails.get(ONTAP_STORAGE_IP_DETAIL);
String targetStorageIp = targetDetails.get(ONTAP_STORAGE_IP_DETAIL);
String currentProtocol = currentDetails.get(ONTAP_PROTOCOL_DETAIL);
String targetProtocol = targetDetails.get(ONTAP_PROTOCOL_DETAIL);
boolean isSameSvm = isNotBlank(currentSvmUuid) || isNotBlank(targetSvmUuid)
? isNotBlank(currentSvmUuid) && currentSvmUuid.equals(targetSvmUuid)
: isNotBlank(currentSvmName) && currentSvmName.equals(targetSvmName);
return isNotBlank(currentStorageIp)
&& currentStorageIp.equals(targetStorageIp)
&& isSameSvm
&& isNotBlank(currentProtocol)
&& currentProtocol.equalsIgnoreCase(targetProtocol)
&& currentPool.getPoolType() != null
&& currentPool.getPoolType() == targetPool.getPoolType();
}

/**
* For each one of the volumes we will map it to a storage pool that is available via the target host.
* An exception is thrown if we cannot find a storage pool that is accessible in the target host to migrate the volume to.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,60 @@ public void allowVolumeMigrationsForPowerFlexStorage() {
verify(storagePoolVoMock, Mockito.times(0)).getId();
}

@Test
public void allowVolumeMigrationBetweenOntapPoolsOnSameSvmAndProtocol() {
StoragePoolVO targetPool = Mockito.mock(StoragePoolVO.class);
Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
Mockito.doReturn(Storage.StoragePoolType.OntapiSCSI).when(storagePoolVoMock).getPoolType();
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(storagePoolVoMock).getStorageProviderName();
Mockito.doReturn(12L).when(targetPool).getId();
Mockito.doReturn(Storage.StoragePoolType.OntapiSCSI).when(targetPool).getPoolType();
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(targetPool).getStorageProviderName();
Mockito.doReturn(Map.of("storageIP", "10.0.0.1", "svmName", "svm1", "protocol", "ISCSI"))
.when(storagePoolDaoMock).getDetails(storagePoolVoMockId);
Mockito.doReturn(Map.of("storageIP", "10.0.0.1", "svmName", "svm1", "protocol", "ISCSI"))
.when(storagePoolDaoMock).getDetails(12L);

virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, targetPool);
}

@Test
public void rejectVolumeMigrationBetweenOntapPoolsUsingDifferentProtocols() {
StoragePoolVO targetPool = Mockito.mock(StoragePoolVO.class);
Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
Mockito.doReturn(Storage.StoragePoolType.OntapiSCSI).when(storagePoolVoMock).getPoolType();
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(storagePoolVoMock).getStorageProviderName();
Mockito.doReturn(12L).when(targetPool).getId();
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(targetPool).getStorageProviderName();
Mockito.doReturn(Map.of("storageIP", "10.0.0.1", "svmName", "svm1", "protocol", "ISCSI"))
.when(storagePoolDaoMock).getDetails(storagePoolVoMockId);
Mockito.doReturn(Map.of("storageIP", "10.0.0.1", "svmName", "svm1", "protocol", "NFS3"))
.when(storagePoolDaoMock).getDetails(12L);

assertThrows(CloudRuntimeException.class,
() -> virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, targetPool));
}

@Test
public void rejectOntapPoolsWithSameSvmNameOnDifferentStorageSystems() {
StoragePoolVO targetPool = Mockito.mock(StoragePoolVO.class);
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(storagePoolVoMock).getStorageProviderName();
Mockito.doReturn(12L).when(targetPool).getId();
Mockito.doReturn(org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider.ONTAP_PLUGIN_NAME)
.when(targetPool).getStorageProviderName();
Mockito.doReturn(Map.of("storageIP", "10.0.0.1", "svmName", "svm1", "protocol", "NFS3"))
.when(storagePoolDaoMock).getDetails(storagePoolVoMockId);
Mockito.doReturn(Map.of("storageIP", "10.0.0.2", "svmName", "svm1", "protocol", "NFS3"))
.when(storagePoolDaoMock).getDetails(12L);

assertFalse(virtualMachineManagerImpl.isSupportedOntapLiveStorageMigration(storagePoolVoMock, targetPool));
}

@Test
public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolEqualsTargetPool() {
Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, we should have our own OntapDataMotionStratgey to handle ontap related copy use cases. All the changes here should be part of that class.

import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
Expand Down Expand Up @@ -233,13 +234,24 @@ public StrategyPriority canHandle(DataObject srcData, DataObject destData) {

if (srcData instanceof VolumeInfo && destData instanceof VolumeInfo) {
VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
VolumeInfo destVolumeInfo = (VolumeInfo)destData;

// OntapDataMotionStrategy also returns HIGHEST for these and a tie keeps the first strategy.
if (srcVolumeInfo.getState() == Volume.State.Migrating
&& srcVolumeInfo.getDataStore().getRole() == DataStoreRole.Primary
&& destVolumeInfo.getDataStore().getRole() == DataStoreRole.Primary) {
StoragePoolVO srcPool = _storagePoolDao.findById(srcVolumeInfo.getDataStore().getId());
StoragePoolVO destPool = _storagePoolDao.findById(destVolumeInfo.getDataStore().getId());
if ((srcPool != null && DataStoreProvider.ONTAP_PLUGIN_NAME.equals(srcPool.getStorageProviderName()))
|| (destPool != null && DataStoreProvider.ONTAP_PLUGIN_NAME.equals(destPool.getStorageProviderName()))) {
return StrategyPriority.CANT_HANDLE;
}
}

if (isVolumeOnManagedStorage(srcVolumeInfo)) {
return StrategyPriority.HIGHEST;
}

VolumeInfo destVolumeInfo = (VolumeInfo)destData;

if (isVolumeOnManagedStorage(destVolumeInfo)) {
return StrategyPriority.HIGHEST;
}
Expand Down Expand Up @@ -320,6 +332,20 @@ public final StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Ho
* Handles migrating volumes on managed Storage.
*/
protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
// OntapDataMotionStrategy also returns HIGHEST for these and a tie keeps the first strategy.
for (VolumeInfo volumeInfo : volumeMap.keySet()) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
if (storagePoolVO != null && DataStoreProvider.ONTAP_PLUGIN_NAME.equals(storagePoolVO.getStorageProviderName())) {
return StrategyPriority.CANT_HANDLE;
}
}
for (DataStore dataStore : volumeMap.values()) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
if (storagePoolVO != null && DataStoreProvider.ONTAP_PLUGIN_NAME.equals(storagePoolVO.getStorageProviderName())) {
return StrategyPriority.CANT_HANDLE;
}
}

Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();

for (VolumeInfo volumeInfo : volumeInfoSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
Expand Down Expand Up @@ -133,6 +134,48 @@ public void internalCanHandleTestNoStoragePoolsIsManaged() {
configureAndTestInternalCanHandle(false, false, StrategyPriority.CANT_HANDLE);
}

@Test
public void canHandleDeclinesMigratingVolumeOnOntapPrimary() {
PrimaryDataStore srcStore = mock(PrimaryDataStoreImpl.class);
PrimaryDataStore destStore = mock(PrimaryDataStoreImpl.class);
VolumeObject srcVolume = mock(VolumeObject.class);
VolumeObject destVolume = mock(VolumeObject.class);
doReturn(Volume.State.Migrating).when(srcVolume).getState();
doReturn(srcStore).when(srcVolume).getDataStore();
doReturn(destStore).when(destVolume).getDataStore();
doReturn(DataStoreRole.Primary).when(srcStore).getRole();
doReturn(DataStoreRole.Primary).when(destStore).getRole();
doReturn(1L).when(srcStore).getId();
doReturn(2L).when(destStore).getId();
StoragePoolVO srcPool = Mockito.spy(new StoragePoolVO());
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
lenient().doReturn(true).when(srcPool).isManaged();
doReturn(DataStoreProvider.ONTAP_PLUGIN_NAME).when(destPool).getStorageProviderName();
doReturn(srcPool).when(primaryDataStoreDao).findById(1L);
doReturn(destPool).when(primaryDataStoreDao).findById(2L);

Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategy.canHandle(srcVolume, destVolume));
}

@Test
public void internalCanHandleDeclinesMapWithOntapPool() {
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.doReturn(1L).when(ds).getId();
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
StoragePoolVO srcPool = Mockito.spy(new StoragePoolVO());
lenient().doReturn(true).when(srcPool).isManaged();
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
Mockito.doReturn(DataStoreProvider.ONTAP_PLUGIN_NAME).when(destPool).getStorageProviderName();
Mockito.doReturn(srcPool).when(primaryDataStoreDao).findById(0L);
Mockito.doReturn(destPool).when(primaryDataStoreDao).findById(1L);

Assert.assertEquals(StrategyPriority.CANT_HANDLE,
strategy.internalCanHandle(volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid")));
}

private void configureAndTestInternalCanHandle(boolean sPool0IsManaged, boolean sPool1IsManaged, StrategyPriority expectedStrategyPriority) {
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn(0l).when(volumeInfo).getPoolId();
Expand Down Expand Up @@ -555,6 +598,7 @@ public void testInternalCanHandle_ClvmSourceManagedDestination() {
Mockito.doReturn(0L).when(volumeInfo).getPoolId();

DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.lenient().doReturn(1L).when(ds).getId();

Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
Expand All @@ -580,6 +624,7 @@ public void testInternalCanHandle_ClvmNgSourceManagedDestination() {
Mockito.doReturn(0L).when(volumeInfo).getPoolId();

DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.lenient().doReturn(1L).when(ds).getId();

Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
Expand All @@ -605,6 +650,7 @@ public void testInternalCanHandle_ClvmToClvmNg() {
Mockito.doReturn(0L).when(volumeInfo).getPoolId();

DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.lenient().doReturn(1L).when(ds).getId();

Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
Expand Down Expand Up @@ -633,6 +679,7 @@ public void testInternalCanHandle_ClvmNgToClvm() {
Mockito.doReturn(0L).when(volumeInfo).getPoolId();

DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.lenient().doReturn(1L).when(ds).getId();

Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ Use VIR_DOMAIN_XML_SECURE (value = 1) prior to v1.0.0.
}

deleteOrDisconnectDisksOnSourcePool(libvirtComputingResource, migrateDiskInfoList, disks);
disconnectDestinationOntapIscsiDisksOnSourceHost(libvirtComputingResource, mapMigrateStorage);
libvirtComputingResource.cleanOldSecretsByDiskDef(conn, disks);
}

Expand Down Expand Up @@ -704,6 +705,28 @@ protected void deleteOrDisconnectDisksOnSourcePool(final LibvirtComputingResourc
}
}

protected void disconnectDestinationOntapIscsiDisksOnSourceHost(final LibvirtComputingResource libvirtComputingResource,
final Map<String, MigrateDiskInfo> migrateStorage) {
if (MapUtils.isEmpty(migrateStorage)) {
return;
}

Set<String> disconnectedPaths = new HashSet<>();
for (MigrateDiskInfo migrateDiskInfo : migrateStorage.values()) {
String destinationPath = migrateDiskInfo.getSourceText();
if (migrateDiskInfo.getDestPoolType() != Storage.StoragePoolType.OntapiSCSI
|| StringUtils.isBlank(destinationPath) || !disconnectedPaths.add(destinationPath)) {
continue;
}

DiskDef destinationDisk = new DiskDef();
destinationDisk.setDiskPath(destinationPath);
if (!libvirtComputingResource.cleanupDisk(destinationDisk)) {
logger.warn("Failed to disconnect destination ONTAP iSCSI disk [{}] from the source host after migration", destinationPath);
}
}
}

/**
* Deletes the local volume from the storage pool.
*/
Expand Down
Loading
Loading