@Test void wontForwardJobOutputRequestIfOnCorrectHost() throws GenieException, GenieCheckedException { this.jobsProperties.getForwarding().setEnabled(true); final String jobId = UUID.randomUUID().toString(); final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); Mockito .when(request.getRequestURL()) .thenReturn(new StringBuffer("https: Mockito .doNothing() .when(this.jobDirectoryServerService) .serveResource( Mockito.eq(jobId), Mockito.any(URL.class), Mockito.anyString(), Mockito.eq(request), Mockito.eq(response) ); Mockito.when(this.persistenceService.getJobArchiveStatus(jobId)).thenReturn(ArchiveStatus.PENDING); Mockito .when(this.agentRoutingService.getHostnameForAgentConnection(jobId)) .thenReturn(Optional.of(this.hostname)); this.controller.getJobOutput(jobId, null, request, response); Mockito.verify(this.agentRoutingService, Mockito.times(1)).getHostnameForAgentConnection(jobId); Mockito .verify(this.restTemplate, Mockito.never()) .execute( Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.anyString() ); Mockito .verify(this.jobDirectoryServerService, Mockito.times(1)) .serveResource( Mockito.eq(jobId), Mockito.any(URL.class), Mockito.anyString(), Mockito.eq(request), Mockito.eq(response) ); }
@GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) public void getJobOutput( @PathVariable("id") final String id, @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false) @Nullable final String forwardedFrom, final HttpServletRequest request, final HttpServletResponse response ) throws GenieException, NotFoundException { final String path = ControllerUtils.getRemainingPath(request); log.info("[getJobOutput] Called to get output path \"{}\" for job with id \"{}\"", path, id); final URL baseUrl; try { baseUrl = forwardedFrom == null ? ControllerUtils.getRequestRoot(request, path) : ControllerUtils.getRequestRoot(new URL(forwardedFrom), path); } catch (final MalformedURLException e) { throw new GenieServerException("Unable to parse base request url", e); } final ArchiveStatus archiveStatus = this.persistenceService.getJobArchiveStatus(id); if (archiveStatus == ArchiveStatus.PENDING) { final String jobHostname; try { jobHostname = this.agentRoutingService .getHostnameForAgentConnection(id) .orElseThrow(() -> new NotFoundException("No hostname found for job - " + id)); } catch (NotFoundException e) { throw new GenieServerException("Failed to route request", e); } final boolean shouldForward = !this.hostname.equals(jobHostname); final boolean canForward = forwardedFrom == null && this.jobsProperties.getForwarding().isEnabled(); if (shouldForward && canForward) { forwardRequest(id, path, jobHostname, request, response); return; } else if (!canForward && shouldForward) { throw new GenieServerException("Job files are not local, but forwarding is disabled"); } } log.debug("Fetching requested resource \"{}\" for job \"{}\"", path, id); this.jobDirectoryServerService.serveResource(id, baseUrl, path, request, response); }
JobRestController { @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) public void getJobOutput( @PathVariable("id") final String id, @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false) @Nullable final String forwardedFrom, final HttpServletRequest request, final HttpServletResponse response ) throws GenieException, NotFoundException { final String path = ControllerUtils.getRemainingPath(request); log.info("[getJobOutput] Called to get output path \"{}\" for job with id \"{}\"", path, id); final URL baseUrl; try { baseUrl = forwardedFrom == null ? ControllerUtils.getRequestRoot(request, path) : ControllerUtils.getRequestRoot(new URL(forwardedFrom), path); } catch (final MalformedURLException e) { throw new GenieServerException("Unable to parse base request url", e); } final ArchiveStatus archiveStatus = this.persistenceService.getJobArchiveStatus(id); if (archiveStatus == ArchiveStatus.PENDING) { final String jobHostname; try { jobHostname = this.agentRoutingService .getHostnameForAgentConnection(id) .orElseThrow(() -> new NotFoundException("No hostname found for job - " + id)); } catch (NotFoundException e) { throw new GenieServerException("Failed to route request", e); } final boolean shouldForward = !this.hostname.equals(jobHostname); final boolean canForward = forwardedFrom == null && this.jobsProperties.getForwarding().isEnabled(); if (shouldForward && canForward) { forwardRequest(id, path, jobHostname, request, response); return; } else if (!canForward && shouldForward) { throw new GenieServerException("Job files are not local, but forwarding is disabled"); } } log.debug("Fetching requested resource \"{}\" for job \"{}\"", path, id); this.jobDirectoryServerService.serveResource(id, baseUrl, path, request, response); } }
JobRestController { @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) public void getJobOutput( @PathVariable("id") final String id, @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false) @Nullable final String forwardedFrom, final HttpServletRequest request, final HttpServletResponse response ) throws GenieException, NotFoundException { final String path = ControllerUtils.getRemainingPath(request); log.info("[getJobOutput] Called to get output path \"{}\" for job with id \"{}\"", path, id); final URL baseUrl; try { baseUrl = forwardedFrom == null ? ControllerUtils.getRequestRoot(request, path) : ControllerUtils.getRequestRoot(new URL(forwardedFrom), path); } catch (final MalformedURLException e) { throw new GenieServerException("Unable to parse base request url", e); } final ArchiveStatus archiveStatus = this.persistenceService.getJobArchiveStatus(id); if (archiveStatus == ArchiveStatus.PENDING) { final String jobHostname; try { jobHostname = this.agentRoutingService .getHostnameForAgentConnection(id) .orElseThrow(() -> new NotFoundException("No hostname found for job - " + id)); } catch (NotFoundException e) { throw new GenieServerException("Failed to route request", e); } final boolean shouldForward = !this.hostname.equals(jobHostname); final boolean canForward = forwardedFrom == null && this.jobsProperties.getForwarding().isEnabled(); if (shouldForward && canForward) { forwardRequest(id, path, jobHostname, request, response); return; } else if (!canForward && shouldForward) { throw new GenieServerException("Job files are not local, but forwarding is disabled"); } } log.debug("Fetching requested resource \"{}\" for job \"{}\"", path, id); this.jobDirectoryServerService.serveResource(id, baseUrl, path, request, response); } @Autowired @SuppressWarnings("checkstyle:parameternumber")  JobRestController(
        final JobLaunchService jobLaunchService,
        final DataServices dataServices,
        final EntityModelAssemblers entityModelAssemblers,
        final GenieHostInfo genieHostInfo,
        @Qualifier("genieRestTemplate") final RestTemplate restTemplate,
        final JobDirectoryServerService jobDirectoryServerService,
        final JobsProperties jobsProperties,
        final MeterRegistry registry,
        final AgentRoutingService agentRoutingService,
        final Environment environment,
        final AttachmentService attachmentService,
        final JobKillService jobKillService
    ); }
