/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.vplexcontroller.job;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import com.emc.storageos.volumecontroller.TaskCompleter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.Migration;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.exceptions.DeviceControllerErrors;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.emc.storageos.volumecontroller.Job;
import com.emc.storageos.volumecontroller.JobContext;
import com.emc.storageos.volumecontroller.impl.JobPollResult;
import com.emc.storageos.vplex.api.VPlexApiClient;
import com.emc.storageos.vplex.api.VPlexApiFactory;
import com.emc.storageos.vplex.api.VPlexMigrationInfo;
import com.emc.storageos.vplexcontroller.VPlexControllerUtils;
import com.emc.storageos.vplexcontroller.completers.MigrationTaskCompleter;
/**
* Job keeps track of the status of a VPlex migration.
*/
public class VPlexMigrationJob extends Job implements Serializable {
// How long we will wait for a successful check on the
// migration status.
private static final int MAX_TIME_FOR_SUCCESSFUL_STATUS_CHECK_MS = 3600000;
// For serialization interface.
private static final long serialVersionUID = 1L;
// A reference to the task completer
private MigrationTaskCompleter _taskCompleter;
// A reference to the poll result.
private JobPollResult _pollResult = new JobPollResult();
// A reference to the job status.
protected JobStatus _status = JobStatus.IN_PROGRESS;
// A reference to an error description.
private String _errorDescription = null;
// The maximum number of times we will try and get the migration status
// before we give up and fail. It is determined by dividing the maximum
// time we will wait by the interval at which the job is polled.
private Integer _maxRetries = null;
// Keeps track of how many times we have tried to get the migration status
// since the last successful attempt to get the status.
private int _retryCount = 0;
// Logger reference.
private static final Logger s_logger = LoggerFactory.getLogger(VPlexMigrationJob.class);
/**
* Constructor.
*
* @param taskCompleter The task completer.
*/
public VPlexMigrationJob(MigrationTaskCompleter taskCompleter) {
_taskCompleter = taskCompleter;
}
/**
* {@inheritDoc}
*/
public JobPollResult poll(JobContext jobContext, long trackingPeriodInMillis) {
s_logger.debug("Polled migration job");
Migration migration = null;
StorageSystem vplexSystem = null;
try {
// Set max retries based on the tracking period. The default
// tracking period is 15 seconds, and we'll wait for an hour
// to make a successful status check before failing the job.
// 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;
}
/**
* Get the HTTP client for making requests to the VPlex at the
* endpoint specified in the passed profile.
*
* @param jobContext The job context
* @param vplexSystem The VPlex storage system
*
* @return A reference to the VPlex API HTTP client.
* @throws URISyntaxException
*/
private VPlexApiClient getVPlexAPIClient(JobContext jobContext, StorageSystem vplexSystem) throws URISyntaxException {
// Create the URI to access the VPlex Management Station based
// on the IP and port for the passed VPlex system.
URI vplexEndpointURI = new URI("https", null, vplexSystem.getIpAddress(), vplexSystem.getPortNumber(), "/", null, null);
s_logger.debug("VPlex base URI is {}", vplexEndpointURI.toString());
VPlexApiFactory vplexApiFactory = jobContext.getVPlexApiFactory();
s_logger.debug("Got VPlex API factory");
VPlexApiClient client = vplexApiFactory.getClient(vplexEndpointURI,
vplexSystem.getUsername(), vplexSystem.getPassword());
s_logger.debug("Got VPlex API client");
return client;
}
/**
* Return the passed percent done as an integer. Returns 0 when the passed
* value cannot be parsed to an integer.
*
* @param percentDoneStr Percent done value as a string.
*
* @return The percent done as an integer.
*/
private int getMigrationPercentDone(String percentDoneStr) {
int percentDone = 0;
try {
percentDone = Integer.parseInt(percentDoneStr);
} catch (NumberFormatException nfe) {
s_logger.warn("Migration percentage {} is not a number.", percentDoneStr);
}
return percentDone;
}
/**
* Update the status after a poll.
*
* @param jobContext the job context.
*/
public void updateStatus(JobContext jobContext) {
try {
if (_status == JobStatus.SUCCESS) {
s_logger.debug("Calling task completer for successful job");
_taskCompleter.ready(jobContext.getDbClient());
} else if (_status == JobStatus.FAILED) {
s_logger.debug("Calling task completer for failed job");
ServiceError error = DeviceControllerErrors.vplex.migrationJobFailed(_errorDescription);
_taskCompleter.error(jobContext.getDbClient(), error);
}
} catch (Exception e) {
s_logger.error("Problem while trying to update status", e);
}
}
/**
* Determines the operation status from the job status.
*
* @return The operation status, based on the job status.
*/
protected Operation.Status getOperationStatus() {
switch (_status) {
case SUCCESS:
return Operation.Status.ready;
case FAILED:
return Operation.Status.error;
case ERROR:
default:
return Operation.Status.pending;
}
}
/**
* Returns the operation message based on the job status.
*
* @return The operation message.
*/
protected String getOperationMessage() {
switch (_status) {
case SUCCESS:
return "Migration succeeded";
case FAILED:
return "Migration failed with error:" + _errorDescription;
case ERROR:
return "Migration encountered internal error:" + _errorDescription;
case IN_PROGRESS:
return "Migration in progress: " + getJobPercentDone() + "% complete...";
default:
return "Undefined migration job status";
}
}
/**
* Returns the operation service code based on the job status.
*
* @return The operation service code.
*/
protected ServiceCode getOperationServiceCode() {
switch (_status) {
case ERROR:
return ServiceCode.CONTROLLER_ERROR;
default:
return null;
}
}
/**
* Gets the job percentage complete.
*
* @return The job percentage complete.
*/
public int getJobPercentDone() {
if (_pollResult == null) {
return 0;
}
return _pollResult.getJobPercentComplete();
}
public TaskCompleter getTaskCompleter() {
return _taskCompleter;
}
}