/*
* Copyright 2014-2015 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.integration.test;
import org.junit.After;
import org.junit.Before;
import org.springframework.batch.core.BatchStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.PagedResources;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import org.springframework.xd.integration.fixtures.Jobs;
import org.springframework.xd.integration.fixtures.ModuleType;
import org.springframework.xd.integration.util.StreamUtils;
import org.springframework.xd.rest.client.impl.SpringXDTemplate;
import org.springframework.xd.rest.domain.JobDefinitionResource;
import org.springframework.xd.rest.domain.JobExecutionInfoResource;
import org.springframework.xd.rest.domain.StepExecutionInfoResource;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.assertTrue;
/**
* /**
* Base Class for Spring XD Integration Job test classes
*
* @author Glenn Renfro
* @author Gunnar Hillert
*/
public abstract class AbstractJobTest extends AbstractIntegrationTest {
protected final static String JOB_NAME = "ec2Job3";
protected final static int DEFAULT_JOB_COMPLETE_COUNT = 1;
@Autowired
protected Jobs jobs;
private SpringXDTemplate springXDTemplate;
@Override
@Before
public void setup() {
initializer();
springXDTemplate = createSpringXDTemplate();
destroyAllJobs();
waitForEmptyJobList(WAIT_TIME);
StreamUtils.destroyAllStreams(adminServer);
waitForXD();
}
/**
* Destroys all streams created in the test.
*/
@Override
@After
public void tearDown() {
destroyAllJobs();
waitForEmptyJobList(WAIT_TIME);
}
/**
* Creates a job on the XD cluster defined by the test's
* Artifact or Environment variables Uses JOB_NAME as default job name.
*
* @param job the job definition
*/
public void job(String job) {
Assert.hasText(job, "job needs to be populated with a definition and can not be null");
job(JOB_NAME, job, true);
}
/**
* Launches a job with the test's JOB_NAME on the XD instance.
*/
public void jobLaunch() {
jobLaunch(JOB_NAME);
}
/**
* Launches a job on the XD instance
*
* @param jobName The name of the job to be launched
*/
public void jobLaunch(String jobName) {
launchJob(jobName, "");
}
/**
* Gets the host of the container where the job was deployed
*
* @return The host that contains the job.
*/
public String getContainerHostForJob() {
return getContainerHostForJob(JOB_NAME);
}
/**
* Gets the host of the container where the job was deployed
*
* @param jobName Used to find the container that contains the job.
* @return The host that contains the job.
*/
public String getContainerHostForJob(String jobName) {
return this.getContainerResolver().getContainerHostForModulePrefix(jobName, ModuleType.job);
}
/*
* Launches a job on the XD instance
*
* @param jobName The name of the job to be launched
* @param jobParameters the job parameters
*/
public void launchJob(String jobName, String jobParameters) {
Assert.hasText(jobName, "The jobName must not be empty nor null");
springXDTemplate.jobOperations().launchJob(jobName, jobParameters);
}
/**
* Deploys the job.
*/
public void deployJob() {
deployJob(JOB_NAME);
}
/**
* Deploys the job with the jobName.
*
* @param jobName The name of the job to deploy.
*/
public void deployJob(String jobName) {
Assert.hasText(jobName, "jobName must not be empty nor null");
springXDTemplate.jobOperations().deploy(jobName, Collections.<String, String>emptyMap());
}
/**
* Verifies that the description and status for the job name is as expected.
*
* @param jobDefinition the definition that the job should have.
* @param deployed if true the job should be deployed. if false the job should not be deployed.
* @return true if the job is as expected. false if the job is not.
*/
public boolean checkJob(String jobDefinition, boolean deployed) {
return checkJob(JOB_NAME, jobDefinition, deployed);
}
/**
* Verifies that the description and status for the job name is as expected.
*
* @param jobName The name of the job to evaluate.
* @param jobDefinition the definition that the job should have.
* @param deployed if true the job should be deployed. if false the job should not be deployed.
* @return true if the job is as expected. false if the job is not.
*/
public boolean checkJob(String jobName, String jobDefinition, boolean deployed) {
Assert.hasText(jobName, "jobName must not be empty nor null");
Assert.hasText(jobDefinition, "jobDefinition must not be empty nor null");
JobDefinitionResource resource = getJobDefinitionResource(jobName);
long timeout = System.currentTimeMillis() + WAIT_TIME;
String deployedStatus = (deployed) ? "deployed" : "undeployed";
boolean status = resource != null && deployedStatus.equals(resource.getStatus())
&& jobDefinition.equals(resource.getDefinition());
while (!status && System.currentTimeMillis() < timeout) {
sleepOneSecond();
resource = getJobDefinitionResource(jobName);
status = resource != null && deployedStatus.equals(resource.getStatus())
&& jobDefinition.equals(resource.getDefinition());
}
return status;
}
/**
* Waits until the number of "Complete" job executions reaches the jobCompleteCount.
*
* @param jobName The name of the job to evaluate.
* @param jobCompleteCount the number of job executions marked complete to return true.
* @param waitTime The milliseconds that the method should wait for the job execution to complete.
* @return True if the number of Complete Jobs is reached before waitTime, else false.
*/
protected boolean waitForJobToComplete(String jobName, long waitTime, int jobCompleteCount) {
Assert.hasText(jobName, "The job name must be specified.");
boolean isJobComplete = isJobComplete(jobName, jobCompleteCount);
long timeout = System.currentTimeMillis() + waitTime;
while (!isJobComplete && System.currentTimeMillis() < timeout) {
sleepOneSecond();
isJobComplete = isJobComplete(jobName, jobCompleteCount);
}
return isJobComplete;
}
/**
* Wait until the job is complete up to default WAIT_TIME. The method will return true
* if any of the job executions are marked complete.
* Hint: use a unique jobName, to guarantee that you will get zero or one jobExecution.
*
* @param jobName The name of the job to evaluate.
*/
protected boolean waitForJobToComplete(String jobName) {
return waitForJobToComplete(jobName, DEFAULT_JOB_COMPLETE_COUNT);
}
/**
* Waits until the number of "Complete" job executions reaches the jobCompleteCount.
*
* @param jobName The name of the job to evaluate.
* @param jobCompleteCount The number of job executions marked complete before returning true.
*/
protected boolean waitForJobToComplete(String jobName, int jobCompleteCount) {
Assert.hasText(jobName, "The job name must be specified.");
return waitForJobToComplete(jobName, WAIT_TIME, jobCompleteCount);
}
/**
* Checks to see if the execution for the job is complete.
* Returns true when the number of job complete executions matches the job completeCount.
*
* @param jobName The name of the job to be evaluated.
* @param jobCompleteCount The number of executions that reached complete to return true.
* @return true if the job is deployed else false
*/
private boolean isJobComplete(String jobName, int jobCompleteCount) {
List<JobExecutionInfoResource> resources = getJobExecInfoByName(jobName);
Iterator<JobExecutionInfoResource> resourceIter = resources.iterator();
int count = 0;
while (resourceIter.hasNext()) {
JobExecutionInfoResource resource = resourceIter.next();
if (jobName.equals(resource.getName())) {
if (BatchStatus.COMPLETED.equals(resource.getJobExecution().getStatus())) {
count++;
break;
} else {
break;
}
}
}
return jobCompleteCount == count;
}
/**
* Creates the job definition and deploys it to the cluster being tested.
*
* @param jobName The name of the job
* @param jobDefinition The definition that needs to be deployed for this job.
* @param isDeploy true deploy the job. False do not deploy the job.
*/
protected void job(final String jobName, final String jobDefinition, boolean isDeploy) {
Assert.hasText(jobName, "The job name must be specified.");
Assert.notNull(jobDefinition, "a job definition must not be empty.");
springXDTemplate.jobOperations().createJob(jobName, jobDefinition, isDeploy);
String action = (isDeploy) ? "deploy" : "undeploy";
assertTrue("The job did not " + action + ". ",
waitForJobDeploymentChange(jobName, WAIT_TIME, isDeploy));
}
/**
* Removes all the jobs from the cluster. Used to guarantee a clean acceptance test.
*/
protected void destroyAllJobs() {
springXDTemplate.jobOperations().destroyAll();
}
/**
* Undeploys the job
*/
protected void undeployJob() {
undeployJob(JOB_NAME);
}
/*
* Destroys the job
*/
protected void destroyJob() {
destroyJob(JOB_NAME);
}
/**
* Destroys the specified job
*
* @param jobName The name of the job to destroy
*/
protected void destroyJob(final String jobName) {
Assert.hasText(jobName, "The jobName must not be empty nor null");
springXDTemplate.jobOperations().destroy(jobName);
}
/**
* Undeploys the specified job
*
* @param jobName The name of the job to undeploy
*/
protected void undeployJob(final String jobName) {
Assert.hasText(jobName, "The jobName must not be empty nor null");
springXDTemplate.jobOperations().undeploy(jobName);
}
/**
* Retrieve a list of JobExecutionInfoResources that have the name contained in the jobName parameter
*
* @param jobName The search name
* @return a list of JobExecutionInfoResources
*/
protected List<JobExecutionInfoResource> getJobExecInfoByName(String jobName) {
PagedResources<JobExecutionInfoResource> jobExecutions = springXDTemplate.jobOperations().listJobExecutions();
Iterator<JobExecutionInfoResource> iter = jobExecutions.iterator();
List<JobExecutionInfoResource> result = new ArrayList<JobExecutionInfoResource>();
while (iter.hasNext()) {
JobExecutionInfoResource resource = iter.next();
if (resource.getName().equals(jobName)) {
result.add(resource);
}
}
return result;
}
/**
* Retrieves a list of step executions for the execution Id.
*
* @param executionId The executionId that will be searched.
* @return a list if StepExecutionInfoResources
*/
protected List<StepExecutionInfoResource> getStepExecutions(long executionId) {
return springXDTemplate.jobOperations().listStepExecutions(executionId);
}
/**
* Waits up to the wait time for a job to be deployed or undeployed.
*
* @param jobName The name of the job to be evaluated.
* @param waitTime the amount of time in millis to wait.
* @param isDeployed if true the method will wait for the job to be deployed. If false it will wait for the job to become undeployed.
* @return true if the job is deployed else false.
*/
protected boolean waitForJobDeploymentChange(String jobName, int waitTime, boolean isDeployed) {
boolean result = isJobDeployed(jobName);
long timeout = System.currentTimeMillis() + waitTime;
while (!result && System.currentTimeMillis() < timeout) {
sleepOneSecond();
result = isDeployed ? isJobDeployed(jobName) : isJobUndeployed(jobName);
}
return result;
}
/**
* Checks to see if the specified job is deployed on the XD cluster.
*
* @param jobName The name of the job to be evaluated.
* @return true if the job is deployed else false
*/
protected boolean isJobDeployed(String jobName) {
Assert.hasText(jobName, "The job name must be specified.");
JobDefinitionResource resource = getJobDefinitionResource(jobName);
boolean result = false;
if ("deployed".equals(resource.getStatus())) {
result = true;
}
return result;
}
/**
* Checks to see if the specified job is undeployed on the XD cluster.
*
* @param jobName The name of the job to be evaluated.
* @return true if the job is deployed else false
*/
protected boolean isJobUndeployed(String jobName) {
Assert.hasText(jobName, "The job name must be specified.");
JobDefinitionResource resource = getJobDefinitionResource(jobName);
boolean result = false;
if ("undeployed".equals(resource.getStatus())) {
result = true;
}
return result;
}
/**
* Wait up to the specified time for the job list to become empty.
*
* @param waitTime The time in Millis to wait.
*/
protected void waitForEmptyJobList(int waitTime) {
PagedResources<JobDefinitionResource> resources = springXDTemplate.jobOperations().list();
int jobSize = resources.getContent().size();
long timeout = System.currentTimeMillis() + waitTime;
while (jobSize > 0 && System.currentTimeMillis() < timeout) {
sleepOneSecond();
jobSize = resources.getContent().size();
}
}
/**
* Create an new instance of the SpringXDTemplate given the Admin Server URL
*
* @return A new instance of SpringXDTemplate
*/
private SpringXDTemplate createSpringXDTemplate() {
try {
return new SpringXDTemplate(adminServer.toURI());
} catch (URISyntaxException uriException) {
throw new IllegalStateException(uriException.getMessage(), uriException);
}
}
private JobDefinitionResource getJobDefinitionResource(String jobName) {
PagedResources<JobDefinitionResource> resources = springXDTemplate.jobOperations().list();
long timeout = System.currentTimeMillis() + WAIT_TIME;
while (!resources.iterator().hasNext() && System.currentTimeMillis() < timeout) {
sleepOneSecond();
resources = springXDTemplate.jobOperations().list();
}
JobDefinitionResource result = null;
Iterator<JobDefinitionResource> resourceIter = resources.iterator();
while (resourceIter.hasNext()) {
JobDefinitionResource resource = resourceIter.next();
if (jobName.equals(resource.getName())) {
result = resource;
break;
}
}
return result;
}
private void sleepOneSecond() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e.getMessage(), e);
}
}
/**
* Retrieves the first job execution from a list of job executions for the job name.
*
* @param jobName a unique job name.
*/
protected BatchStatus getJobExecutionStatus(String jobName) {
List<JobExecutionInfoResource> results = getJobExecInfoByName(jobName);
Assert.isTrue(results.size() > 0, "No Job execution available for the job.");
return results.get(0).getJobExecution().getStatus();
}
/**
* Requests the steps execution details for a specific step from the admin server.
*
* @param jobExecutionId The job execution id of the step to be interrogated.
* @param stepExecutionId The step execution id of the step that will be interrogated.
* @return The JSon Returned from the step execution request.
*/
protected String getStepResultJson(long jobExecutionId, long stepExecutionId) {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject(
"{server}/jobs/executions/{jobexecutionid}/steps/{stepExecutionID}",
String.class, getEnvironment().getAdminServerUrl(), jobExecutionId,
stepExecutionId);
}
}