Skip to content
Snippets Groups Projects
Commit 8eeb0a43 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added synctrans URL parameters endpoint (GET convenience mode) and improved protocol negotiation

parent 911da09f
Branches
Tags
No related merge requests found
package it.inaf.oats.vospace; package it.inaf.oats.vospace;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.persistence.JobDAO; import it.inaf.oats.vospace.persistence.JobDAO;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
...@@ -10,7 +9,6 @@ import net.ivoa.xml.uws.v1.ExecutionPhase; ...@@ -10,7 +9,6 @@ import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.Jobs; import net.ivoa.xml.uws.v1.Jobs;
import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.Transfer;
import net.ivoa.xml.vospace.v2.Param;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -24,8 +22,10 @@ import org.springframework.web.bind.annotation.RequestParam; ...@@ -24,8 +22,10 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import net.ivoa.xml.vospace.v2.Protocol;
@RestController @RestController
public class TransferController { public class TransferController {
...@@ -70,6 +70,41 @@ public class TransferController { ...@@ -70,6 +70,41 @@ public class TransferController {
return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
} }
@GetMapping("/synctrans")
public ResponseEntity<?> syncTransferUrlParamsMode(@RequestParam("TARGET") String target,
@RequestParam("DIRECTION") String direction, @RequestParam("PROTOCOL") String protocolUris, User principal) {
Transfer transfer = new Transfer();
transfer.setTarget(target);
transfer.setDirection(direction);
// CADC client sends multiple protocol parameters and Spring join them using a comma separator.
// Some values seems to be duplicated, so a set is used to avoid the duplication
for (String protocolUri : new HashSet<>(Arrays.asList(protocolUris.split(",")))) {
Protocol protocol = new Protocol();
protocol.setUri(protocolUri);
transfer.getProtocols().add(protocol);
}
JobSummary jobSummary = newJobSummary(transfer, principal);
jobService.createSyncJobResult(jobSummary);
if (jobSummary.getErrorSummary() != null) {
// TODO: decide how to hanlde HTTP error codes
// If an error occurs with the synchronous convenience mode where the preferred endpoint
// is immediately returned as a redirect, the error information is returned directly in
// the response body with the associated HTTP status code.
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(jobSummary.getErrorSummary().getMessage());
}
// Behaves as if REQUEST=redirect was set, for compatibility with CADC client
String endpoint = transfer.getProtocols().get(0).getEndpoint();
HttpHeaders headers = new HttpHeaders();
headers.set("Location", endpoint);
return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
}
@GetMapping(value = "/transfers/{jobId}", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) @GetMapping(value = "/transfers/{jobId}", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JobSummary> getJob(@PathVariable("jobId") String jobId) { public ResponseEntity<JobSummary> getJob(@PathVariable("jobId") String jobId) {
return jobDAO.getJob(jobId).map(j -> ResponseEntity.ok(j)).orElse(ResponseEntity.notFound().build()); return jobDAO.getJob(jobId).map(j -> ResponseEntity.ok(j)).orElse(ResponseEntity.notFound().build());
......
...@@ -8,7 +8,7 @@ import it.inaf.oats.vospace.datamodel.NodeProperties; ...@@ -8,7 +8,7 @@ import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InvalidURIException; import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.exception.ProtocolNotSupportedException; import it.inaf.oats.vospace.exception.ProtocolNotSupportedException;
...@@ -22,6 +22,7 @@ import java.net.URL; ...@@ -22,6 +22,7 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.ResultReference; import net.ivoa.xml.uws.v1.ResultReference;
...@@ -67,21 +68,48 @@ public class UriService { ...@@ -67,21 +68,48 @@ public class UriService {
job.setResults(results); job.setResults(results);
// Moved phase setting to caller method for ERROR management // Moved phase setting to caller method for ERROR management
// job.setPhase(ExecutionPhase.COMPLETED);
} }
/**
* Sets the endpoint value for all valid protocols (protocol negotiation).
*/
public void setSyncTransferEndpoints(JobSummary job) { public void setSyncTransferEndpoints(JobSummary job) {
Transfer transfer = getTransfer(job); Transfer transfer = getTransfer(job);
Protocol protocol = transfer.getProtocols().get(0); if (transfer.getProtocols().isEmpty()) {
// At least one protocol is expected from client
throw new InvalidArgumentException("Transfer contains no protocols");
}
JobService.JobType jobType = JobType.valueOf(transfer.getDirection());
List<String> validProtocolUris = new ArrayList<>();
switch (jobType) {
case pullFromVoSpace:
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpget");
break;
case pushToVoSpace:
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpput");
break;
}
List<Protocol> validProtocols
= transfer.getProtocols().stream()
.filter(protocol -> validProtocolUris.contains(protocol.getUri()))
.collect(Collectors.toList());
if (!"ivo://ivoa.net/vospace/core#httpget".equals(protocol.getUri()) if (validProtocols.isEmpty()) {
&& !"ivo://ivoa.net/vospace/core#httpput".equals(protocol.getUri())) { Protocol protocol = transfer.getProtocols().get(0);
// throw new IllegalStateException("Unsupported protocol " + protocol.getUri());
throw new ProtocolNotSupportedException(protocol.getUri()); throw new ProtocolNotSupportedException(protocol.getUri());
} }
protocol.setEndpoint(getEndpoint(job, transfer));
String endpoint = getEndpoint(job, transfer);
validProtocols.stream().forEach(p -> p.setEndpoint(endpoint));
// Returns modified transfer containing only valid protocols
transfer.getProtocols().clear();
transfer.getProtocols().addAll(validProtocols);
} }
private Node getEndpointNode(String relativePath, private Node getEndpointNode(String relativePath,
...@@ -102,7 +130,6 @@ public class UriService { ...@@ -102,7 +130,6 @@ public class UriService {
default: default:
throw new InternalFaultException("No supported job direction specified"); throw new InternalFaultException("No supported job direction specified");
} }
} }
} }
...@@ -203,13 +230,13 @@ public class UriService { ...@@ -203,13 +230,13 @@ public class UriService {
List<Object> jobPayload = job.getJobInfo().getAny(); List<Object> jobPayload = job.getJobInfo().getAny();
if (jobPayload.isEmpty()) { if (jobPayload.isEmpty()) {
throw new IllegalStateException("Empty job payload for job " + job.getJobId()); throw new InternalFaultException("Empty job payload for job " + job.getJobId());
} }
if (jobPayload.size() > 1) { if (jobPayload.size() > 1) {
throw new IllegalStateException("Multiple objects in job payload not supported"); throw new InternalFaultException("Multiple objects in job payload not supported");
} }
if (!(jobPayload.get(0) instanceof Transfer)) { if (!(jobPayload.get(0) instanceof Transfer)) {
throw new IllegalStateException(jobPayload.get(0).getClass().getCanonicalName() throw new InternalFaultException(jobPayload.get(0).getClass().getCanonicalName()
+ " not supported as job payload. Job id: " + job.getJobId()); + " not supported as job payload. Job id: " + job.getJobId());
} }
......
package it.inaf.oats.vospace.exception;
import net.ivoa.xml.uws.v1.ErrorSummaryFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class InvalidArgumentException extends VoSpaceErrorSummarizableException {
public InvalidArgumentException(String message) {
super(message, ErrorSummaryFactory.VOSpaceFault.NODE_NOT_FOUND);
}
}
...@@ -280,6 +280,22 @@ public class TransferControllerTest { ...@@ -280,6 +280,22 @@ public class TransferControllerTest {
.andReturn().getResponse().getContentAsString(); .andReturn().getResponse().getContentAsString();
} }
@Test
public void testSyncTransferUrlParamsMode() throws Exception {
Node node = mockPublicDataNode();
when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
mockMvc.perform(get("/synctrans")
.header("Authorization", "Bearer user1_token")
.param("TARGET", "vos://example.com!vospace/mynode")
.param("DIRECTION", "pullFromVoSpace")
// testing duplicated protocol (CADC client)
.param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget")
.param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget"))
.andExpect(status().is3xxRedirection());
}
private Jobs getFakeJobs() { private Jobs getFakeJobs() {
Jobs jobs = new Jobs(); Jobs jobs = new Jobs();
jobs.setVersion("1.1"); jobs.setVersion("1.1");
......
...@@ -3,6 +3,8 @@ package it.inaf.oats.vospace; ...@@ -3,6 +3,8 @@ package it.inaf.oats.vospace;
import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.ServletRapClient;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.ProtocolNotSupportedException;
import it.inaf.oats.vospace.persistence.LocationDAO; import it.inaf.oats.vospace.persistence.LocationDAO;
import it.inaf.oats.vospace.persistence.NodeDAO; import it.inaf.oats.vospace.persistence.NodeDAO;
import it.inaf.oats.vospace.persistence.model.Location; import it.inaf.oats.vospace.persistence.model.Location;
...@@ -14,8 +16,10 @@ import net.ivoa.xml.vospace.v2.ContainerNode; ...@@ -14,8 +16,10 @@ import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.Transfer;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
...@@ -66,7 +70,10 @@ public class UriServiceTest { ...@@ -66,7 +70,10 @@ public class UriServiceTest {
@Bean @Bean
@Primary @Primary
public HttpServletRequest servletRequest() { public HttpServletRequest servletRequest() {
return mock(HttpServletRequest.class); HttpServletRequest request = mock(HttpServletRequest.class);
User user = new User().setUserId("anonymous");
when(request.getUserPrincipal()).thenReturn(user);
return request;
} }
} }
...@@ -157,7 +164,6 @@ public class UriServiceTest { ...@@ -157,7 +164,6 @@ public class UriServiceTest {
when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
when(nodeDAO.listNode(eq("/mydata1/mydata2"))).thenReturn(Optional.empty()); when(nodeDAO.listNode(eq("/mydata1/mydata2"))).thenReturn(Optional.empty());
User user = mock(User.class); User user = mock(User.class);
when(user.getAccessToken()).thenReturn("<token>"); when(user.getAccessToken()).thenReturn("<token>");
when(user.getName()).thenReturn("user1"); when(user.getName()).thenReturn("user1");
...@@ -198,6 +204,130 @@ public class UriServiceTest { ...@@ -198,6 +204,130 @@ public class UriServiceTest {
verify(nodeDAO).setNodeLocation(eq("/test/f1/lbtfile.fits"), eq(5), eq("lbtfile.fits")); verify(nodeDAO).setNodeLocation(eq("/test/f1/lbtfile.fits"), eq(5), eq("lbtfile.fits"));
} }
@Test
public void testSetSyncTransferEndpointsPullFromVoSpace() {
mockPublicNode();
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1");
transfer.setDirection("pullFromVoSpace");
Protocol protocol1 = new Protocol();
protocol1.setUri("ivo://ivoa.net/vospace/core#httpget");
transfer.getProtocols().add(protocol1);
Protocol protocol2 = new Protocol();
protocol2.setUri("invalid_protocol");
transfer.getProtocols().add(protocol2);
JobSummary job = new JobSummary();
JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
jobInfo.getAny().add(transfer);
job.setJobInfo(jobInfo);
assertEquals(2, transfer.getProtocols().size());
uriService.setSyncTransferEndpoints(job);
// invalid protocol is removed
assertEquals(1, transfer.getProtocols().size());
assertEquals("ivo://ivoa.net/vospace/core#httpget", transfer.getProtocols().get(0).getUri());
}
@Test
public void testSetSyncTransferEndpointsPushToVoSpace() {
Node node = mockPublicNode();
Property creator = new Property();
creator.setUri(NodeProperties.CREATOR_URI);
creator.setValue("user1");
node.getProperties().add(creator);
User user = mock(User.class);
when(user.getName()).thenReturn("user1");
when(servletRequest.getUserPrincipal()).thenReturn(user);
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1");
transfer.setDirection("pushToVoSpace");
Protocol protocol1 = new Protocol();
protocol1.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol1);
Protocol protocol2 = new Protocol();
protocol2.setUri("invalid_protocol");
transfer.getProtocols().add(protocol2);
JobSummary job = new JobSummary();
JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
jobInfo.getAny().add(transfer);
job.setJobInfo(jobInfo);
assertEquals(2, transfer.getProtocols().size());
uriService.setSyncTransferEndpoints(job);
// invalid protocol is removed
assertEquals(1, transfer.getProtocols().size());
assertEquals("ivo://ivoa.net/vospace/core#httpput", transfer.getProtocols().get(0).getUri());
}
@Test
public void testSetSyncTransferEndpointsUnsupportedProtocol() {
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1");
transfer.setDirection("pullFromVoSpace");
Protocol protocol = new Protocol();
protocol.setUri("invalid_protocol");
transfer.getProtocols().add(protocol);
JobSummary job = new JobSummary();
JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
jobInfo.getAny().add(transfer);
job.setJobInfo(jobInfo);
try {
uriService.setSyncTransferEndpoints(job);
fail("Expected ProtocolNotSupportedException");
} catch (ProtocolNotSupportedException ex) {
}
}
@Test
public void testSetSyncTransferEndpointsNoProtocols() {
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1");
transfer.setDirection("pullFromVoSpace");
JobSummary job = new JobSummary();
JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
jobInfo.getAny().add(transfer);
job.setJobInfo(jobInfo);
try {
uriService.setSyncTransferEndpoints(job);
fail("Expected InvalidArgumentException");
} catch (InvalidArgumentException ex) {
}
}
private Node mockPublicNode() {
DataNode node = new DataNode();
node.setUri("vos://example.com!vospace/mydata1");
Property publicProperty = new Property();
publicProperty.setUri(NodeProperties.PUBLIC_READ_URI);
publicProperty.setValue(String.valueOf(true));
node.getProperties().add(publicProperty);
when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
return node;
}
private JobSummary getJob() { private JobSummary getJob() {
Transfer transfer = new Transfer(); Transfer transfer = new Transfer();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment