diff --git a/vault/src/main/java/org/opencadc/vault/JobAbortedException.java b/vault/src/main/java/org/opencadc/vault/JobAbortedException.java new file mode 100644 index 00000000..64f78bcb --- /dev/null +++ b/vault/src/main/java/org/opencadc/vault/JobAbortedException.java @@ -0,0 +1,105 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +* $Revision: 4 $ +* +************************************************************************ +*/ + +package org.opencadc.vault; + +import ca.nrc.cadc.uws.Job; +import org.opencadc.vospace.VOSException; + +/** + * Exception indicating the a job has been aborted by the client. + * + * @author yeunga + * + */ +public class JobAbortedException extends VOSException +{ + + private static final long serialVersionUID = -4453433718383619175L; + + private Job job; + + public JobAbortedException(Job job) + { + super("job aborted"); + this.job = job; + } + + public JobAbortedException(Job job, String message) + { + super(message); + this.job = job; + } + + public Job getJob() + { + return job; + } + +} diff --git a/vault/src/main/java/org/opencadc/vault/JobManager.java b/vault/src/main/java/org/opencadc/vault/JobManager.java new file mode 100644 index 00000000..44df0c43 --- /dev/null +++ b/vault/src/main/java/org/opencadc/vault/JobManager.java @@ -0,0 +1,88 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +************************************************************************ +*/ + +package org.opencadc.vault; + +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.uws.server.JobPersistence; +import ca.nrc.cadc.uws.server.SimpleJobManager; +import ca.nrc.cadc.uws.server.impl.PostgresJobPersistence; +import org.apache.log4j.Logger; + +public abstract class JobManager extends SimpleJobManager +{ + + private static final Logger log = Logger.getLogger(JobManager.class); + + protected static JobPersistence jp; + + static { + log.info("Creating shared (postgres) job manager"); + jp = new PostgresJobPersistence(AuthenticationUtil.getIdentityManager()); + } + +} diff --git a/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeJobManager.java b/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeJobManager.java new file mode 100644 index 00000000..f857dfc8 --- /dev/null +++ b/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeJobManager.java @@ -0,0 +1,103 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* This file is part of the Ce fichier fait partie du projet +* OpenCADC project. OpenCADC. +* +* OpenCADC is free software: OpenCADC est un logiciel libre ; +* you can redistribute it and/or vous pouvez le redistribuer ou le +* modify it under the terms of modifier suivant les termes de +* the GNU Affero General Public la “GNU Affero General Public +* License as published by the License” telle que publiée +* Free Software Foundation, par la Free Software Foundation +* either version 3 of the : soit la version 3 de cette +* License, or (at your option) licence, soit (à votre gré) +* any later version. toute version ultérieure. +* +* OpenCADC is distributed in the OpenCADC est distribué +* hope that it will be useful, dans l’espoir qu’il vous +* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE +* without even the implied GARANTIE : sans même la garantie +* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ +* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF +* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence +* General Public License for Générale Publique GNU Affero +* more details. pour plus de détails. +* +* You should have received Vous devriez avoir reçu une +* a copy of the GNU Affero copie de la Licence Générale +* General Public License along Publique GNU Affero avec +* with OpenCADC. If not, see OpenCADC ; si ce n’est +* . pas le cas, consultez : +* . +* +* $Revision: 4 $ +* +************************************************************************ +*/ + +package org.opencadc.vault; + +import ca.nrc.cadc.uws.server.JobExecutor; +import ca.nrc.cadc.uws.server.JobUpdater; +import ca.nrc.cadc.uws.server.ThreadPoolExecutor; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler, majorb, yeunga, adriand + */ +public class RecursiveDeleteNodeJobManager extends JobManager +{ + private static final Logger log = Logger.getLogger(RecursiveDeleteNodeJobManager.class); + + private static final Long MAX_EXEC_DURATION = Long.valueOf(12*7200L); // 24 hours? + private static final Long MAX_DESTRUCTION = Long.valueOf(7*24*3600L); // 1 week + private static final Long MAX_QUOTE = Long.valueOf(12*7200L); // same as exec + + public RecursiveDeleteNodeJobManager() + { + super(); + // jp is instantiated in parent org.opencadc.cavern.JobManager + JobUpdater ju = jp; + super.setJobPersistence(jp); + + JobExecutor jobExec = new ThreadPoolExecutor(ju, RecursiveDeleteNodeRunner.class, 3); + super.setJobExecutor(jobExec); + + super.setMaxExecDuration(MAX_EXEC_DURATION); + super.setMaxDestruction(MAX_DESTRUCTION); + super.setMaxQuote(MAX_QUOTE); + } +} diff --git a/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeRunner.java b/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeRunner.java new file mode 100644 index 00000000..ac9a0b65 --- /dev/null +++ b/vault/src/main/java/org/opencadc/vault/RecursiveDeleteNodeRunner.java @@ -0,0 +1,350 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* Government of Canada Gouvernement du Canada +* National Research Council Conseil national de recherches +* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 +* All rights reserved Tous droits réservés +* +* NRC disclaims any warranties, Le CNRC dénie toute garantie +* expressed, implied, or énoncée, implicite ou légale, +* statutory, of any kind with de quelque nature que ce +* respect to the software, soit, concernant le logiciel, +* including without limitation y compris sans restriction +* any warranty of merchantability toute garantie de valeur +* or fitness for a particular marchande ou de pertinence +* purpose. NRC shall not be pour un usage particulier. +* liable in any event for any Le CNRC ne pourra en aucun cas +* damages, whether direct or être tenu responsable de tout +* indirect, special or general, dommage, direct ou indirect, +* consequential or incidental, particulier ou général, +* arising from the use of the accessoire ou fortuit, résultant +* software. Neither the name de l'utilisation du logiciel. Ni +* of the National Research le nom du Conseil National de +* Council of Canada nor the Recherches du Canada ni les noms +* names of its contributors may de ses participants ne peuvent +* be used to endorse or promote être utilisés pour approuver ou +* products derived from this promouvoir les produits dérivés +* software without specific prior de ce logiciel sans autorisation +* written permission. préalable et particulière +* par écrit. +* +* $Revision: 1 $ +* +************************************************************************ +*/ + +package org.opencadc.vault; + +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.io.ResourceIterator; +import ca.nrc.cadc.rest.SyncOutput; +import ca.nrc.cadc.util.MultiValuedProperties; +import ca.nrc.cadc.util.ThrowableUtil; +import ca.nrc.cadc.uws.ErrorSummary; +import ca.nrc.cadc.uws.ErrorType; +import ca.nrc.cadc.uws.ExecutionPhase; +import ca.nrc.cadc.uws.Job; +import ca.nrc.cadc.uws.Parameter; +import ca.nrc.cadc.uws.Result; +import ca.nrc.cadc.uws.server.JobRunner; +import ca.nrc.cadc.uws.server.JobUpdater; +import ca.nrc.cadc.uws.util.JobLogInfo; +import java.io.FileNotFoundException; +import java.net.URI; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import javax.security.auth.Subject; +import org.apache.log4j.Logger; +import org.opencadc.vospace.ContainerNode; +import org.opencadc.vospace.Node; +import org.opencadc.vospace.VOSURI; +import org.opencadc.vospace.server.NodeFault; +import org.opencadc.vospace.server.NodePersistence; +import org.opencadc.vospace.server.PathResolver; +import org.opencadc.vospace.server.Utils; +import org.opencadc.vospace.server.auth.VOSpaceAuthorizer; + +/** + * Class to delete the target node and all child nodes recursively. + * + * + */ +public class RecursiveDeleteNodeRunner implements JobRunner { + + private static Logger log = Logger.getLogger(RecursiveDeleteNodeRunner.class); + + // containers are not locked while being emptied for the recursive delete and might need a few revisits in order + // for the operation to succeed. + private static final long PHASE_CHECK_INTERVAL = 1000; // 1 second + private static final long MAX_ERROR_BEFORE_ABORT = 100; + + private Job job; + private JobUpdater jobUpdater; + private long lastPhaseCheck = System.currentTimeMillis(); + private VOSpaceAuthorizer vospaceAuthorizer; + private NodePersistence nodePersistence; + private long deleteCount = 0; + private long errorCount = 0; + private JobLogInfo logInfo; + + public URI resourceID; + + public RecursiveDeleteNodeRunner() { + MultiValuedProperties mvp = VaultInitAction.getConfig(); + String resourceID = mvp.getFirstPropertyValue(VaultInitAction.RESOURCE_ID_KEY); + this.resourceID = URI.create(resourceID); + } + + @Override + public void setJobUpdater(JobUpdater jobUpdater) { + this.jobUpdater = jobUpdater; + } + + @Override + public void setJob(Job job) { + this.job = job; + } + + @Override + public void setSyncOutput(SyncOutput syncOutput) { + // not used + } + + @Override + public void run() { + log.debug("RUN RecursiveDeleteNodeRunner"); + logInfo = new JobLogInfo(job); + + String startMessage = logInfo.start(); + log.info(startMessage); + + long t1 = System.currentTimeMillis(); + doit(); + long t2 = System.currentTimeMillis(); + + logInfo.setElapsedTime(t2 - t1); + + String endMessage = logInfo.end(); + log.info(endMessage); + } + + private void doit() { + try { + // set the phase to executing + ExecutionPhase ep = jobUpdater.setPhase( + job.getID(), ExecutionPhase.QUEUED, ExecutionPhase.EXECUTING, new Date()); + + if (ep == null) { + throw new IllegalStateException( + "Could not change the job phase from " + ExecutionPhase.QUEUED + + " to " + ExecutionPhase.EXECUTING); + } + + VOSURI nodeURI = null; + for (Parameter param : job.getParameterList()) { + if (param.getName().equalsIgnoreCase("nodeURI")) { + nodeURI = new VOSURI(URI.create(param.getValue())); + break; + } + } + + if (nodeURI == null) { + throw new IllegalArgumentException("nodeURI argument required"); + } + + log.debug("node: " + nodeURI); + + // Create the node persistence and authorizer objects + //TODO move to the library + this.nodePersistence = new NodePersistenceImpl(resourceID); + vospaceAuthorizer = new VOSpaceAuthorizer(nodePersistence); + + PathResolver pathResolver = new PathResolver(nodePersistence, vospaceAuthorizer, true); + String nodePath = nodeURI.getPath(); + Node serverNode = pathResolver.getNode(nodePath); + if (serverNode == null) { + throw NodeFault.NodeNotFound.getStatus(nodePath); + } + + ContainerNode parent = serverNode.parent; + Subject caller = AuthenticationUtil.getCurrentSubject(); + if (!vospaceAuthorizer.hasSingleNodeWritePermission(parent, caller)) { + throw NodeFault.PermissionDenied.getStatus(nodePath); + } + + if (serverNode instanceof ContainerNode) { + try { + deleteContainer((ContainerNode) serverNode, caller); + } catch (JobAbortedException ex) { + // nothing to do here + } + } else { + try { + nodePersistence.delete(serverNode); + log.debug("Recursively deleted data node " + nodePath); + } catch (Exception ex) { + log.debug("Cannot recursively delete node " + nodePath, ex); + incErrorCount(); + } + } + + // set the appropriate end phase and error summary + ErrorSummary error = null; + ExecutionPhase endPhase = ExecutionPhase.COMPLETED; + if (deleteCount == 0) { + endPhase = ExecutionPhase.ERROR; + ep = jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, endPhase, error, new Date()); + } else { + List results = new ArrayList<>(); + results.add(new Result("delcount", URI.create("final:" + deleteCount))); + if (errorCount > 0) { + endPhase = ExecutionPhase.ABORTED; + results.add(new Result("errorcount", URI.create("final:" + errorCount))); + ep = jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, endPhase, results, new Date()); + } else { + ep = jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, endPhase, results, new Date()); + } + } + if (!endPhase.equals(ep)) { + log.warn("Could not change the job phase from " + ExecutionPhase.EXECUTING + + " to " + endPhase); + } + } catch (FileNotFoundException e) { + sendError("NotFound"); + } catch (AccessControlException e) { + sendError("PermissionDenied"); + } catch (Throwable t) { + log.error("Unexpected exception", t); + // if the cause of this throwable is an InterruptedException, and if + // the job has been aborted, then this can be considered a normal + // abort procedure + if (t instanceof InterruptedException || ThrowableUtil.isACause(t, InterruptedException.class)) { + try { + ExecutionPhase ep = jobUpdater.getPhase(job.getID()); + if (ExecutionPhase.ABORTED.equals(ep)) { + return; + } + } catch (Exception e) { + log.error("Could not check job phase: ", e); + } + } + + sendError("Unexpected Exception: " + t.getMessage()); + } + } + + private boolean deleteContainer(ContainerNode node, Subject caller) throws Exception { + + boolean errors = false; + List childContainers = new ArrayList<>(); + log.debug("Deleting container " + Utils.getPath(node)); + ResourceIterator iterator = nodePersistence.iterator(node, null, null); + while (iterator.hasNext()) { + checkJobPhase(); + Node child = iterator.next(); + if (child instanceof ContainerNode) { + childContainers.add((ContainerNode) child); + } else { + try { + if (!vospaceAuthorizer.hasSingleNodeWritePermission(child.parent, caller)) { + throw NodeFault.PermissionDenied.getStatus(Utils.getPath(child)); + } + nodePersistence.delete(child); + log.debug("Recursive deleted non-container node " + Utils.getPath(child)); + deleteCount++; + } catch (Exception ex) { + log.debug("Failed to (recursive) delete non-container node " + Utils.getPath(child), ex); + errors = true; + incErrorCount(); + } + } + } + if (!childContainers.isEmpty()) { + // descend down one level + for (ContainerNode cn : childContainers) { + errors |= deleteContainer(cn, caller); + } + } + if (!errors) { + // delete the empty container + try { + if (!vospaceAuthorizer.hasSingleNodeWritePermission(node.parent, caller)) { + throw NodeFault.PermissionDenied.getStatus(Utils.getPath(node)); + } + nodePersistence.delete(node); + log.debug("Recursive deleted container node " + Utils.getPath(node)); + deleteCount++; + } catch (Exception e) { + log.debug("Failed to (recursive) delete container node " + Utils.getPath(node), e); + incErrorCount(); + errors = true; + } + } + return errors; + } + + private void incErrorCount() throws JobAbortedException { + if (++errorCount > MAX_ERROR_BEFORE_ABORT) { + throw new JobAbortedException(job); + } + } + + /** + * If a minimum of PHASE_CHECK_INTERVAL time has passed, check + * to ensure the job is still in the EXECUTING PHASE + */ + private void checkJobPhase() + throws Exception { + // only check phase if a minimum of PHASE_CHECK_INTERVAL time has passed + long now = System.currentTimeMillis(); + long diff = now - lastPhaseCheck; + log.debug("Last phase check diff: " + diff); + if (diff > PHASE_CHECK_INTERVAL) { + // reset the last phase check time + lastPhaseCheck = now; + + log.debug("Checking job phase"); + ExecutionPhase ep = jobUpdater.getPhase(job.getID()); + log.debug("Job phase is: " + ep); + if (ExecutionPhase.ABORTED.equals(ep)) { + throw new JobAbortedException(job); + } + + if (!ExecutionPhase.EXECUTING.equals(ep)) { + throw new IllegalStateException("Job should be in phase " + + ExecutionPhase.EXECUTING + " but is in phase " + ep); + } + } + } + + /** + * Set the job execution phase to error and save the error message. + * @param message - error message + */ + private void sendError(String message) { + logInfo.setSuccess(false); + logInfo.setMessage(message); + + // set the phase to error + ErrorSummary error = new ErrorSummary(message, ErrorType.FATAL); + + try { + ExecutionPhase ep = jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, + ExecutionPhase.ERROR, error, new Date()); + + if (!ExecutionPhase.ERROR.equals(ep)) { + log.warn("Could not change the job phase from " + ExecutionPhase.EXECUTING + + " to " + ExecutionPhase.ERROR + " because it is " + jobUpdater.getPhase(job.getID())); + } + } catch (Throwable t) { + log.error("Failed to change the job phase from " + ExecutionPhase.EXECUTING + + " to " + ExecutionPhase.ERROR, t); + } + } +} diff --git a/vault/src/main/resources/VOSpacePlugins.properties b/vault/src/main/resources/VOSpacePlugins.properties new file mode 100644 index 00000000..7d3e0042 --- /dev/null +++ b/vault/src/main/resources/VOSpacePlugins.properties @@ -0,0 +1,12 @@ +# +# Configuration information for the vospace storage interface +# + +# Define the class that implements the interface TransferGenerator +# This class will be loaded at runtime to handle transfer requests. +# ca.nrc.cadc.vos.transfers.TransferGenerator = +ca.nrc.cadc.vos.server.transfers.TransferGenerator = ca.nrc.cadc.vospace.transfers.ADTransferGenerator + +# Define an optional class that implements the interface NodePersistence +# This class will be loaded at runtime to node persistence operations. +ca.nrc.cadc.vos.server.NodePersistence = ca.nrc.cadc.vospace.VOSpaceNodePersistence \ No newline at end of file diff --git a/vault/src/main/resources/Views.properties b/vault/src/main/resources/Views.properties new file mode 100644 index 00000000..cde5e1e1 --- /dev/null +++ b/vault/src/main/resources/Views.properties @@ -0,0 +1,72 @@ +############################################################################### +# +# Views.properties +# +# Defines the views available in this instance of VOSpace. These are loaded +# upon the loading of class ca.nrc.cadc.vos.ViewFactory into the VM. +# +# The names of the views to be defined must be listed under the 'views' key +# and separated by a space. For example: +# views = ... +# +# For each view name listed, three settings must be defined +# uri = The URI of the view +# alias = The alias (or shortcut) name of the view +# class = The implementing class of the view +# +# Additionally, two optional settings may be defined: +# accepts = true/false, true if this services accepts the view +# provides = true/false, true if this service provides the view +# +# For example: +# .uri = ivo://cadc.nrc.ca/vospace/core#dataview +# .alias = data +# .class = ca.nrc.cadc.vos.DataView +# .accepts = false +# .provides = true +# +# Notes: +# - View classes must extend class ca.nrc.cadc.vos.AbstractView +# - There cannot be a duplicate of any aliases or URIs in any of the +# view definitions +# +############################################################################### + +#views = data rss manifest cutout header +views = data + +# data view definition +data.uri = ivo://cadc.nrc.ca/vospace/view#data +data.alias = data +data.class = org.opencadc.vospace.server.DataView +data.accepts = false +data.provides = true + +# rss view definition +rss.uri = ivo://cadc.nrc.ca/vospace/view#rss +rss.alias = rss +rss.class = ca.nrc.cadc.vos.server.RssView +rss.accepts = false +rss.provides = true + +# manifest view definition +manifest.uri = ivo://cadc.nrc.ca/vospace/view#manifest +manifest.alias = manifest +manifest.class = ca.nrc.cadc.vos.server.ManifestView +manifest.accepts = false +manifest.provides = true + +# cutout view definition +cutout.uri = ivo://cadc.nrc.ca/vospace/view#cutout +cutout.alias = cutout +cutout.class = ca.nrc.cadc.vospace.CutoutView +cutout.accepts = false +cutout.provides = true + +# header view definition +header.uri = ivo://cadc.nrc.ca/vospace/view#header +header.alias = header +header.class = ca.nrc.cadc.vospace.HeaderView +header.accepts = false +header.provides = true + diff --git a/vault/src/main/webapp/service.yaml b/vault/src/main/webapp/service.yaml new file mode 100644 index 00000000..27bcda43 --- /dev/null +++ b/vault/src/main/webapp/service.yaml @@ -0,0 +1,335 @@ +swagger: '2.0' +info: + version: 2.1.0 + title: VOSpace + description: | + The CANFAR Vault web service, a VOSpace storage implementation. + + VOSpace is the IVOA (International Virtual Observatory Aliance) standard interface to distributed storage. This VOSpace web service is an access point for a distributed storage network. There are three main functions of the VOSpace service: + 1. Add, replace, or delete data objects in a tree data structure. + 2. Manipulate the metadata for the data objects. + 3. Obtain URIs through which the content of the data objects can be accessed. + + Please see the specification for full details: VOSpace 2.1 Specification + + Interaction with the synchronous and asynchronous data object transfer endpoints use the job management patterns defined in the IVOA Universal Worker Service (UWS) specification: UWS 1.1 Specification +schemes: + - https +basePath: /vault +paths: + /nodes/{nodePath}: + put: + description: | + Create a new node at a specified location + tags: + - Nodes + consumes: + - text/xml + responses: + '200': + description: Successful response + '201': + description: Successful response + '403': + description: If the user does not have permission. + '404': + description: If the path to the node could not be found. + '409': + description: If the node already exits. + '500': + description: Internal error + '503': + description: Service busy + default: + description: Unexpeced error + schema: + $ref: '#/definitions/Error' + parameters: + - name: Node + in: body + description: The new Node + required: true + schema: + $ref: '#/definitions/Node' + delete: + description: | + Delete a node. When the target is a ContainerNode, all its children (the contents of the container) SHALL also be deleted. + tags: + - Nodes + responses: + '200': + description: Successful response + '204': + description: Successful response + '403': + description: If the user does not have permission. + '404': + description: If the path to the node could not be found. + '500': + description: Internal error + '503': + description: Service busy + default: + description: Unexpeced error + schema: + $ref: '#/definitions/Error' + get: + description: | + Get the details for a specific Node. + tags: + - Nodes + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/Node' + '403': + description: If the user does not have permission. + '404': + description: If the path to the node could not be found. + '500': + description: Internal error + '503': + description: Service busy + default: + description: Unexpeced error + schema: + $ref: '#/definitions/Error' + parameters: + - name: nodePath + in: path + description: The path for the node + required: true + type: string + post: + description: | + Set the property values for a specific Node + tags: + - Nodes + consumes: + - text/xml + responses: + '200': + description: Successful response + '201': + description: Successful response + '403': + description: If the user does not have permission. + '404': + description: If the path to the node could not be found. + '500': + description: Internal error + '503': + description: Service busy + default: + description: Unexpeced error + schema: + $ref: '#/definitions/Error' + parameters: + - name: Node + in: body + description: The updated Node + required: true + schema: + $ref: '#/definitions/Node' + parameters: + - name: nodePath + in: path + description: The path for the node + required: true + type: string + /recursiveDelete: + post: + description: | + Post a recursive delete command. This is an IVOA UWS end point. + tags: + - Recursive delete + consumes: + - None + responses: + '200': + description: Successful response + '201': + description: Successful response + '403': + description: If the user does not have permission. + '404': + description: If the source node could not be found. + '409': + description: If the destination node already exists. + '500': + description: Internal error + '503': + description: Service busy + default: + description: Unexpeced error + schema: + $ref: '#/definitions/Error' + parameters: + - name: nodeURI + in: query + description: The base node (typically a container) to recursively delete + required: true + type: string + format: uri + /availability: + get: + tags: + - Support Interfaces + summary: VOSI Availability + description: Indicates whether the service is operable and shows the reliability of the service for extended and scheduled requests. If the query parameter 'detail=min' is used, a light weight heart beat test will be performed. The heart beat test returns status 200 if the service is available. + parameters: + - name: detail + in: query + description: specifies heart beat to be used to check for availability of this service, the value 'min' must be used, otherwise the full availability test will be performed + required: false + type: string + responses: + '200': + description: A VOSI availability document in XML. + schema: + $ref: '#/definitions/availability' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + /capabilities: + get: + summary: VOSI Capabilities + tags: + - Support Interfaces + description: | + Provides the service metadata in the form of a list of Capability descriptions. Each of these descriptions is an + XML element that: +
    +
  • states that the service provides a particular, IVOA-standard function;
  • +
  • lists the interfaces for invoking that function;
  • +
  • records any details of the implementation of the function that are not defined as default or constant in the standard for that function.
  • +
+ responses: + '200': + description: A VOSI Capabilities document in XML. + schema: + $ref: '#/definitions/capabilities' + '500': + description: Internal server error + '503': + description: Service too busy + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' +definitions: + Property: + type: object + required: + - uri + description: The property identifier + properties: + uri: + type: string + format: uri + View: + type: object + required: + - uri + description: The view identifier + properties: + uri: + type: string + format: uri + Protocol: + type: object + required: + - uri + description: The protocol identifier + properties: + uri: + type: string + format: uri + Node: + type: object + required: + - uri + description: The node identifier + properties: + uri: + type: string + format: uri + Transfer: + type: object + required: + - target + - direction + - protocol + description: The transfer negotiation document + properties: + target: + type: string + format: uri + direction: + type: string + protocol: + type: string + format: uri + availability: + type: object + xml: + name: availability + namespace: http://www.ivoa.net/xml/VOSIAvailability/v1.0 + prefix: vosi + properties: + available: + type: boolean + xml: + attribute: true + prefix: vosi + note: + type: string + xml: + attribute: true + prefix: vosi + capabilities: + type: array + items: + $ref: '#/definitions/capability' + xml: + namespace: http://www.ivoa.net/xml/VOSICapabilities/v1.0 + prefix: vosi + wrapped: true + capability: + type: object + properties: + standardID: + type: string + format: uri + xml: + attribute: true + interface: + type: object + properties: + accessURL: + type: string + properties: + use: + type: string + xml: + attribute: true + securityMethod: + type: string + properties: + standardID: + type: string + format: uri + xml: + attribute: true + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string