/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.cinder.job;
import java.io.Serializable;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.cinder.CinderEndPointInfo;
import com.emc.storageos.cinder.api.CinderApi;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.exceptions.DeviceControllerErrors;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.volumecontroller.Job;
import com.emc.storageos.volumecontroller.JobContext;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.JobPollResult;
/**
* Cinder Job implementation
*
*/
public class CinderJob extends Job implements Serializable
{
private static final long serialVersionUID = -2317072489614631555L;
private static final Logger logger = LoggerFactory.getLogger(CinderJob.class);
private CinderEndPointInfo epInfo = null;
private String jobId = "";
private URI storageSystemURI = null;
private String jobName = "";
private String errorDescription = "";
private String componentType = "";
private TaskCompleter taskCompleter = null;
private long errorTrackingTime = 0L;
private static final long ERROR_TRACKING_LIMIT = 2 * 60 * 60 * 1000; // tracking limit for transient errors. set for 2 hours
private JobPollResult pollResult = new JobPollResult();
protected JobStatus status = JobStatus.IN_PROGRESS;
public CinderJob(String jobId, String jobName,
URI storageSystem, String componentType,
CinderEndPointInfo ep, TaskCompleter taskCompleter)
{
this.jobId = jobId;
this.jobName = jobName;
this.storageSystemURI = storageSystem;
this.componentType = componentType;
this.epInfo = ep;
this.taskCompleter = taskCompleter;
}
public long getErrorTrackingTime()
{
return errorTrackingTime;
}
public void setErrorTrackingTime(long errorTrackingTime)
{
this.errorTrackingTime = errorTrackingTime;
}
/**
* Sets the status for the job to the error status and updates the
* error description with the passed description.
*
* @param errorDescription A description of the error.
*/
public void setErrorStatus(String errorDescription)
{
status = JobStatus.ERROR;
this.errorDescription = errorDescription;
}
public String getJobId()
{
return jobId;
}
public void setJobId(String jobId)
{
this.jobId = jobId;
}
public String getJobName()
{
return jobName;
}
public void setJobName(String jobName)
{
this.jobName = jobName;
}
public String getErrorDescription()
{
return errorDescription;
}
public void setErrorDescription(String errorDescription)
{
this.errorDescription = errorDescription;
}
public JobStatus getStatus()
{
return status;
}
public void setStatus(JobStatus status)
{
this.status = status;
}
public TaskCompleter getTaskCompleter() {
return taskCompleter;
}
public void setTaskCompleter(TaskCompleter taskCompleter) {
this.taskCompleter = taskCompleter;
}
public URI getStorageSystemURI() {
return storageSystemURI;
}
public void setStorageSystemURI(URI storageSystemURI) {
this.storageSystemURI = storageSystemURI;
}
public CinderEndPointInfo getEndPointInfo() {
return epInfo;
}
public void setEndPointInfo(CinderEndPointInfo epInfo) {
this.epInfo = epInfo;
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.volumecontroller.Job#poll(com.emc.storageos.volumecontroller.JobContext, long)
*/
@Override
public JobPollResult poll(JobContext jobContext, long trackingPeriodInMillis)
{
String messageId = jobId;
try
{
StorageSystem storageSystem = jobContext.getDbClient().queryObject(StorageSystem.class, storageSystemURI);
logger.info("CinderJob: Looking up job: id {}, provider: {} ", messageId, storageSystem.getActiveProviderURI());
CinderApi cinderApi = jobContext.getCinderApiFactory().getApi(storageSystem.getActiveProviderURI(), this.epInfo);
if (cinderApi == null)
{
String errorMessage = "No Cinder client found for provider ip: " + storageSystem.getActiveProviderURI();
processTransientError(messageId, trackingPeriodInMillis, errorMessage, null);
}
else
{
// Gets the current status of the task ( volume creation, snapshot creation etc )
String currentStatus = getCurrentStatus(cinderApi);
pollResult.setJobName(jobName);
pollResult.setJobId(jobId);
if (isJobSucceeded(currentStatus))
{
status = JobStatus.SUCCESS;
pollResult.setJobPercentComplete(100);
logger.info("CinderJob: {} succeeded", messageId);
}
else if (isJobFailed(currentStatus))
{
status = JobStatus.FAILED;
pollResult.setJobPercentComplete(100);
logger.error("CinderJob: {} failed; Details: {}", jobName, errorDescription);
}
}
} catch (Exception e)
{
processTransientError(messageId, trackingPeriodInMillis, e.getMessage(), e);
} finally
{
try
{
updateStatus(jobContext);
} catch (Exception e)
{
setErrorStatus(e.getMessage());
logger.error("Problem while trying to update status", e);
}
}
pollResult.setJobStatus(status);
pollResult.setErrorDescription(errorDescription);
return pollResult;
}
/**
* Checks if the given status indicates success for a Job.
* This method has to be over-ridden by sub class Jobs.
* because status AVAILABLE for Volume indicates success for volume create job,
* but it refers to failure in case of volume attach job.
*
* @param currentStatus the current status
* @return true, if the job has succeeded
*/
protected boolean isJobSucceeded(String currentStatus) {
return (CinderConstants.ComponentStatus.AVAILABLE.getStatus().equalsIgnoreCase(currentStatus)
|| CinderConstants.ComponentStatus.IN_USE.getStatus().equalsIgnoreCase(currentStatus)
|| CinderConstants.ComponentStatus.DELETED.getStatus().equalsIgnoreCase(currentStatus));
}
protected boolean isJobFailed(String currentStatus) {
return (CinderConstants.ComponentStatus.ERROR.getStatus().equalsIgnoreCase(currentStatus)
|| CinderConstants.ComponentStatus.ERROR_DELETING.getStatus().equalsIgnoreCase(currentStatus));
}
protected String getCurrentStatus(CinderApi cinderApi) throws Exception
{
return cinderApi.getTaskStatus(jobId, componentType);
}
/**
* Updates the status of the job.
*
* @param jobContext
* @throws Exception
*/
public void updateStatus(JobContext jobContext) throws Exception
{
if (status == JobStatus.SUCCESS)
{
taskCompleter.ready(jobContext.getDbClient());
}
else if (status == JobStatus.FAILED || status == JobStatus.FATAL_ERROR
|| status == JobStatus.ERROR)
{
ServiceError error = DeviceControllerErrors.cinder.jobFailed(errorDescription);
taskCompleter.error(jobContext.getDbClient(), error);
}
}
private void processTransientError(String jobId, long trackingInterval, String errorMessage, Exception ex)
{
status = JobStatus.ERROR;
errorDescription = errorMessage;
if (ex != null)
{
logger.error(String.format("Error while processing CinderJob - Name: %s, ID: %s, Desc: %s Status: %s",
jobName, jobId, errorDescription, status), ex);
}
else
{
logger.error(String.format("Error while processing CinderJob - Name: %s, ID: %s, Desc: %s Status: %s",
jobName, jobId, errorDescription, status));
}
// Check if job tracking limit was reached. Set status to FAILED in such a case.
setErrorTrackingTime(getErrorTrackingTime() + trackingInterval);
logger.info(String.format("Tracking time of CinderJob in transient error status - %s, Name: %s, ID: %s. Status %s .",
getErrorTrackingTime(), jobName, jobId, status));
if (getErrorTrackingTime() > ERROR_TRACKING_LIMIT)
{
status = JobStatus.FATAL_ERROR;
logger.error(String.format("Reached tracking time limit for CinderJob - Name: %s, ID: %s. Set status to %s .",
jobName, jobId, status));
}
}
}