/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.vplex.api;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.vplex.api.clientdata.VolumeInfo;
import com.sun.jersey.api.client.ClientResponse;
/**
* VPlexApiMigrationManager provides methods creating and managing data
* migrations.
*/
public class VPlexApiMigrationManager {
// Logger reference.
private static Logger s_logger = LoggerFactory
.getLogger(VPlexApiMigrationManager.class);
// A reference to the API client.
private VPlexApiClient _vplexApiClient;
/**
* Package protected constructor.
*
* @param client A reference to the API client.
*/
VPlexApiMigrationManager(VPlexApiClient client) {
_vplexApiClient = client;
}
/**
* For the virtual volume with the passed name, migrates the data on the
* backend volume(s) to the backend volumes identified by the passed native
* volume information.
*
* @param migrationName The name for this migration.
* @param virtualVolumeName The name of the virtual volume whose data is to
* be migrated.
* @param nativeVolumeInfoList The native information for the volume(s) to
* which the data should be migrated.
* @param isRemote true if the the migration is across clusters, else false.
* @param useDeviceMigration true if device migration is required.
* @param discoveryRequired true if the passed native volumes are newly
* exported and need to be discovered by the VPlex.
* @param startNow true to start the migration now, else migration is
* created in a paused state.
* @param transferSize The migration transfer size
* @return A reference to the migration(s) started to migrate the virtual
* volume.
*
* @throws VPlexApiException When an error occurs creating and/or
* initializing the migration.
*/
List<VPlexMigrationInfo> migrateVirtualVolume(String migrationName,
String virtualVolumeName, List<VolumeInfo> nativeVolumeInfoList,
boolean isRemote, boolean useDeviceMigration, boolean discoveryRequired,
boolean startNow, String transferSize) throws VPlexApiException {
s_logger.info("Migrating virtual volume {}", virtualVolumeName);
// Find the storage volumes corresponding to the passed native
// volume information, discovering them if required. If a requested
// volume cannot be found an exception is thrown. Do this first
// as it ensures we have the latest cluster info for finding the
// virtual volume.
VPlexApiVirtualVolumeManager virtualVolumeMgr = _vplexApiClient
.getVirtualVolumeManager();
List<VPlexClusterInfo> clusterInfoList = new ArrayList<VPlexClusterInfo>();
Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap = virtualVolumeMgr
.findStorageVolumes(nativeVolumeInfoList, discoveryRequired, clusterInfoList, null);
s_logger.info("Found storage volumes");
// Now find the virtual volume.
VPlexVirtualVolumeInfo virtualVolumeInfo = null;
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
for (VPlexClusterInfo clusterInfo : clusterInfoList) {
virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(),
virtualVolumeName, false);
if (virtualVolumeInfo != null) {
// Update the virtual volume info with the attribute information.
discoveryMgr.updateVirtualVolumeInfo(clusterInfo.getName(), virtualVolumeInfo);
break;
}
}
if (virtualVolumeInfo == null) {
throw VPlexApiException.exceptions.cantFindRequestedVolume(virtualVolumeName);
}
s_logger.info("Found virtual volume");
// For a distributed virtual volume we migrate one or both extents
// used by the virtual volume. For a local virtual volume we have
// the ability to either migrate the local device or extent. We
// choose to migrate the the local extent used by the virtual volume.
// Originally we used device migration, but the VPlex migration name
// length restriction for a device migration is only 22 characters
// and we could not give it the descriptive name we desired, so
// we switch to extent migration which supports a 63 character name.
// We only use device migration if specifically requested.
if (VPlexVirtualVolumeInfo.Locality.distributed.name().equals(virtualVolumeInfo.getLocality())) {
s_logger.info("Virtual volume is on distributed device {}", virtualVolumeInfo.getSupportingDevice());
return migrateDistributedVirtualVolume(migrationName, virtualVolumeInfo,
storageVolumeInfoMap, startNow, transferSize);
} else {
// The virtual volume was built on a local device.
s_logger.info("Virtual volume is on local device {}", virtualVolumeInfo.getSupportingDevice());
return Arrays.asList(migrateLocalVirtualVolume(migrationName, virtualVolumeInfo,
storageVolumeInfoMap, startNow, isRemote, useDeviceMigration, transferSize));
}
}
/**
* Pauses the executing migrations with the passed names.
*
* @param migrationNamse The names of the migrations.
*
* @throws VPlexApiException When an error occurs pausing the migrations.
*/
void pauseMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Pausing migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = discoveryMgr
.findMigrations(migrationNames);
// Verify that the migrations are in a state in which they can be paused.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
String migrationStatus = migrationInfo.getStatus();
if (VPlexApiConstants.MIGRATION_PAUSED.equals(migrationStatus)) {
// Skip those already paused.
continue;
} else if (!VPlexApiConstants.MIGRATION_INPROGRESS.equals(migrationInfo.getStatus())) {
// TBD maybe queued as well? Not sure if you can pause a queued migration?
throw VPlexApiException.exceptions
.cantPauseMigrationNotInProgress(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// If the migration paths argument is empty, then all the requested
// migrations must already be paused, so just return.
String migrationPaths = migrationArgBuilder.toString();
if (migrationPaths.length() == 0) {
s_logger.info("All requested migrations are already paused");
return;
}
// Pause the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_PAUSE_MIGRATIONS);
s_logger.info("Pause migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Pausing migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationArgBuilder.toString());
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false);
s_logger.info("Pause migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Pause migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Pause migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.pauseMigrationsFailureStatus(
migrationNames, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully paused migrations {}", migrationNames);
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedPauseMigrations(migrationNames, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Resume the paused migrations with with the passed names.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs resuming the migrations.
*/
void resumeMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Resuming migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = discoveryMgr
.findMigrations(migrationNames);
// Verify that the migrations are in a state in which they can be resumed.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
String migrationStatus = migrationInfo.getStatus();
if (VPlexApiConstants.MIGRATION_INPROGRESS.equals(migrationStatus)) {
// Skip those in progress.
continue;
} else if (!VPlexApiConstants.MIGRATION_PAUSED.equals(migrationStatus)) {
throw VPlexApiException.exceptions
.cantResumeMigrationNotPaused(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// If the migration paths argument is empty, then all the requested
// migrations must already be in progress, so just return.
String migrationPaths = migrationArgBuilder.toString();
if (migrationPaths.length() == 0) {
s_logger.info("All requested migrations are already in progress");
return;
}
// Resume the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_RESUME_MIGRATIONS);
s_logger.info("Resume migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Resuming migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationArgBuilder.toString());
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false);
s_logger.info("Resume migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Resume migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Resume migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.resumeMigrationsFailureStatus(
migrationNames, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully resume migrations {}", migrationNames);
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedResumeMigrations(migrationNames, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Commits the completed migrations with the passed names and tears down the
* old devices and unclaims the storage volumes.
*
* @param virtualVolumeName The name of the virtual volume prior to the commit.
* @param migrationNames The names of the migrations.
* @param cleanup true to automatically cleanup after commit.
* @param remove true to automatically remove the migration record.
* @param rename true to rename the volumes after committing the migration.
*
* @return A list of VPlexMigrationInfo instances for the committed
* migrations each of which contains a reference to the
* VPlexVirtualVolumeInfo associated with that migration which can
* be used to update the virtual volume native id, which can change
* as a result of the migration.
*
* @throws VPlexApiException When an error occurs committing the migrations.
*/
List<VPlexMigrationInfo> commitMigrations(String virtualVolumeName, List<String> migrationNames,
boolean cleanup, boolean remove, boolean rename) throws VPlexApiException {
s_logger.info("Committing migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = discoveryMgr
.findMigrations(migrationNames);
// Verify that the migrations have completed successfully and can be
// committed.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
if (!VPlexApiConstants.MIGRATION_COMPLETE.equals(migrationInfo.getStatus())) {
throw VPlexApiException.exceptions
.cantCommitedMigrationNotCompletedSuccessfully(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// Commit the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_COMMIT_MIGRATIONS);
s_logger.info("Commit migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Committing migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationArgBuilder.toString());
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true);
s_logger.info("Commit migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Commit migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Commit migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response, VPlexApiClient.getMaxMigrationAsyncPollingRetries());
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.commitMigrationsFailureStatus(
migrationNames, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully committed migrations {}", migrationNames);
// If specified, first try and cleanup the old resources.
if (cleanup) {
try {
cleanCommittedMigrations(migrationArgBuilder.toString());
} catch (Exception e) {
s_logger.error("Error cleaning migrations after successful commit: {}", e.getMessage(), e);
}
}
// If specified, now try and remove the migration records.
if (remove) {
try {
removeCommittedOrCanceledMigrations(migrationArgBuilder.toString());
} catch (Exception e) {
s_logger.error("Error removing migration records after successful commit: {}", e.getMessage(), e);
}
}
// Update virtual volume info for virtual volume associated with
// each committed migration.
try {
updateVirtualVolumeInfoAfterCommit(virtualVolumeName, migrationInfoList, rename);
} catch (Exception e) {
s_logger.error("Error updating virtual volume after successful commit: {}", e.getMessage(), e);
}
return migrationInfoList;
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedCommitMigrations(migrationNames, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Cleans the committed migrations with the passed names tearing down the
* old devices and unclaiming the storage volumes.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs cleaning the migrations.
*/
void cleanMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Cleaning migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = discoveryMgr
.findMigrations(migrationNames);
// Verify that all migrations are committed.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
if (!VPlexApiConstants.MIGRATION_COMMITTED.equals(migrationInfo.getStatus())) {
throw VPlexApiException.exceptions
.cantCleanMigrationNotCommitted(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// If the migrations are committed, do the cleanup.
cleanCommittedMigrations(migrationArgBuilder.toString());
}
/**
* Cancels the uncommitted, executing, paused, or queued migrations with the
* passed names and tears down the new devices that were created as targets
* for the migration and unclaims the storage volumes.
*
* @param migrationNames The names of the migrations.
* @param cleanup true to automatically cleanup after the cancellation.
* @param remove true to automatically remove the migration record.
*
* @throws VPlexApiException When an error occurs canceling the migrations.
*/
void cancelMigrations(List<String> migrationNames, boolean cleanup, boolean remove)
throws VPlexApiException {
s_logger.info("Canceling migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = discoveryMgr
.findMigrations(migrationNames);
// Verify that the migrations are in a state in which they can be
// canceled.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
String migrationStatus = migrationInfo.getStatus();
if ((VPlexApiConstants.MIGRATION_CANCELED.equals(migrationStatus))
|| (VPlexApiConstants.MIGRATION_PART_CANCELED.equals(migrationStatus))) {
// Skip those already canceled or in the process of being
// canceled.
continue;
} else if ((!VPlexApiConstants.MIGRATION_PAUSED.equals(migrationStatus))
&& (!VPlexApiConstants.MIGRATION_INPROGRESS.equals(migrationStatus))
&& (!VPlexApiConstants.MIGRATION_COMPLETE.equals(migrationStatus))
&& (!VPlexApiConstants.MIGRATION_ERROR.equals(migrationStatus))
&& (!VPlexApiConstants.MIGRATION_QUEUED.equals(migrationStatus))) {
throw VPlexApiException.exceptions
.cantCancelMigrationInvalidState(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// If the migration paths argument is empty, then all the requested
// migrations must already be in progress, so just return.
String migrationPaths = migrationArgBuilder.toString();
if (migrationPaths.length() == 0) {
s_logger.info("All requested migrations are already canceled or " +
"in the process of being canceled.");
return;
}
// Cancel the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_CANCEL_MIGRATIONS);
s_logger.info("Cancel migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Canceling migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationArgBuilder.toString());
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true);
s_logger.info("Cancel migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Cancel migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Cancel migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.cancelMigrationsFailureStatus(
migrationNames, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully canceled migrations {}", migrationNames);
// If specified, cleanup the target device/extents depending on
// whether this was a device or extent migration.
if (cleanup) {
VPlexApiVirtualVolumeManager virtualVolumeMgr = _vplexApiClient
.getVirtualVolumeManager();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
try {
String targetName = migrationInfo.getTarget();
if (migrationInfo.getIsDeviceMigration()) {
virtualVolumeMgr.deleteLocalDevice(targetName);
} else {
virtualVolumeMgr.deleteExtent(targetName);
}
} catch (VPlexApiException vae) {
s_logger.error(
"Error cleaning target for canceled migration {}:{}",
migrationInfo.getName(), vae.getMessage());
}
}
}
// If specified, try and remove the migration records.
if (remove) {
try {
removeCommittedOrCanceledMigrations(migrationArgBuilder.toString());
} catch (VPlexApiException vae) {
s_logger.error(
"Error removing migration records after successful cancel: {}",
vae.getMessage(), vae);
}
}
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedCancelMigrations(migrationNames, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Removes the records for the committed or canceled migrations with the passed names.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs removing the migration
* records.
*/
void removeMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Removing records for migrations {}", migrationNames);
// Find the requested migrations. An exception will be thrown if
// they are not all found.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexMigrationInfo> migrationInfoList = null;
try {
migrationInfoList = discoveryMgr.findMigrations(migrationNames);
} catch (VPlexApiException vae) {
// migrations might have deleted from VPLEX, then we just need to delete them from ViPR DB.
s_logger.info("No migration found in the VPLEX", vae);
return;
}
// Verify that the migrations are in a state in which they can be removed.
StringBuilder migrationArgBuilder = new StringBuilder();
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
String migrationStatus = migrationInfo.getStatus();
if ((!VPlexApiConstants.MIGRATION_COMMITTED.equals(migrationStatus)) &&
(!VPlexApiConstants.MIGRATION_CANCELED.equals(migrationStatus)) &&
(!VPlexApiConstants.MIGRATION_ERROR.equals(migrationStatus))) {
throw VPlexApiException.exceptions
.cantRemoveMigrationInvalidState(migrationInfo.getName());
}
if (migrationArgBuilder.length() != 0) {
migrationArgBuilder.append(",");
}
migrationArgBuilder.append(migrationInfo.getPath());
}
// If the migrations are committed or canceled, do the removal.
removeCommittedOrCanceledMigrations(migrationArgBuilder.toString());
}
/**
* Migrate one or both extents used by the passed virtual volume.
*
* @param migrationName The name for the migration.
* @param virtualVolumeInfo A reference to the virtual volume info.
* @param storageVolumeInfoMap A map specifying the volume(s) to which the
* data is to be migrated.
* @param startNow true to start the migration now, else the migration is
* created in a paused state.
* @param transferSize migration transfer size
*
* @return A list of migration infos.
*
* @throws VPlexApiException When an error occurs migrating the virtual
* volume.
*/
private List<VPlexMigrationInfo> migrateDistributedVirtualVolume(
String migrationName, VPlexVirtualVolumeInfo virtualVolumeInfo,
Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap, boolean startNow,
String transferSize) throws VPlexApiException {
// Get the discovery manager.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
// The virtual volume is built on a distributed device. We
// can find the component structure of the distributed
// device to find the extents.
List<VPlexExtentInfo> srcExtentInfoList = new ArrayList<VPlexExtentInfo>();
String distributedDeviceName = virtualVolumeInfo.getSupportingDevice();
VPlexDistributedDeviceInfo ddInfo = discoveryMgr.findDistributedDevice(distributedDeviceName);
discoveryMgr.setSupportingComponentsForDistributedDevice(ddInfo);
List<VPlexDeviceInfo> localDeviceInfoList = ddInfo.getLocalDeviceInfo();
for (VPlexDeviceInfo localDeviceInfo : localDeviceInfoList) {
String localDeviceName = localDeviceInfo.getName();
s_logger.info("Local device: {}", localDeviceName);
List<VPlexExtentInfo> localDeviceExtentInfoList = localDeviceInfo.getExtentInfo();
int extentCount = localDeviceExtentInfoList.size();
if (extentCount != 1) {
// If the extent count is not 1, then it is likely that a
// distributed volume is being migrated. When a distributed
// volume is migrated two migration instances are created and
// started in parallel by calling this function two separate
// times. When the VPLEX starts a migration, the component
// structure of the local device whose extent is being migrated
// is temporarily modified by the VPLEX such that the local device
// is actually composed of another local device (rather than a
// single extent) which itself is composed of two extents
// representing the src and target extents for the migration.
// We can just ignore this local device as the migration has
// already been created and started for extent associated with
// that local device.
s_logger.info("Extent count for local device {} is {}", localDeviceName, extentCount);
continue;
}
srcExtentInfoList.add(discoveryMgr.findExtent(localDeviceExtentInfoList.get(0).getName()));
}
// Now we need to build extent(s) for the storage volume(s) to
// which the data is to be migrated. This is done in the same way as
// creating a virtual volume. First claim the storage volume(s).
// Note there could only be one volume if only one side of the
// distributed virtual volume is being migrated.
VPlexApiVirtualVolumeManager virtualVolumeMgr = _vplexApiClient
.getVirtualVolumeManager();
virtualVolumeMgr.claimStorageVolumes(storageVolumeInfoMap, false);
s_logger.info("Claimed storage volumes");
// Try and build up the VPLEX extents from the claimed storage
// volumes to which the data will be migrated. If we get an error,
// clean up the VPLEX artifacts and unclaim the storage volumes.
List<VPlexMigrationInfo> migrationInfoList = new ArrayList<VPlexMigrationInfo>();
try {
// Create extent(s).
List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(
storageVolumeInfoMap.values());
virtualVolumeMgr.createExtents(storageVolumeInfoList);
s_logger.info("Created extents on storage volumes");
// Find the extent(s) just created.
List<VPlexExtentInfo> tgtExtentInfoList = discoveryMgr
.findExtents(storageVolumeInfoList);
s_logger.info("Found the target extents");
// Map the source extent(s) to the target extent(s)
// in the same cluster.
Map<VPlexExtentInfo, VPlexExtentInfo> extentMigrationMap = new HashMap<VPlexExtentInfo, VPlexExtentInfo>();
for (VPlexExtentInfo tgtExtentInfo : tgtExtentInfoList) {
String clusterId = tgtExtentInfo.getClusterId();
for (VPlexExtentInfo srcExtentInfo : srcExtentInfoList) {
if (clusterId.equals(srcExtentInfo.getClusterId())) {
extentMigrationMap.put(tgtExtentInfo, srcExtentInfo);
break;
}
}
}
// Migrate each extent for the virtual volume from the
// source to the target.
int migrationCount = 1;
Iterator<Entry<VPlexExtentInfo, VPlexExtentInfo>> tgtExtentIter = extentMigrationMap.entrySet().iterator();
while (tgtExtentIter.hasNext()) {
Entry<VPlexExtentInfo, VPlexExtentInfo> entry = tgtExtentIter.next();
VPlexExtentInfo tgtExtentInfo = entry.getKey();
VPlexExtentInfo srcExtentInfo = entry.getValue();
StringBuilder migrationNameBuilder = new StringBuilder(migrationName);
if (extentMigrationMap.size() > 1) {
migrationNameBuilder.append("_");
migrationNameBuilder.append(String.valueOf(migrationCount++));
}
VPlexMigrationInfo migrationInfo = migrateResource(
migrationNameBuilder.toString(), srcExtentInfo, tgtExtentInfo, false,
startNow, transferSize);
migrationInfo.setVirtualVolumeInfo(virtualVolumeInfo);
migrationInfoList.add(migrationInfo);
}
return migrationInfoList;
} catch (Exception e) {
// An error occurred. Clean up the VPLEX artifacts created for
// the migration and unclaim the storage volumes.
s_logger.info("Exception occurred migrating distributed volume, attempting to cleanup VPLEX artifacts");
try {
// Cancel any migrations that may have been successfully created.
if (!migrationInfoList.isEmpty()) {
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
cancelMigrations(Collections.singletonList(migrationInfo.getName()), true, true);
}
}
// This will look for any artifacts, starting with a virtual
// volume, that use the passed native volume info and destroy
// them and then unclaim the volume.
List<VolumeInfo> nativeVolumeInfoList = new ArrayList<VolumeInfo>();
nativeVolumeInfoList.addAll(storageVolumeInfoMap.keySet());
virtualVolumeMgr.deleteVirtualVolume(nativeVolumeInfoList);
} catch (Exception ex) {
s_logger.error("Failed attempting to cleanup VPLEX after failed attempt " +
"to migrate distributed virtual volume {}", virtualVolumeInfo.getPath(), ex);
}
throw e;
}
}
/**
* Migrate the local device used by the passed virtual volume.
*
* @param migrationName The name for the migration.
* @param virtualVolumeInfo A reference to the virtual volume info.
* @param storageVolumeInfoMap A map specifying the volume(s) to which the
* data is to be migrated.
* @param startNow true to start the migration now, else the migration is
* created in a paused state.
* @param isRemote true if the migration is across clusters, else false.
* @param useDeviceMigration true if device migration is required.
* @param transferSize migration transfer size
* @return A list of migration infos.
*
* @throws VPlexApiException When an error occurs migrating the virtual
* volume.
*/
private VPlexMigrationInfo migrateLocalVirtualVolume(String migrationName,
VPlexVirtualVolumeInfo virtualVolumeInfo,
Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap, boolean startNow,
boolean isRemote, boolean useDeviceMigration, String transferSize) throws VPlexApiException {
// For remote migrations of local virtual volumes we must use
// device migration. Otherwise, we can use either. We choose to
// use extent migration when device migration is not specifically
// requested simply because the length of the migration name is
// much less restrictive and allows Bourne to uniquely identify
// the source and target in the migration name.
if (isRemote || useDeviceMigration) {
return migrateLocalVirtualVolumeDevice(migrationName, virtualVolumeInfo,
storageVolumeInfoMap, startNow, transferSize);
} else {
return migrateLocalVirtualVolumeExtent(migrationName, virtualVolumeInfo,
storageVolumeInfoMap, startNow, transferSize);
}
}
/**
* Migrate the local device used by the passed virtual volume.
*
* @param migrationName The name for the migration.
* @param virtualVolumeInfo A reference to the virtual volume info.
* @param storageVolumeInfoMap A map specifying the volume(s) to which the
* data is to be migrated.
* @param startNow true to start the migration now, else the migration is
* created in a paused state.
* @param transferSize migration transfer size
*
* @return A list of migration infos.
*
* @throws VPlexApiException When an error occurs migrating the virtual
* volume.
*/
private VPlexMigrationInfo migrateLocalVirtualVolumeDevice(String migrationName,
VPlexVirtualVolumeInfo virtualVolumeInfo, Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap, boolean startNow,
String transferSize)
throws VPlexApiException {
// Find the local device.
String localDeviceName = virtualVolumeInfo.getSupportingDevice();
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
VPlexDeviceInfo srcDeviceInfo = discoveryMgr.findLocalDevice(localDeviceName);
// Now we need to build a local device for the storage volume to
// which the data is to be migrated. This is done in the same way as
// creating a virtual volume. First claim the storage volume.
VPlexApiVirtualVolumeManager virtualVolumeMgr = _vplexApiClient
.getVirtualVolumeManager();
virtualVolumeMgr.claimStorageVolumes(storageVolumeInfoMap, false);
s_logger.info("Claimed storage volume");
// Try and build up the VPLEX local device from the claimed storage
// volume and start a migration from the local device of the passed
// virtual volume to this new local device. If we get an error,
// clean up the VPLEX artifacts and unclaim the storage volume.
try {
// Create the extent.
List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(
storageVolumeInfoMap.values());
virtualVolumeMgr.createExtents(storageVolumeInfoList);
s_logger.info("Created extent on storage volume");
// Find the extent just created and create local device on
// the extent.
List<VPlexExtentInfo> extentInfoList = discoveryMgr
.findExtents(storageVolumeInfoList);
virtualVolumeMgr.createLocalDevices(extentInfoList);
s_logger.info("Created local device on extent");
// Find the local device just created.
VPlexDeviceInfo tgtDeviceInfo = discoveryMgr.findLocalDevices(extentInfoList).get(0);
// Migrate the source local device to the target local device.
VPlexMigrationInfo migrationInfo = migrateResource(
migrationName, srcDeviceInfo, tgtDeviceInfo, true, startNow, transferSize);
migrationInfo.setVirtualVolumeInfo(virtualVolumeInfo);
return migrationInfo;
} catch (Exception e) {
// An error occurred. Clean up the VPLEX artifacts created for
// the migration and unclaim the storage volumes.
s_logger.info("Exception occurred migrating local volume device, attempting to cleanup VPLEX artifacts");
try {
// This will look for any artifacts, starting with a virtual
// volume, that use the passed native volume info and destroy
// them and then unclaim the volume.
List<VolumeInfo> nativeVolumeInfoList = new ArrayList<VolumeInfo>();
nativeVolumeInfoList.addAll(storageVolumeInfoMap.keySet());
virtualVolumeMgr.deleteVirtualVolume(nativeVolumeInfoList);
} catch (Exception ex) {
s_logger.error("Failed attempting to cleanup VPLEX after failed attempt " +
"to migrate local virtual volume {}", virtualVolumeInfo.getPath(), ex);
}
throw e;
}
}
/**
* Migrate the local device used by the passed virtual volume.
*
* @param migrationName The name for the migration.
* @param virtualVolumeInfo A reference to the virtual volume info.
* @param storageVolumeInfoMap A map specifying the volume(s) to which the
* data is to be migrated.
* @param startNow true to start the migration now, else the migration is
* created in a paused state.
* @param transferSize migration transfer size
*
* @return A list of migration infos.
*
* @throws VPlexApiException When an error occurs migrating the virtual
* volume.
*/
private VPlexMigrationInfo migrateLocalVirtualVolumeExtent(String migrationName,
VPlexVirtualVolumeInfo virtualVolumeInfo,
Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap, boolean startNow,
String transferSize) throws VPlexApiException {
// Get the extent name from the local device name and find the extent.
// This is the source extent for the migration.
String localDeviceName = virtualVolumeInfo.getSupportingDevice();
s_logger.info("Finding local device with name {}", localDeviceName);
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
VPlexDeviceInfo deviceInfo = discoveryMgr.findLocalDevice(localDeviceName);
if (null == deviceInfo) {
throw VPlexApiException.exceptions.cantFindLocalDevice(localDeviceName);
}
discoveryMgr.setSupportingComponentsForLocalDevice(deviceInfo);
List<VPlexExtentInfo> extentInfoList = deviceInfo.getExtentInfo();
if (null == extentInfoList || extentInfoList.isEmpty()) {
throw VPlexApiException.exceptions.cantFindExtentForLocalDevice(localDeviceName);
}
String extentName = extentInfoList.get(0).getName();
s_logger.info("Finding extent with name {}", extentName);
VPlexExtentInfo srcExtentInfo = discoveryMgr.findExtent(extentName);
s_logger.info("Found source extent");
// Now we need to build the target extent for the storage volume to
// which the data is to be migrated. This is done in the same way as
// creating a virtual volume. First claim the storage volume.
VPlexApiVirtualVolumeManager virtualVolumeMgr = _vplexApiClient
.getVirtualVolumeManager();
virtualVolumeMgr.claimStorageVolumes(storageVolumeInfoMap, false);
s_logger.info("Claimed storage volume");
// Try and build up the VPLEX extent from the claimed storage
// volume and start a migration from the extent of the passed
// virtual volume to this new extent. If we get an error,
// clean up the VPLEX artifacts and unclaim the storage volume.
try {
// Now create the extent.
List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(
storageVolumeInfoMap.values());
virtualVolumeMgr.createExtents(storageVolumeInfoList);
s_logger.info("Created extent on storage volume");
// Find the extent just created.
VPlexExtentInfo tgtExtentInfo = discoveryMgr.findExtents(storageVolumeInfoList)
.get(0);
s_logger.info("Found target extent");
// Migrate the source local device to the target local device.
VPlexMigrationInfo migrationInfo = migrateResource(
migrationName, srcExtentInfo, tgtExtentInfo, false, startNow, transferSize);
migrationInfo.setVirtualVolumeInfo(virtualVolumeInfo);
return migrationInfo;
} catch (Exception e) {
// An error occurred. Clean up the VPLEX artifacts created for
// the migration and unclaim the storage volumes.
s_logger.info("Exception occurred migrating local volume extent, attempting to cleanup VPLEX artifacts");
try {
// This will look for any artifacts, starting with a virtual
// volume, that use the passed native volume info and destroy
// them and then unclaim the volume.
List<VolumeInfo> nativeVolumeInfoList = new ArrayList<VolumeInfo>();
nativeVolumeInfoList.addAll(storageVolumeInfoMap.keySet());
virtualVolumeMgr.deleteVirtualVolume(nativeVolumeInfoList);
} catch (Exception ex) {
s_logger.error("Failed attempting to cleanup VPLEX after failed attempt " +
"to migrate local virtual volume by extent {}", virtualVolumeInfo.getPath(), ex);
}
throw e;
}
}
/**
* Creates a migration to migrate the data on the passed source
* device/extent to the passed target device/extent. The migration is
* created in the paused state if the startNow flag is false.
*
* @param migrationName The name for the migration.
* @param sourceInfo A reference to the source device/extent info.
* @param targetInfo A reference to the target device/extent info.
* @param isDeviceMigration true when migrating a local device, false when
* migrating an extent.
* @param startNow true to start the migration, false to create the
* migration in the paused state.
* @param transferSize migration transfer size
*
* @return A reference to the VPlex migration info.
*
* @throws VPlexApiException If an error occurs creating or starting the
* migration.
*/
private VPlexMigrationInfo migrateResource(String migrationName,
VPlexResourceInfo sourceInfo, VPlexResourceInfo targetInfo,
boolean isDeviceMigration, boolean startNow, String transferSize) throws VPlexApiException {
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_START_MIGRATION);
s_logger.info("Start migration URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Start migration {} from {} to {}", new Object[] {
migrationName, sourceInfo.getName(), targetInfo.getName() });
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_N, migrationName);
argsMap.put(VPlexApiConstants.ARG_DASH_F, sourceInfo.getPath());
argsMap.put(VPlexApiConstants.ARG_DASH_T, targetInfo.getPath());
if (transferSize != null && !transferSize.isEmpty()) {
argsMap.put(VPlexApiConstants.ARG_TRANSFER_SIZE, transferSize);
}
if (!startNow) {
argsMap.put(VPlexApiConstants.ARG_PAUSED, "");
}
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true);
s_logger.info("Start migration POST data is {}", postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Start migration response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Start migration is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.migrationFailureStatus(
sourceInfo.getName(), targetInfo.getName(),
String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully started migration {}", migrationName);
// Find and return the migration.
URI migrationPath = (isDeviceMigration ? VPlexApiConstants.URI_DEVICE_MIGRATIONS
: VPlexApiConstants.URI_EXTENT_MIGRATIONS);
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
VPlexMigrationInfo migrationInfo = discoveryMgr.findMigration(migrationName,
migrationPath, true);
migrationInfo.setIsDeviceMigration(isDeviceMigration);
return migrationInfo;
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedStartMigration(migrationName, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Cleans the migrations identified by the passed concatenated string of
* migration context paths after the migrations have been successfully
* committed.
*
* @param migrationPaths Comma separated migration context paths.
*
* @throws VPlexApiException When an error occurs cleaning the migrations.
*/
private void cleanCommittedMigrations(String migrationPaths) throws VPlexApiException {
// Clean the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_CLEAN_MIGRATIONS);
s_logger.info("Clean migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Cleaning committed migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationPaths);
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true);
s_logger.info("Clean migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Clean migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Clean migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.cleanMigrationsFailureStatus(
migrationPaths, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully cleaned migrations {}", migrationPaths);
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedCleanMigrations(migrationPaths, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Removes the records for the canceled and/or committed migrations
* identified by the passed concatenated string of migration context paths
*
* @param migrationPaths Comma separated migration context paths.
*
* @throws VPlexApiException When an error occurs removing the migration
* records.
*/
private void removeCommittedOrCanceledMigrations(String migrationPaths)
throws VPlexApiException {
// Remove the migrations.
URI requestURI = _vplexApiClient.getBaseURI().resolve(
VPlexApiConstants.URI_REMOVE_MIGRATIONS);
s_logger.info("Remove migrations URI is {}", requestURI.toString());
ClientResponse response = null;
try {
s_logger.info("Removing migrations");
Map<String, String> argsMap = new HashMap<String, String>();
argsMap.put(VPlexApiConstants.ARG_DASH_M, migrationPaths);
JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true);
s_logger.info("Remove migrations POST data is {}",
postDataObject.toString());
response = _vplexApiClient.post(requestURI,
postDataObject.toString());
String responseStr = response.getEntity(String.class);
s_logger.info("Remove migrations response is {}", responseStr);
if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) {
if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) {
s_logger.info("Remove migrations is completing asynchronously");
_vplexApiClient.waitForCompletion(response);
} else {
String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr);
throw VPlexApiException.exceptions.removeMigrationsFailureStatus(
migrationPaths, String.valueOf(response.getStatus()), cause);
}
}
s_logger.info("Successfully removed migrations {}", migrationPaths);
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedRemoveMigrations(migrationPaths, e);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Ensures that the name of the virtual volume associated with the committed
* migration is updated to reflect the target of the migration. When
* created, the name of the virtual volume will include the migration
* source, and we want to be sure the name continues to reflect the backend
* storage volumes used by the virtual volume.
*
* @param virtualVolumeName The name of the virtual volume prior to migration.
* @param migrationInfoList The list of committed migrations.
* @param rename true to rename the volumes.
*
* @throws VPlexApiException When an error occurs making the updates.
*/
private void updateVirtualVolumeInfoAfterCommit(String virtualVolumeName,
List<VPlexMigrationInfo> migrationInfoList, boolean rename)
throws VPlexApiException {
// Get the cluster information.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
List<VPlexClusterInfo> clusterInfoList = discoveryMgr.getClusterInfoLite();
// Process each migration. Note that there will only be 1.
for (VPlexMigrationInfo migrationInfo : migrationInfoList) {
if (migrationInfo.getIsDeviceMigration()) {
updateVolumeInfoAfterCommitDeviceMigration(virtualVolumeName, migrationInfo,
clusterInfoList, rename);
} else {
updateVolumeInfoAfterCommitExtentMigration(virtualVolumeName, migrationInfo,
clusterInfoList, rename);
}
}
s_logger.info("Done updating volume info after commit");
}
/**
* Updates the virtual volume information after a device migration is successfully
* committed. Note that the volume will be a local virtual volume as device migration
* is not supported for distributed volumes.
*
* @param originalVolumeName The name of the volume prior to the commit.
* @param migrationInfo The migration information.
* @param clusterInfoList The cluster info
* @param rename Whether or not the volume should be renamed after commit.
*/
private void updateVolumeInfoAfterCommitDeviceMigration(String originalVolumeName,
VPlexMigrationInfo migrationInfo, List<VPlexClusterInfo> clusterInfoList,
boolean rename) {
// Find the volume after the migration.
VPlexVirtualVolumeInfo virtualVolumeInfo = findVirtualVolumeAfterDeviceMigration(originalVolumeName,
migrationInfo, clusterInfoList);
if (virtualVolumeInfo == null) {
s_logger.warn("Could not find virtual volume {} after device migration", originalVolumeName);
return;
}
// Check to see if the volume is to be renamed after migration to maintain
// the "<local-device-name>_vol" naming convention. Since it is now using a
// new local device, its current name will not match this convention.
if ((rename) && (volumeHasDefaultNamingConvention(originalVolumeName, false))) {
// In some cases for a device migration, the virtual volume name is
// automatically updated by VPLEX after the commit and there is nothing
// to do. In other cases it remains unchanged, and in others it simply ends
// up as the target volume name without the "_vol" suffix. We have seen
// all these behaviors on the VPLEX. So, we check if the volume name needs
// to be updated and if so, we update the name.
String migrationTgtName = migrationInfo.getTarget();
if ((originalVolumeName.equals(virtualVolumeInfo.getName())) ||
(migrationTgtName.equals(virtualVolumeInfo.getName()))) {
// If we are here then VPLEX didn't rename the volume, so make a call to
// rename the volume. Build the name for volume so as to rename the vplex
// volume that is created with the same name as the device name to follow
// the name pattern _vol as the suffix for the vplex volumes.
String volumeNameAfterMigration = virtualVolumeInfo.getName();
String volumePathAfterMigration = virtualVolumeInfo.getPath();
StringBuilder volumeNameBuilder = new StringBuilder();
volumeNameBuilder.append(migrationTgtName);
// Only append the suffix if the volume name doesn't already end with it.
// This will prevent issues where we could be appending the suffix ("_vol")
// multiple times on the volume name.
if (!volumeNameBuilder.toString().endsWith(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX)) {
volumeNameBuilder.append(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX);
}
// Rename the VPLEX volume name and update the migration information.
virtualVolumeInfo = _vplexApiClient.renameResource(virtualVolumeInfo, volumeNameBuilder.toString());
s_logger.info(String.format("Renamed virtual volume after migration from name: %s path: %s to %s",
volumeNameAfterMigration, volumePathAfterMigration, volumeNameBuilder.toString()));
}
} else if (!originalVolumeName.equals(virtualVolumeInfo.getName())) {
// We are not to rename the volume, but it could be that VPLEX renamed it
// automatically. So we will rename it back to its original name if necessary
// and update the migration info.
String newName = virtualVolumeInfo.getName();
virtualVolumeInfo = _vplexApiClient.renameResource(virtualVolumeInfo, originalVolumeName);
s_logger.info("Renamed virtual volume {} back to its orginal name {}", newName, originalVolumeName);
}
// virtual volume info should be set so that the caller can tell
// if the thin-capable flag changed and needs further
migrationInfo.setVirtualVolumeInfo(virtualVolumeInfo);
}
/**
* Finds the virtual volume after a device migration.
*
* @param originalVolumeName The name of the virtual volume prior migration.
* @param migrationInfo The migration information.
* @param clusterInfoList The VPLEX cluster information.
*
* @return A VPlexVirtualVolumeInfo representing the volume, or null when not found.
*/
private VPlexVirtualVolumeInfo findVirtualVolumeAfterDeviceMigration(String originalVolumeName, VPlexMigrationInfo migrationInfo,
List<VPlexClusterInfo> clusterInfoList) {
VPlexVirtualVolumeInfo virtualVolumeInfo = null;
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
// Initially we found that during a device migration, VPLEX automatically updated the
// volume name to reflect the new local device name, based on the new backend storage
// volume. As such, there was no need to update the name after migration. However, it
// then seemed that some versions of VPLEX did not do this and the volume simply had
// the same name as the new local device without the "_vol" suffix. Then we found cases
// where the name is not changed at all and maintain the original volume name. So to
// find the volume after a device migration, we need to handle these possible conditions.
for (VPlexClusterInfo clusterInfo : clusterInfoList) {
String clusterName = clusterInfo.getName();
virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterName, originalVolumeName, false);
// First look for it by it original name.
if (virtualVolumeInfo != null) {
s_logger.info("Found virtual volume after device migration with name {} on cluster {}",
originalVolumeName, clusterName);
break;
} else {
// Now try to find the volume based on the fact that the name was not
// updated to append the "_vol" suffix. This means the volume would have
// the same name as the new local device on which the volume is now built
// which is captured by the migration target. The name would have the
// format "device_<sysid>-<devid>".
String virtualVolumeName = migrationInfo.getTarget();
virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterName, virtualVolumeName, false);
if (virtualVolumeInfo != null) {
s_logger.info("Found virtual volume after device migration with name {} on cluster {}",
virtualVolumeName, clusterName);
break;
} else {
// If not found, then VPLEX must have renamed the volume automatically to
// append the "_vol" suffix to the volume, which it also does when you create
// a new virtual volume.
virtualVolumeName += VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX;
virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(), virtualVolumeName, false);
if (virtualVolumeInfo != null) {
s_logger.info("Found virtual volume after device migration with name {} on cluster {}",
virtualVolumeName, clusterName);
break;
}
}
}
}
return virtualVolumeInfo;
}
/**
* Updates the virtual volume information after an extent migration is successfully
* committed. Note that the volume can be local or distributed for an extent migration.
*
* @param originalVolumeName The name of the volume prior to the commit.
* @param migrationInfo The migration information.
* @param clusterInfoList The cluster info
* @param rename Whether or not the volume should be renamed after commit.
*/
private void updateVolumeInfoAfterCommitExtentMigration(String originalVolumeName,
VPlexMigrationInfo migrationInfo, List<VPlexClusterInfo> clusterInfoList,
boolean rename) {
s_logger.info("Updating volume information after committing extent migration for volume {}", originalVolumeName);
// Find the virtual volume.
VPlexVirtualVolumeInfo virtualVolumeInfo = null;
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
for (VPlexClusterInfo clusterInfo : clusterInfoList) {
virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(), originalVolumeName, false);
if (virtualVolumeInfo != null) {
break;
}
}
if (virtualVolumeInfo == null) {
s_logger.warn("Could not find virtual volume {}", originalVolumeName);
return;
}
// Check to see if we should rename the volume and/or supporting devices to
// maintain the the ViPR default naming convention. We don't want to rename if
// the passed rename flag is false or the volume does not conform to the
// default ViPR naming convention for example, ingested volumes.
if (rename) {
boolean isDistributed = VPlexVirtualVolumeInfo.Locality.distributed.name().equals(virtualVolumeInfo.getLocality());
if (volumeHasDefaultNamingConvention(originalVolumeName, isDistributed)) {
renameVolumeAfterExtentMigration(virtualVolumeInfo, migrationInfo);
renameSupportingDevicesAfterExtentMigration(virtualVolumeInfo, migrationInfo);
} else if (supportingDeviceHasDefaultNamingConvention(virtualVolumeInfo.getSupportingDevice(), isDistributed)) {
renameSupportingDevicesAfterExtentMigration(virtualVolumeInfo, migrationInfo);
}
}
// virtual volume info should be set so that the caller can tell
// if the thin-capable flag changed and needs further
migrationInfo.setVirtualVolumeInfo(virtualVolumeInfo);
}
/**
* Determines if the volume has default naming conventions. A volume with default naming
* convention has a format like below:
*
* local volume: device_<claimed_storage_volume_name>_vol.
* distribute volume: dd_<claimed_storage_volume_name1>_<claimed_storage_volume_name2>_vol.
*
* We should be able to parse the passed volume name to get the claimed storage
* volume names and then call the utility method to verify the name. If there is
* any kind of parsing error, then we know the volume does not have the default
* naming convention. This could be a volume which was created with custom naming enabled
* or it could be a volume that was ingested.
*
* @param volumeName The volume name.
* @param isDistributed true if the volume is distributed, false for local.
*
* @return true if the volume has the default naming convention, else false.
*/
private boolean volumeHasDefaultNamingConvention(String volumeName, boolean isDistributed) {
try {
int endIndex = volumeName.length() - VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX.length();
String supportingDeviceName = volumeName.substring(0, endIndex);
List<String> claimedVolumeNames = new ArrayList<String>();
if (!isDistributed) {
int startIndex = VPlexApiConstants.DEVICE_PREFIX.length();
claimedVolumeNames.add(volumeName.substring(startIndex, endIndex));
} else {
String distVolPrefix = VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM;
int startIndex = distVolPrefix.length();
String supportingDeviceNameNoPrefix = supportingDeviceName.substring(startIndex);
claimedVolumeNames.addAll(Arrays.asList(supportingDeviceNameNoPrefix.split(VPlexApiConstants.DIST_DEVICE_NAME_DELIM)));
}
return VPlexApiUtils.volumeHasDefaultNamingConvention(volumeName, supportingDeviceName, isDistributed, claimedVolumeNames);
} catch (Exception e) {
// Exceptions will occur while indexing the passed volume name if the volume
// does not conform to the default naming convention.
s_logger.info("Volume {} does not conform to default naming convention: {}", volumeName, e.getMessage());
return false;
}
}
/**
* Determines if the passed device has default naming conventions. A device with
* default naming convention has a format like below:
*
* local device: device_<claimed_storage_volume_name>.
* distribute device: dd_<claimed_storage_volume_name1>_<claimed_storage_volume_name2>.
*
* We should be able to parse the passed device name to get the claimed storage
* volume names and then call the utility method to verify the name. If there is
* any kind of parsing error, then we know the device does not have the default
* naming convention.
*
* We need to check the device in cases of custom naming. For custom naming the
* the virtual volume may not have the default naming convention, but since we
* we always build the underlying artifacts using the default conventions, the
* supporting device for a volume may have the default convention. If the supporting
* device does have the default convention, we want to update it after successful
* commit of a migration.
*
* @param deviceName The deviceName
* @param isDistributed true if the device is distributed, false for local.
*
* @return true if the device has the default naming convention, else false.
*/
private boolean supportingDeviceHasDefaultNamingConvention(String deviceName, boolean isDistributed) {
try {
List<String> claimedVolumeNames = new ArrayList<String>();
if (!isDistributed) {
int startIndex = VPlexApiConstants.DEVICE_PREFIX.length();
claimedVolumeNames.add(deviceName.substring(startIndex));
} else {
String distVolPrefix = VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM;
int startIndex = distVolPrefix.length();
String supportingDeviceNameNoPrefix = deviceName.substring(startIndex);
claimedVolumeNames.addAll(Arrays.asList(supportingDeviceNameNoPrefix.split(VPlexApiConstants.DIST_DEVICE_NAME_DELIM)));
}
return VPlexApiUtils.deviceHasDefaultNamingConvention(deviceName, isDistributed, claimedVolumeNames);
} catch (Exception e) {
// Exceptions will occur while indexing the passed volume name if the volume
// does not conform to the default naming convention.
s_logger.info("Device {} does not conform to default naming convention: {}", deviceName, e.getMessage());
return false;
}
}
/**
* Renames the virtual volume after an extent migration to maintain the default ViPR
* naming conventions.
*
* @param virtualVolumeInfo The virtual volume information
* @param migrationInfo The migration information.
*/
private void renameVolumeAfterExtentMigration(VPlexVirtualVolumeInfo virtualVolumeInfo, VPlexMigrationInfo migrationInfo) {
// Update the virtual volume name and associated distributed
// device name to reflect the target rather than the source.
String migrationSrcName = migrationInfo.getSource();
String srcVolumeName = migrationSrcName.substring(VPlexApiConstants.EXTENT_PREFIX.length(),
migrationSrcName.indexOf(VPlexApiConstants.EXTENT_SUFFIX));
String migrationTgtName = migrationInfo.getTarget();
String tgtVolumeName = migrationTgtName.substring(
VPlexApiConstants.EXTENT_PREFIX.length(),
migrationTgtName.indexOf(VPlexApiConstants.EXTENT_SUFFIX));
String virtualVolumeName = virtualVolumeInfo.getName();
String updatedVirtualVolumeName = virtualVolumeName.replace(
srcVolumeName, tgtVolumeName);
virtualVolumeInfo = _vplexApiClient.getVirtualVolumeManager()
.renameVPlexResource(virtualVolumeInfo, updatedVirtualVolumeName);
// Update the virtual volume information and set it
// into the migration info.
virtualVolumeInfo.updateNameOnMigrationCommit(updatedVirtualVolumeName);
}
/**
* Renames the supporting devices of a virtual volume after an extent migration to
* maintain the default ViPR naming conventions. This would rename the distributed
* device and the underlying local devices.
*
* @param vvInfo The virtual volume info.
* @param migrationInfo The migration information.
*/
private void renameSupportingDevicesAfterExtentMigration(VPlexVirtualVolumeInfo vvInfo, VPlexMigrationInfo migrationInfo) {
// Get the discovery and virtual volume managers.
VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager();
VPlexApiVirtualVolumeManager vvolMgr = _vplexApiClient.getVirtualVolumeManager();
// Get the supporting device name.
String supportingDeviceName = vvInfo.getSupportingDevice();
// Get the source and target volume names from the migration info.
String migrationSrcName = migrationInfo.getSource();
String srcVolumeName = migrationSrcName.substring(VPlexApiConstants.EXTENT_PREFIX.length(),
migrationSrcName.indexOf(VPlexApiConstants.EXTENT_SUFFIX));
String migrationTgtName = migrationInfo.getTarget();
String tgtVolumeName = migrationTgtName.substring(
VPlexApiConstants.EXTENT_PREFIX.length(),
migrationTgtName.indexOf(VPlexApiConstants.EXTENT_SUFFIX));
// Update the distributed device if the virtual volume is a distributed
// virtual volume.
if (supportingDeviceName.startsWith(VPlexApiConstants.DIST_DEVICE_PREFIX)) {
VPlexDistributedDeviceInfo supportingDeviceInfo = discoveryMgr.findDistributedDevice(supportingDeviceName);
if (supportingDeviceInfo == null) {
s_logger.info("Could not find distributed device {} for the virtual volume {}, hence distributed "
+ "device name will not be updated. ", supportingDeviceName, vvInfo.getName());
return;
}
String updatedDistDeviceName = supportingDeviceName.replace(srcVolumeName, tgtVolumeName);
supportingDeviceInfo = vvolMgr.renameVPlexResource(supportingDeviceInfo, updatedDistDeviceName);
// Lastly, update the names of the distributed device components, i.e.,
// the local device names.
String srcDeviceName = VPlexApiConstants.DEVICE_PREFIX + srcVolumeName;
String tgtDeviceName = VPlexApiConstants.DEVICE_PREFIX + tgtVolumeName;
List<VPlexDistributedDeviceComponentInfo> componentList = vvolMgr
.getDistributedDeviceComponents(supportingDeviceInfo.getName());
for (VPlexResourceInfo component : componentList) {
if (component.getName().equals(srcDeviceName)) {
vvolMgr.renameVPlexResource(component, tgtDeviceName);
}
}
} else {
// Update the local device name for local volumes.
s_logger.info("Updating device {} to reflect new volume {}", supportingDeviceName, tgtVolumeName);
VPlexDeviceInfo supportingDeviceInfo = discoveryMgr.findLocalDevice(supportingDeviceName);
if (supportingDeviceInfo == null) {
s_logger.info("Could not find local device {} for the virtual volume {}, hence "
+ "device name will not be updated. ", supportingDeviceName, vvInfo.getName());
return;
}
String updatedDeviceName = supportingDeviceName.replace(srcVolumeName, tgtVolumeName);
vvolMgr.renameVPlexResource(supportingDeviceInfo, updatedDeviceName);
}
}
}