Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shanoir vip back to back #1979

Merged
merged 30 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d154df0
shanoir-VIP-back-to-back
jcomedouteau Dec 12, 2023
0d4d781
shanoir-VIP-back-to-back
jcomedouteau Dec 12, 2023
cb96f2f
create monitoring
jcomedouteau Dec 12, 2023
d6ceb73
back to back
jcomedouteau Dec 15, 2023
4ff46a5
Merge branch 'develop' into shanoir-VIP-back-to-back
jcomedouteau Dec 15, 2023
a599ae9
create execution
jcomedouteau Dec 15, 2023
f37539e
back to back
jcomedouteau Dec 19, 2023
ee95ce0
Add API parameters
jcomedouteau Jan 8, 2024
49e0e1e
Add API parameters
jcomedouteau Jan 8, 2024
071ffa7
Add API parameters
jcomedouteau Jan 8, 2024
e5e163a
Add API parameters
jcomedouteau Jan 8, 2024
3eb75f0
Add API parameters
jcomedouteau Jan 8, 2024
ab6dd24
Remove dataset ids argument
jcomedouteau Jan 10, 2024
08bf1ea
Remove dataset ids argument
jcomedouteau Jan 10, 2024
f0b8c14
change arguments
jcomedouteau Jan 10, 2024
0633c8b
change URI
jcomedouteau Jan 12, 2024
f724dea
change URI and logic
jcomedouteau Jan 12, 2024
099b5d1
get the information about %22
jcomedouteau Jan 12, 2024
ded4109
VIP back to back - add execution status
jcomedouteau Jan 16, 2024
0a77926
Back to back - add token to API and exec DTO
jcomedouteau Jan 16, 2024
b96187b
add result location
jcomedouteau Jan 16, 2024
02b7d91
add result location + foramt + extension
jcomedouteau Jan 16, 2024
6c579ff
add result location + foramt + extension
jcomedouteau Jan 16, 2024
5acf37f
corrections ?
jcomedouteau Jan 16, 2024
c92a324
return actual execution and not first
jcomedouteau Jan 17, 2024
1d3d840
Correct authentication with adapted API + use clientId + correct priv…
jcomedouteau Jan 17, 2024
a6a4fa8
Extract method + error management
jcomedouteau Jan 17, 2024
f9a19c4
errir management
jcomedouteau Jan 17, 2024
f2f516a
errir management
jcomedouteau Jan 17, 2024
b5f39c5
Merge branch 'develop' into shanoir-VIP-back-to-back
jcomedouteau Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading