/*
* Copyright 2013-2014 the original author or authors.
*
* 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 org.springframework.xd.shell.command;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.junit.After;
import org.junit.Before;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.shell.core.CommandResult;
import org.springframework.xd.dirt.test.ResourceStateVerifier;
import org.springframework.xd.shell.AbstractShellIntegrationTest;
import org.springframework.xd.shell.util.Table;
import org.springframework.xd.shell.util.TableRow;
/**
* Provides an @After JUnit lifecycle method that will destroy the jobs that were created by calling executeJobCreate
*
* @author Glenn Renfro
* @author Gunnar Hillert
* @author Ilayaperumal Gopinathan
* @author Mark Fisher
* @author David Turanski
*/
public abstract class AbstractJobIntegrationTest extends AbstractShellIntegrationTest {
public static final String MY_JOB = "myJob";
public static final String MY_TEST = "myTest";
public static final String MY_JOB_WITH_PARAMETERS = "myJobWithParameters";
public static final String JOB_WITH_PARAMETERS_DESCRIPTOR = "jobWithParameters";
public static final String JOB_WITH_STEP_EXECUTIONS = "jobWithStepExecutions";
public static final String MY_JOB_WITH_PARTITIONS = "myJobWithPartitions";
public static final String JOB_WITH_PARTITIONS_DESCRIPTOR = "jobWithPartitions";
public static final String NESTED_JOB_DESCRIPTOR = "nestedJob";
public static final String JAVA_CONFIG_JOB_DESCRIPTOR = "customJob";
private static final String NESTED_JOB_TASKLET = "nestedJob.xml";
public static final String INVALID_NESTED_JOB_DESCRIPTOR = "invalidJob";
private List<String> jobs = new ArrayList<String>();
private List<String> streams = new ArrayList<String>();
private final ConcurrentMap<String, PollableChannel> jobTapChannels = new ConcurrentHashMap<String, PollableChannel>();
private ResourceStateVerifier jobStateVerifier;
@Before
public void before() {
this.jobStateVerifier = integrationTestSupport.jobStateVerifier();
}
@After
public void after() {
for (String job : jobs) {
try {
executeJobDestroy(job);
}
catch (Exception e) {
// ignore
}
}
for (String stream : streams) {
try {
executeStreamDestroy(stream);
}
catch (Exception e) {
// ignore
}
}
}
/**
* Execute 'job destroy' for the supplied job names
*/
protected void executeJobDestroy(String... jobNames) {
for (String jobName : jobNames) {
CommandResult cr = executeCommand("job destroy --name " + jobName);
assertTrue("Failure to destroy job " + jobName + ". CommandResult = " + cr.toString(), cr.isSuccess());
jobStateVerifier.waitForDestroy(jobName);
}
}
protected CommandResult jobDestroy(String jobName) {
CommandResult result = getShell().executeCommand("job destroy --name " + jobName);
jobStateVerifier.waitForDestroy(jobName);
return result;
}
protected void executeStreamDestroy(String... streamNames) {
for (String streamName : streamNames) {
CommandResult cr = executeCommand("stream destroy --name " + streamName);
assertTrue("Failure to destroy stream " + streamName + ". CommandResult = " + cr.toString(),
cr.isSuccess());
jobStateVerifier.waitForDestroy(streamName);
}
}
protected void executeJobCreate(String jobName, String jobDefinition) {
executeJobCreate(jobName, jobDefinition, true);
}
protected String executeJobCreate(String jobDefinition) {
String jobName = generateJobName();
executeJobCreate(jobName, jobDefinition, true);
return jobName;
}
/**
* Execute job create for the supplied job name/definition, and verify the command result.
*/
protected void executeJobCreate(String jobName, String jobDefinition, boolean deploy) {
CommandResult cr = executeCommand("job create --definition \"" + jobDefinition + "\" --name " + jobName
+ (deploy ? " --deploy" : ""));
String prefix = (deploy) ? "Successfully created and deployed job '" : "Successfully created job '";
assertEquals(prefix + jobName + "'", cr.getResult());
jobs.add(jobName);
if (deploy) {
bindJobTap(jobName);
jobStateVerifier.waitForDeploy(jobName);
}
else {
jobStateVerifier.waitForCreate(jobName);
}
}
protected CommandResult createJob(String jobName, String definition) {
return createJob(jobName, definition, "true");
}
/**
* Execute job create without any assertions
*/
protected CommandResult createJob(String jobName, String definition, String deploy) {
return getShell().executeCommand(
"job create --definition \"" + definition + "\" --name " + jobName + " --deploy " + deploy);
}
/**
* Execute job deploy without any assertions
*/
protected CommandResult deployJob(String jobName) {
CommandResult result = getShell().executeCommand("job deploy " + jobName);
bindJobTap(jobName);
jobStateVerifier.waitForDeploy(jobName);
return result;
}
/**
* Execute job undeploy without any assertions
*/
protected CommandResult undeployJob(String jobName) {
CommandResult result = getShell().executeCommand("job undeploy " + jobName);
unbindJobTap(jobName);
jobStateVerifier.waitForUndeploy(jobName);
return result;
}
/**
* Launch a job that is already deployed
*/
protected void executeJobLaunch(String jobName, String jobParameters) {
CommandResult cr = executeCommand("job launch --name " + jobName + " --params " + jobParameters);
String prefix = "Successfully submitted launch request for job '";
assertEquals(prefix + jobName + "'", cr.getResult());
waitForJobCompletion(jobName);
}
/**
* Launch a job that is already deployed
*/
protected void executeJobLaunch(String jobName) {
CommandResult cr = executeCommand("job launch --name " + jobName);
String prefix = "Successfully submitted launch request for job '";
assertEquals(prefix + jobName + "'", cr.getResult());
waitForJobCompletion(jobName);
}
protected void checkForJobInList(String jobName, String jobDescriptor, boolean shouldBeDeployed) {
Table t = listJobs();
assertTrue(t.getRows().contains(
new TableRow().addValue(1, jobName).addValue(2, jobDescriptor).addValue(3,
shouldBeDeployed ? "deployed" : "undeployed")));
}
protected void checkForFail(CommandResult cr) {
assertTrue("Failure. CommandResult = " + cr.toString(), !cr.isSuccess());
}
protected void checkForSuccess(CommandResult cr) {
assertTrue("Failure. CommandResult = " + cr.toString(), cr.isSuccess());
}
protected void checkDeployedJobMessage(CommandResult cr, String jobName) {
assertEquals("Deployed job '" + jobName + "'", cr.getResult());
}
protected void checkUndeployedJobMessage(CommandResult cr, String jobName) {
assertEquals("Un-deployed Job '" + jobName + "'", cr.getResult());
}
protected void checkErrorMessages(CommandResult cr, String expectedMessage) {
assertTrue("Failure. CommandResult = " + cr.toString(),
cr.getException().getMessage().contains(expectedMessage));
}
protected void checkDuplicateJobErrorMessage(CommandResult cr, String jobName) {
checkErrorMessages(cr, "There is already a job named '" + jobName + "'");
}
protected void checkBatchJobExistsErrorMessage(CommandResult cr, String jobName) {
checkErrorMessages(cr, "Batch Job with the name " + jobName + " already exists");
}
protected void triggerJob(String jobName) {
String streamName = generateStreamName();
CommandResult cr = getShell().executeCommand(
"stream create --name " + streamName + " --definition \"trigger > "
+ getJobLaunchQueue(jobName) + "\" --deploy true");
streams.add(streamName);
waitForJobCompletion(jobName);
checkForSuccess(cr);
}
protected void triggerJobWithParams(String jobName, String parameters) {
String streamName = generateStreamName();
CommandResult cr = getShell().executeCommand(
String.format("stream create --name " + streamName
+ " --definition \"trigger --payload='%s' > " + getJobLaunchQueue(jobName)
+ "\" --deploy true", parameters.replaceAll("\"", "\\\\\"")));
streams.add(streamName);
waitForJobCompletion(jobName);
checkForSuccess(cr);
}
protected void triggerJobWithDelay(String jobName, String fixedDelay) {
String streamName = generateStreamName();
CommandResult cr = getShell().executeCommand(
"stream create --name " + streamName + " --definition \"trigger --fixedDelay=" + fixedDelay
+ " > " + getJobLaunchQueue(jobName) + "\" --deploy true");
streams.add(streamName);
waitForJobCompletion(jobName);
checkForSuccess(cr);
}
private Table listJobs() {
Object result = getShell().executeCommand("job list").getResult();
return (result instanceof Table) ? (Table) result : new Table();
}
protected Table listJobExecutions() {
return (Table) getShell().executeCommand("job execution list").getResult();
}
protected String displayJobExecution(String id) {
final CommandResult commandResult = getShell().executeCommand("job execution display " + id);
if (!commandResult.isSuccess()) {
throw new IllegalStateException("Expected a successful command execution.", commandResult.getException());
}
return (String) commandResult.getResult();
}
protected TableRow getJobExecutionRow(String jobName) {
for (TableRow row : listJobExecutions().getRows()) {
if (jobName.equals(row.getValue(2))) {
return row;
}
}
return null;
}
protected String getJobExecutionId(TableRow jobExecution) {
assertNotNull(jobExecution);
return jobExecution.getValue(1);
}
protected String getJobExecutionId(String jobName) {
return getJobExecutionId(getJobExecutionRow(jobName));
}
protected String getJobExecutionStatus(TableRow jobExecution) {
assertNotNull(jobExecution);
return jobExecution.getValue(5);
}
protected String getJobExecutionStatus(String jobName) {
return getJobExecutionStatus(getJobExecutionRow(jobName));
}
protected Table listStepExecutions(String id) {
final CommandResult commandResult = getShell().executeCommand("job execution step list " + id);
if (!commandResult.isSuccess()) {
throw new IllegalStateException("Expected a successful command execution.", commandResult.getException());
}
return (Table) commandResult.getResult();
}
protected Table getStepExecutionProgress(String jobExecutionId, String stepExecutionId) {
final CommandResult commandResult = getShell().executeCommand(
"job execution step progress " + stepExecutionId + " --jobExecutionId " + jobExecutionId);
if (!commandResult.isSuccess()) {
throw new IllegalStateException("Expected a successful command execution.", commandResult.getException());
}
return (Table) commandResult.getResult();
}
protected Table getDisplayStepExecution(String jobExecutionId, String stepExecutionId) {
final CommandResult commandResult = getShell().executeCommand(
"job execution step display " + stepExecutionId + " --jobExecutionId " + jobExecutionId);
if (!commandResult.isSuccess()) {
throw new IllegalStateException("Expected a successful command execution.", commandResult.getException());
}
return (Table) commandResult.getResult();
}
private void bindJobTap(String jobName) {
MessageChannel alreadyBound = jobTapChannels.putIfAbsent(jobName, new QueueChannel());
if (alreadyBound == null) {
getMessageBus().bindPubSubConsumer("tap:job:" + jobName, jobTapChannels.get(jobName), null);
}
}
private void unbindJobTap(String jobName) {
jobTapChannels.remove(jobName);
getMessageBus().unbindConsumers("tap:job:" + jobName);
}
private void waitForJobCompletion(String jobName) {
// could match on parameters if necessary (for disambiguation of concurrent executions)
PollableChannel channel = jobTapChannels.get(jobName);
Message<?> message = null;
do {
message = channel.receive(10000);
if (message != null) {
Object payload = message.getPayload();
if (payload instanceof JobExecution) {
// could add a waitForJobLaunch that returns as soon as it sees STARTED status
if (BatchStatus.COMPLETED.equals(((JobExecution) payload).getStatus())) {
return;
}
}
}
}
while (message != null);
throw new IllegalStateException(String.format("timed out while waiting for job: %s", jobName));
}
}