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

Handled errors in transfer (empty protocols list)

parent feba8ee8
Branches
Tags
No related merge requests found
...@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; ...@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.VOSpaceUiApplication; import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
import it.inaf.ia2.vospace.ui.data.Job; import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException; import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.io.IOException; import java.io.IOException;
...@@ -21,11 +22,16 @@ import java.net.http.HttpClient; ...@@ -21,11 +22,16 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
...@@ -96,7 +102,7 @@ public class VOSpaceClient { ...@@ -96,7 +102,7 @@ public class VOSpaceClient {
return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, JobSummary.class)); return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, JobSummary.class));
} }
public List<Protocol> getFileServiceEndpoints(Transfer transfer) { public String getFileServiceEndpoint(Transfer transfer) {
HttpRequest request = getRequest("/synctrans") HttpRequest request = getRequest("/synctrans")
.header("Accept", useJson ? "application/json" : "text/xml") .header("Accept", useJson ? "application/json" : "text/xml")
...@@ -104,7 +110,32 @@ public class VOSpaceClient { ...@@ -104,7 +110,32 @@ public class VOSpaceClient {
.POST(HttpRequest.BodyPublishers.ofString(marshal(transfer))) .POST(HttpRequest.BodyPublishers.ofString(marshal(transfer)))
.build(); .build();
return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)).getProtocols(); List<Protocol> protocols = new ArrayList<>();
HttpResponse<InputStream> redirectResponse = call(request, BodyHandlers.ofInputStream(), 200, (res, previousRes) -> {
Transfer transferRes = unmarshal(res, Transfer.class);
protocols.addAll(transferRes.getProtocols());
// IMPORTANT: HTTP call for checking the error status (in case of empty protocols list) must be
// performed outside this lambda otherwise the exception "IllegalStateException: No thread-bound request found"
// will be thrown. For this reason the previous response data is returned here and checked later.
return previousRes;
});
if (protocols.isEmpty()) {
redirectResponse.headers().firstValue("Location").ifPresent(url -> {
Pattern pattern = Pattern.compile(".*/transfers/(.+)/results/transferDetails");
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
String jobId = matcher.group(1);
String errorDetail = getErrorDetail(jobId);
if (!errorDetail.isBlank()) {
throw new BadRequestException(errorDetail);
}
}
});
throw new BadRequestException("Protocol negotiation failed");
}
return protocols.get(0).getEndpoint();
} }
public Node createNode(Node node) { public Node createNode(Node node) {
...@@ -170,16 +201,25 @@ public class VOSpaceClient { ...@@ -170,16 +201,25 @@ public class VOSpaceClient {
} }
private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) { private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) {
return call(request, responseBodyHandler, expectedStatusCode, (res, prev) -> {
return responseHandler.apply(res);
});
}
private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, BiFunction<T, HttpResponse<T>, U> responseHandler) {
try { try {
return httpClient.sendAsync(request, responseBodyHandler) return httpClient.sendAsync(request, responseBodyHandler)
.thenApply(response -> { .thenApply(response -> {
if (response.statusCode() == expectedStatusCode) { if (response.statusCode() == expectedStatusCode) {
return response.body(); return response;
} }
logServerError(request, response); logServerError(request, response);
throw new VOSpaceException("Error calling " + request.uri().toString() + ". Server response code is " + response.statusCode()); throw new VOSpaceException("Error calling " + request.uri().toString() + ". Server response code is " + response.statusCode());
}) })
.thenApplyAsync(response -> responseHandler.apply(response), jaxbExecutor) .thenApplyAsync(response -> {
HttpResponse<T> prev = response.previousResponse().orElse(null);
return responseHandler.apply(response.body(), prev);
}, jaxbExecutor)
.join(); .join();
} catch (CompletionException ex) { } catch (CompletionException ex) {
if (ex.getCause() != null) { if (ex.getCause() != null) {
......
...@@ -158,7 +158,7 @@ public class JobController extends BaseController { ...@@ -158,7 +158,7 @@ public class JobController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpput"); protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol); transfer.getProtocols().add(protocol);
return client.getFileServiceEndpoints(transfer).get(0).getEndpoint(); return client.getFileServiceEndpoint(transfer);
} }
private void upload(String endpoint, String content) { private void upload(String endpoint, String content) {
......
...@@ -80,7 +80,7 @@ public class NodesController extends BaseController { ...@@ -80,7 +80,7 @@ public class NodesController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
transfer.getProtocols().add(protocol); transfer.getProtocols().add(protocol);
String url = client.getFileServiceEndpoints(transfer).get(0).getEndpoint(); String url = client.getFileServiceEndpoint(transfer);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.set("Location", url); headers.set("Location", url);
return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
......
...@@ -96,6 +96,6 @@ public class UploadController extends BaseController { ...@@ -96,6 +96,6 @@ public class UploadController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpput"); protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol); transfer.getProtocols().add(protocol);
return client.getFileServiceEndpoints(transfer).get(0).getEndpoint(); return client.getFileServiceEndpoint(transfer);
} }
} }
...@@ -5,17 +5,23 @@ ...@@ -5,17 +5,23 @@
*/ */
package it.inaf.ia2.vospace.ui.client; package it.inaf.ia2.vospace.ui.client;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Protocol;
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.assertThrows;
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 org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
...@@ -104,6 +110,81 @@ public class VOSpaceClientTest { ...@@ -104,6 +110,81 @@ public class VOSpaceClientTest {
assertEquals("error message", voSpaceClient.getErrorDetail("123")); assertEquals("error message", voSpaceClient.getErrorDetail("123"));
} }
@Test
public void testGetFileServiceEndpointSuccess() throws Exception {
Transfer transfer = new Transfer();
transfer.setDirection("pushToVoSpace");
transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");
Protocol protocol = new Protocol();
protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol);
CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("transfer-response-ok.xml"));
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
assertEquals("http://storage1.example.com/trans/mynode", voSpaceClient.getFileServiceEndpoint(transfer));
}
@Test
public void testGetFileServiceEndpointError() {
Transfer transfer = new Transfer();
transfer.setDirection("pushToVoSpace");
transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");
Protocol protocol = new Protocol();
protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol);
HttpResponse<InputStream> redirectResponse = mock(HttpResponse.class);
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.firstValue("Location")).thenReturn(Optional.of("/vospace/transfers/1234/results/transferDetails"));
when(redirectResponse.headers()).thenReturn(headers);
HttpResponse<InputStream> mockedStreamResponse = getMockedStreamResponse(200, getResourceFileContent("transfer-response-no-protocols.xml"));
when(mockedStreamResponse.previousResponse()).thenReturn(Optional.of(redirectResponse));
CompletableFuture response1 = CompletableFuture.completedFuture(mockedStreamResponse);
CompletableFuture response2 = getMockedStringResponseFuture(200, "error message");
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response1).thenReturn(response2);
BadRequestException ex = assertThrows(BadRequestException.class, () -> {
voSpaceClient.getFileServiceEndpoint(transfer);
});
assertEquals("error message", ex.getMessage());
}
@Test
public void testGetFileServiceEndpointErrorWithoutMessage() {
Transfer transfer = new Transfer();
transfer.setDirection("pushToVoSpace");
transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");
Protocol protocol = new Protocol();
protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol);
HttpResponse<InputStream> redirectResponse = mock(HttpResponse.class);
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.firstValue("Location")).thenReturn(Optional.of("/vospace/transfers/1234/results/transferDetails"));
when(redirectResponse.headers()).thenReturn(headers);
HttpResponse<InputStream> mockedStreamResponse = getMockedStreamResponse(200, getResourceFileContent("transfer-response-no-protocols.xml"));
when(mockedStreamResponse.previousResponse()).thenReturn(Optional.of(redirectResponse));
CompletableFuture response1 = CompletableFuture.completedFuture(mockedStreamResponse);
CompletableFuture response2 = getMockedStringResponseFuture(200, "");
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response1).thenReturn(response2);
BadRequestException ex = assertThrows(BadRequestException.class, () -> {
voSpaceClient.getFileServiceEndpoint(transfer);
});
assertEquals("Protocol negotiation failed", ex.getMessage());
}
protected static String getResourceFileContent(String fileName) { protected static String getResourceFileContent(String fileName) {
try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) { try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8); return new String(in.readAllBytes(), StandardCharsets.UTF_8);
......
...@@ -6,13 +6,11 @@ ...@@ -6,13 +6,11 @@
package it.inaf.ia2.vospace.ui.controller; package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import java.util.Collections;
import java.util.function.Consumer; import java.util.function.Consumer;
import net.ivoa.xml.uws.v1.ErrorSummary; import net.ivoa.xml.uws.v1.ErrorSummary;
import net.ivoa.xml.uws.v1.ErrorType; import net.ivoa.xml.uws.v1.ErrorType;
import net.ivoa.xml.uws.v1.ExecutionPhase; 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.vospace.v2.Protocol;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -68,10 +66,7 @@ public class JobControllerTest { ...@@ -68,10 +66,7 @@ public class JobControllerTest {
return transfer.getTarget().startsWith("vos://example.com!vospace/path/to/.tmp-"); return transfer.getTarget().startsWith("vos://example.com!vospace/path/to/.tmp-");
}))).thenReturn(job); }))).thenReturn(job);
Protocol protocol = new Protocol(); when(client.getFileServiceEndpoint(any())).thenReturn("http://file-service/path/to/file");
protocol.setEndpoint("http://file-service/path/to/file");
when(client.getFileServiceEndpoints(any())).thenReturn(Collections.singletonList(protocol));
mockMvc.perform(post("/recall") mockMvc.perform(post("/recall")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
......
<?xml version="1.0" encoding="UTF-8"?>
<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" version="2.1">
<vos:target>vos://example.com!vospace/mynode</vos:target>
<vos:direction>pushToVoSpace</vos:direction>
</vos:transfer>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" version="2.1">
<vos:target>vos://example.com!vospace/mynode</vos:target>
<vos:direction>pushToVoSpace</vos:direction>
<vos:protocol uri="ivo://ivoa.net/vospace/core#httpput">
<vos:endpoint>http://storage1.example.com/trans/mynode</vos:endpoint>
</vos:protocol>
</vos:transfer>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment