/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive_grid_cloud_portal.smartproxy;
import static org.ow2.proactive.scheduler.rest.ds.IDataSpaceClient.Dataspace.USER;
import java.io.File;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.apache.log4j.Logger;
import org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector;
import org.ow2.proactive.authentication.ConnectionInfo;
import org.ow2.proactive.scheduler.common.Page;
import org.ow2.proactive.scheduler.common.SchedulerConstants;
import org.ow2.proactive.scheduler.common.SchedulerEvent;
import org.ow2.proactive.scheduler.common.SchedulerEventListener;
import org.ow2.proactive.scheduler.common.SortSpecifierContainer;
import org.ow2.proactive.scheduler.common.exception.JobCreationException;
import org.ow2.proactive.scheduler.common.exception.NotConnectedException;
import org.ow2.proactive.scheduler.common.exception.PermissionException;
import org.ow2.proactive.scheduler.common.exception.SubmissionClosedException;
import org.ow2.proactive.scheduler.common.exception.UnknownJobException;
import org.ow2.proactive.scheduler.common.exception.UnknownTaskException;
import org.ow2.proactive.scheduler.common.job.Job;
import org.ow2.proactive.scheduler.common.job.JobId;
import org.ow2.proactive.scheduler.common.job.JobInfo;
import org.ow2.proactive.scheduler.common.job.JobResult;
import org.ow2.proactive.scheduler.common.job.JobState;
import org.ow2.proactive.scheduler.common.job.TaskFlowJob;
import org.ow2.proactive.scheduler.common.task.Task;
import org.ow2.proactive.scheduler.common.task.TaskId;
import org.ow2.proactive.scheduler.common.task.TaskResult;
import org.ow2.proactive.scheduler.common.task.TaskState;
import org.ow2.proactive.scheduler.common.task.dataspaces.InputSelector;
import org.ow2.proactive.scheduler.common.task.dataspaces.OutputSelector;
import org.ow2.proactive.scheduler.rest.ISchedulerClient;
import org.ow2.proactive.scheduler.rest.SchedulerClient;
import org.ow2.proactive.scheduler.rest.ds.DataSpaceClient;
import org.ow2.proactive.scheduler.rest.ds.IDataSpaceClient;
import org.ow2.proactive.scheduler.rest.ds.LocalDestination;
import org.ow2.proactive.scheduler.rest.ds.LocalDirSource;
import org.ow2.proactive.scheduler.rest.ds.RemoteDestination;
import org.ow2.proactive.scheduler.rest.ds.RemoteSource;
import org.ow2.proactive.scheduler.smartproxy.common.AbstractSmartProxy;
import org.ow2.proactive.scheduler.smartproxy.common.AwaitedJob;
import org.ow2.proactive.scheduler.smartproxy.common.AwaitedTask;
import org.ow2.proactive.scheduler.smartproxy.common.SchedulerEventListenerExtended;
import org.ow2.proactive_grid_cloud_portal.common.FileType;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
/**
* Smart proxy implementation that relies on the REST API for communicating with dataspaces
* and websockets to push notifications to clients.
* <p>
* Any instance of this class must be initialized by calling the {@link ISchedulerClient#init(ConnectionInfo)}
* method before executing any subsequent method. Not
* respecting this rule may lead to an NPE or an unexpected behaviour.
*
* @author The ProActive Team
*/
public class RestSmartProxyImpl extends AbstractSmartProxy<RestJobTrackerImpl>
implements ISchedulerClient, SchedulerEventListener {
private static final Logger logger = Logger.getLogger(RestSmartProxyImpl.class);
private ISchedulerClient restSchedulerClient;
private IDataSpaceClient restDataSpaceClient;
public RestSmartProxyImpl() {
super(new RestJobTrackerImpl());
}
@Override
public void init(ConnectionInfo connectionInfo) {
try {
this.connectionInfo = connectionInfo;
this.restSchedulerClient = SchedulerClient.createInstance();
this.restSchedulerClient.init(new ConnectionInfo(connectionInfo.getUrl(),
connectionInfo.getLogin(),
connectionInfo.getPassword(),
connectionInfo.getCredentialFile(),
connectionInfo.isInsecure()));
DataSpaceClient restDsClient = new DataSpaceClient();
restDsClient.init(connectionInfo.getUrl(), this.restSchedulerClient);
this.restDataSpaceClient = restDsClient;
super.jobTracker.setRestDataSpaceClient(this.restDataSpaceClient);
this.jobTracker.loadJobs();
setInitialized(true);
registerAsListener();
syncAwaitedJobs();
} catch (Exception e) {
Throwables.propagate(e);
}
}
@Override
public void disconnect() throws PermissionException {
try {
_getScheduler().disconnect();
} catch (Exception e) {
}
setInitialized(false);
}
@Override
protected ISchedulerClient _getScheduler() {
checkInitialized();
return restSchedulerClient;
}
@Override
protected void createFolder(String fUri) throws NotConnectedException, PermissionException {
RemoteSource remoteSource = new RemoteSource(USER, fUri);
remoteSource.setType(FileType.FOLDER);
restDataSpaceClient.create(remoteSource);
}
@Override
protected void removeJobIO(Job job, String pushURL, String pullURL, String newFolderName) {
pushURL = pushURL + "/" + newFolderName;
RemoteSource remoteSource = new RemoteSource(IDataSpaceClient.Dataspace.USER, pushURL);
try {
restDataSpaceClient.delete(remoteSource);
} catch (NotConnectedException | PermissionException e) {
logger.debug("Error in removeJobIO push for job " + job.getName());
}
pullURL = pullURL + "/" + newFolderName;
remoteSource.setPath(pullURL);
try {
restDataSpaceClient.delete(remoteSource);
} catch (NotConnectedException | PermissionException e) {
logger.debug("Error in removeJobIO pull for job " + job.getName());
}
}
@Override
public JobId submit(Job job)
throws NotConnectedException, PermissionException, SubmissionClosedException, JobCreationException {
checkInitialized();
String inputSpace = job.getInputSpace();
if (inputSpace == null) {
throw new IllegalArgumentException("'InputSpace' is NULL. The InputSpace must be set in order to transfer inputfiles by the" +
" SmartProxy. As a default, you may use the 'UserSpace' value.");
}
String outputSpace = job.getOutputSpace();
if (outputSpace == null) {
throw new IllegalArgumentException("'OutputSpace' is NULL. The OutputSpace must be set in order to retrieve outputfiles by" +
" the SmartProxy. As a default, you may use the 'UserSpace' value.");
}
return _getScheduler().submit(job);
}
@Override
public JobId submit(File job)
throws NotConnectedException, PermissionException, SubmissionClosedException, JobCreationException {
return _getScheduler().submit(job);
}
@Override
public JobId submit(URL job)
throws NotConnectedException, PermissionException, SubmissionClosedException, JobCreationException {
return _getScheduler().submit(job);
}
@Override
public JobId submit(File job, Map<String, String> variables)
throws NotConnectedException, PermissionException, SubmissionClosedException, JobCreationException {
return _getScheduler().submit(job, variables);
}
@Override
public JobId submit(URL job, Map<String, String> variables)
throws NotConnectedException, PermissionException, SubmissionClosedException, JobCreationException {
return _getScheduler().submit(job, variables);
}
@Override
public JobState getJobState(String jobId) throws NotConnectedException, UnknownJobException, PermissionException {
return _getScheduler().getJobState(jobId);
}
@Override
public TaskResult getTaskResult(String jobId, String taskName)
throws NotConnectedException, UnknownJobException, UnknownTaskException, PermissionException {
return _getScheduler().getTaskResult(jobId, taskName);
}
@Override
public boolean finishInErrorTask(String jobId, String taskName)
throws NotConnectedException, UnknownJobException, UnknownTaskException, PermissionException {
return _getScheduler().finishInErrorTask(jobId, taskName);
}
@Override
public boolean restartInErrorTask(String jobId, String taskName)
throws NotConnectedException, UnknownJobException, UnknownTaskException, PermissionException {
return _getScheduler().restartInErrorTask(jobId, taskName);
}
@Override
public boolean restartAllInErrorTasks(String jobId)
throws NotConnectedException, UnknownJobException, PermissionException {
return _getScheduler().restartAllInErrorTasks(jobId);
}
@Override
public List<String> getGlobalSpaceURIs() throws NotConnectedException, PermissionException {
return _getScheduler().getGlobalSpaceURIs();
}
@Override
public List<String> getUserSpaceURIs() throws NotConnectedException, PermissionException {
return _getScheduler().getUserSpaceURIs();
}
@Override
public void addEventListener(SchedulerEventListenerExtended listener, boolean myEventsOnly,
SchedulerEvent... events) throws NotConnectedException, PermissionException {
_getScheduler().addEventListener(listener, myEventsOnly, events);
}
/**
* @throws NotConnectedException
* @throws PermissionException
* @see AbstractSmartProxy#uploadInputfiles(TaskFlowJob, String)
*/
@Override
public boolean uploadInputfiles(TaskFlowJob job, String localInputFolderPath)
throws NotConnectedException, PermissionException {
String userSpace = getLocalUserSpace();
String inputSpace = job.getInputSpace();
if (!inputSpace.startsWith(userSpace)) {
// NOTE: only works for USERSPACE urls
logger.warn("RestSmartProxy does not support data transfers outside USERSPACE.");
return false;
}
String remotePath = inputSpace.substring(userSpace.length() + (userSpace.endsWith("/") ? 0 : 1));
String jname = job.getName();
logger.debug("Pushing files for job " + jname + " from " + localInputFolderPath + " to " + remotePath);
TaskFlowJob tfj = job;
for (Task t : tfj.getTasks()) {
logger.debug("Pushing files for task " + t.getName());
List<String> includes = Lists.newArrayList();
List<String> excludes = Lists.newArrayList();
List<InputSelector> inputFilesList = t.getInputFilesList();
if (inputFilesList != null) {
for (InputSelector is : inputFilesList) {
addfileSelection(is.getInputFiles(), includes, excludes);
}
}
LocalDirSource source = new LocalDirSource(localInputFolderPath);
source.setIncludes(includes);
source.setExcludes(excludes);
RemoteDestination dest = new RemoteDestination(USER, remotePath);
restDataSpaceClient.upload(source, dest);
}
logger.debug("Finished push operation from " + localInputFolderPath + " to " + remotePath);
return true;
}
@Override
protected void downloadTaskOutputFiles(AwaitedJob awaitedjob, String jobId, String taskName, String localFolder)
throws NotConnectedException, PermissionException {
AwaitedTask atask = awaitedjob.getAwaitedTask(taskName);
if (atask == null) {
throw new IllegalArgumentException("The task " + taskName + " does not belong to job " + jobId +
" or has already been removed");
}
if (atask.isTransferring()) {
logger.warn("The task " + taskName + " of job " + jobId + " is already transferring its output");
return;
}
String outputSpace = awaitedjob.getOutputSpaceURL();
String sourceFile;
try {
String userSpace = getLocalUserSpace();
if (!outputSpace.startsWith(userSpace)) {
logger.warn("RestSmartProxy does not support data transfers outside USERSPACE.");
}
sourceFile = outputSpace.substring(userSpace.length() + 1);
} catch (Throwable error) {
throw Throwables.propagate(error);
}
if (awaitedjob.isIsolateTaskOutputs()) {
sourceFile = sourceFile.replace(SchedulerConstants.TASKID_DIR_DEFAULT_NAME,
SchedulerConstants.TASKID_DIR_DEFAULT_NAME + "/" + atask.getTaskId());
}
List<OutputSelector> outputFileSelectors = atask.getOutputSelectors();
List<String> includes = Lists.newArrayList();
List<String> excludes = Lists.newArrayList();
if (outputFileSelectors != null) {
for (OutputSelector os : outputFileSelectors) {
addfileSelection(os.getOutputFiles(), includes, excludes);
}
}
jobTracker.setTaskTransferring(jobId, taskName, true);
if (awaitedjob.isAutomaticTransfer()) {
threadPool.submit(new DownloadHandler(jobId, taskName, sourceFile, includes, excludes, localFolder));
} else {
try {
RemoteSource source = new RemoteSource(USER, sourceFile);
source.setIncludes(includes);
source.setExcludes(excludes);
File localDir = new File(localFolder);
LocalDestination dest = new LocalDestination(localDir);
restDataSpaceClient.download(source, dest);
} catch (NotConnectedException | PermissionException e) {
logger.error(String.format("Cannot download files, jobId=%s, taskId=%s, source=%s, destination=%s",
jobId,
taskName,
sourceFile,
localFolder),
e);
throw e;
} finally {
jobTracker.setTaskTransferring(jobId, taskName, false);
jobTracker.removeAwaitedTask(jobId, taskName);
}
}
}
private void addfileSelection(FileSelector fs, List<String> includes, List<String> excludes) {
includes.addAll(fs.getIncludes());
excludes.addAll(fs.getExcludes());
}
@Override
public void registerAsListener() throws NotConnectedException, PermissionException {
_getScheduler().addEventListener(this, true, PROXY_SCHED_EVENTS);
}
/*
* Implementation of the ISchedulerClient interface
*/
@Override
public void setSession(String sid) {
((ISchedulerClient) _getScheduler()).setSession(sid);
}
@Override
public String getSession() {
return ((ISchedulerClient) _getScheduler()).getSession();
}
@Override
public boolean isJobFinished(JobId jobId) throws NotConnectedException, UnknownJobException, PermissionException {
return ((ISchedulerClient) _getScheduler()).isJobFinished(jobId);
}
@Override
public boolean isJobFinished(String jobId) throws NotConnectedException, UnknownJobException, PermissionException {
return ((ISchedulerClient) _getScheduler()).isJobFinished(jobId);
}
@Override
public JobResult waitForJob(JobId jobId, long timeout)
throws NotConnectedException, UnknownJobException, PermissionException, TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForJob(jobId, timeout);
}
@Override
public JobResult waitForJob(String jobId, long timeout)
throws NotConnectedException, UnknownJobException, PermissionException, TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForJob(jobId, timeout);
}
@Override
public boolean isTaskFinished(String jobId, String taskName)
throws UnknownJobException, NotConnectedException, PermissionException, UnknownTaskException {
return ((ISchedulerClient) _getScheduler()).isTaskFinished(jobId, taskName);
}
@Override
public TaskResult waitForTask(String jobId, String taskName, long timeout) throws UnknownJobException,
NotConnectedException, PermissionException, UnknownTaskException, TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForTask(jobId, taskName, timeout);
}
@Override
public List<JobResult> waitForAllJobs(List<String> jobIds, long timeout)
throws NotConnectedException, UnknownJobException, PermissionException, TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForAllJobs(jobIds, timeout);
}
@Override
public Map.Entry<String, JobResult> waitForAnyJob(List<String> jobIds, long timeout)
throws NotConnectedException, UnknownJobException, PermissionException, TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForAnyJob(jobIds, timeout);
}
@Override
public Map.Entry<String, TaskResult> waitForAnyTask(String jobId, List<String> taskNames, long timeout)
throws UnknownJobException, NotConnectedException, PermissionException, UnknownTaskException,
TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForAnyTask(jobId, taskNames, timeout);
}
@Override
public List<Map.Entry<String, TaskResult>> waitForAllTasks(String jobId, List<String> taskNames, long timeout)
throws UnknownJobException, NotConnectedException, PermissionException, UnknownTaskException,
TimeoutException {
return ((ISchedulerClient) _getScheduler()).waitForAllTasks(jobId, taskNames, timeout);
}
@Override
public boolean pushFile(String spacename, String pathname, String filename, String file)
throws NotConnectedException, PermissionException {
return ((ISchedulerClient) _getScheduler()).pushFile(spacename, pathname, filename, file);
}
@Override
public void pullFile(String space, String pathname, String outputFile)
throws NotConnectedException, PermissionException {
((ISchedulerClient) _getScheduler()).pullFile(space, pathname, outputFile);
}
@Override
public boolean deleteFile(String space, String pathname) throws NotConnectedException, PermissionException {
return ((ISchedulerClient) _getScheduler()).deleteFile(space, pathname);
}
private class DownloadHandler implements Runnable {
private String jobId;
private String taskName;
private String sourceFile;
private List<String> includes;
private List<String> excludes;
private String localFolder;
public DownloadHandler(String jobId, String taskName, String sourceFile, List<String> includes,
List<String> excludes, String localFolder) {
this.jobId = jobId;
this.taskName = taskName;
this.sourceFile = sourceFile;
this.includes = includes;
this.excludes = excludes;
this.localFolder = localFolder;
}
@Override
public void run() {
try {
RemoteSource source = new RemoteSource(USER, sourceFile);
source.setIncludes(includes);
source.setExcludes(excludes);
File localDir = new File(localFolder);
LocalDestination dest = new LocalDestination(localDir);
restDataSpaceClient.download(source, dest);
} catch (Throwable error) {
logger.error(String.format("Cannot download output files: job_id=%s, task_name=%s, source=%s, destination=%s",
jobId,
taskName,
sourceFile,
localFolder),
error);
logger.warn(String.format("[job_id=%s, task_name=%s] will be removed from the known task list. The system will not attempt again to retrieve data for this task. You could try to manually copy the data from %s in userspace.",
jobId,
taskName,
sourceFile));
Iterator<SchedulerEventListenerExtended> it = eventListeners.iterator();
while (it.hasNext()) {
SchedulerEventListenerExtended l = it.next();
try {
l.pullDataFailed(jobId, taskName, sourceFile, error);
} catch (Exception e1) {
// if an exception occurs we remove the listener
it.remove();
}
}
removeAwaitedTask(jobId, taskName);
return;
}
Iterator<SchedulerEventListenerExtended> it = eventListeners.iterator();
while (it.hasNext()) {
SchedulerEventListenerExtended l = it.next();
try {
l.pullDataFinished(jobId, taskName, localFolder);
} catch (Exception e1) {
// if an exception occurs we remove the listener
it.remove();
}
}
removeAwaitedTask(jobId, taskName);
}
}
@Override
public Page<TaskId> getTaskIds(String taskTag, long from, long to, boolean mytasks, boolean running,
boolean pending, boolean finished, int offset, int limit)
throws NotConnectedException, PermissionException {
return _getScheduler().getTaskIds(taskTag, from, to, mytasks, running, pending, finished, offset, limit);
}
@Override
public Page<TaskState> getTaskStates(String taskTag, long from, long to, boolean mytasks, boolean running,
boolean pending, boolean finished, int offset, int limit, SortSpecifierContainer sortParams)
throws NotConnectedException, PermissionException {
return _getScheduler().getTaskStates(taskTag,
from,
to,
mytasks,
running,
pending,
finished,
offset,
limit,
sortParams);
}
@Override
public JobInfo getJobInfo(String jobId) throws UnknownJobException, NotConnectedException, PermissionException {
return _getScheduler().getJobInfo(jobId);
}
@Override
public boolean changeStartAt(JobId jobId, String startAt)
throws NotConnectedException, UnknownJobException, PermissionException {
return ((ISchedulerClient) _getScheduler()).changeStartAt(jobId, startAt);
}
@Override
public JobId copyJobAndResubmitWithGeneralInfo(JobId jobId, Map<String, String> generalInfo)
throws NotConnectedException, UnknownJobException, PermissionException, SubmissionClosedException,
JobCreationException {
return ((ISchedulerClient) _getScheduler()).copyJobAndResubmitWithGeneralInfo(jobId, generalInfo);
}
@Override
public Map<Object, Object> getPortalConfiguration() throws NotConnectedException, PermissionException {
return _getScheduler().getPortalConfiguration();
}
@Override
public String getCurrentUser() throws NotConnectedException {
return ((ISchedulerClient) _getScheduler()).getCurrentUser();
}
}