JobRestController { @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) public void getJobOutput( @PathVariable("id") final String id, @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false) @Nullable final String forwardedFrom, final HttpServletRequest request, final HttpServletResponse response ) throws GenieException, NotFoundException { final String path = ControllerUtils.getRemainingPath(request); log.info("[getJobOutput] Called to get output path \"{}\" for job with id \"{}\"", path, id); final URL baseUrl; try { baseUrl = forwardedFrom == null ? ControllerUtils.getRequestRoot(request, path) : ControllerUtils.getRequestRoot(new URL(forwardedFrom), path); } catch (final MalformedURLException e) { throw new GenieServerException("Unable to parse base request url", e); } final ArchiveStatus archiveStatus = this.persistenceService.getJobArchiveStatus(id); if (archiveStatus == ArchiveStatus.PENDING) { final String jobHostname; try { jobHostname = this.agentRoutingService .getHostnameForAgentConnection(id) .orElseThrow(() -> new NotFoundException("No hostname found for job - " + id)); } catch (NotFoundException e) { throw new GenieServerException("Failed to route request", e); } final boolean shouldForward = !this.hostname.equals(jobHostname); final boolean canForward = forwardedFrom == null && this.jobsProperties.getForwarding().isEnabled(); if (shouldForward && canForward) { forwardRequest(id, path, jobHostname, request, response); return; } else if (!canForward && shouldForward) { throw new GenieServerException("Job files are not local, but forwarding is disabled"); } } log.debug("Fetching requested resource \"{}\" for job \"{}\"", path, id); this.jobDirectoryServerService.serveResource(id, baseUrl, path, request, response); } @Autowired @SuppressWarnings("checkstyle:parameternumber")  JobRestController(
        final JobLaunchService jobLaunchService,
        final DataServices dataServices,
        final EntityModelAssemblers entityModelAssemblers,
        final GenieHostInfo genieHostInfo,
        @Qualifier("genieRestTemplate") final RestTemplate restTemplate,
        final JobDirectoryServerService jobDirectoryServerService,
        final JobsProperties jobsProperties,
        final MeterRegistry registry,
        final AgentRoutingService agentRoutingService,
        final Environment environment,
        final AttachmentService attachmentService,
        final JobKillService jobKillService
    ); @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) ResponseEntity<Void> submitJob(
        @Valid @RequestBody final JobRequest jobRequest,
        @RequestHeader(value = FORWARDED_FOR_HEADER, required = false) @Nullable final String clientHost,
        @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) @Nullable final String userAgent,
        final HttpServletRequest httpServletRequest
    ); @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) ResponseEntity<Void> submitJob(
        @Valid @RequestPart("request") final JobRequest jobRequest,
        @RequestPart("attachment") final MultipartFile[] attachments,
        @RequestHeader(value = FORWARDED_FOR_HEADER, required = false) @Nullable final String clientHost,
        @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) @Nullable final String userAgent,
        final HttpServletRequest httpServletRequest
    ); @GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) EntityModel<Job> getJob(@PathVariable("id") final String id); @GetMapping(value = "/{id}/status", produces = MediaType.APPLICATION_JSON_VALUE) JsonNode getJobStatus(@PathVariable("id") final String id); @GetMapping(produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @SuppressWarnings("checkstyle:parameternumber") PagedModel<EntityModel<JobSearchResult>> findJobs(
        @RequestParam(value = "id", required = false) @Nullable final String id,
        @RequestParam(value = "name", required = false) @Nullable final String name,
        @RequestParam(value = "user", required = false) @Nullable final String user,
        @RequestParam(value = "status", required = false) @Nullable final Set<String> statuses,
        @RequestParam(value = "tag", required = false) @Nullable final Set<String> tags,
        @RequestParam(value = "clusterName", required = false) @Nullable final String clusterName,
        @RequestParam(value = "clusterId", required = false) @Nullable final String clusterId,
        @RequestParam(value = "commandName", required = false) @Nullable final String commandName,
        @RequestParam(value = "commandId", required = false) @Nullable final String commandId,
        @RequestParam(value = "minStarted", required = false) @Nullable final Long minStarted,
        @RequestParam(value = "maxStarted", required = false) @Nullable final Long maxStarted,
        @RequestParam(value = "minFinished", required = false) @Nullable final Long minFinished,
        @RequestParam(value = "maxFinished", required = false) @Nullable final Long maxFinished,
        @RequestParam(value = "grouping", required = false) @Nullable final String grouping,
        @RequestParam(value = "groupingInstance", required = false) @Nullable final String groupingInstance,
        @PageableDefault(sort = {"created"}, direction = Sort.Direction.DESC) final Pageable page,
        final PagedResourcesAssembler<JobSearchResult> assembler
    ); @DeleteMapping(value = "/{id}") @ResponseStatus(HttpStatus.ACCEPTED) void killJob(
        @PathVariable("id") final String id,
        @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false)
        @Nullable final String forwardedFrom,
        final HttpServletRequest request,
        final HttpServletResponse response
    ); @GetMapping(value = "/{id}/request", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobRequest> getJobRequest(
        @PathVariable("id") final String id); @GetMapping(value = "/{id}/execution", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobExecution> getJobExecution(
        @PathVariable("id") final String id
    ); @GetMapping(value = "/{id}/metadata", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobMetadata> getJobMetadata(@PathVariable("id") final String id); @GetMapping(value = "/{id}/cluster", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<Cluster> getJobCluster(@PathVariable("id") final String id); @GetMapping(value = "/{id}/command", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<Command> getJobCommand(@PathVariable("id") final String id); @GetMapping(value = "/{id}/applications", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) List<EntityModel<Application>> getJobApplications(
        @PathVariable("id") final String id
    ); @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) void getJobOutput(
        @PathVariable("id") final String id,
        @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false)
        @Nullable final String forwardedFrom,
        final HttpServletRequest request,
        final HttpServletResponse response
    ); }
JobRestController { @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) public void getJobOutput( @PathVariable("id") final String id, @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false) @Nullable final String forwardedFrom, final HttpServletRequest request, final HttpServletResponse response ) throws GenieException, NotFoundException { final String path = ControllerUtils.getRemainingPath(request); log.info("[getJobOutput] Called to get output path \"{}\" for job with id \"{}\"", path, id); final URL baseUrl; try { baseUrl = forwardedFrom == null ? ControllerUtils.getRequestRoot(request, path) : ControllerUtils.getRequestRoot(new URL(forwardedFrom), path); } catch (final MalformedURLException e) { throw new GenieServerException("Unable to parse base request url", e); } final ArchiveStatus archiveStatus = this.persistenceService.getJobArchiveStatus(id); if (archiveStatus == ArchiveStatus.PENDING) { final String jobHostname; try { jobHostname = this.agentRoutingService .getHostnameForAgentConnection(id) .orElseThrow(() -> new NotFoundException("No hostname found for job - " + id)); } catch (NotFoundException e) { throw new GenieServerException("Failed to route request", e); } final boolean shouldForward = !this.hostname.equals(jobHostname); final boolean canForward = forwardedFrom == null && this.jobsProperties.getForwarding().isEnabled(); if (shouldForward && canForward) { forwardRequest(id, path, jobHostname, request, response); return; } else if (!canForward && shouldForward) { throw new GenieServerException("Job files are not local, but forwarding is disabled"); } } log.debug("Fetching requested resource \"{}\" for job \"{}\"", path, id); this.jobDirectoryServerService.serveResource(id, baseUrl, path, request, response); } @Autowired @SuppressWarnings("checkstyle:parameternumber")  JobRestController(
        final JobLaunchService jobLaunchService,
        final DataServices dataServices,
        final EntityModelAssemblers entityModelAssemblers,
        final GenieHostInfo genieHostInfo,
        @Qualifier("genieRestTemplate") final RestTemplate restTemplate,
        final JobDirectoryServerService jobDirectoryServerService,
        final JobsProperties jobsProperties,
        final MeterRegistry registry,
        final AgentRoutingService agentRoutingService,
        final Environment environment,
        final AttachmentService attachmentService,
        final JobKillService jobKillService
    ); @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) ResponseEntity<Void> submitJob(
        @Valid @RequestBody final JobRequest jobRequest,
        @RequestHeader(value = FORWARDED_FOR_HEADER, required = false) @Nullable final String clientHost,
        @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) @Nullable final String userAgent,
        final HttpServletRequest httpServletRequest
    ); @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.ACCEPTED) ResponseEntity<Void> submitJob(
        @Valid @RequestPart("request") final JobRequest jobRequest,
        @RequestPart("attachment") final MultipartFile[] attachments,
        @RequestHeader(value = FORWARDED_FOR_HEADER, required = false) @Nullable final String clientHost,
        @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) @Nullable final String userAgent,
        final HttpServletRequest httpServletRequest
    ); @GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) EntityModel<Job> getJob(@PathVariable("id") final String id); @GetMapping(value = "/{id}/status", produces = MediaType.APPLICATION_JSON_VALUE) JsonNode getJobStatus(@PathVariable("id") final String id); @GetMapping(produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @SuppressWarnings("checkstyle:parameternumber") PagedModel<EntityModel<JobSearchResult>> findJobs(
        @RequestParam(value = "id", required = false) @Nullable final String id,
        @RequestParam(value = "name", required = false) @Nullable final String name,
        @RequestParam(value = "user", required = false) @Nullable final String user,
        @RequestParam(value = "status", required = false) @Nullable final Set<String> statuses,
        @RequestParam(value = "tag", required = false) @Nullable final Set<String> tags,
        @RequestParam(value = "clusterName", required = false) @Nullable final String clusterName,
        @RequestParam(value = "clusterId", required = false) @Nullable final String clusterId,
        @RequestParam(value = "commandName", required = false) @Nullable final String commandName,
        @RequestParam(value = "commandId", required = false) @Nullable final String commandId,
        @RequestParam(value = "minStarted", required = false) @Nullable final Long minStarted,
        @RequestParam(value = "maxStarted", required = false) @Nullable final Long maxStarted,
        @RequestParam(value = "minFinished", required = false) @Nullable final Long minFinished,
        @RequestParam(value = "maxFinished", required = false) @Nullable final Long maxFinished,
        @RequestParam(value = "grouping", required = false) @Nullable final String grouping,
        @RequestParam(value = "groupingInstance", required = false) @Nullable final String groupingInstance,
        @PageableDefault(sort = {"created"}, direction = Sort.Direction.DESC) final Pageable page,
        final PagedResourcesAssembler<JobSearchResult> assembler
    ); @DeleteMapping(value = "/{id}") @ResponseStatus(HttpStatus.ACCEPTED) void killJob(
        @PathVariable("id") final String id,
        @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false)
        @Nullable final String forwardedFrom,
        final HttpServletRequest request,
        final HttpServletResponse response
    ); @GetMapping(value = "/{id}/request", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobRequest> getJobRequest(
        @PathVariable("id") final String id); @GetMapping(value = "/{id}/execution", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobExecution> getJobExecution(
        @PathVariable("id") final String id
    ); @GetMapping(value = "/{id}/metadata", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<JobMetadata> getJobMetadata(@PathVariable("id") final String id); @GetMapping(value = "/{id}/cluster", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<Cluster> getJobCluster(@PathVariable("id") final String id); @GetMapping(value = "/{id}/command", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) EntityModel<Command> getJobCommand(@PathVariable("id") final String id); @GetMapping(value = "/{id}/applications", produces = MediaTypes.HAL_JSON_VALUE) @ResponseStatus(HttpStatus.OK) List<EntityModel<Application>> getJobApplications(
        @PathVariable("id") final String id
    ); @GetMapping( value = { "/{id}/output", "/{id}/output/", "/{id}/output/**" } ) void getJobOutput(
        @PathVariable("id") final String id,
        @RequestHeader(name = JobConstants.GENIE_FORWARDED_FROM_HEADER, required = false)
        @Nullable final String forwardedFrom,
        final HttpServletRequest request,
        final HttpServletResponse response
    );  }