Skip to content

Commit 4a5e329

Browse files
authored
Merge pull request #68 from common-workflow-language/update-wes-1.0
Update to advertise support for WES 1.0. Update README.
2 parents f8f12f2 + ea384b0 commit 4a5e329

File tree

10 files changed

+275
-183
lines changed

10 files changed

+275
-183
lines changed

README.md

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Workflow as a Service
22

3-
This provides client and server implementations of the [GA4GH Workflow
4-
Execution Service](https://github.com/ga4gh/workflow-execution-schemas) API for
5-
the Common Workflow Language.
3+
This is a client and server implementation of the [GA4GH Workflow
4+
Execution Service](https://github.com/ga4gh/workflow-execution-schemas) 1.0.0 API.
65

7-
It provides an [Arvados](https://github.com/curoverse/arvados) backend. It
6+
It provides [Arvados](https://arvados.org/) and [Toil](http://toil.ucsc-cgl.org/) backends. It
87
also works with any `cwl-runner` that supports the CWL standard command line
98
interface: http://www.commonwl.org/v1.0/CommandLineTool.html#Executing_CWL_documents_as_scripts
109

@@ -16,43 +15,66 @@ pip install wes-service
1615

1716
## Usage
1817

19-
Run a standalone server with default `cwl-runner` backend:
18+
### Client configuration
19+
20+
Command line parameter or environment variable.
21+
22+
`--host` or `WES_API_HOST`
23+
24+
The host to contact.
25+
26+
`--proto` or `WES_API_PROTO`
27+
28+
The protocol (http or https) to use.
29+
30+
`--auth` or `WES_API_AUTH`
31+
32+
Credentials. Format is 'Header: value' or just 'value'. If header name is not provided, value goes in the 'Authorization'.
33+
34+
### Get service info
2035

2136
```
22-
$ wes-server
37+
$ wes-client --info
2338
```
2439

2540
### Submit a workflow to run:
2641

27-
Note! All inputs files must be accessible from the filesystem.
42+
Attachments must be accessible from the filesystem. Workflow runners may also support http URLs or other storage systems.
2843

2944
```
30-
$ wes-client --host=localhost:8080 --proto=http --attachments="testdata/dockstore-tool-md5sum.cwl,testdata/md5sum.input" testdata/md5sum.cwl testdata/md5sum.cwl.json
45+
$ wes-client --attachments="testdata/dockstore-tool-md5sum.cwl,testdata/md5sum.input" testdata/md5sum.cwl testdata/md5sum.cwl.json
3146
```
3247

3348
### List workflows
3449

3550
```
36-
$ wes-client --proto http --host=localhost:8080 --list
51+
$ wes-client --list
3752
```
3853

3954
### Get workflow status
4055

4156
```
42-
$ wes-client --proto http --host=localhost:8080 --get <workflow-id>
57+
$ wes-client --get <run-id>
4358
```
4459

4560
### Get stderr log from workflow:
4661

4762
```
48-
$ wes-client --proto http --host=localhost:8080 --log <workflow-id>
63+
$ wes-client --log <run-id>
4964
```
5065

5166
## Server Configuration
5267

68+
### Run a standalone server with default `cwl-runner` backend:
69+
70+
```
71+
$ wes-server
72+
```
73+
5374
### Run a standalone server with Arvados backend:
5475

5576
```
77+
$ pip install arvados-cwl-runner
5678
$ wes-server --backend=wes_service.arvados_wes
5779
```
5880

@@ -63,46 +85,21 @@ $ pip install toil[all]
6385
$ wes-server --backend=wes_service.toil_wes --opt extra=--clean=never
6486
```
6587

66-
### Use a different executable with cwl_runner backend
88+
### Use alternate executable with cwl-runner backend
6789

6890
```
69-
$ pip install toil[all]
70-
$ wes-server --backend=wes_service.cwl_runner --opt runner=cwltoil --opt extra=--logLevel=CRITICAL
91+
$ pip install cwltool
92+
$ wes-server --backend=wes_service.cwl_runner --opt runner=cwltool --opt extra=--logLevel=CRITICAL
7193
```
7294

7395
### Pass parameters to cwl-runner
7496

75-
```
76-
$ wes-server --backend=wes_service.cwl_runner --opt extra=--workDir=/
77-
```
78-
79-
## Client Configuration
80-
81-
These options will be read in as defaults when running the client from the
82-
command line. The default protocol is https, to support secure communications,
83-
but the server starts using http, to ease development.
84-
85-
Set service endpoint:
86-
87-
```
88-
$ export WES_API_HOST=localhost:8080
89-
```
90-
91-
Set the value to pass in the `Authorization` header:
97+
Use "--opt" following by "key=value"
9298

9399
```
94-
$ export WES_API_AUTH=my_api_token
100+
$ wes-server --backend=wes_service.cwl_runner --opt extra=--workDir=/tmp/work
95101
```
96102

97-
Set the protocol (one of http, https)
98-
99-
```
100-
$ export WES_API_PROTO=http
101-
```
102-
103-
Then, when you call `wes-client` these defaults will be used in place of the
104-
flags, `--host`, `--auth`, and `proto` respectively.
105-
106103
## Development
107104
If you would like to develop against `workflow-service` make sure you pass the provided test and it is flake8 compliant
108105

@@ -113,7 +110,7 @@ $ virtualenv venv && source venv/bin/activate && pip install toil[all] && pip in
113110
```
114111

115112
#### Running Tests
116-
From path `workflow-service` run
113+
From path `workflow-service` run
117114

118115
```
119116
$ pytest && flake8

setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
long_description = readmeFile.read()
1212

1313
setup(name='wes-service',
14-
version='2.9',
14+
version='3.0',
1515
description='GA4GH Workflow Execution Service reference implementation',
1616
long_description=long_description,
1717
author='GA4GH Containers and Workflows task team',
@@ -25,9 +25,9 @@
2525
install_requires=[
2626
'future',
2727
'connexion==1.4.2',
28-
'ruamel.yaml >= 0.12.4, < 0.15',
28+
'ruamel.yaml >= 0.12.4, <= 0.15.77',
2929
'cwlref-runner==1.0',
30-
'schema-salad>=2.6, <3',
30+
'schema-salad >= 3.0, < 3.1',
3131
'subprocess32==3.5.2'
3232
],
3333
entry_points={
@@ -37,7 +37,7 @@
3737
extras_require={
3838
"arvados": ["arvados-cwl-runner"
3939
],
40-
"toil": ["toil[all]==3.16.0"
40+
"toil": ["toil[all]==3.18.0"
4141
]},
4242
zip_safe=False
4343
)

test/test_integration.py

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import shutil
99
import logging
1010
import sys
11+
import requests
1112

1213
pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # noqa
1314
sys.path.insert(0, pkg_root) # noqa
@@ -23,7 +24,7 @@ class IntegrationTest(unittest.TestCase):
2324
def setUpClass(cls):
2425
# cwl
2526
cls.cwl_dockstore_url = 'https://dockstore.org:8443/api/ga4gh/v2/tools/quay.io%2Fbriandoconnor%2Fdockstore-tool-md5sum/versions/master/plain-CWL/descriptor/%2FDockstore.cwl'
26-
cls.cwl_local_path = os.path.abspath('testdata/md5sum.cwl')
27+
cls.cwl_local_path = "file://" + os.path.abspath('testdata/md5sum.cwl')
2728
cls.cwl_json_input = "file://" + os.path.abspath('testdata/md5sum.json')
2829
cls.cwl_attachments = ['file://' + os.path.abspath('testdata/md5sum.input'),
2930
'file://' + os.path.abspath('testdata/dockstore-tool-md5sum.cwl')]
@@ -52,33 +53,37 @@ def tearDown(self):
5253
time.sleep(3)
5354
except OSError as e:
5455
print(e)
55-
if os.path.exists('workflows'):
56-
shutil.rmtree('workflows')
5756
unittest.TestCase.tearDown(self)
5857

5958
def test_dockstore_md5sum(self):
6059
"""HTTP md5sum cwl (dockstore), run it on the wes-service server, and check for the correct output."""
61-
outfile_path, _ = self.run_md5sum(wf_input=self.cwl_dockstore_url,
60+
outfile_path, run_id = self.run_md5sum(wf_input=self.cwl_dockstore_url,
6261
json_input=self.cwl_json_input,
6362
workflow_attachment=self.cwl_attachments)
64-
self.assertTrue(check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
63+
state = self.wait_for_finish(run_id)
64+
self.check_complete(run_id)
65+
self.assertTrue(self.check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
6566

6667
def test_local_md5sum(self):
6768
"""LOCAL md5sum cwl to the wes-service server, and check for the correct output."""
6869
outfile_path, run_id = self.run_md5sum(wf_input=self.cwl_local_path,
6970
json_input=self.cwl_json_input,
7071
workflow_attachment=self.cwl_attachments)
71-
self.assertTrue(check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
72+
state = self.wait_for_finish(run_id)
73+
self.check_complete(run_id)
74+
self.assertTrue(self.check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
7275

7376
def test_run_attachments(self):
7477
"""LOCAL md5sum cwl to the wes-service server, check for attachments."""
7578
outfile_path, run_id = self.run_md5sum(wf_input=self.cwl_local_path,
7679
json_input=self.cwl_json_input,
7780
workflow_attachment=self.cwl_attachments)
7881
get_response = self.client.get_run_log(run_id)["request"]
79-
self.assertTrue(check_for_file(outfile_path), 'Output file was not found: ' + get_response["workflow_attachment"])
82+
state = self.wait_for_finish(run_id)
83+
self.check_complete(run_id)
84+
self.assertTrue(self.check_for_file(outfile_path), 'Output file was not found: ' + get_response["workflow_attachment"])
8085
attachment_tool_path = get_response["workflow_attachment"][7:] + "/dockstore-tool-md5sum.cwl"
81-
self.assertTrue(check_for_file(attachment_tool_path), 'Attachment file was not found: ' + get_response["workflow_attachment"])
86+
self.assertTrue(self.check_for_file(attachment_tool_path), 'Attachment file was not found: ' + get_response["workflow_attachment"])
8287

8388
def test_get_service_info(self):
8489
"""
@@ -90,7 +95,7 @@ def test_get_service_info(self):
9095
assert 'workflow_type_versions' in r
9196
assert 'supported_wes_versions' in r
9297
assert 'supported_filesystem_protocols' in r
93-
assert 'engine_versions' in r
98+
assert 'workflow_engine_versions' in r
9499

95100
def test_list_runs(self):
96101
"""
@@ -121,6 +126,37 @@ def run_md5sum(self, wf_input, json_input, workflow_attachment=None):
121126
output_dir = os.path.abspath(os.path.join('workflows', response['run_id'], 'outdir'))
122127
return os.path.join(output_dir, 'md5sum.txt'), response['run_id']
123128

129+
def wait_for_finish(self, run_id, seconds=120):
130+
"""Return True if a file exists within a certain amount of time."""
131+
wait_counter = 0
132+
r = self.client.get_run_status(run_id)
133+
while r["state"] in ("QUEUED", "INITIALIZING", "RUNNING"):
134+
time.sleep(1)
135+
wait_counter += 1
136+
if wait_counter > seconds:
137+
return None
138+
r = self.client.get_run_status(run_id)
139+
return r["state"]
140+
141+
def check_complete(self, run_id):
142+
s = self.client.get_run_log(run_id)
143+
if s["state"] != "COMPLETE":
144+
logging.info(str(s["run_log"]["stderr"]))
145+
if str(s["run_log"]["stderr"]).startswith("http"):
146+
logs = requests.get(s["run_log"]["stderr"], headers=self.client.auth).text
147+
logging.info("Run log:\n" + logs)
148+
assert s["state"] == "COMPLETE"
149+
150+
def check_for_file(self, filepath, seconds=120):
151+
"""Return True if a file exists within a certain amount of time."""
152+
wait_counter = 0
153+
while not os.path.exists(filepath):
154+
time.sleep(1)
155+
wait_counter += 1
156+
if wait_counter > seconds:
157+
return False
158+
return True
159+
124160

125161
def get_server_pids():
126162
try:
@@ -130,16 +166,6 @@ def get_server_pids():
130166
return pids
131167

132168

133-
def check_for_file(filepath, seconds=120):
134-
"""Return True if a file exists within a certain amount of time."""
135-
wait_counter = 0
136-
while not os.path.exists(filepath):
137-
time.sleep(1)
138-
wait_counter += 1
139-
if wait_counter > seconds:
140-
return False
141-
return True
142-
143169

144170
class CwltoolTest(IntegrationTest):
145171
"""Test using cwltool."""
@@ -149,9 +175,13 @@ def setUp(self):
149175
Start a (local) wes-service server to make requests against.
150176
Use cwltool as the wes-service server 'backend'.
151177
"""
178+
if os.path.exists('workflows'):
179+
shutil.rmtree('workflows')
152180
self.wes_server_process = subprocess.Popen(
153-
'python {}'.format(os.path.abspath('wes_service/wes_service_main.py')),
154-
shell=True)
181+
['python', os.path.abspath('wes_service/wes_service_main.py'),
182+
'--backend=wes_service.cwl_runner',
183+
'--port=8080',
184+
'--debug'])
155185
time.sleep(5)
156186

157187

@@ -176,8 +206,31 @@ def test_local_wdl(self):
176206
outfile_path, run_id = self.run_md5sum(wf_input=self.wdl_local_path,
177207
json_input=self.wdl_json_input,
178208
workflow_attachment=self.wdl_attachments)
179-
self.assertTrue(check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
209+
self.assertTrue(self.check_for_file(outfile_path), 'Output file was not found: ' + str(outfile_path))
210+
211+
212+
class ArvadosTest(IntegrationTest):
213+
"""Test using arvados-cwl-runner."""
214+
215+
def setUp(self):
216+
"""
217+
Start a (local) wes-service server to make requests against.
218+
Use arvados-cwl-runner as the wes-service server 'backend'.
219+
Requires ARVADOS_API_HOST and ARVADOS_API_TOKEN to be set in the environment.
220+
"""
221+
if os.path.exists('workflows'):
222+
shutil.rmtree('workflows')
223+
self.wes_server_process = subprocess.Popen(
224+
['python', os.path.abspath('wes_service/wes_service_main.py'),
225+
'--backend=wes_service.arvados_wes',
226+
'--port=8080',
227+
'--debug'])
228+
self.client.auth = {"Authorization": "Bearer " + os.environ["ARVADOS_API_TOKEN"]}
229+
time.sleep(5)
180230

231+
def check_for_file(self, filepath, seconds=120):
232+
# Doesn't make sense for arvados
233+
return True
181234

182235
# Prevent pytest/unittest's discovery from attempting to discover the base test class.
183236
del IntegrationTest

testdata/md5sum.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
{"output_file": {"path": "/tmp/md5sum.txt", "class": "File"},
2-
"input_file": {"path": "md5sum.input", "class": "File"}}
1+
{"input_file": {"path": "md5sum.input", "class": "File"}}

0 commit comments

Comments
 (0)