diff --git a/src/main/java/it/inaf/oats/vospace/JobService.java b/src/main/java/it/inaf/oats/vospace/JobService.java index 2f397b160636a428ec3ead255e9b4ac0a2d3a128..95e11ada1c54b48f296b0f6dcbbf22daec66fade 100644 --- a/src/main/java/it/inaf/oats/vospace/JobService.java +++ b/src/main/java/it/inaf/oats/vospace/JobService.java @@ -1,8 +1,10 @@ package it.inaf.oats.vospace; +import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.persistence.JobDAO; import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.JobSummary; +import net.ivoa.xml.vospace.v2.Protocol; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -43,35 +45,48 @@ public class JobService { private void startJob(JobSummary job) { - switch (getJobType(job)) { + Transfer transfer = uriService.getTransfer(job); + + switch (getJobType(transfer)) { case pullToVoSpace: - handlePullToVoSpace(job); + handlePullToVoSpace(job, transfer); break; case pullFromVoSpace: case pushToVoSpace: - handleVoSpaceUrlsListResult(job); + handleVoSpaceUrlsListResult(job, transfer); break; default: throw new UnsupportedOperationException("Not implemented yet"); } } - private void handlePullToVoSpace(JobSummary job) { - // TODO: check protocol - asyncTransfService.startJob(job); + private void handlePullToVoSpace(JobSummary job, Transfer transfer) { + + for (Protocol protocol : transfer.getProtocols()) { + switch (protocol.getUri()) { + case "ia2:async-recall": + asyncTransfService.startJob(job); + return; + case "ivo://ivoa.net/vospace/core#httpget": + String nodeUri = transfer.getTarget(); + String contentUri = protocol.getEndpoint(); + uriService.setNodeRemoteLocation(nodeUri, contentUri); + uriService.setTransferJobResult(job, transfer); + jobDAO.updateJob(job); + return; + default: + throw new InternalFaultException("Unsupported pullToVoSpace protocol: " + protocol.getUri()); + } + } } - private void handleVoSpaceUrlsListResult(JobSummary job) { + private void handleVoSpaceUrlsListResult(JobSummary job, Transfer transfer) { job.setPhase(ExecutionPhase.EXECUTING); - uriService.setTransferJobResult(job); + uriService.setTransferJobResult(job, transfer); jobDAO.updateJob(job); } - private JobType getJobType(JobSummary job) { - - // TODO: check types - Transfer transfer = (Transfer) job.getJobInfo().getAny().get(0); - + private JobType getJobType(Transfer transfer) { return JobType.valueOf(transfer.getDirection()); } diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java index f95a87e7f9ef0a56cd407fab609667952d6ca88c..5e80e132d94fb0e8dcabf312803bdfb0263521d4 100644 --- a/src/main/java/it/inaf/oats/vospace/UriService.java +++ b/src/main/java/it/inaf/oats/vospace/UriService.java @@ -5,11 +5,18 @@ import it.inaf.ia2.aa.data.User; import it.inaf.ia2.rap.client.call.TokenExchangeRequest; import it.inaf.oats.vospace.datamodel.NodeProperties; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; +import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.NodeNotFoundException; +import it.inaf.oats.vospace.exception.PermissionDeniedException; +import it.inaf.oats.vospace.persistence.LocationDAO; import it.inaf.oats.vospace.persistence.NodeDAO; +import it.inaf.oats.vospace.persistence.model.Location; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; 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.ResultReference; import net.ivoa.xml.vospace.v2.Node; @@ -30,6 +37,9 @@ public class UriService { @Autowired private NodeDAO nodeDao; + + @Autowired + private LocationDAO locationDAO; @Autowired private HttpServletRequest servletRequest; @@ -37,15 +47,16 @@ public class UriService { @Autowired private ServletRapClient rapClient; - public void setTransferJobResult(JobSummary job) { + public void setTransferJobResult(JobSummary job, Transfer transfer) { List<ResultReference> results = new ArrayList<>(); ResultReference result = new ResultReference(); - result.setHref(getEndpoint(job)); + result.setHref(getEndpoint(job, transfer)); results.add(result); job.setResults(results); + job.setPhase(ExecutionPhase.COMPLETED); } public void setSyncTransferEndpoints(JobSummary job) { @@ -58,20 +69,33 @@ public class UriService { && !"ivo://ivoa.net/vospace/core#httpput".equals(protocol.getUri())) { throw new IllegalStateException("Unsupported protocol " + protocol.getUri()); } - protocol.setEndpoint(getEndpoint(job)); + protocol.setEndpoint(getEndpoint(job, transfer)); } - private String getEndpoint(JobSummary job) { - - Transfer transfer = getTransfer(job); + private String getEndpoint(JobSummary job, Transfer transfer) { String relativePath = transfer.getTarget().substring("vos://".length() + authority.length()); Node node = nodeDao.listNode(relativePath).orElseThrow(() -> new NodeNotFoundException(relativePath)); - // TODO build the path according to node type - // - String endpoint = fileServiceUrl + urlEncodePath(relativePath) + "?jobId=" + job.getJobId(); + Location location = locationDAO.getNodeLocation(relativePath).orElseThrow(() + -> new InternalFaultException("No registered location found for vos_path " + relativePath)); + + String endpoint; + switch (location.getType()) { + case PORTAL: + String fileName = nodeDao.getNodeOsName(relativePath); + endpoint = "http://" + location.getSource().getHostname() + location.getSource().getBasePath(); + if (!endpoint.endsWith("/")) { + endpoint += "/"; + } + endpoint += fileName; + break; + default: + endpoint = fileServiceUrl + urlEncodePath(relativePath); + } + + endpoint += "?jobId=" + job.getJobId(); if (!"true".equals(NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI))) { endpoint += "&token=" + getEndpointToken(fileServiceUrl + relativePath); @@ -85,8 +109,7 @@ public class UriService { String token = ((User) servletRequest.getUserPrincipal()).getAccessToken(); if (token == null) { - // TODO: use PermissionDenied VoSpaceException - throw new IllegalStateException("Token is null"); + throw new PermissionDeniedException("Token is null"); } TokenExchangeRequest exchangeRequest = new TokenExchangeRequest() @@ -97,8 +120,39 @@ public class UriService { return rapClient.exchangeToken(exchangeRequest, servletRequest); } - private Transfer getTransfer(JobSummary job) { - // TODO add checks on data type + public void setNodeRemoteLocation(String nodeUri, String contentUri) { + + URL url; + try { + url = new URL(contentUri); + } catch (MalformedURLException ex) { + throw new InternalFaultException(ex); + } + + Location location = locationDAO.findPortalLocation(url.getHost()).orElseThrow(() + -> new InternalFaultException("No registered location found for host " + url.getHost())); + + String vosPath = nodeUri.replaceAll("vos://[^/]+", ""); + + String fileName = url.getPath().substring(url.getPath().lastIndexOf("/") + 1); + + nodeDao.setNodeLocation(vosPath, location.getId(), fileName); + } + + public Transfer getTransfer(JobSummary job) { + + List<Object> jobPayload = job.getJobInfo().getAny(); + if (jobPayload.isEmpty()) { + throw new IllegalStateException("Empty job payload for job " + job.getJobId()); + } + if (jobPayload.size() > 1) { + throw new IllegalStateException("Multiple objects in job payload not supported"); + } + if (!(jobPayload.get(0) instanceof Transfer)) { + throw new IllegalStateException(jobPayload.get(0).getClass().getCanonicalName() + + " not supported as job payload. Job id: " + job.getJobId()); + } + return (Transfer) job.getJobInfo().getAny().get(0); } } diff --git a/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java b/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java index 2429c070a061a09f10ea8eb712c1993847dcd1f8..635bf575bdbf5ec2d48c58fec7547346f9eebfa7 100644 --- a/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java +++ b/src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java @@ -1,19 +1,25 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package it.inaf.oats.vospace.exception; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; - @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) // Status code 500 -public class InternalFaultException extends VoSpaceException { +public class InternalFaultException extends VoSpaceException { + + private static final Logger LOG = LoggerFactory.getLogger(InternalFaultException.class); public InternalFaultException(String msg) { super("InternalFaultException: " + msg); } - + + public InternalFaultException(Throwable cause) { + super("InternalFaultException: " + getMessage(cause)); + } + + private static String getMessage(Throwable cause) { + LOG.error("Exception caught", cause); + return cause.getMessage() != null ? cause.getMessage() : cause.getClass().getCanonicalName(); + } } diff --git a/src/main/java/it/inaf/oats/vospace/persistence/LocationDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/LocationDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..ef8618c646bfc08b45de78ea5bdc50d3294a5202 --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/persistence/LocationDAO.java @@ -0,0 +1,113 @@ +package it.inaf.oats.vospace.persistence; + +import it.inaf.oats.vospace.persistence.model.Location; +import it.inaf.oats.vospace.persistence.model.LocationType; +import it.inaf.oats.vospace.persistence.model.Storage; +import it.inaf.oats.vospace.persistence.model.StorageType; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class LocationDAO { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public LocationDAO(DataSource dataSource) { + jdbcTemplate = new JdbcTemplate(dataSource); + } + + public Optional<Location> findPortalLocation(String host) { + + String sql = "SELECT location_id, location_type, storage_src_id, storage_dest_id,\n" + + "storage_id, storage_type, base_path, hostname\n" + + "FROM location l\n" + + "JOIN storage s ON l.storage_src_id = s.storage_id OR l.storage_dest_id = s.storage_id\n" + + "WHERE hostname = ?"; + + return jdbcTemplate.query(sql, ps -> { + ps.setString(1, host); + }, rs -> { + return getLocation(rs, () -> new IllegalStateException("Found multiple locations for the same hostname")); + }); + } + + public Optional<Location> getNodeLocation(String vosPath) { + + String sql = "SELECT location_id, location_type, storage_src_id, storage_dest_id,\n" + + "storage_id, storage_type, base_path, hostname\n" + + "FROM location l\n" + + "JOIN storage s ON l.storage_src_id = s.storage_id OR l.storage_dest_id = s.storage_id\n" + + "WHERE location_id = (\n" + + "SELECT location_id FROM node n\n" + + "JOIN node_vos_path p ON n.node_id = p.node_id\n" + + "WHERE p.vos_path = ?)"; + + return jdbcTemplate.query(sql, ps -> { + ps.setString(1, vosPath); + }, rs -> { + return getLocation(rs, () -> new IllegalStateException("Found multiple locations for the same vos_path")); + }); + } + + private Optional<Location> getLocation(ResultSet rs, Supplier<IllegalStateException> exceptionSupplier) throws SQLException { + List<Location> locations = getLocations(rs); + if (locations.isEmpty()) { + return Optional.empty(); + } + if (locations.size() > 1) { + throw exceptionSupplier.get(); + } + return Optional.of(locations.get(0)); + } + + private List<Location> getLocations(ResultSet rs) throws SQLException { + + Map<Integer, Storage> storagesMap = new HashMap<>(); + Map<Integer, Location> locationsMap = new HashMap<>(); + + while (rs.next()) { + int locationId = rs.getInt("location_id"); + Location location = locationsMap.get(locationId); + if (location == null) { + location = new Location(); + location.setId(locationId); + locationsMap.put(locationId, location); + } + location.setType(LocationType.parse(rs.getString("location_type"))); + + int storageId = rs.getInt("storage_id"); + Storage storage = storagesMap.get(storageId); + if (storage == null) { + storage = new Storage(); + storage.setId(storageId); + storagesMap.put(storageId, storage); + } + + storage.setType(StorageType.parse(rs.getString("storage_type"))); + storage.setBasePath(rs.getString("base_path")); + storage.setHostname(rs.getString("hostname")); + + Storage storageSrc = storagesMap.get(rs.getInt("storage_src_id")); + if (storageSrc != null) { + location.setSource(storageSrc); + } + Storage storageDest = storagesMap.get(rs.getInt("storage_dest_id")); + if (storageDest != null) { + location.setDestination(storageDest); + } + } + + return new ArrayList<>(locationsMap.values()); + } +} diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java index 216f64c9264dcdae970c28672da39e0686e338b8..9267722d57563ecbe790ea86d12291337b794c98 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java @@ -1,5 +1,7 @@ package it.inaf.oats.vospace.persistence; +import it.inaf.oats.vospace.datamodel.NodeUtils; +import it.inaf.oats.vospace.exception.InternalFaultException; import java.sql.Array; import net.ivoa.xml.vospace.v2.Node; import java.sql.PreparedStatement; @@ -239,6 +241,35 @@ public class NodeDAO { return jdbcTemplate.queryForObject(sql, args, types, Integer.class); } + + public String getNodeOsName(String vosPath) { + String sql = "SELECT \n" + + "(CASE WHEN os_name IS NOT NULL THEN os_name ELSE name END) AS os_name\n" + + "FROM node n\n" + + "JOIN node_vos_path p ON n.node_id = p.node_id\n" + + "WHERE p.vos_path = ?"; + + Object[] args = {vosPath}; + int[] types = {Types.VARCHAR}; + + return jdbcTemplate.queryForObject(sql, args, types, String.class); + } + + public void setNodeLocation(String vosPath, int locationId, String nodeOsName) { + + String sql = "UPDATE node SET location_id = ?, os_name = ? WHERE node_id = " + + "(SELECT node_id FROM node_vos_path WHERE vos_path = ?)"; + + int updated = jdbcTemplate.update(sql, ps -> { + ps.setInt(1, locationId); + ps.setString(2, nodeOsName); + ps.setString(3, vosPath); + }); + + if (updated != 1) { + throw new InternalFaultException("Unable to set node location for path " + vosPath); + } + } private String getPropertyURI(String propertyName) { return "ivo://ivoa.net/vospace/core#".concat(propertyName); diff --git a/src/main/java/it/inaf/oats/vospace/persistence/model/Location.java b/src/main/java/it/inaf/oats/vospace/persistence/model/Location.java new file mode 100644 index 0000000000000000000000000000000000000000..3d6b2814ffaa35c1997c956a323020f838d46d6f --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/persistence/model/Location.java @@ -0,0 +1,41 @@ +package it.inaf.oats.vospace.persistence.model; + +public class Location { + + private int id; + private LocationType type; + private Storage source; + private Storage destination; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public LocationType getType() { + return type; + } + + public void setType(LocationType type) { + this.type = type; + } + + public Storage getSource() { + return source; + } + + public void setSource(Storage source) { + this.source = source; + } + + public Storage getDestination() { + return destination; + } + + public void setDestination(Storage destination) { + this.destination = destination; + } +} diff --git a/src/main/java/it/inaf/oats/vospace/persistence/model/LocationType.java b/src/main/java/it/inaf/oats/vospace/persistence/model/LocationType.java new file mode 100644 index 0000000000000000000000000000000000000000..d64a231705c5763bef41f7b160d5e6cc13ce179b --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/persistence/model/LocationType.java @@ -0,0 +1,28 @@ +package it.inaf.oats.vospace.persistence.model; + +public enum LocationType { + + ASYNC("async"), + PORTAL("portal"), + USER("user"); + + private final String name; + + private LocationType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public static LocationType parse(String value) { + for (LocationType type : LocationType.values()) { + if (type.name.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Invalid LocationType " + value); + } +} diff --git a/src/main/java/it/inaf/oats/vospace/persistence/model/Storage.java b/src/main/java/it/inaf/oats/vospace/persistence/model/Storage.java new file mode 100644 index 0000000000000000000000000000000000000000..f936ac95aca3fc447c8dbfaa46afceeb42968696 --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/persistence/model/Storage.java @@ -0,0 +1,41 @@ +package it.inaf.oats.vospace.persistence.model; + +public class Storage { + + private int id; + private StorageType type; + private String basePath; + private String hostname; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public StorageType getType() { + return type; + } + + public void setType(StorageType type) { + this.type = type; + } + + public String getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = basePath; + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } +} diff --git a/src/main/java/it/inaf/oats/vospace/persistence/model/StorageType.java b/src/main/java/it/inaf/oats/vospace/persistence/model/StorageType.java new file mode 100644 index 0000000000000000000000000000000000000000..65b07c2f7fdf29365ba1dbadc5e24ce95e6c0af7 --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/persistence/model/StorageType.java @@ -0,0 +1,29 @@ +package it.inaf.oats.vospace.persistence.model; + +public enum StorageType { + + COLD("cold"), + HOT("hot"), + LOCAL("local"), + PORTAL("portal"); + + private final String name; + + private StorageType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public static StorageType parse(String value) { + for (StorageType type : StorageType.values()) { + if (type.name.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Invalid StorageType " + value); + } +} diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java index 5dd3cb6af781e1cdb6ea77f18bd4af62ce12842c..a15c8811ff056214080ff00c05bec9218db79539 100644 --- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java @@ -3,7 +3,11 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument; import it.inaf.oats.vospace.persistence.JobDAO; +import it.inaf.oats.vospace.persistence.LocationDAO; import it.inaf.oats.vospace.persistence.NodeDAO; +import it.inaf.oats.vospace.persistence.model.Location; +import it.inaf.oats.vospace.persistence.model.LocationType; +import it.inaf.oats.vospace.persistence.model.Storage; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.sql.Timestamp; @@ -41,6 +45,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.w3c.dom.Document; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import static org.mockito.ArgumentMatchers.argThat; @SpringBootTest @AutoConfigureMockMvc @@ -55,11 +62,32 @@ public class TransferControllerTest { private NodeDAO nodeDao; @MockBean - private AsyncTransferService tapeService; + private LocationDAO locationDao; + + @MockBean + private AsyncTransferService asyncTransfService; @Autowired private MockMvc mockMvc; + @BeforeEach + public void init() { + Location asyncLocation = new Location(); + asyncLocation.setType(LocationType.ASYNC); + asyncLocation.setId(1); + when(locationDao.getNodeLocation(eq("/mynode"))).thenReturn(Optional.of(asyncLocation)); + + Location portalLocation = new Location(); + portalLocation.setType(LocationType.PORTAL); + portalLocation.setId(2); + Storage portalStorage = new Storage(); + portalStorage.setHostname("archive.lbto.org"); + portalStorage.setBasePath("/files"); + portalLocation.setSource(portalStorage); + when(locationDao.getNodeLocation(eq("/portalnode"))).thenReturn(Optional.of(portalLocation)); + when(locationDao.findPortalLocation(any())).thenReturn(Optional.of(portalLocation)); + } + @Test public void testPullFromVoSpaceAsync() throws Exception { @@ -99,9 +127,32 @@ public class TransferControllerTest { } @Test - public void testPullToVoSpace() throws Exception { + public void testPullToVoSpaceTape() throws Exception { + testPullToVoSpace("/mynode", getResourceFileContent("pullToVoSpace-tape.xml")); + + verify(asyncTransfService, times(1)).startJob(any()); + } + + @Test + public void testPullToVoSpacePortal() throws Exception { + + when(nodeDao.getNodeOsName(eq("/portalnode"))).thenReturn("file.fits"); + + testPullToVoSpace("/portalnode", getResourceFileContent("pullToVoSpace-portal.xml")); + + verify(nodeDao, times(1)).setNodeLocation(eq("/portalnode"), eq(2), eq("lbcr.20130512.060722.fits.gz")); + + verify(jobDao, times(1)).updateJob(argThat(j -> { + assertTrue(j.getResults().get(0).getHref().startsWith("http://archive.lbto.org")); + assertEquals(ExecutionPhase.COMPLETED, j.getPhase()); + return true; + })); + } + + private void testPullToVoSpace(String path, String requestBody) throws Exception { - String requestBody = getResourceFileContent("pullToVoSpace.xml"); + Node node = mockPublicDataNode(); + when(nodeDao.listNode(eq(path))).thenReturn(Optional.of(node)); String redirect = mockMvc.perform(post("/transfers?PHASE=RUN") .content(requestBody) @@ -112,8 +163,6 @@ public class TransferControllerTest { .andReturn().getResponse().getHeader("Location"); assertThat(redirect, matchesPattern("^/transfers/.*")); - - verify(tapeService, times(1)).startJob(any()); } @Test @@ -196,24 +245,23 @@ public class TransferControllerTest { verify(jobDao, times(1)).getJob(eq("123")); } - + @Test public void testGetJobs() throws Exception { - + when(jobDao.getJobs(eq("user1"), any(), any(), any(), any())) .thenReturn(this.getFakeJobs()); - + mockMvc.perform(get("/transfers") .header("Authorization", "Bearer user1_token") .param("LAST", "-3") - .accept(MediaType.APPLICATION_XML)) + .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); - - + String xml2 = mockMvc.perform(get("/transfers") .header("Authorization", "Bearer user1_token") - .accept(MediaType.APPLICATION_XML)) + .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); diff --git a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java index b50eda77988ef4ed2c4b466e080c5637257393a2..817d13b4e710fbf2679f85f4e47bc1c53cb9d147 100644 --- a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java +++ b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java @@ -2,7 +2,10 @@ package it.inaf.oats.vospace; import it.inaf.ia2.aa.ServletRapClient; import it.inaf.ia2.aa.data.User; +import it.inaf.oats.vospace.persistence.LocationDAO; import it.inaf.oats.vospace.persistence.NodeDAO; +import it.inaf.oats.vospace.persistence.model.Location; +import it.inaf.oats.vospace.persistence.model.LocationType; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.uws.v1.JobSummary; @@ -11,11 +14,13 @@ import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Transfer; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -32,8 +37,11 @@ import org.springframework.test.context.TestPropertySource; public class UriServiceTest { @MockBean - private NodeDAO dao; + private NodeDAO nodeDAO; + @MockBean + private LocationDAO locationDAO; + @MockBean private ServletRapClient rapClient; @@ -55,6 +63,13 @@ public class UriServiceTest { return mock(HttpServletRequest.class); } } + + @BeforeEach + public void init() { + Location location = new Location(); + location.setType(LocationType.ASYNC); + when(locationDAO.getNodeLocation(any())).thenReturn(Optional.of(location)); + } @Test public void testPublicUrl() { @@ -65,10 +80,10 @@ public class UriServiceTest { property.setValue("true"); node.getProperties().add(property); - when(dao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); + when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); JobSummary job = getJob(); - uriService.setTransferJobResult(job); + uriService.setTransferJobResult(job, uriService.getTransfer(job)); assertEquals("http://file-service/mydata1?jobId=job-id", job.getResults().get(0).getHref()); } @@ -78,7 +93,7 @@ public class UriServiceTest { Node node = new DataNode(); - when(dao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); + when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); User user = mock(User.class); when(user.getAccessToken()).thenReturn("<token>"); @@ -92,11 +107,27 @@ public class UriServiceTest { }), any())).thenReturn("<new-token>"); JobSummary job = getJob(); - uriService.setTransferJobResult(job); + uriService.setTransferJobResult(job, uriService.getTransfer(job)); assertEquals("http://file-service/mydata1?jobId=job-id&token=<new-token>", job.getResults().get(0).getHref()); } + @Test + public void setNodeRemoteLocationTest() { + + String nodeUri = "vos://example.com!vospace/test/f1/lbtfile.fits"; + String contentUri = "http://archive.lbto.org/files/lbtfile.fits"; + + Location location = new Location(); + location.setId(5); + + when(locationDAO.findPortalLocation(eq("archive.lbto.org"))).thenReturn(Optional.of(location)); + + uriService.setNodeRemoteLocation(nodeUri, contentUri); + + verify(nodeDAO).setNodeLocation(eq("/test/f1/lbtfile.fits"), eq(5), eq("lbtfile.fits")); + } + private JobSummary getJob() { Transfer transfer = new Transfer(); diff --git a/src/test/java/it/inaf/oats/vospace/persistence/LocationDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/LocationDAOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..067c2e6ba429437f370e9feffb4a5b33613ffe87 --- /dev/null +++ b/src/test/java/it/inaf/oats/vospace/persistence/LocationDAOTest.java @@ -0,0 +1,52 @@ +package it.inaf.oats.vospace.persistence; + +import it.inaf.oats.vospace.persistence.model.Location; +import java.util.Optional; +import javax.sql.DataSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {DataSourceConfig.class}) +@TestPropertySource(locations = "classpath:test.properties") +public class LocationDAOTest { + + @Autowired + private DataSource dataSource; + private LocationDAO dao; + + @BeforeEach + public void init() { + dao = new LocationDAO(dataSource); + } + + @Test + public void testGetPortalLocation() { + + String hostname = "archive.lbto.org"; + + Optional<Location> optLocation = dao.findPortalLocation(hostname); + assertTrue(optLocation.isPresent()); + + Location location = optLocation.get(); + + assertEquals(hostname, location.getSource().getHostname()); + } + + @Test + public void testInexistentLocation() { + assertTrue(dao.findPortalLocation("foo").isEmpty()); + } + + @Test + public void testGetRootLocation() { + dao.getNodeLocation("/"); + } +} diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java index 073e2646f0058c1e209e6f67f290c6b9fc52f040..eb27450df9117773d25175b58b150fc9abddb82c 100644 --- a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java +++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java @@ -1,7 +1,7 @@ package it.inaf.oats.vospace.persistence; import it.inaf.oats.vospace.datamodel.NodeProperties; -import it.inaf.oats.vospace.datamodel.NodeUtils; +import it.inaf.oats.vospace.exception.InternalFaultException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; @@ -89,6 +89,27 @@ public class NodeDAOTest { assertEquals(0, dao.countNodesWithPath("/test1/f1/f2_renamed/f3")); } + + @Test + public void testSetNodeLocation() { + + DataNode dataNode = new DataNode(); + dataNode.setUri("vos://example.com!vospace/mydata2"); + dao.createNode(dataNode); + + dao.setNodeLocation("/mydata2", 1, "mydata2"); + } + + @Test + public void testSetNodeLocationFailure() { + boolean exception = false; + try { + dao.setNodeLocation("/foo", 1, "foo"); + } catch (InternalFaultException ex) { + exception = true; + } + assertTrue(exception); + } private String getProperty(Node node, String uri) { for (Property property : node.getProperties()) { diff --git a/src/test/resources/pullToVoSpace-portal.xml b/src/test/resources/pullToVoSpace-portal.xml new file mode 100644 index 0000000000000000000000000000000000000000..f83a08136da420a523657939a102367828f95718 --- /dev/null +++ b/src/test/resources/pullToVoSpace-portal.xml @@ -0,0 +1,7 @@ +<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" version="2.1"> + <vos:target>vos://example.com!vospace/portalnode</vos:target> + <vos:direction>pullToVoSpace</vos:direction> + <vos:protocol uri="ivo://ivoa.net/vospace/core#httpget"> + <vos:endpoint>http://archive.lbto.org/files/lbcr.20130512.060722.fits.gz</vos:endpoint> + </vos:protocol> +</vos:transfer> \ No newline at end of file diff --git a/src/test/resources/pullToVoSpace.xml b/src/test/resources/pullToVoSpace-tape.xml similarity index 76% rename from src/test/resources/pullToVoSpace.xml rename to src/test/resources/pullToVoSpace-tape.xml index 8673c57e68c120f52a1b92b7988b3835cb3c4d51..51e99e9f8218a90b89f960bf4173e5e598e4b577 100644 --- a/src/test/resources/pullToVoSpace.xml +++ b/src/test/resources/pullToVoSpace-tape.xml @@ -1,5 +1,5 @@ <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>pullToVoSpace</vos:direction> - <vos:protocol uri="ivo://ivoa.net/vospace/core#httpget" /> + <vos:protocol uri="ia2:async-recall" /> </vos:transfer> \ No newline at end of file