use of com.emc.storageos.vplex.api.VPlexMigrationInfo in project coprhd-controller by CoprHD.
the class VPlexDeviceController method commitMigration.
/**
* Invoked by the migration workflow to commit the migration after it has
* been completed.
*
* @param vplexURI
* The URI of the VPlex storage system.
* @param virtualVolumeURI
* The URI of the virtual volume.
* @param migrationURI
* The URI of the data migration.
* @param rename
* Indicates if the volume should be renamed after commit to
* conform to ViPR standard naming conventions.
* @param newVpoolURI - the new virtual pool for the virtual volume (or null if not changing)
* @param newVarrayURI - the new varray for the virtual volume (or null if not changing)
* @param stepId
* The workflow step identifier.
*
* @throws WorkflowException
*/
public void commitMigration(URI vplexURI, URI virtualVolumeURI, URI migrationURI, Boolean rename, URI newVpoolURI, URI newVarrayURI, String stepId) throws WorkflowException {
_log.info("Committing migration {}", migrationURI);
Migration migration = null;
VPlexApiClient client = null;
try {
// Update step state to executing.
WorkflowStepCompleter.stepExecuting(stepId);
// Get the migration.
migration = getDataObject(Migration.class, migrationURI, _dbClient);
// workflow, so check the status.
if (!VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue().equals(migration.getMigrationStatus())) {
// Get the VPlex API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, _dbClient);
client = getVPlexAPIClient(_vplexApiFactory, vplexSystem, _dbClient);
_log.info("Got VPlex API client for system {}", vplexURI);
// Make a call to the VPlex API client to commit the migration.
// Note that for ingested VPLEX volumes created outside ViPR, we
// don't want to update the name.
List<VPlexMigrationInfo> migrationInfoList = new ArrayList<VPlexMigrationInfo>();
Volume virtualVolume = getDataObject(Volume.class, virtualVolumeURI, _dbClient);
try {
migrationInfoList = client.commitMigrations(virtualVolume.getDeviceLabel(), Arrays.asList(migration.getLabel()), true, true, rename.booleanValue());
_log.info("Committed migration {}", migration.getLabel());
} catch (VPlexApiException vae) {
_log.error("Exception committing VPlex migration: " + vae.getMessage(), vae);
boolean committed = false;
// Check the migration status. Maybe it committed even though we had an error.
VPlexMigrationInfo migrationInfo = client.getMigrationInfo(migration.getLabel());
if (migrationInfo.getStatus().equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMMITTED.name())) {
_log.info("Migration {} has committed despite exception", migration.getLabel());
migrationInfoList.clear();
migrationInfoList.add(migrationInfo);
committed = true;
} else {
_log.info("Migration {} status {}", migration.getLabel(), migrationInfo.getStatus());
}
if (!committed) {
// This was observed at customer site COP-21257
if (vae.getServiceCode() == ServiceCode.VPLEX_API_RESPONSE_TIMEOUT_ERROR) {
// We are going to throw an error, but we don't want to rollback completely
_workflowService.setWorkflowRollbackContOnError(stepId, false);
}
WorkflowStepCompleter.stepFailed(stepId, vae);
return;
}
}
// Below this point migration is committed, no turning back.
// Initialize the migration info in the database.
migration.setMigrationStatus(VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue());
_dbClient.updateObject(migration);
_log.info("Update migration status to committed");
// Update the virtual volume native id and associated
// volumes. Note that we don't update CoS until all
// commits are successful.
VPlexVirtualVolumeInfo updatedVirtualVolumeInfo = migrationInfoList.get(0).getVirtualVolumeInfo();
// update any properties that were changed after migration including deviceLabel, nativeGuid, and nativeId.
// also, if the updated volume isn't thin-enabled, it is thin-capable, and the target vpool supports thin
// provisioning, then a call should be made to the VPLEX to flip the thin-enabled flag on for this volume.
URI targetVolumeUri = migration.getTarget();
Volume targetVolume = getDataObject(Volume.class, targetVolumeUri, _dbClient);
if (updatedVirtualVolumeInfo != null) {
_log.info(String.format("New virtual volume is %s", updatedVirtualVolumeInfo.toString()));
// if the new virtual volume is thin-capable, but thin-enabled is not true,
// that means we need to ask the VPLEX to convert it to a thin-enabled volume.
// this doesn't happen automatically for thick-to-thin data migrations.
boolean isThinEnabled = updatedVirtualVolumeInfo.isThinEnabled();
if (!isThinEnabled && VPlexApiConstants.TRUE.equalsIgnoreCase(updatedVirtualVolumeInfo.getThinCapable())) {
if (verifyVplexSupportsThinProvisioning(vplexSystem)) {
if (null != targetVolume) {
_log.info(String.format("migration target Volume is %s", targetVolume.forDisplay()));
VirtualPool targetVirtualPool = getDataObject(VirtualPool.class, targetVolume.getVirtualPool(), _dbClient);
if (null != targetVirtualPool) {
_log.info(String.format("migration target VirtualPool is %s", targetVirtualPool.forDisplay()));
boolean doEnableThin = VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(targetVirtualPool.getSupportedProvisioningType());
if (doEnableThin) {
_log.info(String.format("the new VirtualPool is thin, requesting VPLEX to enable thin provisioning on %s", updatedVirtualVolumeInfo.getName()));
isThinEnabled = client.setVirtualVolumeThinEnabled(updatedVirtualVolumeInfo);
}
}
}
}
}
virtualVolume.setDeviceLabel(updatedVirtualVolumeInfo.getName());
virtualVolume.setNativeId(updatedVirtualVolumeInfo.getPath());
virtualVolume.setNativeGuid(updatedVirtualVolumeInfo.getPath());
virtualVolume.setThinlyProvisioned(isThinEnabled);
}
// Note that for ingested volumes, there will be no associated volumes
// at first.
StringSet assocVolumes = virtualVolume.getAssociatedVolumes();
if ((assocVolumes != null) && (!assocVolumes.isEmpty())) {
// For a distributed volume, there could be multiple
// migrations. When the first completes, there will
// be no associated volumes. However, when the second
// completes, there will be associated volumes. However,
// the migration source could be null.
URI sourceVolumeUri = migration.getSource();
if (sourceVolumeUri != null) {
assocVolumes.remove(sourceVolumeUri.toString());
// Retain any previous RP fields on the new target volume.
Volume sourceVolume = getDataObject(Volume.class, sourceVolumeUri, _dbClient);
if (sourceVolume != null) {
boolean targetUpdated = false;
if (NullColumnValueGetter.isNotNullValue(sourceVolume.getRpCopyName())) {
targetVolume.setRpCopyName(sourceVolume.getRpCopyName());
targetUpdated = true;
}
if (NullColumnValueGetter.isNotNullValue(sourceVolume.getInternalSiteName())) {
targetVolume.setInternalSiteName(sourceVolume.getInternalSiteName());
targetUpdated = true;
}
if (targetUpdated) {
_dbClient.updateObject(targetVolume);
}
}
}
assocVolumes.add(migration.getTarget().toString());
} else {
// NOTE: Now an ingested volume will have associated volumes.
// It will no longer be considered an ingested volume.
assocVolumes = new StringSet();
assocVolumes.add(migration.getTarget().toString());
virtualVolume.setAssociatedVolumes(assocVolumes);
}
updateMigratedVirtualVolumeVpoolAndVarray(virtualVolume, newVpoolURI, newVarrayURI);
_dbClient.updateObject(virtualVolume);
_log.info("Updated virtual volume.");
} else {
_log.info("The migration is already committed.");
// Note that we don't set the device label and native id. If the
// migration was committed outside of Bourne, the virtual volume
// will still have the old name. If it was committed through
// Bourne, these values would already have been update.
// Regardless, we have to update the vpool, and we update the
// associated volumes in case it was committed outside of
// Bourne.
associateVplexVolumeWithMigratedTarget(migration, virtualVolumeURI);
_log.info("Updated virtual volume.");
}
// Update the workflow step status.
StringBuilder successMsgBuilder = new StringBuilder();
successMsgBuilder.append("VPlex System: ");
successMsgBuilder.append(vplexURI);
successMsgBuilder.append(" migration: ");
successMsgBuilder.append(migrationURI);
successMsgBuilder.append(" was committed");
_log.info(successMsgBuilder.toString());
WorkflowStepCompleter.stepSucceded(stepId);
_log.info("Updated workflow step state to success");
} catch (VPlexApiException vae) {
_log.error("Exception committing VPlex migration: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
_log.error("Exception committing VPlex migration: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.COMMIT_VOLUME_MIGRATION.getName();
ServiceError serviceError = VPlexApiException.errors.commitMigrationFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
use of com.emc.storageos.vplex.api.VPlexMigrationInfo in project coprhd-controller by CoprHD.
the class VPlexDeviceController method rollbackCommitMigration.
/**
* Rollback when a migration commit fails.
*
* @param migrationURIs
* The URIs for all migrations.
* @param newVpoolURI
* The URI of the new Vpool after migration commit
* @param newVarrayURI
* The URI of the new Varray after migration commit
* @param commitStepId
* The commit step id.
* @param stepId
* The rollback step id.
*
* @throws WorkflowException
*/
public void rollbackCommitMigration(List<URI> migrationURIs, URI newVpoolURI, URI newVarrayURI, String commitStepId, String stepId) throws WorkflowException {
// Update step state to executing.
WorkflowStepCompleter.stepExecuting(stepId);
try {
// Determine if any migration was successfully committed.
boolean migrationCommitted = false;
Iterator<URI> migrationIter = migrationURIs.iterator();
while (migrationIter.hasNext()) {
URI migrationURI = migrationIter.next();
Migration migration = _dbClient.queryObject(Migration.class, migrationURI);
Volume volume = _dbClient.queryObject(Volume.class, migration.getVolume());
// Check migration database record for committed state
if (VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue().equals(migration.getMigrationStatus())) {
migrationCommitted = true;
updateMigratedVirtualVolumeVpoolAndVarray(volume, newVpoolURI, newVarrayURI);
_dbClient.updateObject(volume);
inventoryDeleteMigrationSource(migration.getSource(), volume);
continue;
}
// Check vplex hardware migration records for committed state
VPlexApiClient client = getVPlexAPIClient(_vplexApiFactory, volume.getStorageController(), _dbClient);
VPlexMigrationInfo migrationInfo = client.getMigrationInfo(migration.getLabel());
if (migrationInfo.getStatus().equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMMITTED.name())) {
migrationCommitted = true;
migration.setMigrationStatus(VPlexMigrationInfo.MigrationStatus.COMMITTED.name());
_dbClient.updateObject(migration);
associateVplexVolumeWithMigratedTarget(migration, migration.getVolume());
updateMigratedVirtualVolumeVpoolAndVarray(volume, newVpoolURI, newVarrayURI);
_dbClient.updateObject(volume);
inventoryDeleteMigrationSource(migration.getSource(), volume);
continue;
}
}
// creation step will cancel the migration.
if (migrationCommitted) {
_log.info("The migration has already been committed or the migration state can not be determined, failing rollback");
// Don't allow rollback to go further than the first error.
_workflowService.setWorkflowRollbackContOnError(stepId, false);
ServiceError serviceError = VPlexApiException.errors.cantRollbackCommittedMigration();
WorkflowStepCompleter.stepFailed(stepId, serviceError);
} else {
_log.info("No Migrations are not committed");
WorkflowStepCompleter.stepSucceded(stepId);
}
} catch (Exception e) {
_log.info("Exception determining commit rollback state", e);
// Don't allow rollback to go further than the first error.
_workflowService.setWorkflowRollbackContOnError(stepId, false);
ServiceError serviceError = VPlexApiException.errors.cantRollbackExceptionDeterminingCommitState(e);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
use of com.emc.storageos.vplex.api.VPlexMigrationInfo in project coprhd-controller by CoprHD.
the class VPlexDeviceController method migrateVirtualVolume.
/**
* Creates and starts a VPlex data migration for the passed virtual volume
* on the passed VPlex storage system. The passed target is a newly created
* backend volume to which the data will be migrated. The source for the
* data migration is the current backend volume for the virtual volume that
* is in the same varray as the passed target. The method also creates
* a migration job to monitor the progress of the migration. The workflow
* step will complete when the migration completes, at which point the
* migration is automatically committed.
*
* @param vplexURI
* The URI of the VPlex storage system.
* @param virtualVolumeURI
* The URI of the virtual volume.
* @param targetVolumeURI
* The URI of the migration target.
* @param migrationURI
* The URI of the migration.
* @param newNhURI
* The URI of the new varray for the virtual volume
* when a local virtual volume is being migrated to the other
* cluster, or null.
* @param stepId
* The workflow step identifier.
* @throws WorkflowException
*/
public void migrateVirtualVolume(URI vplexURI, URI virtualVolumeURI, URI targetVolumeURI, URI migrationURI, URI newNhURI, String stepId) throws WorkflowException {
_log.info("Migration {} using target {}", migrationURI, targetVolumeURI);
try {
// Update step state to executing.
WorkflowStepCompleter.stepExecuting(stepId);
// Initialize the step data. The step data indicates if we
// successfully started the migration and is used in
// rollback.
_workflowService.storeStepData(stepId, Boolean.FALSE);
// Get the virtual volume.
Volume virtualVolume = getDataObject(Volume.class, virtualVolumeURI, _dbClient);
String virtualVolumeName = virtualVolume.getDeviceLabel();
_log.info("Virtual volume name is {}", virtualVolumeName);
// Setup the native volume info for the migration target.
Volume migrationTarget = getDataObject(Volume.class, targetVolumeURI, _dbClient);
StorageSystem targetStorageSystem = getDataObject(StorageSystem.class, migrationTarget.getStorageController(), _dbClient);
_log.info("Storage system for migration target is {}", migrationTarget.getStorageController());
List<String> itls = VPlexControllerUtils.getVolumeITLs(migrationTarget);
VolumeInfo nativeVolumeInfo = new VolumeInfo(targetStorageSystem.getNativeGuid(), targetStorageSystem.getSystemType(), migrationTarget.getWWN().toUpperCase().replaceAll(":", ""), migrationTarget.getNativeId(), migrationTarget.getThinlyProvisioned().booleanValue(), itls);
// Get the migration associated with the target.
Migration migration = getDataObject(Migration.class, migrationURI, _dbClient);
// Determine the unique name for the migration. We identifying
// the migration source and target, using array serial number
// and volume native id, in the migration name. This was fine
// for VPlex extent migration, which has a max length of 63
// for the migration name. However, for remote migrations,
// which require VPlex device migration, the max length is much
// more restrictive, like 20 characters. So, we switched over
// timestamps.
StringBuilder migrationNameBuilder = new StringBuilder(MIGRATION_NAME_PREFIX);
DateFormat dateFormatter = new SimpleDateFormat(MIGRATION_NAME_DATE_FORMAT);
migrationNameBuilder.append(dateFormatter.format(new Date()));
String migrationName = migrationNameBuilder.toString();
migration.setLabel(migrationName);
_dbClient.updateObject(migration);
_log.info("Migration name is {}", migrationName);
// Get the VPlex API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, _dbClient);
VPlexApiClient client = getVPlexAPIClient(_vplexApiFactory, vplexSystem, _dbClient);
_log.info("Got VPlex API client for VPlex {}", vplexURI);
// Get the configured migration speed
String speed = customConfigHandler.getComputedCustomConfigValue(CustomConfigConstants.MIGRATION_SPEED, vplexSystem.getSystemType(), null);
_log.info("Migration speed is {}", speed);
String transferSize = migrationSpeedToTransferSizeMap.get(speed);
// Make a call to the VPlex API client to migrate the virtual
// volume. Note that we need to do a remote migration when a
// local virtual volume is being migrated to the other VPlex
// cluster. If the passed new varray is not null, then
// this is the case.
Boolean isRemoteMigration = newNhURI != null;
// We support both device and extent migrations, however,
// when we don't know anything about the backend volumes
// we must use device migration.
Boolean useDeviceMigration = migration.getSource() == null;
List<VPlexMigrationInfo> migrationInfoList = client.migrateVirtualVolume(migrationName, virtualVolumeName, Arrays.asList(nativeVolumeInfo), isRemoteMigration, useDeviceMigration, true, true, transferSize);
_log.info("Started VPlex migration");
// We store step data indicating that the migration was successfully
// create and started. We will use this to determine the behavior
// on rollback. If we never got to the point that the migration
// was created and started, then there is no rollback to attempt
// on the VLPEX as the migrate API already tried to clean everything
// up on the VLPEX.
_workflowService.storeStepData(stepId, Boolean.TRUE);
// Initialize the migration info in the database.
VPlexMigrationInfo migrationInfo = migrationInfoList.get(0);
migration.setMigrationStatus(VPlexMigrationInfo.MigrationStatus.READY.getStatusValue());
migration.setPercentDone("0");
migration.setStartTime(migrationInfo.getStartTime());
_dbClient.updateObject(migration);
_log.info("Update migration info");
// Create a migration task completer and queue a job to monitor
// the migration progress. The completer will be invoked by the
// job when the migration completes.
MigrationTaskCompleter migrationCompleter = new MigrationTaskCompleter(migrationURI, stepId);
VPlexMigrationJob migrationJob = new VPlexMigrationJob(migrationCompleter);
migrationJob.setTimeoutTimeMsec(MINUTE_TO_MILLISECONDS * Long.valueOf(ControllerUtils.getPropertyValueFromCoordinator(coordinator, CONTROLLER_VPLEX_MIGRATION_TIMEOUT_MINUTES)));
ControllerServiceImpl.enqueueJob(new QueueJob(migrationJob));
_log.info("Queued job to monitor migration progress.");
} catch (VPlexApiException vae) {
_log.error("Exception migrating VPlex virtual volume: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
_log.error("Exception migrating VPlex virtual volume: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.MIGRATE_VIRTUAL_VOLUME.getName();
ServiceError serviceError = VPlexApiException.errors.migrateVirtualVolume(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
use of com.emc.storageos.vplex.api.VPlexMigrationInfo in project coprhd-controller by CoprHD.
the class VPlexMigrationJob method poll.
/**
* {@inheritDoc}
*/
public JobPollResult poll(JobContext jobContext, long trackingPeriodInMillis) {
s_logger.debug("Polled migration job");
Migration migration = null;
StorageSystem vplexSystem = null;
try {
// Therefore, the default max retries is 240.
if (_maxRetries == null) {
if (trackingPeriodInMillis < MAX_TIME_FOR_SUCCESSFUL_STATUS_CHECK_MS) {
_maxRetries = new Integer(Math.round(MAX_TIME_FOR_SUCCESSFUL_STATUS_CHECK_MS / trackingPeriodInMillis));
} else {
_maxRetries = new Integer(1);
}
}
// Get the DB client from the job context.
DbClient dbClient = jobContext.getDbClient();
// Get the migration associated with this job.
migration = dbClient.queryObject(Migration.class, _taskCompleter.getId());
String migrationName = migration.getLabel();
s_logger.debug("Migration is {}", migration.getId());
// Get the virtual volume associated with the migration
// and then get the VPlex storage system for that virtual
// volume.
Volume virtualVolume = dbClient.queryObject(Volume.class, migration.getVolume());
s_logger.debug("Virtual volume is {}", virtualVolume.getId());
vplexSystem = dbClient.queryObject(StorageSystem.class, virtualVolume.getStorageController());
s_logger.debug("VPlex system is {}", vplexSystem.getId());
// Get the VPlex API client for this VPlex storage system
// and get the latest info for the migration.
VPlexApiClient vplexApiClient = VPlexControllerUtils.getVPlexAPIClient(jobContext.getVPlexApiFactory(), vplexSystem, dbClient);
s_logger.debug("Got VPlex APi Client");
VPlexMigrationInfo migrationInfo = vplexApiClient.getMigrationInfo(migrationName);
s_logger.debug("Got migration info from VPlex");
// Update the migration in the database to reflect the
// current status and percent done.
String migrationStatus = migrationInfo.getStatus();
s_logger.debug("Migration status is {}", migrationStatus);
migration.setMigrationStatus(migrationStatus);
int percentDone = getMigrationPercentDone(migrationInfo.getPercentageDone());
s_logger.debug("Migration percent done is {}", percentDone);
migration.setPercentDone(String.valueOf(percentDone));
dbClient.persistObject(migration);
// Update the job info.
_pollResult.setJobName(migrationName);
_pollResult.setJobId(virtualVolume.getId().toString());
_pollResult.setJobPercentComplete(percentDone);
s_logger.debug("Updated poll result");
// Examine the status.
if (VPlexMigrationInfo.MigrationStatus.COMPLETE.getStatusValue().equals(migrationStatus)) {
// Completed successfully
s_logger.info("Migration: {} completed sucessfully", migration.getId());
_status = JobStatus.SUCCESS;
} else if (VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue().equals(migrationStatus)) {
// The migration job completed and somehow it was committed
// outside the scope of the workflow that created the
// migration job. We return success here to ensure that there
// is no rollback in the workflow that could end up deleting
// the target volume of the migration.
s_logger.info("Migration: {} completed and was committed", migration.getId());
_status = JobStatus.SUCCESS;
} else if (VPlexMigrationInfo.MigrationStatus.CANCELLED.getStatusValue().equals(migrationStatus)) {
// The migration job was cancelled outside the scope of the
// workflow that created the migration job.
_errorDescription = "The migration was cancelled";
s_logger.info("Migration: {} was cancelled prior to completion", migration.getId());
_status = JobStatus.FAILED;
} else if (VPlexMigrationInfo.MigrationStatus.ERROR.getStatusValue().equals(migrationStatus)) {
// The migration failed.
_errorDescription = "The migration failed";
s_logger.error("Migration {} failed prior to completion", migration.getId());
_status = JobStatus.FAILED;
}
// We had a successful check of the status. Reset the retry
// count in case the job is still in progress and the next
// attempt to check the status fails.
_retryCount = 0;
} catch (Exception e) {
s_logger.error(String.format("Unexpected error getting status of migration %s on VPlex %s: %s", (migration != null ? migration.getId() : "null"), (vplexSystem != null ? vplexSystem.getId() : "null"), _errorDescription), e);
if (++_retryCount > _maxRetries) {
_errorDescription = e.getMessage();
_status = JobStatus.FAILED;
}
} finally {
s_logger.debug("Updating status {}", _status);
updateStatus(jobContext);
}
_pollResult.setJobStatus(_status);
_pollResult.setErrorDescription(_errorDescription);
return _pollResult;
}
Aggregations