X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/860b7ed9f1562da5b602d21e7996c25b1361a64a..6563930ed602f47c1489b39b2eebfb54f9945e3c:/sdk/cwl/arvados_cwl/arvworkflow.py diff --git a/sdk/cwl/arvados_cwl/arvworkflow.py b/sdk/cwl/arvados_cwl/arvworkflow.py index f86641bfd6..97c5fafe79 100644 --- a/sdk/cwl/arvados_cwl/arvworkflow.py +++ b/sdk/cwl/arvados_cwl/arvworkflow.py @@ -2,27 +2,32 @@ # # SPDX-License-Identifier: Apache-2.0 +from past.builtins import basestring +from future.utils import viewitems + import os import json import copy import logging from schema_salad.sourceline import SourceLine, cmap +import schema_salad.ref_resolver from cwltool.pack import pack -from cwltool.load_tool import fetch_document +from cwltool.load_tool import fetch_document, resolve_and_validate_document from cwltool.process import shortname -from cwltool.workflow import Workflow, WorkflowException +from cwltool.workflow import Workflow, WorkflowException, WorkflowStep from cwltool.pathmapper import adjustFileObjs, adjustDirObjs, visit_class -from cwltool.builder import Builder from cwltool.context import LoadingContext import ruamel.yaml as yaml from .runner import (upload_dependencies, packed_workflow, upload_workflow_collection, - trim_anonymous_location, remove_redundant_fields, discover_secondary_files) + trim_anonymous_location, remove_redundant_fields, discover_secondary_files, + make_builder) from .pathmapper import ArvPathMapper, trim_listing -from .arvtool import ArvadosCommandTool +from .arvtool import ArvadosCommandTool, set_cluster_target + from .perf import Perf logger = logging.getLogger('arvados.cwl-runner') @@ -118,25 +123,47 @@ def get_overall_res_req(res_reqs): overall_res_req["class"] = "ResourceRequirement" return cmap(overall_res_req) +class ArvadosWorkflowStep(WorkflowStep): + def __init__(self, + toolpath_object, # type: Dict[Text, Any] + pos, # type: int + loadingContext, # type: LoadingContext + arvrunner, + *argc, + **argv + ): # type: (...) -> None + + super(ArvadosWorkflowStep, self).__init__(toolpath_object, pos, loadingContext, *argc, **argv) + self.tool["class"] = "WorkflowStep" + self.arvrunner = arvrunner + + def job(self, joborder, output_callback, runtimeContext): + runtimeContext = runtimeContext.copy() + runtimeContext.toplevel = True # Preserve behavior for #13365 + + builder = make_builder({shortname(k): v for k,v in viewitems(joborder)}, self.hints, self.requirements, + runtimeContext, self.metadata) + runtimeContext = set_cluster_target(self.tool, self.arvrunner, builder, runtimeContext) + return super(ArvadosWorkflowStep, self).job(joborder, output_callback, runtimeContext) + + class ArvadosWorkflow(Workflow): """Wrap cwltool Workflow to override selected methods.""" def __init__(self, arvrunner, toolpath_object, loadingContext): - super(ArvadosWorkflow, self).__init__(toolpath_object, loadingContext) self.arvrunner = arvrunner self.wf_pdh = None self.dynamic_resource_req = [] self.static_resource_req = [] self.wf_reffiles = [] self.loadingContext = loadingContext + super(ArvadosWorkflow, self).__init__(toolpath_object, loadingContext) + self.cluster_target_req, _ = self.get_requirement("http://arvados.org/cwl#ClusterTarget") def job(self, joborder, output_callback, runtimeContext): - cluster_target_req, _ = self.get_requirement("http://arvados.org/cwl#ClusterTarget") - if runtimeContext.cluster_target is None or runtimeContext.cluster_target.instance != id(cluster_target_req): - runtimeContext.cluster_target = ClusterTarget(id(cluster_target_req), - builder.do_eval(cluster_target_req.get("clusterID")), - builder.do_eval(cluster_target_req.get("ownerUUID"))) + builder = make_builder(joborder, self.hints, self.requirements, runtimeContext, self.metadata) + runtimeContext = set_cluster_target(self.tool, self.arvrunner, builder, runtimeContext) req, _ = self.get_requirement("http://arvados.org/cwl#RunInSingleContainer") if not req: @@ -147,30 +174,29 @@ class ArvadosWorkflow(Workflow): with SourceLine(self.tool, None, WorkflowException, logger.isEnabledFor(logging.DEBUG)): if "id" not in self.tool: raise WorkflowException("%s object must have 'id'" % (self.tool["class"])) - document_loader, workflowobj, uri = (self.doc_loader, self.doc_loader.fetch(self.tool["id"]), self.tool["id"]) - discover_secondary_files(self.tool["inputs"], joborder) + discover_secondary_files(self.arvrunner.fs_access, builder, + self.tool["inputs"], joborder) with Perf(metrics, "subworkflow upload_deps"): upload_dependencies(self.arvrunner, os.path.basename(joborder.get("id", "#")), - document_loader, + self.doc_loader, joborder, joborder.get("id", "#"), False) if self.wf_pdh is None: - workflowobj["requirements"] = dedup_reqs(self.requirements) - workflowobj["hints"] = dedup_reqs(self.hints) + packed = pack(self.loadingContext, self.tool["id"], loader=self.doc_loader) - packed = pack(document_loader, workflowobj, uri, self.metadata) - - builder = Builder(joborder, - requirements=workflowobj["requirements"], - hints=workflowobj["hints"], - resources={}) + for p in packed["$graph"]: + if p["id"] == "#main": + p["requirements"] = dedup_reqs(self.requirements) + p["hints"] = dedup_reqs(self.hints) def visit(item): + if "requirements" in item: + item["requirements"] = [i for i in item["requirements"] if i["class"] != "DockerRequirement"] for t in ("hints", "requirements"): if t not in item: continue @@ -198,9 +224,9 @@ class ArvadosWorkflow(Workflow): upload_dependencies(self.arvrunner, runtimeContext.name, - document_loader, + self.doc_loader, packed, - uri, + self.tool["id"], False) # Discover files/directories referenced by the @@ -209,11 +235,6 @@ class ArvadosWorkflow(Workflow): if self.dynamic_resource_req: - builder = Builder(joborder, - requirements=self.requirements, - hints=self.hints, - resources={}) - # Evaluate dynamic resource requirements using current builder rs = copy.copy(self.static_resource_req) for dyn_rs in self.dynamic_resource_req: @@ -272,6 +293,20 @@ class ArvadosWorkflow(Workflow): adjustDirObjs(packed, keepmount) self.wf_pdh = upload_workflow_collection(self.arvrunner, shortname(self.tool["id"]), packed) + self.loadingContext = self.loadingContext.copy() + self.loadingContext.metadata = self.loadingContext.metadata.copy() + self.loadingContext.metadata["http://commonwl.org/cwltool#original_cwlVersion"] = "v1.0" + + if len(job_res_reqs) == 1: + # RAM request needs to be at least 128 MiB or the workflow + # runner itself won't run reliably. + if job_res_reqs[0].get("ramMin", 1024) < 128: + job_res_reqs[0]["ramMin"] = 128 + + arguments = ["--no-container", "--move-outputs", "--preserve-entire-environment", "workflow.cwl", "cwl.input.yml"] + if runtimeContext.debug: + arguments.insert(0, '--debug') + wf_runner = cmap({ "class": "CommandLineTool", "baseCommand": "cwltool", @@ -291,7 +326,17 @@ class ArvadosWorkflow(Workflow): }] }], "hints": self.hints, - "arguments": ["--no-container", "--move-outputs", "--preserve-entire-environment", "workflow.cwl#main", "cwl.input.yml"], + "arguments": arguments, "id": "#" }) return ArvadosCommandTool(self.arvrunner, wf_runner, self.loadingContext).job(joborder_resolved, output_callback, runtimeContext) + + def make_workflow_step(self, + toolpath_object, # type: Dict[Text, Any] + pos, # type: int + loadingContext, # type: LoadingContext + *argc, + **argv + ): + # (...) -> WorkflowStep + return ArvadosWorkflowStep(toolpath_object, pos, loadingContext, self.arvrunner, *argc, **argv)