/*
* Copyright © 2014-2016 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.runtime;
import co.cask.cdap.AppWithAnonymousWorkflow;
import co.cask.cdap.MissingMapReduceWorkflowApp;
import co.cask.cdap.MissingSparkWorkflowApp;
import co.cask.cdap.NonUniqueProgramsInWorkflowApp;
import co.cask.cdap.NonUniqueProgramsInWorkflowWithForkApp;
import co.cask.cdap.OneActionWorkflowApp;
import co.cask.cdap.ScheduleAppWithMissingWorkflow;
import co.cask.cdap.WorkflowApp;
import co.cask.cdap.WorkflowSchedulesWithSameNameApp;
import co.cask.cdap.app.program.Program;
import co.cask.cdap.app.runtime.ProgramController;
import co.cask.cdap.app.runtime.ProgramOptions;
import co.cask.cdap.app.runtime.ProgramRunner;
import co.cask.cdap.app.runtime.ProgramRunnerFactory;
import co.cask.cdap.app.store.Store;
import co.cask.cdap.common.app.RunIds;
import co.cask.cdap.internal.AppFabricTestHelper;
import co.cask.cdap.internal.app.deploy.pipeline.ApplicationWithPrograms;
import co.cask.cdap.internal.app.runtime.AbstractListener;
import co.cask.cdap.internal.app.runtime.BasicArguments;
import co.cask.cdap.internal.app.runtime.ProgramOptionConstants;
import co.cask.cdap.internal.app.runtime.SimpleProgramOptions;
import co.cask.cdap.proto.ProgramType;
import co.cask.cdap.test.XSlowTests;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Injector;
import org.apache.twill.common.Threads;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import javax.annotation.Nullable;
/**
*
*/
@Category(XSlowTests.class)
public class WorkflowTest {
private static final Logger LOG = LoggerFactory.getLogger(WorkflowTest.class);
@ClassRule
public static TemporaryFolder tmpFolder = new TemporaryFolder();
private static final Supplier<File> TEMP_FOLDER_SUPPLIER = new Supplier<File>() {
@Override
public File get() {
try {
return tmpFolder.newFolder();
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
};
@Test(timeout = 120 * 1000L)
public void testWorkflow() throws Exception {
final ApplicationWithPrograms app = AppFabricTestHelper.deployApplicationWithManager(WorkflowApp.class,
TEMP_FOLDER_SUPPLIER);
final Injector injector = AppFabricTestHelper.getInjector();
ProgramRunnerFactory runnerFactory = injector.getInstance(ProgramRunnerFactory.class);
final Program program = Iterators.filter(app.getPrograms().iterator(), new Predicate<Program>() {
@Override
public boolean apply(Program input) {
return input.getType() == ProgramType.WORKFLOW;
}
}).next();
ProgramRunner programRunner = runnerFactory.create(program.getType());
String inputPath = createInput();
String outputPath = new File(tmpFolder.newFolder(), "output").getAbsolutePath();
final String runId = RunIds.generate().getId();
BasicArguments systemArgs = new BasicArguments(ImmutableMap.of(
ProgramOptionConstants.RUN_ID, runId,
ProgramOptionConstants.HOST, InetAddress.getLoopbackAddress().getCanonicalHostName()
));
BasicArguments userArgs = new BasicArguments(ImmutableMap.of("inputPath", inputPath, "outputPath", outputPath));
ProgramOptions options = new SimpleProgramOptions(program.getName(), systemArgs, userArgs);
final SettableFuture<String> completion = SettableFuture.create();
programRunner.run(program, options).addListener(new AbstractListener() {
@Override
public void init(ProgramController.State currentState, @Nullable Throwable cause) {
LOG.info("Starting");
injector.getInstance(Store.class).setStart(program.getId(), runId, System.currentTimeMillis());
}
@Override
public void completed() {
LOG.info("Completed");
completion.set("Completed");
}
@Override
public void error(Throwable cause) {
LOG.info("Error", cause);
completion.setException(cause);
}
}, Threads.SAME_THREAD_EXECUTOR);
completion.get();
}
@Test(timeout = 120 * 1000L)
public void testBadInputInWorkflow() throws Exception {
// try deploying app containing Workflow configured with non-existent MapReduce program
try {
AppFabricTestHelper.deployApplicationWithManager(MissingMapReduceWorkflowApp.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because MapReduce program is missing in the Application.");
} catch (Exception ex) {
Assert.assertEquals("MapReduce program 'SomeMapReduceProgram' is not configured with the Application.",
ex.getCause().getMessage());
}
// try deploying app containing Workflow configured with non-existent Spark program
try {
AppFabricTestHelper.deployApplicationWithManager(MissingSparkWorkflowApp.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because Spark program is missing in the Application.");
} catch (Exception ex) {
Assert.assertEquals("Spark program 'SomeSparkProgram' is not configured with the Application.",
ex.getCause().getMessage());
}
// try deploying app containing Workflow configured with multiple schedules with the same name
try {
AppFabricTestHelper.deployApplicationWithManager(WorkflowSchedulesWithSameNameApp.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because Workflow is configured with schedules having same name.");
} catch (Exception ex) {
Assert.assertEquals("Schedule with the name 'DailySchedule' already exists.",
ex.getCause().getCause().getMessage());
}
// try deploying app containing a schedule for non existent workflow
try {
AppFabricTestHelper.deployApplicationWithManager(ScheduleAppWithMissingWorkflow.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because Schedule is configured for non existent Workflow.");
} catch (Exception ex) {
Assert.assertEquals("Workflow 'NonExistentWorkflow' is not configured with the Application.",
ex.getCause().getMessage());
}
// try deploying app containing anonymous workflow
try {
AppFabricTestHelper.deployApplicationWithManager(AppWithAnonymousWorkflow.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because Workflow does not have name.");
} catch (Exception ex) {
Assert.assertEquals("'' name is not an ID. ID should be non empty and can contain only characters A-Za-z0-9_-",
ex.getCause().getMessage());
}
// try deploying app containing workflow with non-unique programs
try {
AppFabricTestHelper.deployApplicationWithManager(NonUniqueProgramsInWorkflowApp.class, TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because 'NoOpMR' added multiple times in the workflow " +
"'NonUniqueProgramsInWorkflow'.");
} catch (Exception ex) {
Assert.assertEquals("Node 'NoOpMR' already exists in workflow 'NonUniqueProgramsInWorkflow'.",
ex.getCause().getMessage());
}
// try deploying app containing workflow fork with non-unique programs
try {
AppFabricTestHelper.deployApplicationWithManager(NonUniqueProgramsInWorkflowWithForkApp.class,
TEMP_FOLDER_SUPPLIER);
Assert.fail("Should have thrown Exception because 'MyTestPredicate' added multiple times in the workflow " +
"'NonUniqueProgramsInWorkflowWithFork'");
} catch (Exception ex) {
Assert.assertEquals("Node 'MyTestPredicate' already exists in workflow 'NonUniqueProgramsInWorkflowWithFork'.",
ex.getCause().getMessage());
}
}
@Test(timeout = 120 * 1000L)
public void testOneActionWorkflow() throws Exception {
final ApplicationWithPrograms app = AppFabricTestHelper.deployApplicationWithManager(OneActionWorkflowApp.class,
TEMP_FOLDER_SUPPLIER);
final Injector injector = AppFabricTestHelper.getInjector();
ProgramRunnerFactory runnerFactory = injector.getInstance(ProgramRunnerFactory.class);
final Program program = Iterators.filter(app.getPrograms().iterator(), new Predicate<Program>() {
@Override
public boolean apply(Program input) {
return input.getType() == ProgramType.WORKFLOW;
}
}).next();
ProgramRunner programRunner = runnerFactory.create(program.getType());
final String runId = RunIds.generate().getId();
BasicArguments systemArgs = new BasicArguments(ImmutableMap.of(
ProgramOptionConstants.RUN_ID, runId,
ProgramOptionConstants.HOST, InetAddress.getLoopbackAddress().getCanonicalHostName()
));
ProgramOptions options = new SimpleProgramOptions(program.getName(), systemArgs, new BasicArguments());
final SettableFuture<String> completion = SettableFuture.create();
programRunner.run(program, options).addListener(new AbstractListener() {
@Override
public void init(ProgramController.State currentState, @Nullable Throwable cause) {
LOG.info("Initializing");
injector.getInstance(Store.class).setStart(program.getId(), runId, System.currentTimeMillis());
}
@Override
public void completed() {
LOG.info("Completed");
completion.set("Completed");
}
@Override
public void error(Throwable cause) {
LOG.info("Error", cause);
completion.setException(cause);
}
}, Threads.SAME_THREAD_EXECUTOR);
String run = completion.get();
Assert.assertEquals("Completed", run);
}
private String createInput() throws IOException {
File inputDir = tmpFolder.newFolder();
File inputFile = new File(inputDir.getPath() + "/words.txt");
inputFile.deleteOnExit();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(inputFile))) {
writer.write("this text has");
writer.newLine();
writer.write("two words text inside");
}
return inputDir.getAbsolutePath();
}
}