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
No related branches found
No related tags found
No related merge requests found
......@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
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 static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.io.IOException;
......@@ -21,11 +22,16 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
......@@ -96,7 +102,7 @@ public class VOSpaceClient {
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")
.header("Accept", useJson ? "application/json" : "text/xml")
......@@ -104,7 +110,32 @@ public class VOSpaceClient {
.POST(HttpRequest.BodyPublishers.ofString(marshal(transfer)))
.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) {
......@@ -170,16 +201,25 @@ public class VOSpaceClient {
}
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 {
return httpClient.sendAsync(request, responseBodyHandler)
.thenApply(response -> {
if (response.statusCode() == expectedStatusCode) {
return response.body();
return response;
}
logServerError(request, response);
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();
} catch (CompletionException ex) {
if (ex.getCause() != null) {
......
......@@ -158,7 +158,7 @@ public class JobController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol);
return client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
return client.getFileServiceEndpoint(transfer);
}
private void upload(String endpoint, String content) {
......
......@@ -80,7 +80,7 @@ public class NodesController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
transfer.getProtocols().add(protocol);
String url = client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
String url = client.getFileServiceEndpoint(transfer);
HttpHeaders headers = new HttpHeaders();
headers.set("Location", url);
return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
......
......@@ -96,6 +96,6 @@ public class UploadController extends BaseController {
protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
transfer.getProtocols().add(protocol);
return client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
return client.getFileServiceEndpoint(transfer);
}
}
......@@ -5,17 +5,23 @@
*/
package it.inaf.ia2.vospace.ui.client;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletRequest;
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.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
......@@ -104,6 +110,81 @@ public class VOSpaceClientTest {
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) {
try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
......
......@@ -6,13 +6,11 @@
package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import java.util.Collections;
import java.util.function.Consumer;
import net.ivoa.xml.uws.v1.ErrorSummary;
import net.ivoa.xml.uws.v1.ErrorType;
import net.ivoa.xml.uws.v1.ExecutionPhase;
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.fail;
import org.junit.jupiter.api.Test;
......@@ -68,10 +66,7 @@ public class JobControllerTest {
return transfer.getTarget().startsWith("vos://example.com!vospace/path/to/.tmp-");
}))).thenReturn(job);
Protocol protocol = new Protocol();
protocol.setEndpoint("http://file-service/path/to/file");
when(client.getFileServiceEndpoints(any())).thenReturn(Collections.singletonList(protocol));
when(client.getFileServiceEndpoint(any())).thenReturn("http://file-service/path/to/file");
mockMvc.perform(post("/recall")
.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