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

Handled polling of moveNode operations

parent a5974e7f
No related branches found
No related tags found
No related merge requests found
Pipeline #2003 passed
......@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXB;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.Jobs;
import net.ivoa.xml.vospace.v2.Node;
......@@ -189,6 +190,21 @@ public class VOSpaceClient {
});
}
public ExecutionPhase getJobPhase(String jobId) {
HttpRequest request = getRequest("/transfers/" + jobId + "/phase")
.GET()
.build();
return call(request, BodyHandlers.ofInputStream(), 200, res -> {
try {
return ExecutionPhase.valueOf(new String(res.readAllBytes()));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});
}
public String getErrorDetail(String jobId) {
HttpRequest request = getRequest("/transfers/" + jobId + "/error")
......
......@@ -7,7 +7,9 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.data.ListNodeData;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import it.inaf.ia2.vospace.ui.service.MainNodesHtmlGenerator;
import it.inaf.ia2.vospace.ui.service.MoveNodeModalHtmlGenerator;
import it.inaf.oats.vospace.datamodel.NodeUtils;
......@@ -18,6 +20,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node;
......@@ -46,6 +49,9 @@ public class NodesController extends BaseController {
@Value("${vospace-authority}")
private String authority;
@Value("${maxPollingAttempts:10}")
private int maxPollingAttempts;
@Autowired
private VOSpaceClient client;
......@@ -154,7 +160,7 @@ public class NodesController extends BaseController {
}
@PostMapping(value = "/move")
public void moveNode(@RequestBody Map<String, Object> params) {
public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) {
String target = getRequiredParam(params, "target");
String direction = getRequiredParam(params, "direction");
......@@ -165,7 +171,28 @@ public class NodesController extends BaseController {
JobSummary job = client.startTransferJob(transfer);
// TODO: polling
// Try to perform polling until completion. If it takes too much time
// sends the execution phase to the UI and let it handles the polling.
ExecutionPhase phase;
int i = 0;
do {
phase = client.getJobPhase(job.getJobId());
if (phase == ExecutionPhase.COMPLETED) {
break;
} else if (phase == ExecutionPhase.ERROR) {
String errorDetail = client.getErrorDetail(job.getJobId());
throw new VOSpaceException("MoveNode operation failed: " + errorDetail);
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
i++;
} while (i < maxPollingAttempts);
job.setPhase(phase);
return ResponseEntity.ok(new Job(job));
}
protected String getPath(String prefix) {
......
......@@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
......@@ -186,6 +187,15 @@ public class VOSpaceClientTest {
assertEquals("Protocol negotiation failed", ex.getMessage());
}
@Test
public void testGetJobPhase() {
CompletableFuture response = getMockedStreamResponseFuture(200, "COMPLETED");
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
assertEquals(ExecutionPhase.COMPLETED, voSpaceClient.getJobPhase("job_id"));
}
protected static String getResourceFileContent(String fileName) {
try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
......
......@@ -7,12 +7,19 @@ package it.inaf.ia2.vospace.ui.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
......@@ -25,7 +32,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
......@@ -33,6 +42,7 @@ import org.springframework.web.util.NestedServletException;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {"vospace-authority=example.com!vospace", "maxPollingAttempts=2"})
public class NodesControllerTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
......@@ -139,4 +149,66 @@ public class NodesControllerTest {
verify(client, times(1)).getNode(eq("/a/b/c"));
}
@Test
public void testMoveNodeSuccess() throws Exception {
when(client.getJobPhase("job_id"))
.thenReturn(ExecutionPhase.EXECUTING)
.thenReturn(ExecutionPhase.COMPLETED);
String response = testMoveNode()
.andExpect(status().isOk())
.andReturn().getResponse()
.getContentAsString();
Job job = MAPPER.readValue(response, Job.class);
assertEquals(ExecutionPhase.COMPLETED, job.getPhase());
}
@Test
public void testMoveNodeExecuting() throws Exception {
when(client.getJobPhase("job_id"))
.thenReturn(ExecutionPhase.EXECUTING);
String response = testMoveNode()
.andExpect(status().isOk())
.andReturn().getResponse()
.getContentAsString();
Job job = MAPPER.readValue(response, Job.class);
assertEquals(ExecutionPhase.EXECUTING, job.getPhase());
}
@Test
public void testMoveNodeError() throws Exception {
when(client.getJobPhase("job_id"))
.thenReturn(ExecutionPhase.ERROR);
when(client.getErrorDetail("job_id")).thenReturn("move_error");
try {
testMoveNode();
fail("Exception was expected");
} catch (Exception ex) {
assertTrue(ex.getCause() instanceof VOSpaceException);
assertTrue(ex.getCause().getMessage().contains("move_error"));
}
}
private ResultActions testMoveNode() throws Exception {
JobSummary job = new JobSummary();
job.setJobId("job_id");
when(client.startTransferJob(any())).thenReturn(job);
Map<String, String> params = Map.of("target", "/path/to/target", "direction", "/path/to/direction");
return mockMvc.perform(post("/move")
.contentType(MediaType.APPLICATION_JSON)
.content(MAPPER.writeValueAsString(params)));
}
}
......@@ -207,11 +207,16 @@ export default new Vuex.Store({
dispatch('setPath', state.path);
});
},
moveNode({ state, dispatch }, data) {
moveNode({ state, commit, dispatch }, data) {
client.moveNode(data)
.then(() => {
.then(job => {
if (job.phase === 'COMPLETED') {
// Reload current node
dispatch('setPath', state.path);
} else {
main.showInfo('Move operation is taking some time and will be handled in background');
commit('addJob', job);
}
});
},
openNodeInMoveModal({ state, commit }, path) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment