/* * * 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.Sets; import com.netflix.genie.common.exceptions.GenieException; import com.netflix.genie.core.properties.JobsProperties; import com.netflix.genie.core.services.AttachmentService; import com.netflix.genie.core.services.JobCoordinatorService; import com.netflix.genie.core.services.JobSearchService; import com.netflix.genie.test.categories.UnitTest; import com.netflix.genie.web.hateoas.assemblers.ApplicationResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.ClusterResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.CommandResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.JobExecutionResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.JobRequestResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.JobResourceAssembler; import com.netflix.genie.web.hateoas.assemblers.JobSearchResultResourceAssembler; import com.netflix.genie.web.resources.handlers.GenieResourceHttpRequestHandler; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import org.apache.catalina.ssi.ByteArrayServletOutputStream; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.mockito.Mockito; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.Collections; import java.util.Set; import java.util.UUID; /** * Unit tests for the Job rest controller. * * @author tgianos * @since 3.0.0 */ @Category(UnitTest.class) public class JobRestControllerUnitTests { private static final Charset UTF_8 = Charset.forName("UTF-8"); //Mocked variables private JobSearchService jobSearchService; private String hostname; private RestTemplate restTemplate; private GenieResourceHttpRequestHandler genieResourceHttpRequestHandler; private JobsProperties jobsProperties; private JobRestController controller; /** * Setup for the tests. */ @Before public void setup() { this.jobSearchService = Mockito.mock(JobSearchService.class); this.hostname = UUID.randomUUID().toString(); this.restTemplate = Mockito.mock(RestTemplate.class); this.genieResourceHttpRequestHandler = Mockito.mock(GenieResourceHttpRequestHandler.class); this.jobsProperties = new JobsProperties(); final Registry registry = Mockito.mock(Registry.class); final Counter counter = Mockito.mock(Counter.class); Mockito.when(registry.counter(Mockito.anyString())).thenReturn(counter); this.controller = new JobRestController( Mockito.mock(JobCoordinatorService.class), this.jobSearchService, Mockito.mock(AttachmentService.class), Mockito.mock(ApplicationResourceAssembler.class), Mockito.mock(ClusterResourceAssembler.class), Mockito.mock(CommandResourceAssembler.class), Mockito.mock(JobResourceAssembler.class), Mockito.mock(JobRequestResourceAssembler.class), Mockito.mock(JobExecutionResourceAssembler.class), Mockito.mock(JobSearchResultResourceAssembler.class), this.hostname, this.restTemplate, this.genieResourceHttpRequestHandler, this.jobsProperties, registry ); } /** * Make sure if forwarding isn't enabled we don't even try to forward no matter where the job is running. * * @throws IOException On error * @throws ServletException On Error * @throws GenieException On Error */ @Test public void wontForwardKillRequestIfNotEnabled() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(false); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); this.controller.killJob(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.never()).getJobHost(jobId); } /** * Make sure won't forward job request if it's already been forwarded. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void wontForwardJobKillRequestIfAlreadyForwarded() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = UUID.randomUUID().toString(); final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); this.controller.killJob(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.never()).getJobHost(Mockito.eq(jobId)); } /** * Makes sure we don't forward the request if we're already on the right host. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void wontForwardJobKillRequestIfOnCorrectHost() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(this.hostname); this.controller.killJob(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(jobId); Mockito.verify(this.restTemplate, Mockito.never()) .execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); } /** * Makes sure if we do forward and get back an error we return it to the user. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void canRespondToKillRequestForwardError() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(UUID.randomUUID().toString())); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(UUID.randomUUID().toString()); final StatusLine statusLine = Mockito.mock(StatusLine.class); Mockito.when(statusLine.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND.value()); final HttpResponse forwardResponse = Mockito.mock(HttpResponse.class); Mockito.when(forwardResponse.getStatusLine()).thenReturn(statusLine); Mockito.when(this.restTemplate.execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any())) .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); this.controller.killJob(jobId, forwardedFrom, request, response); Mockito .verify(response, Mockito.times(1)) .sendError(Mockito.eq(HttpStatus.NOT_FOUND.value()), Mockito.anyString()); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(jobId); Mockito.verify(this.restTemplate, Mockito.times(1)) .execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); } /** * Makes sure we can successfully forward a job kill request. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void canForwardJobKillRequest() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(UUID.randomUUID().toString())); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(UUID.randomUUID().toString()); final StatusLine statusLine = Mockito.mock(StatusLine.class); Mockito.when(statusLine.getStatusCode()).thenReturn(HttpStatus.ACCEPTED.value()); final HttpResponse forwardResponse = Mockito.mock(HttpResponse.class); Mockito.when(forwardResponse.getStatusLine()).thenReturn(statusLine); Mockito.when(forwardResponse.getAllHeaders()).thenReturn(new Header[0]); Mockito.when(this.restTemplate.execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(null); this.controller.killJob(jobId, forwardedFrom, request, response); Mockito.verify(response, Mockito.never()).sendError(Mockito.anyInt(), Mockito.anyString()); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(jobId); Mockito.verify(this.restTemplate, Mockito.times(1)) .execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); } /** * Make sure if directory forwarding isn't enabled it never fires. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void wontForwardJobOutputRequestIfNotEnabled() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(false); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.doNothing().when(this.genieResourceHttpRequestHandler).handleRequest(request, response); this.controller.getJobOutput(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.never()).getJobHost(Mockito.eq(jobId)); Mockito.verify(this.genieResourceHttpRequestHandler, Mockito.times(1)).handleRequest(request, response); } /** * Make sure if directory forwarding doesn't fire if already forwarded. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void wontForwardJobOutputRequestIfAlreadyForwarded() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = UUID.randomUUID().toString(); final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.doNothing().when(this.genieResourceHttpRequestHandler).handleRequest(request, response); this.controller.getJobOutput(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.never()).getJobHost(Mockito.eq(jobId)); Mockito.verify(this.genieResourceHttpRequestHandler, Mockito.times(1)).handleRequest(request, response); } /** * Make sure if directory forwarding doesn't fire if host name matches. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void wontForwardJobOutputRequestIfOnCorrectHost() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.doNothing().when(this.genieResourceHttpRequestHandler).handleRequest(request, response); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(this.hostname); this.controller.getJobOutput(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(Mockito.eq(jobId)); Mockito.verify(this.restTemplate, Mockito.never()) .execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.verify(this.genieResourceHttpRequestHandler, Mockito.times(1)).handleRequest(request, response); } /** * Make sure directory forwarding happens when all conditions are met. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void canHandleForwardJobOutputRequestWithError() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.doNothing().when(this.genieResourceHttpRequestHandler).handleRequest(request, response); final String jobHostName = UUID.randomUUID().toString(); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(jobHostName); //Mock parts of the http request final String http = "http"; Mockito.when(request.getScheme()).thenReturn(http); final int port = 8080; Mockito.when(request.getServerPort()).thenReturn(port); final String requestURI = "/" + jobId + "/" + UUID.randomUUID().toString(); Mockito.when(request.getRequestURI()).thenReturn(requestURI); Mockito.when(request.getHeaderNames()).thenReturn(null); final String requestUrl = UUID.randomUUID().toString(); Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(requestUrl)); final int errorCode = 404; Mockito.when(this.restTemplate.execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any())) .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); this.controller.getJobOutput(jobId, forwardedFrom, request, response); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(Mockito.eq(jobId)); Mockito.verify(this.restTemplate, Mockito.times(1)) .execute(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.verify(response, Mockito.times(1)).sendError(Mockito.eq(errorCode), Mockito.anyString()); Mockito.verify(this.genieResourceHttpRequestHandler, Mockito.never()).handleRequest(request, response); } /** * Make sure directory forwarding happens when all conditions are met. * * @throws IOException on error * @throws ServletException on error * @throws GenieException on error */ @Test public void canHandleForwardJobOutputRequestWithSuccess() throws IOException, ServletException, GenieException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final String forwardedFrom = null; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito.doNothing().when(this.genieResourceHttpRequestHandler).handleRequest(request, response); final String jobHostName = UUID.randomUUID().toString(); Mockito.when(this.jobSearchService.getJobHost(jobId)).thenReturn(jobHostName); //Mock parts of the http request final String http = "http"; Mockito.when(request.getScheme()).thenReturn(http); final int port = 8080; Mockito.when(request.getServerPort()).thenReturn(port); final String requestURI = "/" + jobId + "/" + UUID.randomUUID().toString(); Mockito.when(request.getRequestURI()).thenReturn(requestURI); final Set<String> headerNames = Sets.newHashSet(HttpHeaders.ACCEPT); Mockito.when(request.getHeaderNames()).thenReturn(Collections.enumeration(headerNames)); Mockito.when(request.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaType.APPLICATION_JSON_VALUE); final String requestUrl = UUID.randomUUID().toString(); Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(requestUrl)); //Mock parts of forward response final HttpResponse forwardResponse = Mockito.mock(HttpResponse.class); final StatusLine statusLine = Mockito.mock(StatusLine.class); Mockito.when(forwardResponse.getStatusLine()).thenReturn(statusLine); final int successCode = 200; Mockito.when(statusLine.getStatusCode()).thenReturn(successCode); final Header contentTypeHeader = Mockito.mock(Header.class); Mockito.when(contentTypeHeader.getName()).thenReturn(HttpHeaders.CONTENT_TYPE); Mockito.when(contentTypeHeader.getValue()).thenReturn(MediaType.TEXT_PLAIN_VALUE); Mockito.when(forwardResponse.getAllHeaders()).thenReturn(new Header[]{contentTypeHeader}); final String text = UUID.randomUUID().toString() + UUID.randomUUID().toString() + UUID.randomUUID().toString(); final ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes(UTF_8)); final HttpEntity entity = Mockito.mock(HttpEntity.class); Mockito.when(entity.getContent()).thenReturn(bis); Mockito.when(forwardResponse.getEntity()).thenReturn(entity); final ByteArrayServletOutputStream bos = new ByteArrayServletOutputStream(); Mockito.when(response.getOutputStream()).thenReturn(bos); final ClientHttpRequestFactory factory = Mockito.mock(ClientHttpRequestFactory.class); final ClientHttpRequest clientHttpRequest = Mockito.mock(ClientHttpRequest.class); Mockito.when(clientHttpRequest.execute()) .thenReturn(new MockClientHttpResponse(text.getBytes(UTF_8), HttpStatus.OK)); Mockito.when(clientHttpRequest.getHeaders()) .thenReturn(new HttpHeaders()); Mockito.when(factory.createRequest(Mockito.any(), Mockito.any())).thenReturn(clientHttpRequest); final RestTemplate template = new RestTemplate(factory); final Registry registry = Mockito.mock(Registry.class); final Counter counter = Mockito.mock(Counter.class); Mockito.when(registry.counter(Mockito.anyString())).thenReturn(counter); final JobRestController jobController = new JobRestController( Mockito.mock(JobCoordinatorService.class), this.jobSearchService, Mockito.mock(AttachmentService.class), Mockito.mock(ApplicationResourceAssembler.class), Mockito.mock(ClusterResourceAssembler.class), Mockito.mock(CommandResourceAssembler.class), Mockito.mock(JobResourceAssembler.class), Mockito.mock(JobRequestResourceAssembler.class), Mockito.mock(JobExecutionResourceAssembler.class), Mockito.mock(JobSearchResultResourceAssembler.class), this.hostname, template, this.genieResourceHttpRequestHandler, this.jobsProperties, registry ); jobController.getJobOutput(jobId, forwardedFrom, request, response); Assert.assertThat(new String(bos.toByteArray(), UTF_8), Matchers.is(text)); Mockito.verify(request, Mockito.times(1)).getHeader(HttpHeaders.ACCEPT); Mockito.verify(this.jobSearchService, Mockito.times(1)).getJobHost(Mockito.eq(jobId)); Mockito.verify(response, Mockito.never()).sendError(Mockito.anyInt()); Mockito.verify(this.genieResourceHttpRequestHandler, Mockito.never()).handleRequest(request, response); } }