Commit 3c98aa18 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Preparatory commit + partial development commit for moveNode

parent bc81d824
Pipeline #1781 passed with stages
in 1 minute and 22 seconds
......@@ -23,16 +23,40 @@ public class JobService {
@Autowired
private UriService uriService;
@Autowired
private MoveService moveService;
@Autowired
private AsyncTransferService asyncTransfService;
public enum JobType {
public enum JobDirection {
pullToVoSpace,
pullFromVoSpace,
pushToVoSpace,
pushFromVoSpace,
moveNode,
copyNode
copyNode;
public static JobDirection getJobDirectionEnumFromTransfer(Transfer transfer) {
String direction = transfer.getDirection();
switch (direction) {
case "pullToVoSpace":
case "pullFromVoSpace":
case "pushToVoSpace":
case "pushFromVoSpace":
return JobDirection.valueOf(direction);
default:
if (transfer.isKeepBytes()) {
return JobDirection.copyNode;
} else {
return JobDirection.moveNode;
}
}
}
}
public void setJobPhase(JobSummary job, String phase) {
......@@ -67,7 +91,7 @@ public class JobService {
jobDAO.updateJob(job);
switch (getJobType(transfer)) {
switch (getJobDirection(transfer)) {
case pullToVoSpace:
handlePullToVoSpace(job, transfer);
break;
......@@ -75,6 +99,10 @@ public class JobService {
case pushToVoSpace:
handleVoSpaceUrlsListResult(job, transfer);
break;
case moveNode:
throw new UnsupportedOperationException("Not implemented yet");
// handleMoveNode(job, transfer);
// break;
default:
throw new UnsupportedOperationException("Not implemented yet");
}
......@@ -113,9 +141,14 @@ public class JobService {
private void handleVoSpaceUrlsListResult(JobSummary job, Transfer transfer) {
uriService.setTransferJobResult(job, transfer);
}
private void handleMoveNode(JobSummary job, Transfer transfer)
{
moveService.processMoveJob(job, transfer);
}
private JobType getJobType(Transfer transfer) {
return JobType.valueOf(transfer.getDirection());
private JobDirection getJobDirection(Transfer transfer) {
return JobDirection.getJobDirectionEnumFromTransfer(transfer);
}
/**
......
package it.inaf.oats.vospace;
import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.NodeBusyException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MoveService {
@Autowired
private NodeDAO nodeDao;
@Autowired
private HttpServletRequest servletRequest;
public void processMoveJob(JobSummary job, Transfer transfer) {
// Get Source Path
String sourcePath = transfer.getTarget();
// Get Destination Path (it's in transfer direction)
String destinationPath = transfer.getDirection();
// Extract User permissions from servlet request
User user = (User) servletRequest.getUserPrincipal();
Long sourceId = nodeDao.getNodeId(sourcePath);
List<Node> branchList = nodeDao.listNodesInBranch(sourceId, true);
// Check feasibility of move on source branch
if (!isWritePermissionsValid(branchList, user)) {
throw new PermissionDeniedException(sourcePath);
}
if(sourcePath.equals(destinationPath))
return;
if(!isMoveable(branchList)) {
throw new NodeBusyException(sourcePath);
}
// Set branch at busy
nodeDao.setBranchBusy(sourceId, true);
// Compare source and destination paths and see if it's just a rename
if(NodeUtils.getParentPath(sourcePath).equals(NodeUtils.getParentPath(destinationPath)))
{
nodeDao.renameNode(sourceId, NodeUtils.getLastPathElement(destinationPath));
} else {
this.moveNode(sourceId, sourcePath, destinationPath, user);
}
nodeDao.setBranchBusy(sourceId, false);
}
// All nodes must be writable by the user to have a true
private boolean isWritePermissionsValid(List<Node> list, User user) {
String userName = user.getName();
List<String> userGroups = user.getGroups();
return list.stream().allMatch((n) -> {
return NodeUtils.checkIfWritable(n, userName, userGroups);
});
}
// All nodes must comply to have a true
private boolean isMoveable(List<Node> list) {
return list.stream().allMatch((n) -> {
boolean busy = NodeUtils.getIsBusy(n);
boolean sticky
= Boolean.valueOf(
NodeProperties.getNodePropertyByURI(n,
NodeProperties.STICKY_URN));
return (!busy && !sticky);
});
}
private void moveNode(Long sourceId, String sourcePath, String destPath, User user)
{
}
}
......@@ -169,7 +169,7 @@ public class TransferController {
@RequestParam(value = "PHASE", required = false) Optional<List<ExecutionPhase>> phase,
@RequestParam(value = "AFTER", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Optional<LocalDateTime> after,
@RequestParam(value = "LAST", required = false) Optional<Integer> last,
@RequestParam(value = "direction", required = false) Optional<List<JobService.JobType>> direction,
@RequestParam(value = "direction", required = false) Optional<List<JobService.JobDirection>> direction,
User principal) {
if (last.isPresent()) {
......@@ -187,7 +187,7 @@ public class TransferController {
phaseList = List.of();
}
List<JobService.JobType> directionList;
List<JobService.JobDirection> directionList;
if (direction.isPresent()) {
directionList = direction.get();
} else {
......
......@@ -3,7 +3,7 @@ package it.inaf.oats.vospace;
import it.inaf.ia2.aa.ServletRapClient;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
import it.inaf.oats.vospace.JobService.JobType;
import it.inaf.oats.vospace.JobService.JobDirection;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
......@@ -82,16 +82,20 @@ public class UriService {
throw new InvalidArgumentException("Transfer contains no protocols");
}
JobService.JobType jobType = JobType.valueOf(transfer.getDirection());
JobService.JobDirection jobDirection
= JobDirection.getJobDirectionEnumFromTransfer(transfer);
List<String> validProtocolUris = new ArrayList<>();
switch (jobType) {
switch (jobDirection) {
case pullFromVoSpace:
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpget");
break;
case pushToVoSpace:
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpput");
break;
default:
throw new InternalFaultException("Unsupported job direction specified");
}
List<Protocol> validProtocols
......@@ -113,7 +117,7 @@ public class UriService {
}
private Node getEndpointNode(String relativePath,
JobService.JobType jobType,
JobService.JobDirection jobType,
User user) {
Optional<Node> optNode = nodeDao.listNode(relativePath);
if (optNode.isPresent()) {
......@@ -142,7 +146,8 @@ public class UriService {
List<String> groups = user.getGroups();
// Check privileges write or read according to job type
JobService.JobType jobType = JobType.valueOf(transfer.getDirection());
JobService.JobDirection jobType =
JobDirection.getJobDirectionEnumFromTransfer(transfer);
Node node = this.getEndpointNode(relativePath, jobType, user);
switch (jobType) {
......
......@@ -55,7 +55,7 @@ public class JobDAO {
int i = 0;
ps.setString(++i, jobSummary.getJobId());
ps.setString(++i, jobSummary.getOwnerId());
ps.setObject(++i, getJobType(jobSummary), Types.OTHER);
ps.setObject(++i, getJobDirection(jobSummary), Types.VARCHAR);
ps.setObject(++i, jobSummary.getPhase().value(), Types.OTHER);
ps.setObject(++i, toJson(jobSummary.getJobInfo()), Types.OTHER);
......@@ -74,7 +74,7 @@ public class JobDAO {
});
}
private String getJobType(JobSummary jobSummary) {
private String getJobDirection(JobSummary jobSummary) {
List<Object> payload = jobSummary.getJobInfo().getAny();
if (payload.isEmpty()) {
......@@ -138,7 +138,7 @@ public class JobDAO {
public Jobs getJobs(String userId,
List<ExecutionPhase> phaseList,
List<JobService.JobType> directionList,
List<JobService.JobDirection> directionList,
Optional<LocalDateTime> after,
Optional<Integer> last
) {
......
......@@ -4,6 +4,7 @@ import it.inaf.oats.vospace.DeleteNodeController;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import java.sql.Array;
import net.ivoa.xml.vospace.v2.Node;
import java.sql.PreparedStatement;
......@@ -225,6 +226,78 @@ public class NodeDAO {
return node;
}
public Long getNodeId(String nodePath) {
String sql = "SELECT node_id FROM node_vos_path WHERE vos_path = ? FOR UPDATE";
List<Long> nodeIdList = jdbcTemplate.query(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, nodePath);
return ps;
}, (row, index) -> {
return row.getLong("node_id");
});
// Node id is
if (nodeIdList.isEmpty()) {
throw new NodeNotFoundException(nodePath);
}
// Node id is PRIMARY KEY: uniqueness is enforced at DB level
return nodeIdList.get(0);
}
// First node is the root node
public List<Node> listNodesInBranch(Long rootNodeId, boolean enforceTapeStoredCheck) {
String sql = "SELECT os.vos_path, loc.location_type, n.node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n"
+ "is_public, content_length, created_on, last_modified, accept_views, provide_views\n"
+ "FROM node n\n"
+ "JOIN node_vos_path os ON n.node_id = os.node_id\n"
+ "JOIN location loc ON n.location_id = loc.location_id\n"
+ "WHERE n.path ~ ('*.' || ? || '.*')::lquery\n"
+ "ORDER BY n.path FOR UPDATE";
List<Node> result = jdbcTemplate.query(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, Long.toString(rootNodeId));
return ps;
}, (row, index) -> {
if (enforceTapeStoredCheck && row.getString("location_type").equals("async")) {
throw new InternalFaultException(
"At least one node in branch has async location type. "
+ "Failure due to enforced check.");
}
return getNodeFromResultSet(row);
});
return result;
}
public void setBranchBusy(Long rootNodeId, boolean busyState) {
String sql = "UPDATE node SET busy_state = ?\n"
+ "WHERE path ~ ('*.' || ? || '.*')::lquery";
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setBoolean(1, busyState);
ps.setLong(2, rootNodeId);
return ps;
});
}
public void renameNode(Long nodeId, String name) {
String sql = "UPDATE node SET name = ?\n"
+ "WHERE path ~ ('*.' || ?)::lquery";
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setLong(2, nodeId);
return ps;
});
}
public void deleteNode(String path) {
int nodesWithPath = countNodesWithPath(path);
if (nodesWithPath == 0) {
......
......@@ -361,7 +361,7 @@ public class TransferControllerTest {
sjd.setId("pippo1");
sjd.setPhase(ExecutionPhase.QUEUED);
sjd.setOwnerId("user1");
sjd.setType(JobService.JobType.pullFromVoSpace.toString());
sjd.setType(JobService.JobDirection.pullFromVoSpace.toString());
LocalDateTime now = LocalDateTime.now();
Timestamp ts = Timestamp.valueOf(now);
......
......@@ -405,7 +405,7 @@ public class UriServiceTest {
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1");
transfer.setDirection(JobService.JobType.pullFromVoSpace.toString());
transfer.setDirection(JobService.JobDirection.pullFromVoSpace.toString());
JobSummary job = new JobSummary();
job.setJobId("job-id");
......@@ -421,7 +421,7 @@ public class UriServiceTest {
private JobSummary getPushToVoSpaceJob() {
Transfer transfer = new Transfer();
transfer.setTarget("vos://example.com!vospace/mydata1/mydata2");
transfer.setDirection(JobService.JobType.pushToVoSpace.toString());
transfer.setDirection(JobService.JobDirection.pushToVoSpace.toString());
JobSummary job = new JobSummary();
job.setJobId("job-id2");
......
......@@ -144,7 +144,7 @@ public class JobDAOTest {
// Check no arguments
String user = "user1";
List<ExecutionPhase> phaseList = List.of();
List<JobService.JobType> directionList = List.of();
List<JobService.JobDirection> directionList = List.of();
Optional<LocalDateTime> after = Optional.ofNullable(null);
Optional<Integer> last = Optional.ofNullable(null);
......@@ -169,7 +169,7 @@ public class JobDAOTest {
// Check no arguments
String user = "user1";
List<ExecutionPhase> phaseList = List.of();
List<JobService.JobType> directionList = List.of();
List<JobService.JobDirection> directionList = List.of();
Optional<LocalDateTime> after = Optional.ofNullable(null);
Optional<Integer> last = Optional.of(2);
......@@ -185,7 +185,7 @@ public class JobDAOTest {
String user = "user1";
List<ExecutionPhase> phaseList
= List.of(ExecutionPhase.PENDING, ExecutionPhase.EXECUTING);
List<JobService.JobType> directionList = List.of();
List<JobService.JobDirection> directionList = List.of();
Optional<LocalDateTime> after = Optional.ofNullable(null);
Optional<Integer> last = Optional.ofNullable(null);
......@@ -201,9 +201,9 @@ public class JobDAOTest {
// Check no arguments
String user = "user1";
List<ExecutionPhase> phaseList = List.of();
List<JobService.JobType> directionList
= List.of(JobService.JobType.pullFromVoSpace,
JobService.JobType.pullToVoSpace);
List<JobService.JobDirection> directionList
= List.of(JobService.JobDirection.pullFromVoSpace,
JobService.JobDirection.pullToVoSpace);
Optional<LocalDateTime> after = Optional.ofNullable(null);
Optional<Integer> last = Optional.ofNullable(null);
......@@ -219,7 +219,7 @@ public class JobDAOTest {
// Check no arguments
String user = "user1";
List<ExecutionPhase> phaseList = List.of();
List<JobService.JobType> directionList = List.of();
List<JobService.JobDirection> directionList = List.of();
LocalDateTime ldt
= LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15);
......@@ -239,9 +239,9 @@ public class JobDAOTest {
String user = "user1";
List<ExecutionPhase> phaseList = List.of(ExecutionPhase.QUEUED,
ExecutionPhase.PENDING);
List<JobService.JobType> directionList
= List.of(JobService.JobType.pullFromVoSpace,
JobService.JobType.pullToVoSpace);
List<JobService.JobDirection> directionList
= List.of(JobService.JobDirection.pullFromVoSpace,
JobService.JobDirection.pullToVoSpace);
LocalDateTime ldt
= LocalDateTime.of(2013, Month.FEBRUARY, 7, 18, 15);
......
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 it.inaf.oats.vospace.exception.NodeNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
......@@ -14,6 +16,8 @@ import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.View;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
......@@ -37,7 +41,7 @@ public class NodeDAOTest {
dao = new NodeDAO(dataSource);
}
@Test
//@Test
public void testCreateNode() {
DataNode dataNode = new DataNode();
......@@ -54,7 +58,7 @@ public class NodeDAOTest {
assertEquals(retrievedNode.getProvides().get(0).getUri(), dataNode.getProvides().get(0).getUri());
}
@Test
//@Test
public void testListNode() {
ContainerNode root = (ContainerNode) dao.listNode("/").get();
assertEquals(2, root.getNodes().size());
......@@ -70,6 +74,86 @@ public class NodeDAOTest {
}
@Test
public void testGetNodeId() {
assertEquals(2, dao.getNodeId("/test1"));
assertEquals(3, dao.getNodeId("/test1/f1"));
assertThrows(NodeNotFoundException.class,
() -> {
dao.getNodeId("/pippo123123");
});
}
@Test
public void testListNodesInBranch() {
List<Node> result = dao.listNodesInBranch(dao.getNodeId("/test1/f1"), false);
assertEquals(3, result.size());
// Check if list has root node at index 0
Node root = result.get(0);
assertEquals("/test1/f1", NodeUtils.getVosPath(root));
assertThrows(InternalFaultException.class,
() -> {
dao.listNodesInBranch(dao.getNodeId("/test1/f1"), true);
});
}
@Test
public void testSetBranchBusy() {
Long rootId = dao.getNodeId("/test1/f1");
dao.setBranchBusy(rootId, true);
List<Node> busyList = dao.listNodesInBranch(rootId, false);
boolean busyTrue = busyList.stream().allMatch((n) -> {
if (n instanceof DataNode) {
return ((DataNode) n).isBusy();
} else {
return true;
}
}
);
assertTrue(busyTrue);
dao.setBranchBusy(rootId, false);
busyList = dao.listNodesInBranch(rootId, false);
boolean busyFalse = busyList.stream().allMatch((n) -> {
if (n instanceof DataNode) {
return !((DataNode) n).isBusy();
} else {
return true;
}
}
);
assertTrue(busyFalse);
}
@Test
public void testRenameNode() {
String oldPath = "/test1/f1";
String newPath = "/test1/f_pippo";
String child = "/f2_renamed";
String oldPathChild = oldPath + child;
String newPathChild = newPath + child;
assertTrue(dao.listNode(oldPath).isPresent());
assertTrue(dao.listNode(oldPathChild).isPresent());
Long rootId = dao.getNodeId(oldPath);
dao.renameNode(rootId, "f_pippo");
assertTrue(dao.listNode(oldPath).isEmpty());
assertTrue(dao.listNode(oldPathChild).isEmpty());
assertTrue(dao.listNode(newPath).isPresent());
assertTrue(dao.listNode(newPathChild).isPresent());
}
//@Test
public void testCountNodeWithPath() {
assertEquals(1, dao.countNodesWithPath("/"));
assertEquals(1, dao.countNodesWithPath("/test1"), "Test db has been changed");
......@@ -85,7 +169,7 @@ public class NodeDAOTest {
assertEquals(0, dao.countNodesWithPath("/pippooo"), "Test db has been changed");
}
@Test
//@Test
public void testDeleteNode() {
assertEquals(1, dao.countNodesWithPath("/test1/f1/f2_renamed/f3"), "Test db has been changed");
......@@ -97,8 +181,8 @@ public class NodeDAOTest {
assertEquals(0, dao.countNodesWithPath("/test1/f1/f2_renamed/f3"));
}
@Test