/*
* 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 functionaltests;
import static com.google.common.truth.Truth.assertThat;
import static functionaltests.RestFuncTHelper.getRestServerUrl;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.ow2.proactive.authentication.ConnectionInfo;
import org.ow2.proactive.scheduler.common.NotificationData;
import org.ow2.proactive.scheduler.common.SchedulerEvent;
import org.ow2.proactive.scheduler.common.exception.NotConnectedException;
import org.ow2.proactive.scheduler.common.exception.PermissionException;
import org.ow2.proactive.scheduler.common.exception.UnknownJobException;
import org.ow2.proactive.scheduler.common.exception.UserException;
import org.ow2.proactive.scheduler.common.job.JobId;
import org.ow2.proactive.scheduler.common.job.JobInfo;
import org.ow2.proactive.scheduler.common.job.JobState;
import org.ow2.proactive.scheduler.common.job.JobStatus;
import org.ow2.proactive.scheduler.common.job.TaskFlowJob;
import org.ow2.proactive.scheduler.common.job.UserIdentification;
import org.ow2.proactive.scheduler.common.job.factories.Job2XMLTransformer;
import org.ow2.proactive.scheduler.common.task.ForkEnvironment;
import org.ow2.proactive.scheduler.common.task.JavaTask;
import org.ow2.proactive.scheduler.common.task.OnTaskError;
import org.ow2.proactive.scheduler.common.task.ScriptTask;
import org.ow2.proactive.scheduler.common.task.TaskInfo;
import org.ow2.proactive.scheduler.common.task.TaskStatus;
import org.ow2.proactive.scheduler.common.task.dataspaces.InputAccessMode;
import org.ow2.proactive.scheduler.common.task.dataspaces.OutputAccessMode;
import org.ow2.proactive.scheduler.smartproxy.common.SchedulerEventListenerExtended;
import org.ow2.proactive.scripting.InvalidScriptException;
import org.ow2.proactive.scripting.SimpleScript;
import org.ow2.proactive.scripting.TaskScript;
import org.ow2.proactive_grid_cloud_portal.smartproxy.RestSmartProxyImpl;
import com.google.common.base.Throwables;
public final class RestSmartProxyTest extends AbstractRestFuncTestCase {
private static final long ONE_SECOND = TimeUnit.SECONDS.toMillis(1);
private static final long TEN_MINUTES = 600000; // in milliseconds
protected static int NB_TASKS = 4;
protected File inputLocalFolder;
protected File outputLocalFolder;
protected String userspace;
protected String pushUrl;
protected String pullUrl;
protected static final String TASK_NAME = "TestJavaTask";
// we add special characters to ensure they are supported
public final static String INPUT_FILE_BASE_NAME = "input é";
public final static String INPUT_FILE_EXT = ".txt";
public final static String OUTPUT_FILE_BASE_NAME = "output é";
public final static String OUTPUT_FILE_EXT = ".out";
public final static String inerrorTaskName = "inerror_task";
protected RestSmartProxyImpl restSmartProxy;
@Rule
public TemporaryFolder tempDir = new TemporaryFolder();
@BeforeClass
public static void beforeClass() throws Exception {
init();
}
@Before
public void setup() throws Exception {
initializeRestSmartProxyInstance();
}
@After
public void teardown() throws Exception {
if (restSmartProxy != null) {
restSmartProxy.terminate();
}
}
private void initializeRestSmartProxyInstance() throws Exception {
restSmartProxy = new RestSmartProxyImpl();
restSmartProxy.cleanDatabase();
restSmartProxy.setSessionName(uniqueSessionId());
restSmartProxy.init(new ConnectionInfo(getRestServerUrl(), getLogin(), getPassword(), null, true));
userspace = restSmartProxy.getUserSpaceURIs().get(0);
pushUrl = userspace;
pullUrl = userspace;
// we add special characters and space to the folders to make sure
// transfer occurs normally
inputLocalFolder = tempDir.newFolder("input é");
outputLocalFolder = tempDir.newFolder("output é");
}
@Test(timeout = TEN_MINUTES)
public void testNoAutomaticTransfer() throws Exception {
testJobSubmission(false, false);
}
@Test(timeout = TEN_MINUTES)
public void testAutomaticTransfer() throws Exception {
testJobSubmission(false, true);
}
@Test(timeout = TEN_MINUTES)
public void testInErrorEventsReception() throws Exception {
TaskFlowJob job = createInErrorJob();
final Semaphore semaphore = new Semaphore(0);
printJobXmlRepresentation(job);
final MutableBoolean taskHasBeenInError = new MutableBoolean(false);
final MutableBoolean taskMarkedAsFinished = new MutableBoolean(false);
SchedulerEventListenerExtended listener = new SchedulerEventListenerExtended() {
@Override
public void schedulerStateUpdatedEvent(SchedulerEvent eventType) {
System.out.println("RestSmartProxyTest.schedulerStateUpdatedEvent " + eventType);
}
@Override
public void jobSubmittedEvent(JobState job) {
System.out.println("RestSmartProxyTest.jobSubmittedEvent");
}
@Override
public void jobStateUpdatedEvent(NotificationData<JobInfo> notification) {
JobStatus status = notification.getData().getStatus();
System.out.println("RestSmartProxyTest.jobStateUpdatedEvent, eventType=" + notification.getEventType() +
", jobStatus=" + status);
if (status == JobStatus.IN_ERROR) {
semaphore.release();
}
}
@Override
public void taskStateUpdatedEvent(NotificationData<TaskInfo> notification) {
TaskStatus status = notification.getData().getStatus();
System.out.println("RestSmartProxyTest.taskStateUpdatedEvent, taskStatus=" + status);
if (status == TaskStatus.WAITING_ON_ERROR || status == TaskStatus.IN_ERROR) { // IN_ERROR previously
taskHasBeenInError.setTrue();
}
if (status == TaskStatus.FINISHED && taskHasBeenInError.isTrue()) {
taskMarkedAsFinished.setTrue();
}
}
@Override
public void usersUpdatedEvent(NotificationData<UserIdentification> notification) {
System.out.println("RestSmartProxyTest.usersUpdatedEvent " + notification.getData());
}
@Override
public void pullDataFinished(String jobId, String taskName, String localFolderPath) {
System.out.println("RestSmartProxyTest.pullDataFinished");
}
@Override
public void pullDataFailed(String jobId, String taskName, String remoteFolder_URL, Throwable t) {
System.out.println("RestSmartProxyTest.pullDataFailed");
}
@Override
public void jobUpdatedFullDataEvent(JobState job) {
System.out.println("RestSmartProxyTest.jobUpdatedFullDataEvent");
}
};
restSmartProxy.addEventListener(listener);
JobId jobId = restSmartProxy.submit(job,
inputLocalFolder.getAbsolutePath(),
pushUrl,
outputLocalFolder.getAbsolutePath(),
pullUrl,
false,
false);
// the next line blocks until jobStateUpdatedEvent is called on the
// listener
// with job status set to IN_ERROR
semaphore.acquire();
String jobIdAsString = jobId.value();
System.out.println("Finish in-error task");
restSmartProxy.finishInErrorTask(jobIdAsString, inerrorTaskName);
waitForJobFinishState(jobIdAsString);
assertThat(taskHasBeenInError.booleanValue()).isTrue();
assertThat(taskMarkedAsFinished.booleanValue()).isTrue();
}
private JobState waitForJobFinishState(String jobIdAsString)
throws InterruptedException, NotConnectedException, UnknownJobException, PermissionException {
JobState jobState = restSmartProxy.getJobState(jobIdAsString);
Thread.sleep(ONE_SECOND);
while (jobState.getStatus().isJobAlive()) {
jobState = restSmartProxy.getJobState(jobIdAsString);
Thread.sleep(ONE_SECOND);
}
return jobState;
}
private void printJobXmlRepresentation(TaskFlowJob job)
throws TransformerException, ParserConfigurationException, IOException {
// debugging the job produced
String jobXml = new Job2XMLTransformer().jobToxmlString(job);
System.out.println(jobXml);
}
private TaskFlowJob createInErrorJob() throws InvalidScriptException, UserException {
TaskFlowJob job = new TaskFlowJob();
job.setName("JobWithInErrorTask");
ScriptTask scriptTask = new ScriptTask();
scriptTask.setName(inerrorTaskName);
scriptTask.setScript(new TaskScript(new SimpleScript("syntax error", "python")));
scriptTask.setOnTaskError(OnTaskError.PAUSE_TASK);
scriptTask.setMaxNumberOfExecution(2);
job.addTask(scriptTask);
job.setInputSpace(userspace);
job.setOutputSpace(userspace);
return job;
}
@Test
public void testTerminate() throws Exception {
restSmartProxy.terminate();
Assert.assertFalse(restSmartProxy.isConnected());
try {
restSmartProxy.getStatus();
fail("Using the restsmartproxy after termination should throw an exception");
} catch (Throwable t) {
} finally {
restSmartProxy = null;
}
}
@Test
public void testReconnection() throws Exception {
restSmartProxy.reconnect();
Assert.assertTrue(restSmartProxy.isConnected());
// try a random method and verify that no exception is thrown
restSmartProxy.getStatus();
restSmartProxy.disconnect();
Assert.assertFalse(restSmartProxy.isConnected());
}
private void testJobSubmission(boolean isolateTaskOutput, boolean automaticTransfer) throws Exception {
TaskFlowJob job = createTestJob(isolateTaskOutput);
printJobXmlRepresentation(job);
DataTransferNotifier notifier = new DataTransferNotifier();
if (automaticTransfer) {
restSmartProxy.addEventListener(notifier);
}
JobId id = restSmartProxy.submit(job,
inputLocalFolder.getAbsolutePath(),
pushUrl,
outputLocalFolder.getAbsolutePath(),
pullUrl,
isolateTaskOutput,
automaticTransfer);
JobState jobState = waitForJobFinishState(id.toString());
assertEquals(JobStatus.FINISHED, jobState.getStatus());
if (!automaticTransfer) {
for (int i = 0; i < NB_TASKS; i++) {
restSmartProxy.pullData(id.toString(), TASK_NAME + i, outputLocalFolder.getAbsolutePath());
}
} else {
List<String> taskNames = taskNameList();
while (!taskNames.isEmpty()) {
String finishedTask = notifier.finishedTask();
if (taskNames.contains(finishedTask)) {
taskNames.remove(finishedTask);
}
}
}
// check the presence of output files
for (int i = 0; i < NB_TASKS; i++) {
String outputFileName = OUTPUT_FILE_BASE_NAME + "_" + i + OUTPUT_FILE_EXT;
File outputFile = new File(outputLocalFolder, outputFileName);
Assert.assertTrue(String.format("%s does not exist.", outputFile.getAbsolutePath()), outputFile.exists());
}
}
private TaskFlowJob createTestJob(boolean isolateOutputs) throws Exception {
TaskFlowJob job = new TaskFlowJob();
// add a special character to the job name to ensure the job is parsed
// correctly by the server
job.setName(this.getClass().getSimpleName() + " é");
for (int i = 0; i < NB_TASKS; i++) {
JavaTask testTask = new JavaTask();
testTask.setName(TASK_NAME + i);
testTask.setExecutableClassName(SimpleJavaExecutable.class.getName());
testTask.setForkEnvironment(new ForkEnvironment());
File inputFile = new File(inputLocalFolder, INPUT_FILE_BASE_NAME + "_" + i + INPUT_FILE_EXT);
String outputFileName = OUTPUT_FILE_BASE_NAME + "_" + i + OUTPUT_FILE_EXT;
// delete files after the test is finished
File outputFile = new File(outputLocalFolder, outputFileName);
outputFile.deleteOnExit();
inputFile.deleteOnExit();
FileWriter fileWriter = new FileWriter(inputFile);
for (int j = 0; j <= Math.round(Math.random() * 100) + 1; j++) {
fileWriter.write("Some random input");
}
fileWriter.close();
// Add dummy input files, make sure no error happen
testTask.addInputFiles("DUMMY", InputAccessMode.TransferFromInputSpace);
testTask.addInputFiles(inputFile.getName(), InputAccessMode.TransferFromInputSpace);
if (isolateOutputs) {
testTask.addOutputFiles("*.out", OutputAccessMode.TransferToOutputSpace);
} else {
testTask.addOutputFiles(outputFileName, OutputAccessMode.TransferToOutputSpace);
}
job.addTask(testTask);
}
job.setInputSpace(userspace);
job.setOutputSpace(userspace);
return job;
}
private String uniqueSessionId() {
return String.format("TEST_SID_%s", Long.toHexString(System.currentTimeMillis()));
}
private List<String> taskNameList() {
List<String> taskNames = new ArrayList<>(NB_TASKS);
for (int i = 0; i < NB_TASKS; i++) {
taskNames.add(TASK_NAME + i);
}
return taskNames;
}
private static final class DataTransferNotifier implements SchedulerEventListenerExtended {
private final BlockingQueue<String> finishedTask = new ArrayBlockingQueue<>(NB_TASKS);
@Override
public void pullDataFailed(String jobId, String taskName, String localFolderPath, Throwable error) {
try {
finishedTask.put(taskName);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
@Override
public void pullDataFinished(String jobId, String taskName, String localFolderPath) {
try {
finishedTask.put(taskName);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
public String finishedTask() {
try {
return finishedTask.take();
} catch (InterruptedException e) {
throw Throwables.propagate(e);
}
}
@Override
public void jobStateUpdatedEvent(NotificationData<JobInfo> arg0) {
}
@Override
public void jobSubmittedEvent(JobState arg0) {
}
@Override
public void schedulerStateUpdatedEvent(SchedulerEvent arg0) {
}
@Override
public void taskStateUpdatedEvent(NotificationData<TaskInfo> arg0) {
}
@Override
public void usersUpdatedEvent(NotificationData<UserIdentification> arg0) {
}
@Override
public void jobUpdatedFullDataEvent(JobState job) {
}
}
}