1
1
import arvados
2
2
import arvados .util
3
3
import arvados .collection
4
+ import arvados .errors
4
5
import os
5
6
import connexion
6
7
import json
7
8
import subprocess
8
9
import tempfile
10
+ import functools
11
+ import threading
12
+ import logging
13
+
9
14
from wes_service .util import visit , WESBackend
10
15
16
+ class MissingAuthorization (Exception ):
17
+ pass
11
18
12
19
def get_api ():
20
+ if not connexion .request .headers .get ('Authorization' ):
21
+ raise MissingAuthorization ()
13
22
return arvados .api_from_config (version = "v1" , apiconfig = {
14
23
"ARVADOS_API_HOST" : os .environ ["ARVADOS_API_HOST" ],
15
24
"ARVADOS_API_TOKEN" : connexion .request .headers ['Authorization' ],
@@ -26,6 +35,23 @@ def get_api():
26
35
}
27
36
28
37
38
+ def catch_exceptions (orig_func ):
39
+ """Catch uncaught exceptions and turn them into http errors"""
40
+
41
+ @functools .wraps (orig_func )
42
+ def catch_exceptions_wrapper (self , * args , ** kwargs ):
43
+ try :
44
+ return orig_func (self , * args , ** kwargs )
45
+ except arvados .errors .ApiError as e :
46
+ logging .exception ("Failure" )
47
+ return {"msg" : e ._get_reason (), "status_code" : e .resp .status }, int (e .resp .status )
48
+ except subprocess .CalledProcessError as e :
49
+ return {"msg" : str (e ), "status_code" : 500 }, 500
50
+ except MissingAuthorization :
51
+ return {"msg" : "'Authorization' header is missing or empty, expecting Arvados API token" , "status_code" : 401 }, 401
52
+
53
+ return catch_exceptions_wrapper
54
+
29
55
class ArvadosBackend (WESBackend ):
30
56
def GetServiceInfo (self ):
31
57
return {
@@ -39,6 +65,7 @@ def GetServiceInfo(self):
39
65
"key_values" : {}
40
66
}
41
67
68
+ @catch_exceptions
42
69
def ListWorkflows (self ):
43
70
api = get_api ()
44
71
@@ -60,28 +87,87 @@ def ListWorkflows(self):
60
87
"next_page_token" : ""
61
88
}
62
89
90
+ def invoke_cwl_runner (self , cr_uuid , workflow_url , workflow_params , env , workflow_descriptor_file ):
91
+ api = arvados .api_from_config (version = "v1" , apiconfig = {
92
+ "ARVADOS_API_HOST" : env ["ARVADOS_API_HOST" ],
93
+ "ARVADOS_API_TOKEN" : env ['ARVADOS_API_TOKEN' ],
94
+ "ARVADOS_API_HOST_INSECURE" : env ["ARVADOS_API_HOST_INSECURE" ] # NOQA
95
+ })
96
+
97
+ try :
98
+ with tempfile .NamedTemporaryFile () as inputtemp :
99
+ json .dump (workflow_params , inputtemp )
100
+ inputtemp .flush ()
101
+ # TODO: run submission process in a container to prevent
102
+ # a-c-r submission processes from seeing each other.
103
+ proc = subprocess .Popen (["arvados-cwl-runner" , "--submit-request-uuid=" + cr_uuid , # NOQA
104
+ "--submit" , "--no-wait" , "--api=containers" , # NOQA
105
+ workflow_url , inputtemp .name ], env = env ,
106
+ stdout = subprocess .PIPE , stderr = subprocess .PIPE ) # NOQA
107
+ (stdoutdata , stderrdata ) = proc .communicate ()
108
+ if proc .returncode != 0 :
109
+ api .container_requests ().update (uuid = cr_uuid , body = {"priority" : 0 ,
110
+ "properties" : {"arvados-cwl-runner-log" : stderrdata }}).execute ()
111
+ else :
112
+ api .container_requests ().update (uuid = cr_uuid , body = {"properties" : {"arvados-cwl-runner-log" : stderrdata }}).execute ()
113
+ except subprocess .CalledProcessError as e :
114
+ api .container_requests ().update (uuid = cr_uuid , body = {"priority" : 0 ,
115
+ "properties" : {"arvados-cwl-runner-log" : str (e )}}).execute ()
116
+ finally :
117
+ if workflow_descriptor_file is not None :
118
+ workflow_descriptor_file .close ()
119
+
120
+ @catch_exceptions
63
121
def RunWorkflow (self , body ):
64
122
if body ["workflow_type" ] != "CWL" or body ["workflow_type_version" ] != "v1.0" : # NOQA
65
123
return
66
124
125
+ if not connexion .request .headers .get ('Authorization' ):
126
+ raise MissingAuthorization ()
127
+
67
128
env = {
68
129
"PATH" : os .environ ["PATH" ],
69
130
"ARVADOS_API_HOST" : os .environ ["ARVADOS_API_HOST" ],
70
131
"ARVADOS_API_TOKEN" : connexion .request .headers ['Authorization' ],
71
132
"ARVADOS_API_HOST_INSECURE" : os .environ .get ("ARVADOS_API_HOST_INSECURE" , "false" ) # NOQA
72
133
}
73
- with tempfile .NamedTemporaryFile () as inputtemp :
74
- json .dump (body ["workflow_params" ], inputtemp )
75
- inputtemp .flush ()
76
- workflow_id = subprocess .check_output (["arvados-cwl-runner" , "--submit" , "--no-wait" , "--api=containers" , # NOQA
77
- body .get ("workflow_url" ), inputtemp .name ], env = env ).strip () # NOQA
78
- return {"workflow_id" : workflow_id }
79
134
135
+ api = get_api ()
136
+
137
+ cr = api .container_requests ().create (body = {"container_request" :
138
+ {"command" : ["" ],
139
+ "container_image" : "n/a" ,
140
+ "state" : "Uncommitted" ,
141
+ "output_path" : "n/a" ,
142
+ "priority" : 500 }}).execute ()
143
+
144
+ workflow_url = body .get ("workflow_url" )
145
+ workflow_descriptor_file = None
146
+ if body .get ("workflow_descriptor" ):
147
+ workflow_descriptor_file = tempfile .NamedTemporaryFile ()
148
+ workflow_descriptor_file .write (body .get ('workflow_descriptor' ))
149
+ workflow_descriptor_file .flush ()
150
+ workflow_url = workflow_descriptor_file .name
151
+
152
+ threading .Thread (target = self .invoke_cwl_runner , args = (cr ["uuid" ],
153
+ workflow_url ,
154
+ body ["workflow_params" ],
155
+ env ,
156
+ workflow_descriptor_file )).start ()
157
+
158
+ return {"workflow_id" : cr ["uuid" ]}
159
+
160
+ @catch_exceptions
80
161
def GetWorkflowLog (self , workflow_id ):
81
162
api = get_api ()
82
163
83
164
request = api .container_requests ().get (uuid = workflow_id ).execute ()
84
- container = api .containers ().get (uuid = request ["container_uuid" ]).execute () # NOQA
165
+ if request ["container_uuid" ]:
166
+ container = api .containers ().get (uuid = request ["container_uuid" ]).execute () # NOQA
167
+ else :
168
+ container = {"state" : "Queued" , "exit_code" : None }
169
+
170
+ stderr = request ["properties" ].get ("arvados-cwl-runner-log" , "" )
85
171
86
172
outputobj = {}
87
173
if request ["output_uuid" ]:
@@ -91,16 +177,15 @@ def GetWorkflowLog(self, workflow_id):
91
177
92
178
def keepref (d ):
93
179
if isinstance (d , dict ) and "location" in d :
94
- d ["location" ] = "keep:%s/ %s" % (c .portable_data_hash (), d ["location" ]) # NOQA
180
+ d ["location" ] = "%sc=%s/_/ %s" % (api . _resourceDesc [ "keepWebServiceUrl" ], c .portable_data_hash (), d ["location" ]) # NOQA
95
181
96
182
visit (outputobj , keepref )
97
183
98
- stderr = ""
99
184
if request ["log_uuid" ]:
100
185
c = arvados .collection .CollectionReader (request ["log_uuid" ], api_client = api )
101
186
if "stderr.txt" in c :
102
187
with c .open ("stderr.txt" ) as f :
103
- stderr = f .read ()
188
+ stderr + = f .read ()
104
189
105
190
r = {
106
191
"workflow_id" : request ["uuid" ],
@@ -120,15 +205,22 @@ def keepref(d):
120
205
r ["workflow_log" ]["exit_code" ] = container ["exit_code" ]
121
206
return r
122
207
208
+ @catch_exceptions
123
209
def CancelJob (self , workflow_id ): # NOQA
124
210
api = get_api ()
125
- request = api .container_requests ().update (body = {"priority" : 0 }).execute () # NOQA
211
+ request = api .container_requests ().update (uuid = workflow_id , body = {"priority" : 0 }).execute () # NOQA
126
212
return {"workflow_id" : request ["uuid" ]}
127
213
214
+ @catch_exceptions
128
215
def GetWorkflowStatus (self , workflow_id ):
129
216
api = get_api ()
130
217
request = api .container_requests ().get (uuid = workflow_id ).execute ()
131
- container = api .containers ().get (uuid = request ["container_uuid" ]).execute () # NOQA
218
+ if request ["container_uuid" ]:
219
+ container = api .containers ().get (uuid = request ["container_uuid" ]).execute () # NOQA
220
+ elif request ["priority" ] == 0 :
221
+ container = {"state" : "Cancelled" }
222
+ else :
223
+ container = {"state" : "Queued" }
132
224
return {"workflow_id" : request ["uuid" ],
133
225
"state" : statemap [container ["state" ]]}
134
226
0 commit comments