/* * * Copyright 2016 Netflix, 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 com.netflix.genie.web.controllers; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.netflix.genie.common.dto.Application; import com.netflix.genie.common.dto.ApplicationStatus; import com.netflix.genie.common.dto.Cluster; import com.netflix.genie.common.dto.ClusterCriteria; import com.netflix.genie.common.dto.ClusterStatus; import com.netflix.genie.common.dto.Command; import com.netflix.genie.common.dto.CommandStatus; import com.netflix.genie.common.dto.JobExecution; import com.netflix.genie.common.dto.JobRequest; import com.netflix.genie.common.dto.JobStatus; import com.netflix.genie.core.jpa.repositories.JpaApplicationRepository; import com.netflix.genie.core.jpa.repositories.JpaClusterRepository; import com.netflix.genie.core.jpa.repositories.JpaCommandRepository; import com.netflix.genie.core.jpa.repositories.JpaJobExecutionRepository; import com.netflix.genie.core.jpa.repositories.JpaJobMetadataRepository; import com.netflix.genie.core.jpa.repositories.JpaJobRepository; import com.netflix.genie.core.jpa.repositories.JpaJobRequestRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.SystemUtils; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.hateoas.MediaTypes; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.headers.HeaderDocumentation; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.operation.preprocess.Preprocessors; import org.springframework.restdocs.payload.PayloadDocumentation; import org.springframework.restdocs.request.RequestDocumentation; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; /** * Integration tests for Jobs REST API. * * @author amsharma * @author tgianos * @since 3.0.0 */ @Slf4j public class JobRestControllerIntegrationTests extends RestControllerIntegrationTestsBase { private static final long SLEEP_TIME = 1000L; private static final String COMMAND_ARGS_PATH = "$.commandArgs"; private static final String STATUS_MESSAGE_PATH = "$.statusMsg"; private static final String CLUSTER_NAME_PATH = "$.clusterName"; private static final String COMMAND_NAME_PATH = "$.commandName"; private static final String ARCHIVE_LOCATION_PATH = "$.archiveLocation"; private static final String STARTED_PATH = "$.started"; private static final String FINISHED_PATH = "$.finished"; private static final String CLUSTER_CRITERIAS_PATH = "$.clusterCriterias"; private static final String COMMAND_CRITERIA_PATH = "$.commandCriteria"; private static final String GROUP_PATH = "$.group"; private static final String DISABLE_LOG_ARCHIVAL_PATH = "$.disableLogArchival"; private static final String EMAIL_PATH = "$.email"; private static final String CPU_PATH = "$.cpu"; private static final String MEMORY_PATH = "$.memory"; private static final String DEPENDENCIES_PATH = "$.dependencies"; private static final String APPLICATIONS_PATH = "$.applications"; private static final String HOST_NAME_PATH = "$.hostName"; private static final String PROCESS_ID_PATH = "$.processId"; private static final String CHECK_DELAY_PATH = "$.checkDelay"; private static final String EXIT_CODE_PATH = "$.exitCode"; private static final long CHECK_DELAY = 1L; private static final String BASE_DIR = "com/netflix/genie/web/controllers/JobRestControllerIntegrationTests/"; private static final String FILE_DELIMITER = "/"; private static final String JOB_NAME = "List * ... Directories bash job"; private static final String JOB_USER = "genie"; private static final String JOB_VERSION = "1.0"; private static final String JOB_DESCRIPTION = "Genie 3 Test Job"; private static final String JOB_STATUS_MSG = "Job finished successfully."; private static final String APP1_ID = "app1"; private static final String APP1_NAME = "Application 1"; private static final String APP1_USER = "genie"; private static final String APP1_VERSION = "1.0"; private static final String APP2_ID = "app2"; private static final String APP2_NAME = "Application 2"; private static final String CMD1_ID = "cmd1"; private static final String CMD1_NAME = "Unix Bash command"; private static final String CMD1_USER = "genie"; private static final String CMD1_VERSION = "1.0"; private static final String CMD1_EXECUTABLE = "/bin/bash"; private static final String CLUSTER1_ID = "cluster1"; private static final String CLUSTER1_NAME = "Local laptop"; private static final String CLUSTER1_USER = "genie"; private static final String CLUSTER1_VERSION = "1.0"; private static final String JOBS_LIST_PATH = EMBEDDED_PATH + ".jobSearchResultList"; private ResourceLoader resourceLoader; @Autowired private JpaJobRepository jobRepository; @Autowired private JpaJobRequestRepository jobRequestRepository; @Autowired private JpaJobMetadataRepository jobRequestMetadataRepository; @Autowired private JpaJobExecutionRepository jobExecutionRepository; @Autowired private JpaApplicationRepository applicationRepository; @Autowired private JpaCommandRepository commandRepository; @Autowired private JpaClusterRepository clusterRepository; @Autowired private String hostname; @Autowired private Resource jobDirResource; @Value("${genie.file.cache.location}") private String baseCacheLocation; /** * Setup for tests. * * @throws Exception If there is an error. */ @Before public void setup() throws Exception { this.resourceLoader = new DefaultResourceLoader(); //this.jobsBaseUrl = "http://localhost:" + this.port + "/api/v3/jobs"; createAnApplication(APP1_ID, APP1_NAME); createAnApplication(APP2_ID, APP2_NAME); createAllClusters(); createAllCommands(); linkAllEntities(); } private void linkAllEntities() throws Exception { final List<String> apps = new ArrayList<>(); apps.add(APP1_ID); apps.add(APP2_ID); this.mvc .perform( MockMvcRequestBuilders .post(COMMANDS_API + FILE_DELIMITER + CMD1_ID + FILE_DELIMITER + APPLICATIONS_LINK_KEY) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(apps)) ) .andExpect(MockMvcResultMatchers.status().isNoContent()); final List<String> cmds = Lists.newArrayList(CMD1_ID); this.mvc .perform( MockMvcRequestBuilders .post(CLUSTERS_API + FILE_DELIMITER + CLUSTER1_ID + FILE_DELIMITER + COMMANDS_LINK_KEY) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(cmds)) ) .andExpect(MockMvcResultMatchers.status().isNoContent()); } private void createAnApplication(final String id, final String appName) throws Exception { final String setUpFile = this.resourceLoader.getResource( BASE_DIR + id + FILE_DELIMITER + "setupfile" ).getFile().getAbsolutePath(); final Set<String> app1Dependencies = new HashSet<>(); final String depFile1 = this.resourceLoader.getResource( BASE_DIR + id + FILE_DELIMITER + "dep1" ).getFile().getAbsolutePath(); final String depFile2 = this.resourceLoader.getResource( BASE_DIR + id + FILE_DELIMITER + "dep2" ).getFile().getAbsolutePath(); app1Dependencies.add(depFile1); app1Dependencies.add(depFile2); final Set<String> app1Configs = new HashSet<>(); final String configFile1 = this.resourceLoader.getResource( BASE_DIR + id + FILE_DELIMITER + "config1" ).getFile().getAbsolutePath(); final String configFile2 = this.resourceLoader.getResource( BASE_DIR + id + FILE_DELIMITER + "config2" ).getFile().getAbsolutePath(); app1Configs.add(configFile1); app1Configs.add(configFile2); final Application app = new Application.Builder( appName, APP1_USER, APP1_VERSION, ApplicationStatus.ACTIVE) .withId(id) .withSetupFile(setUpFile) .withConfigs(app1Configs) .withDependencies(app1Dependencies) .build(); this.mvc .perform( MockMvcRequestBuilders .post(APPLICATIONS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(app)) ) .andExpect(MockMvcResultMatchers.status().isCreated()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())); } private void createAllClusters() throws Exception { final String setUpFile = this.resourceLoader.getResource( BASE_DIR + CLUSTER1_ID + FILE_DELIMITER + "setupfile" ).getFile().getAbsolutePath(); final Set<String> configs = new HashSet<>(); final String configFile1 = this.resourceLoader.getResource( BASE_DIR + CLUSTER1_ID + FILE_DELIMITER + "config1" ).getFile().getAbsolutePath(); final String configFile2 = this.resourceLoader.getResource( BASE_DIR + CLUSTER1_ID + FILE_DELIMITER + "config2" ).getFile().getAbsolutePath(); configs.add(configFile1); configs.add(configFile2); final Set<String> tags = new HashSet<>(); tags.add("localhost"); final Cluster cluster = new Cluster.Builder( CLUSTER1_NAME, CLUSTER1_USER, CLUSTER1_VERSION, ClusterStatus.UP ) .withId(CLUSTER1_ID) .withSetupFile(setUpFile) .withConfigs(configs) .withTags(tags) .build(); this.mvc .perform( MockMvcRequestBuilders .post(CLUSTERS_API) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(cluster)) ) .andExpect(MockMvcResultMatchers.status().isCreated()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())); } private void createAllCommands() throws Exception { final String setUpFile = this.resourceLoader.getResource( BASE_DIR + CMD1_ID + FILE_DELIMITER + "setupfile" ).getFile().getAbsolutePath(); final Set<String> configs = new HashSet<>(); final String configFile1 = this.resourceLoader.getResource( BASE_DIR + CMD1_ID + FILE_DELIMITER + "config1" ).getFile().getAbsolutePath(); final String configFile2 = this.resourceLoader.getResource( BASE_DIR + CMD1_ID + FILE_DELIMITER + "config2" ).getFile().getAbsolutePath(); configs.add(configFile1); configs.add(configFile2); final Set<String> tags = new HashSet<>(); tags.add("bash"); final Command cmd = new Command.Builder( CMD1_NAME, CMD1_USER, CMD1_VERSION, CommandStatus.ACTIVE, CMD1_EXECUTABLE, CHECK_DELAY ) .withId(CMD1_ID) .withSetupFile(setUpFile) .withConfigs(configs) .withTags(tags) .build(); this.mvc .perform( MockMvcRequestBuilders .post(COMMANDS_API) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(cmd)) ) .andExpect(MockMvcResultMatchers.status().isCreated()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())); } /** * Cleanup after tests. * * @throws Exception who cares */ @After public void cleanup() throws Exception { this.jobRequestMetadataRepository.deleteAll(); this.jobExecutionRepository.deleteAll(); this.jobRepository.deleteAll(); this.jobRequestRepository.deleteAll(); this.clusterRepository.deleteAll(); this.commandRepository.deleteAll(); this.applicationRepository.deleteAll(); } /** * Test the job submit method for success. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodSuccess() throws Exception { this.submitAndCheckJob(1); } private void submitAndCheckJob(final int documentationId) throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'echo hello world'"; final String clusterTag = "localhost"; final List<ClusterCriteria> clusterCriteriaList = Lists.newArrayList( new ClusterCriteria(Sets.newHashSet(clusterTag)) ); final String setUpFile = this.resourceLoader .getResource(BASE_DIR + "job" + FILE_DELIMITER + "jobsetupfile") .getFile() .getAbsolutePath(); final String depFile1 = this.resourceLoader .getResource(BASE_DIR + "job" + FILE_DELIMITER + "dep1") .getFile() .getAbsolutePath(); final Set<String> dependencies = Sets.newHashSet(depFile1); final String commandTag = "bash"; final Set<String> commandCriteria = Sets.newHashSet(commandTag); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withDisableLogArchival(true) .withSetupFile(setUpFile) .withDependencies(dependencies) .withDescription(JOB_DESCRIPTION) .build(); final String id = this.submitJob(documentationId, jobRequest, null); this.waitForDone(id); this.checkJobStatus(documentationId, id); this.checkJob(documentationId, id, commandArgs); this.checkJobOutput(documentationId, id); this.checkJobRequest(documentationId, id, commandArgs, setUpFile, clusterTag, commandTag, depFile1); this.checkJobExecution(documentationId, id); this.checkJobCluster(documentationId, id); this.checkJobCommand(documentationId, id); this.checkJobApplications(documentationId, id); this.checkFindJobs(documentationId, id, JOB_USER); Assert.assertThat(this.jobRepository.count(), Matchers.is(1L)); Assert.assertThat(this.jobRequestRepository.count(), Matchers.is(1L)); Assert.assertThat(this.jobRequestMetadataRepository.count(), Matchers.is(1L)); Assert.assertThat(this.jobExecutionRepository.count(), Matchers.is(1L)); // Check if the cluster setup file is cached final String clusterSetUpFilePath = this.resourceLoader .getResource(BASE_DIR + CMD1_ID + FILE_DELIMITER + "setupfile") .getFile() .getAbsolutePath(); Assert.assertTrue( Files.exists( Paths.get( new URI(this.baseCacheLocation).getPath(), UUID.nameUUIDFromBytes(clusterSetUpFilePath.getBytes(Charset.forName("UTF-8"))).toString() ) ) ); // Test for conflicts this.testForConflicts(id, commandArgs, clusterCriteriaList, commandCriteria); } private String submitJob( final int documentationId, final JobRequest jobRequest, final List<MockMultipartFile> attachments ) throws Exception { final MvcResult result; if (attachments != null) { final RestDocumentationResultHandler createResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/submitJobWithAttachments/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), HeaderDocumentation.requestHeaders( HeaderDocumentation .headerWithName(HttpHeaders.CONTENT_TYPE) .description(MediaType.MULTIPART_FORM_DATA_VALUE) ), // Request headers RequestDocumentation.requestParts( RequestDocumentation .partWithName("request") .description("The job request JSON. Content type must be application/json for part"), RequestDocumentation .partWithName("attachment") .description("An attachment file. There can be multiple. Type should be octet-stream") ), // Request parts Snippets.LOCATION_HEADER // Response Headers ); final MockMultipartFile json = new MockMultipartFile( "request", "", MediaType.APPLICATION_JSON_VALUE, this.objectMapper.writeValueAsBytes(jobRequest) ); final MockMultipartHttpServletRequestBuilder builder = RestDocumentationRequestBuilders .fileUpload(JOBS_API) .file(json); for (final MockMultipartFile attachment : attachments) { builder.file(attachment); } builder.contentType(MediaType.MULTIPART_FORM_DATA); result = this.mvc .perform(builder) .andExpect(MockMvcResultMatchers.status().isAccepted()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) .andDo(createResultHandler) .andReturn(); } else { // Use regular POST final RestDocumentationResultHandler createResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/submitJobWithoutAttachments/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.CONTENT_TYPE_HEADER, // Request headers Snippets.getJobRequestRequestPayload(), // Request Fields Snippets.LOCATION_HEADER // Response Headers ); result = this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(jobRequest)) ) .andExpect(MockMvcResultMatchers.status().isAccepted()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) .andDo(createResultHandler) .andReturn(); } return this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); } private void checkJobStatus(final int documentationId, final String id) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobStatus/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.JSON_CONTENT_TYPE_HEADER, // Response Headers PayloadDocumentation.responseFields( PayloadDocumentation .fieldWithPath("status") .description("The job status. One of: " + Arrays.toString(JobStatus.values())) .attributes(Snippets.EMPTY_CONSTRAINTS) ) // Response fields ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/status", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(JobStatus.SUCCEEDED.toString()))) .andDo(getResultHandler); } private void checkJob(final int documentationId, final String id, final String commandArgs) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJob/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers Snippets.getJobResponsePayload(), // Response fields Snippets.JOB_LINKS // Links ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id))) .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(JOB_VERSION))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(JOB_USER))) .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(JOB_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is(JOB_DESCRIPTION))) .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_ARGS_PATH, Matchers.is(commandArgs))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(JobStatus.SUCCEEDED.toString()))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_MESSAGE_PATH, Matchers.is(JOB_STATUS_MSG))) .andExpect(MockMvcResultMatchers.jsonPath(STARTED_PATH, Matchers.not(new Date(0)))) .andExpect(MockMvcResultMatchers.jsonPath(FINISHED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(ARCHIVE_LOCATION_PATH, Matchers.isEmptyOrNullString())) .andExpect(MockMvcResultMatchers.jsonPath(CLUSTER_NAME_PATH, Matchers.is(CLUSTER1_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_NAME_PATH, Matchers.is(CMD1_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH + ".*", Matchers.hasSize(8))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey(SELF_LINK_KEY))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("request"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("execution"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("output"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("status"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("cluster"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("command"))) .andExpect(MockMvcResultMatchers.jsonPath(LINKS_PATH, Matchers.hasKey("applications"))) .andDo(getResultHandler); } private void checkJobOutput(final int documentationId, final String id) throws Exception { // Check getting a directory as json final RestDocumentationResultHandler jsonResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobOutput/json/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM.and( RequestDocumentation .parameterWithName("filePath") .description("The path to the directory to get") .optional() ), // Path parameters HeaderDocumentation.requestHeaders( HeaderDocumentation .headerWithName(HttpHeaders.ACCEPT) .description(MediaType.APPLICATION_JSON_VALUE) .optional() ), // Request header HeaderDocumentation.responseHeaders( HeaderDocumentation .headerWithName(HttpHeaders.CONTENT_TYPE) .description(MediaType.APPLICATION_JSON_VALUE) ), // Response Headers Snippets.OUTPUT_DIRECTORY_FIELDS ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/output/{filePath}", id, "")) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.jsonPath("parent", Matchers.isEmptyOrNullString())) .andExpect(MockMvcResultMatchers.jsonPath("$.directories[0].name", Matchers.is("genie/"))) .andExpect(MockMvcResultMatchers.jsonPath("$.files[0].name", Matchers.is("dep1"))) .andExpect(MockMvcResultMatchers.jsonPath("$.files[1].name", Matchers.is("jobsetupfile"))) .andExpect(MockMvcResultMatchers.jsonPath("$.files[2].name", Matchers.is("run"))) .andExpect(MockMvcResultMatchers.jsonPath("$.files[3].name", Matchers.is("stderr"))) .andExpect(MockMvcResultMatchers.jsonPath("$.files[4].name", Matchers.is("stdout"))) .andDo(jsonResultHandler); // Check getting a directory as HTML final RestDocumentationResultHandler htmlResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobOutput/html/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM.and( RequestDocumentation .parameterWithName("filePath") .description("The path to the directory to get") .optional() ), // Path parameters HeaderDocumentation.requestHeaders( HeaderDocumentation .headerWithName(HttpHeaders.ACCEPT) .description(MediaType.TEXT_HTML) ), // Request header HeaderDocumentation.responseHeaders( HeaderDocumentation .headerWithName(HttpHeaders.CONTENT_TYPE) .description(MediaType.TEXT_HTML) ) // Response Headers ); this.mvc .perform(RestDocumentationRequestBuilders .get(JOBS_API + "/{id}/output/{filePath}", id, "") .accept(MediaType.TEXT_HTML) ) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andDo(htmlResultHandler); // Check getting a file // Check getting a directory as HTML final RestDocumentationResultHandler fileResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobOutput/file/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM.and( RequestDocumentation .parameterWithName("filePath") .description("The path to the file to get") .optional() ), // Path parameters HeaderDocumentation.requestHeaders( HeaderDocumentation .headerWithName(HttpHeaders.ACCEPT) .description(MediaType.ALL_VALUE) .optional() ), // Request header HeaderDocumentation.responseHeaders( HeaderDocumentation .headerWithName(HttpHeaders.CONTENT_TYPE) .description("The content type of the file being returned") .optional() ) // Response Headers ); // check that the generated run file is correct final String runShFileName = SystemUtils.IS_OS_LINUX ? "linux-runsh.txt" : "non-linux-runsh.txt"; final String runShFile = this.resourceLoader .getResource(BASE_DIR + runShFileName) .getFile() .getAbsolutePath(); final String runFileContents = new String(Files.readAllBytes(Paths.get(runShFile)), "UTF-8"); final String jobWorkingDir = this.jobDirResource.getFile().getCanonicalPath() + FILE_DELIMITER + id; final String expectedRunScriptContent = this.getExpectedRunContents(runFileContents, jobWorkingDir, id); this.mvc .perform(RestDocumentationRequestBuilders .get(JOBS_API + "/{id}/output/{filePath}", id, "run") ) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string(expectedRunScriptContent)) .andDo(fileResultHandler); } private void checkJobRequest( final int documentationId, final String id, final String commandArgs, final String setupFile, final String clusterTag, final String commandTag, final String depFile1 ) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobRequest/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers Snippets.getJobRequestResponsePayload(), // Response fields Snippets.JOB_REQUEST_LINKS // Links ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/request", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id))) .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(JOB_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(JOB_VERSION))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(JOB_USER))) .andExpect(MockMvcResultMatchers.jsonPath(DESCRIPTION_PATH, Matchers.is(JOB_DESCRIPTION))) .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_ARGS_PATH, Matchers.is(commandArgs))) .andExpect(MockMvcResultMatchers.jsonPath(SETUP_FILE_PATH, Matchers.is(setupFile))) .andExpect(MockMvcResultMatchers.jsonPath(CLUSTER_CRITERIAS_PATH, Matchers.hasSize(1))) .andExpect(MockMvcResultMatchers.jsonPath(CLUSTER_CRITERIAS_PATH + "[0].tags", Matchers.hasSize(1))) .andExpect(MockMvcResultMatchers.jsonPath(CLUSTER_CRITERIAS_PATH + "[0].tags[0]", Matchers.is(clusterTag))) .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_CRITERIA_PATH, Matchers.hasSize(1))) .andExpect(MockMvcResultMatchers.jsonPath(COMMAND_CRITERIA_PATH + "[0]", Matchers.is(commandTag))) .andExpect(MockMvcResultMatchers.jsonPath(GROUP_PATH, Matchers.nullValue())) .andExpect(MockMvcResultMatchers.jsonPath(DISABLE_LOG_ARCHIVAL_PATH, Matchers.is(true))) .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH, Matchers.hasSize(1))) .andExpect(MockMvcResultMatchers.jsonPath(DEPENDENCIES_PATH + "[0]", Matchers.is(depFile1))) .andExpect(MockMvcResultMatchers.jsonPath(EMAIL_PATH, Matchers.nullValue())) .andExpect(MockMvcResultMatchers.jsonPath(CPU_PATH, Matchers.nullValue())) .andExpect(MockMvcResultMatchers.jsonPath(MEMORY_PATH, Matchers.nullValue())) .andExpect(MockMvcResultMatchers.jsonPath(APPLICATIONS_PATH, Matchers.empty())) .andDo(getResultHandler); } private void checkJobExecution(final int documentationId, final String id) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobExecution/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers Snippets.getJobExecutionResponsePayload(), // Response fields Snippets.JOB_EXECUTION_LINKS // Links ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/execution", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id))) .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(HOST_NAME_PATH, Matchers.is(this.hostname))) .andExpect(MockMvcResultMatchers.jsonPath(PROCESS_ID_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(CHECK_DELAY_PATH, Matchers.is((int) CHECK_DELAY))) .andExpect(MockMvcResultMatchers.jsonPath(EXIT_CODE_PATH, Matchers.is(JobExecution.SUCCESS_EXIT_CODE))) .andDo(getResultHandler); } private void checkJobCluster(final int documentationId, final String id) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobCluster/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers Snippets.getClusterResponsePayload(), // Response fields Snippets.CLUSTER_LINKS // Links ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/cluster", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(CLUSTER1_ID))) .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(CLUSTER1_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(CLUSTER1_USER))) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(CLUSTER1_VERSION))) .andDo(getResultHandler); } private void checkJobCommand(final int documentationId, final String id) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobCommand/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers Snippets.getCommandResponsePayload(), // Response fields Snippets.COMMAND_LINKS // Links ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/command", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(CMD1_ID))) .andExpect(MockMvcResultMatchers.jsonPath(CREATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(UPDATED_PATH, Matchers.notNullValue())) .andExpect(MockMvcResultMatchers.jsonPath(NAME_PATH, Matchers.is(CMD1_NAME))) .andExpect(MockMvcResultMatchers.jsonPath(USER_PATH, Matchers.is(CMD1_USER))) .andExpect(MockMvcResultMatchers.jsonPath(VERSION_PATH, Matchers.is(CMD1_VERSION))) .andExpect(MockMvcResultMatchers.jsonPath("$.executable", Matchers.is(CMD1_EXECUTABLE))) .andDo(getResultHandler); } private void checkJobApplications(final int documentationId, final String id) throws Exception { final RestDocumentationResultHandler getResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/getJobApplications/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM, // Path parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response Headers PayloadDocumentation.responseFields( PayloadDocumentation .fieldWithPath("[]") .description("The applications for the job") .attributes(Snippets.EMPTY_CONSTRAINTS) ) // Response fields ); this.mvc .perform(RestDocumentationRequestBuilders.get(JOBS_API + "/{id}/applications", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasSize(2))) .andExpect(MockMvcResultMatchers.jsonPath("$[0].id", Matchers.is(APP1_ID))) .andExpect(MockMvcResultMatchers.jsonPath("$[1].id", Matchers.is(APP2_ID))) .andDo(getResultHandler); } private void checkFindJobs(final int documentationId, final String id, final String user) throws Exception { final RestDocumentationResultHandler findResultHandler = MockMvcRestDocumentation.document( "{class-name}/" + documentationId + "/findJobs/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.JOB_SEARCH_QUERY_PARAMETERS, // Request query parameters Snippets.HAL_CONTENT_TYPE_HEADER, // Response headers Snippets.JOB_SEARCH_RESULT_FIELDS, // Result fields Snippets.SEARCH_LINKS // HAL Links ); this.mvc .perform(MockMvcRequestBuilders.get(JOBS_API).param("user", user)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(JOBS_LIST_PATH, Matchers.hasSize(1))) .andExpect(MockMvcResultMatchers.jsonPath(JOBS_LIST_PATH + "[0].id", Matchers.is(id))) .andDo(findResultHandler); } private void testForConflicts( final String id, final String commandArgs, final List<ClusterCriteria> clusterCriteriaList, final Set<String> commandCriteria ) throws Exception { final JobRequest jobConflictRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withId(id) .withDisableLogArchival(true) .build(); this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(jobConflictRequest)) ) .andExpect(MockMvcResultMatchers.status().isConflict()); } /** * Test the job submit method for success twice to validate the file cache use. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodTwiceSuccess() throws Exception { submitAndCheckJob(2); cleanup(); setup(); submitAndCheckJob(3); } /** * Test to make sure we can submit a job with attachments. * * @throws Exception on any error */ @Test public void canSubmitJobWithAttachments() throws Exception { final String commandArgs = "-c 'echo hello world'"; final String clusterTag = "localhost"; final List<ClusterCriteria> clusterCriteriaList = Lists.newArrayList( new ClusterCriteria(Sets.newHashSet(clusterTag)) ); final String setUpFile = this.resourceLoader .getResource(BASE_DIR + "job" + FILE_DELIMITER + "jobsetupfile") .getFile() .getAbsolutePath(); final File attachment1File = this.resourceLoader .getResource(BASE_DIR + "job/query.sql") .getFile(); final MockMultipartFile attachment1; try (final InputStream is = new FileInputStream(attachment1File)) { attachment1 = new MockMultipartFile( "attachment", attachment1File.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, is ); } final File attachment2File = this.resourceLoader .getResource(BASE_DIR + "job/query2.sql") .getFile(); final MockMultipartFile attachment2; try (final InputStream is = new FileInputStream(attachment2File)) { attachment2 = new MockMultipartFile( "attachment", attachment2File.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, is ); } final Set<String> commandCriteria = Sets.newHashSet("bash"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withDisableLogArchival(true) .withSetupFile(setUpFile) .withDescription(JOB_DESCRIPTION) .build(); this.waitForDone(this.submitJob(4, jobRequest, Lists.newArrayList(attachment1, attachment2))); } /** * Test the job submit method for incorrect cluster resolved. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodMissingCluster() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'echo hello world'"; final List<ClusterCriteria> clusterCriteriaList = new ArrayList<>(); final Set<String> clusterTags = new HashSet<>(); clusterTags.add("undefined"); final ClusterCriteria clusterCriteria = new ClusterCriteria(clusterTags); clusterCriteriaList.add(clusterCriteria); final String jobId = UUID.randomUUID().toString(); final Set<String> commandCriteria = new HashSet<>(); commandCriteria.add("bash"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withId(jobId) .withDisableLogArchival(true) .build(); this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(jobRequest)) .accept(MediaType.APPLICATION_JSON) ) .andExpect(MockMvcResultMatchers.status().isPreconditionFailed()); Assert.assertThat(this.getStatus(jobId), Matchers.is("{\"status\":\"FAILED\"}")); } /** * Test the job submit method for incorrect command resolved. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodMissingCommand() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'echo hello world'"; final List<ClusterCriteria> clusterCriteriaList = new ArrayList<>(); final Set<String> clusterTags = new HashSet<>(); clusterTags.add("localhost"); final ClusterCriteria clusterCriteria = new ClusterCriteria(clusterTags); clusterCriteriaList.add(clusterCriteria); final String jobId = UUID.randomUUID().toString(); final Set<String> commandCriteria = new HashSet<>(); commandCriteria.add("undefined"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withId(jobId) .withDisableLogArchival(true) .build(); this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(jobRequest)) ) .andExpect(MockMvcResultMatchers.status().isPreconditionFailed()); Assert.assertThat(this.getStatus(jobId), Matchers.is("{\"status\":\"FAILED\"}")); } /** * Test the job submit method for when the job is killed by sending a DELETE HTTP call. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodKill() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'sleep 60'"; final List<ClusterCriteria> clusterCriteriaList = new ArrayList<>(); final Set<String> clusterTags = new HashSet<>(); clusterTags.add("localhost"); final ClusterCriteria clusterCriteria = new ClusterCriteria(clusterTags); clusterCriteriaList.add(clusterCriteria); final Set<String> commandCriteria = new HashSet<>(); commandCriteria.add("bash"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withDisableLogArchival(true) .build(); final MvcResult result = this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(jobRequest)) ) .andExpect(MockMvcResultMatchers.status().isAccepted()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) .andReturn(); final String jobId = this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); this.waitForRunning(jobId); // Let it run for a couple of seconds Thread.sleep(2000); // Send a kill request to the job. final RestDocumentationResultHandler killResultHandler = MockMvcRestDocumentation.document( "{class-name}/killJob/", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), Snippets.ID_PATH_PARAM ); this.mvc .perform(RestDocumentationRequestBuilders.delete(JOBS_API + "/{id}", jobId)) .andExpect(MockMvcResultMatchers.status().isAccepted()) .andDo(killResultHandler); this.waitForDone(jobId); this.mvc .perform(MockMvcRequestBuilders.get(JOBS_API + "/" + jobId)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(jobId))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(JobStatus.KILLED.toString()))); // Kill the job again to make sure it doesn't cause a problem. this.mvc .perform(MockMvcRequestBuilders.delete(JOBS_API + "/" + jobId)) .andExpect(MockMvcResultMatchers.status().isAccepted()); } /** * Test the job submit method for when the job is killed as it times out. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodKillOnTimeout() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'sleep 60'"; final List<ClusterCriteria> clusterCriteriaList = new ArrayList<>(); final Set<String> clusterTags = new HashSet<>(); clusterTags.add("localhost"); final ClusterCriteria clusterCriteria = new ClusterCriteria(clusterTags); clusterCriteriaList.add(clusterCriteria); final Set<String> commandCriteria = new HashSet<>(); commandCriteria.add("bash"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withTimeout(5) .withDisableLogArchival(true) .build(); final MvcResult result = this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(jobRequest)) ).andReturn(); if (result.getResponse().getStatus() != HttpStatus.ACCEPTED.value()) { log.error( "RESPONSE WASN'T 202 IT WAS: {} AND THE ERROR MESSAGE IS: {} AND THE CONTENT IS {}", result.getResponse().getStatus(), result.getResponse().getErrorMessage(), result.getResponse().getContentAsString() ); Assert.fail(); } final String id = this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); this.waitForDone(id); this.mvc .perform(MockMvcRequestBuilders.get(JOBS_API + "/{id}", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(JobStatus.KILLED.toString()))); } /** * Test the job submit method for when the job fails. * * @throws Exception If there is a problem. */ @Test public void testSubmitJobMethodFailure() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); final String commandArgs = "-c 'exit 1'"; final List<ClusterCriteria> clusterCriteriaList = new ArrayList<>(); final Set<String> clusterTags = new HashSet<>(); clusterTags.add("localhost"); final ClusterCriteria clusterCriteria = new ClusterCriteria(clusterTags); clusterCriteriaList.add(clusterCriteria); final Set<String> commandCriteria = new HashSet<>(); commandCriteria.add("bash"); final JobRequest jobRequest = new JobRequest.Builder( JOB_NAME, JOB_USER, JOB_VERSION, commandArgs, clusterCriteriaList, commandCriteria ) .withDisableLogArchival(true) .build(); final MvcResult result = this.mvc .perform( MockMvcRequestBuilders .post(JOBS_API) .contentType(MediaType.APPLICATION_JSON) .content(this.objectMapper.writeValueAsBytes(jobRequest)) ) .andExpect(MockMvcResultMatchers.status().isAccepted()) .andExpect(MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.notNullValue())) .andReturn(); final String id = this.getIdFromLocation(result.getResponse().getHeader(HttpHeaders.LOCATION)); this.waitForDone(id); Assert.assertEquals(this.getStatus(id), "{\"status\":\"FAILED\"}"); this.mvc .perform(MockMvcRequestBuilders.get(JOBS_API + "/{id}", id)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaTypes.HAL_JSON)) .andExpect(MockMvcResultMatchers.jsonPath(ID_PATH, Matchers.is(id))) .andExpect(MockMvcResultMatchers.jsonPath(STATUS_PATH, Matchers.is(JobStatus.FAILED.toString()))); } private String getIdFromLocation(final String location) { return location.substring(location.lastIndexOf("/") + 1); } private String getExpectedRunContents( final String runFileContents, final String jobWorkingDir, final String jobId ) { return runFileContents .replace("TEST_GENIE_JOB_WORKING_DIR_PLACEHOLDER", jobWorkingDir) .replace("JOB_ID_PLACEHOLDER", jobId) .replace("COMMAND_ID_PLACEHOLDER", CMD1_ID) .replace("COMMAND_NAME_PLACEHOLDER", CMD1_NAME) .replace("CLUSTER_ID_PLACEHOLDER", CLUSTER1_ID) .replace("CLUSTER_NAME_PLACEHOLDER", CLUSTER1_NAME); } private String getStatus(final String jobId) throws Exception { return this.mvc .perform(MockMvcRequestBuilders.get(JOBS_API + "/{id}/status", jobId)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn() .getResponse() .getContentAsString(); } private void waitForDone(final String jobId) throws Exception { int counter = 0; while (true) { final String statusString = this.getStatus(jobId); if (statusString.contains("INIT") || statusString.contains("RUNNING")) { log.info("Iteration {} sleeping for {} ms", counter, SLEEP_TIME); Thread.sleep(SLEEP_TIME); counter++; } else { break; } } } private void waitForRunning(final String jobId) throws Exception { int counter = 0; while (true) { final String statusString = this.getStatus(jobId); if (statusString.contains("INIT")) { log.info("Iteration {} sleeping for {} ms", counter, SLEEP_TIME); Thread.sleep(SLEEP_TIME); counter++; } else { break; } } } }