Skip to content

Commit

Permalink
[#1979] Shanoir backend 2 VIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jcomedouteau authored Jan 18, 2024
1 parent 150753d commit dfdb0aa
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
import jakarta.validation.constraints.NotNull;
import org.shanoir.ng.shared.exception.EntityNotFoundException;
import org.shanoir.ng.shared.exception.RestServiceException;
import org.shanoir.ng.shared.exception.SecurityException;
import org.shanoir.ng.vip.monitoring.model.ExecutionDTO;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.List;

/**
* @author Alae Es-saki
Expand All @@ -46,11 +47,32 @@ public interface ExecutionDataApi {
@ApiResponse(responseCode = "403", description = "forbidden"),
@ApiResponse(responseCode = "404", description = "No dataset found"),
@ApiResponse(responseCode = "500", description = "unexpected error") })

@RequestMapping(value = "/path/{completePath}",
produces = { "application/json", "application/octet-stream" },
method = RequestMethod.GET)
ResponseEntity<?> getPath(@Parameter(name = "the complete path on which to request information. It can contain non-encoded slashes. Except for the \"exists\" action, any request on a non-existing path should return an error", required=true) @PathVariable("completePath") String completePath, @NotNull @Parameter(name = "The \"content\" action downloads the raw file. If the path points to a directory, a tarball of this directory is returned. The \"exists\" action returns a BooleanResponse object (see definition) indicating if the path exists or not. The \"properties\" action returns a Path object (see definition) with the path properties. The \"list\" action returns a DirectoryList object (see definition) with the properties of all the files of the directory (if the path is not a directory an error must be returned). The \"md5\" action is optional and returns a PathMd5 object (see definition)." ,required=true
) @Valid @RequestParam(value = "action", required = true) String action, @Valid @RequestParam(value = "format", required = false, defaultValue = "dcm") final String format, HttpServletResponse response) throws IOException, RestServiceException, EntityNotFoundException;

@Operation(summary = "Creates an execution inside VIP", description = "Creates the ressources and path control in shanoir before creating an execution in VIP", tags={ })
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Execution successfully created response."),
@ApiResponse(responseCode = "403", description = "forbidden"),
@ApiResponse(responseCode = "500", description = "unexpected error") })
@RequestMapping(value = "/createExecution",
produces = {"application/json"},
consumes = {"application/json"},
method = RequestMethod.POST)
ResponseEntity<?> createExecution(
@Parameter(name = "execution", required = true) @RequestBody final String execution) throws EntityNotFoundException, SecurityException;

@Operation(summary = "Get status for the given execution identifier", description = "Returns the status of the VIP execution that has the given identifier in parameter.", tags={ })
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "successful response, returns the status"),
@ApiResponse(responseCode = "403", description = "forbidden"),
@ApiResponse(responseCode = "500", description = "unexpected error") })
@RequestMapping(value = "/execution/{identifier}",
produces = { "application/json", "application/octet-stream" },
method = RequestMethod.GET)
ResponseEntity<String> getexecutionStatus(@Parameter(name = "The execution identifier", required=true) @PathVariable("identifier") String identifier) throws IOException, RestServiceException, EntityNotFoundException, SecurityException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,52 @@

package org.shanoir.ng.vip.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response;
import org.apache.xmlbeans.impl.jam.JParameter;
import org.hibernate.jpa.internal.util.LockOptionsHelper;
import org.keycloak.representations.AccessTokenResponse;
import org.shanoir.ng.dataset.model.Dataset;
import org.shanoir.ng.dataset.security.DatasetSecurityService;
import org.shanoir.ng.dataset.service.DatasetDownloaderServiceImpl;
import org.shanoir.ng.dataset.service.DatasetService;
import org.shanoir.ng.processing.dto.ParameterResourcesDTO;
import org.shanoir.ng.processing.model.DatasetProcessingType;
import org.shanoir.ng.shared.exception.ErrorModel;
import org.shanoir.ng.shared.exception.SecurityException;
import org.shanoir.ng.shared.security.KeycloakServiceAccountUtils;
import org.shanoir.ng.utils.KeycloakUtil;
import org.shanoir.ng.vip.dto.ExecutionMonitoringDTO;
import org.shanoir.ng.vip.monitoring.model.*;
import org.shanoir.ng.vip.monitoring.schedule.ExecutionStatusMonitorService;
import org.shanoir.ng.vip.monitoring.service.ExecutionMonitoringService;
import org.shanoir.ng.vip.resource.ProcessingResourceService;
import org.shanoir.ng.shared.exception.EntityNotFoundException;
import org.shanoir.ng.shared.exception.RestServiceException;
import org.shanoir.ng.vip.resulthandler.ResultHandlerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.List;
import java.sql.Array;
import java.time.LocalDate;
import java.util.*;

@Controller
public class ExecutionDataApiController implements ExecutionDataApi {
Expand All @@ -49,6 +75,27 @@ public class ExecutionDataApiController implements ExecutionDataApi {
@Autowired
private ProcessingResourceService processingResourceService;

@Autowired
private DatasetService datasetService;

@Autowired
private DatasetSecurityService datasetSecurityService;

@Autowired
private RestTemplate restTemplate;

@Autowired
private KeycloakServiceAccountUtils keycloakServiceAccountUtils;

@Autowired
private ExecutionMonitoringService executionMonitoringService;

@Autowired
private ExecutionStatusMonitorService executionStatusMonitorService;

@Value("${vip.uri}")
private String VIP_URI;

@Override
public ResponseEntity<?> getPath(
@Parameter(name = "the complete path on which to request information. It can contain non-encoded slashes. Except for the \"exists\" action, any request on a non-existing path should return an error",
Expand Down Expand Up @@ -84,4 +131,123 @@ public ResponseEntity<?> getPath(

}

@Override
public ResponseEntity<ExecutionDTO> createExecution(
@Parameter(name = "execution", required = true) @RequestBody final String executionAsString) throws EntityNotFoundException, SecurityException {

String authenticationToken = KeycloakUtil.getToken();

// 1: Get dataset IDS and check rights
List<Long> datasetsIds = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
ExecutionDTO execution = null;
try {
execution = mapper.readValue(executionAsString, ExecutionDTO.class);
} catch (JsonProcessingException e) {
LOG.error("Could not parse execution DTO from input, please respect the expected structure.", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
String clientId = execution.getClient();

for (ParameterResourcesDTO param : execution.getParametersRessources()) {
datasetsIds.addAll(param.getDatasetIds());
}

if (!this.datasetSecurityService.hasRightOnEveryDataset(datasetsIds, "CAN_IMPORT")) {
LOG.error("Import right is mandatory for every study we are updating");
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
};

List<Dataset> inputDatasets = this.datasetService.findByIdIn(datasetsIds);

// 2: Create monitoring on shanoir
ExecutionMonitoring executionMonitoring = createExecutionMonitoring(execution, inputDatasets);

// Save monitoring in db.
final ExecutionMonitoring createdMonitoring = executionMonitoringService.create(executionMonitoring);

List<ParameterResourcesDTO> parametersDatasets = executionMonitoringService.createProcessingResources(createdMonitoring, execution.getParametersRessources());

// 3: create Execution on VIP
// init headers with the active access token

execution.setResultsLocation("shanoir:/" + createdMonitoring.getResultsLocation() + "?token=" + authenticationToken + "&refreshToken=" + execution.getRefreshToken() + "&clientId=" + clientId + "&md5=none&type=File");

String exportFormat = execution.getExportFormat();
String extension = ".nii.gz";
if ("dcm".equals(execution.getExportFormat())) {
extension = ".zip";
}

Map<String, List<String>> parametersDatasetsInputValues = new HashMap<>();
for (ParameterResourcesDTO parameterResourcesDTO : parametersDatasets) {
parametersDatasetsInputValues.put(parameterResourcesDTO.getParameter(), new ArrayList<>());

for (String ressourceId : parameterResourcesDTO.getResourceIds()) {
String entityName = "resource_id+" + ressourceId + "+" + parameterResourcesDTO.getGroupBy().name().toLowerCase() + extension;
String inputValue = "shanoir:/" + entityName + "?format=" + exportFormat + "&datasetId=" + ressourceId
+ "&token=" + authenticationToken + "&refreshToken=" + execution.getRefreshToken() + "&clientId=" + clientId + "&md5=none&type=File";
parametersDatasetsInputValues.get(parameterResourcesDTO.getParameter()).add(inputValue);
}
}
execution.setInputValues(parametersDatasetsInputValues);

try {
LOG.error(mapper.writeValueAsString(execution));
} catch (JsonProcessingException e) {
LOG.error("Could not parse execution DTO from VIP response, please contact an administrator", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

HttpHeaders headers = KeycloakUtil.getKeycloakHeader();
HttpEntity<ExecutionDTO> entity = new HttpEntity<>(execution, headers);

ResponseEntity<ExecutionDTO> execResult = this.restTemplate.exchange(VIP_URI, HttpMethod.POST, entity, ExecutionDTO.class);

ExecutionDTO execCreated = execResult.getBody();

executionMonitoring.setIdentifier(execCreated.getIdentifier());
executionMonitoring.setStatus(execCreated.getStatus());
executionMonitoring.setStartDate(execCreated.getStartDate());

executionMonitoring = this.executionMonitoringService.update(executionMonitoring);
this.executionStatusMonitorService.startMonitoringJob(executionMonitoring.getIdentifier());

return new ResponseEntity<>(execCreated, HttpStatus.OK);
}

private ExecutionMonitoring createExecutionMonitoring(ExecutionDTO execution, List<Dataset> inputDatasets) {
ExecutionMonitoring executionMonitoring = new ExecutionMonitoring();
executionMonitoring.setName(execution.getName());
executionMonitoring.setPipelineIdentifier(execution.getPipelineIdentifier());
executionMonitoring.setResultsLocation(KeycloakUtil.getTokenUserId() + "/" + LocalDate.now());
executionMonitoring.setTimeout(20);
executionMonitoring.setStudyId(Long.valueOf(execution.getStudyIdentifier()));
executionMonitoring.setStatus(ExecutionStatus.RUNNING);
executionMonitoring.setComment(execution.getName());
executionMonitoring.setDatasetProcessingType(DatasetProcessingType.valueOf(execution.getProcessingType()));
executionMonitoring.setOutputProcessing(execution.getOutputProcessing());
executionMonitoring.setInputDatasets(inputDatasets);
return executionMonitoring;
}

@Override
public ResponseEntity<String> getexecutionStatus(@Parameter(name = "The execution identifier", required=true) @PathVariable("identifier") String identifier) throws IOException, RestServiceException, EntityNotFoundException, SecurityException {

AccessTokenResponse accessTokenResponse = keycloakServiceAccountUtils.getServiceAccountAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessTokenResponse.getToken());
HttpEntity<String> entity = new HttpEntity<>(headers);

Execution execution;
try {
String uri = VIP_URI + identifier + "/summary";
ResponseEntity<Execution> execResult = this.restTemplate.exchange(uri, HttpMethod.GET, entity, Execution.class);
execution = execResult.getBody();
} catch (HttpStatusCodeException e) {
// in case of an error with response payload
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(execution.getStatus().getRestLabel(), HttpStatus.OK);
}
}
Loading

0 comments on commit dfdb0aa

Please sign in to comment.