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
Branches
No related tags found
No related merge requests found
Pipeline #2003 passed
...@@ -35,6 +35,7 @@ import java.util.stream.Collectors; ...@@ -35,6 +35,7 @@ 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;
import javax.xml.bind.JAXB; 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.JobSummary;
import net.ivoa.xml.uws.v1.Jobs; import net.ivoa.xml.uws.v1.Jobs;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
...@@ -189,6 +190,21 @@ public class VOSpaceClient { ...@@ -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) { public String getErrorDetail(String jobId) {
HttpRequest request = getRequest("/transfers/" + jobId + "/error") HttpRequest request = getRequest("/transfers/" + jobId + "/error")
......
...@@ -7,7 +7,9 @@ package it.inaf.ia2.vospace.ui.controller; ...@@ -7,7 +7,9 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; 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.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.MainNodesHtmlGenerator;
import it.inaf.ia2.vospace.ui.service.MoveNodeModalHtmlGenerator; import it.inaf.ia2.vospace.ui.service.MoveNodeModalHtmlGenerator;
import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.NodeUtils;
...@@ -18,6 +20,7 @@ import java.util.Optional; ...@@ -18,6 +20,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
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.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
...@@ -46,6 +49,9 @@ public class NodesController extends BaseController { ...@@ -46,6 +49,9 @@ public class NodesController extends BaseController {
@Value("${vospace-authority}") @Value("${vospace-authority}")
private String authority; private String authority;
@Value("${maxPollingAttempts:10}")
private int maxPollingAttempts;
@Autowired @Autowired
private VOSpaceClient client; private VOSpaceClient client;
...@@ -154,7 +160,7 @@ public class NodesController extends BaseController { ...@@ -154,7 +160,7 @@ public class NodesController extends BaseController {
} }
@PostMapping(value = "/move") @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 target = getRequiredParam(params, "target");
String direction = getRequiredParam(params, "direction"); String direction = getRequiredParam(params, "direction");
...@@ -165,7 +171,28 @@ public class NodesController extends BaseController { ...@@ -165,7 +171,28 @@ public class NodesController extends BaseController {
JobSummary job = client.startTransferJob(transfer); 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) { protected String getPath(String prefix) {
......
...@@ -18,6 +18,7 @@ import java.util.Arrays; ...@@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.Optional; 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.uws.v1.ExecutionPhase;
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.Protocol;
import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.Transfer;
...@@ -186,6 +187,15 @@ public class VOSpaceClientTest { ...@@ -186,6 +187,15 @@ public class VOSpaceClientTest {
assertEquals("Protocol negotiation failed", ex.getMessage()); 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) { 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);
......
...@@ -7,12 +7,19 @@ package it.inaf.ia2.vospace.ui.controller; ...@@ -7,12 +7,19 @@ package it.inaf.ia2.vospace.ui.controller;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; 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.Arrays;
import java.util.List; 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.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode; 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.assertFalse;
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 org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
...@@ -25,7 +32,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock ...@@ -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.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc; 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.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
...@@ -33,6 +42,7 @@ import org.springframework.web.util.NestedServletException; ...@@ -33,6 +42,7 @@ import org.springframework.web.util.NestedServletException;
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@TestPropertySource(properties = {"vospace-authority=example.com!vospace", "maxPollingAttempts=2"})
public class NodesControllerTest { public class NodesControllerTest {
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
...@@ -139,4 +149,66 @@ public class NodesControllerTest { ...@@ -139,4 +149,66 @@ public class NodesControllerTest {
verify(client, times(1)).getNode(eq("/a/b/c")); 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({ ...@@ -207,11 +207,16 @@ export default new Vuex.Store({
dispatch('setPath', state.path); dispatch('setPath', state.path);
}); });
}, },
moveNode({ state, dispatch }, data) { moveNode({ state, commit, dispatch }, data) {
client.moveNode(data) client.moveNode(data)
.then(() => { .then(job => {
if (job.phase === 'COMPLETED') {
// Reload current node // Reload current node
dispatch('setPath', state.path); 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) { 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