t2-server 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -2
- data/bin/delete_all_runs +6 -1
- data/bin/run_workflow +18 -9
- data/bin/server_info +14 -9
- data/lib/t2server.rb +31 -0
- data/lib/t2server/exceptions.rb +153 -0
- data/lib/t2server/run.rb +215 -31
- data/lib/t2server/server.rb +175 -95
- data/test/tc_paths.rb +60 -0
- data/test/tc_run.rb +72 -0
- data/test/tc_server.rb +71 -0
- data/test/ts_t2server.rb +52 -0
- data/test/workflows/hello.t2flow +21 -0
- metadata +27 -8
data/lib/t2server/server.rb
CHANGED
@@ -37,6 +37,9 @@ require 'rexml/document'
|
|
37
37
|
include REXML
|
38
38
|
|
39
39
|
module T2Server
|
40
|
+
|
41
|
+
# An interface for directly communicating with one or more Taverna 2 Server
|
42
|
+
# instances.
|
40
43
|
class Server
|
41
44
|
private_class_method :new
|
42
45
|
attr_reader :uri, :run_limit
|
@@ -44,9 +47,11 @@ module T2Server
|
|
44
47
|
# list of servers we know about
|
45
48
|
@@servers = []
|
46
49
|
|
50
|
+
# :stopdoc:
|
51
|
+
# New is private but rdoc does not get it right! Hence :stopdoc: section.
|
47
52
|
def initialize(uri)
|
48
|
-
@uri = uri
|
49
|
-
uri = URI.parse(uri)
|
53
|
+
@uri = uri.strip_path
|
54
|
+
uri = URI.parse(@uri)
|
50
55
|
@host = uri.host
|
51
56
|
@port = uri.port
|
52
57
|
@base_path = uri.path
|
@@ -61,7 +66,15 @@ module T2Server
|
|
61
66
|
@runs = {}
|
62
67
|
@runs = get_runs
|
63
68
|
end
|
69
|
+
# :startdoc:
|
64
70
|
|
71
|
+
# :call-seq:
|
72
|
+
# Server.connect(uri) -> server
|
73
|
+
#
|
74
|
+
# Connect to the server specified by _uri_ which should be of the form:
|
75
|
+
# http://example.com:8888/blah
|
76
|
+
#
|
77
|
+
# A Server instance is returned that represents the connection.
|
65
78
|
def Server.connect(uri)
|
66
79
|
# see if we've already got this server
|
67
80
|
server = @@servers.find {|s| s.uri == uri}
|
@@ -74,17 +87,30 @@ module T2Server
|
|
74
87
|
|
75
88
|
server
|
76
89
|
end
|
77
|
-
|
90
|
+
|
91
|
+
# :call-seq:
|
92
|
+
# server.create_run(workflow) -> run
|
93
|
+
#
|
94
|
+
# Create a run on this server using the specified _workflow_.
|
78
95
|
def create_run(workflow)
|
79
96
|
uuid = initialize_run(workflow)
|
80
97
|
@runs[uuid] = Run.create(self, "", uuid)
|
81
98
|
end
|
82
|
-
|
99
|
+
|
100
|
+
# :call-seq:
|
101
|
+
# server.initialize_run(workflow) -> string
|
102
|
+
#
|
103
|
+
# Create a run on this server using the specified _workflow_ but do not
|
104
|
+
# return it as a Run instance. Return its UUID instead.
|
83
105
|
def initialize_run(workflow)
|
84
|
-
request = Net::HTTP::Post.new("#{@links[:runs]}"
|
106
|
+
request = Net::HTTP::Post.new("#{@links[:runs]}")
|
85
107
|
request.content_type = "application/xml"
|
86
|
-
|
87
|
-
|
108
|
+
begin
|
109
|
+
response = Net::HTTP.new(@host, @port).start do |http|
|
110
|
+
http.request(request, Fragments::WORKFLOW % workflow)
|
111
|
+
end
|
112
|
+
rescue InternalHTTPError => e
|
113
|
+
raise ConnectionError.new(e)
|
88
114
|
end
|
89
115
|
|
90
116
|
case response
|
@@ -93,26 +119,39 @@ module T2Server
|
|
93
119
|
epr = URI.parse(response['location'])
|
94
120
|
epr.path[-36..-1]
|
95
121
|
when Net::HTTPForbidden
|
96
|
-
|
97
|
-
puts "Please try again later."
|
98
|
-
""
|
122
|
+
raise ServerAtCapacityError.new(@run_limit)
|
99
123
|
else
|
100
|
-
|
101
|
-
""
|
124
|
+
raise UnexpectedServerResponse.new(response)
|
102
125
|
end
|
103
126
|
end
|
104
|
-
|
127
|
+
|
128
|
+
# :call-seq:
|
129
|
+
# server.runs -> [runs]
|
130
|
+
#
|
131
|
+
# Return the set of runs on this server.
|
105
132
|
def runs
|
106
133
|
get_runs.values
|
107
134
|
end
|
108
|
-
|
135
|
+
|
136
|
+
# :call-seq:
|
137
|
+
# server.run(uuid) -> run
|
138
|
+
#
|
139
|
+
# Return the specified run.
|
109
140
|
def run(uuid)
|
110
141
|
get_runs[uuid]
|
111
142
|
end
|
112
143
|
|
144
|
+
# :call-seq:
|
145
|
+
# server.delete_run(uuid) -> bool
|
146
|
+
#
|
147
|
+
# Delete the specified run from the server, discarding all of its state.
|
113
148
|
def delete_run(uuid)
|
114
|
-
request = Net::HTTP::Delete.new("#{@links[:runs]}/#{uuid}"
|
115
|
-
|
149
|
+
request = Net::HTTP::Delete.new("#{@links[:runs]}/#{uuid}")
|
150
|
+
begin
|
151
|
+
response = Net::HTTP.new(@host, @port).start {|http| http.request(request)}
|
152
|
+
rescue InternalHTTPError => e
|
153
|
+
raise ConnectionError.new(e)
|
154
|
+
end
|
116
155
|
|
117
156
|
case response
|
118
157
|
when Net::HTTPNoContent
|
@@ -120,131 +159,183 @@ module T2Server
|
|
120
159
|
@runs.delete(uuid)
|
121
160
|
true
|
122
161
|
when Net::HTTPNotFound
|
123
|
-
|
124
|
-
|
162
|
+
raise RunNotFoundError.new(uuid)
|
163
|
+
when Net::HTTPForbidden
|
164
|
+
raise AccessForbiddenError.new("run #{uuid}")
|
125
165
|
else
|
126
|
-
|
166
|
+
raise UnexpectedServerResponse.new(response)
|
127
167
|
end
|
128
168
|
end
|
129
|
-
|
169
|
+
|
170
|
+
# :call-seq:
|
171
|
+
# server.delete_all_runs
|
172
|
+
#
|
173
|
+
# Delete all runs on this server, discarding all of their state.
|
130
174
|
def delete_all_runs
|
131
175
|
# first refresh run list
|
132
176
|
runs.each {|run| run.delete}
|
133
177
|
end
|
134
|
-
|
178
|
+
|
179
|
+
# :call-seq:
|
180
|
+
# server.set_run_input(run, input, value) -> bool
|
181
|
+
#
|
182
|
+
# Set the workflow input port _input_ on run _run_ to _value_.
|
135
183
|
def set_run_input(run, input, value)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
case response
|
143
|
-
when Net::HTTPOK
|
144
|
-
# Yay!
|
145
|
-
true
|
184
|
+
path = "#{@links[:runs]}/#{run.uuid}/#{run.inputs}/input/#{input}"
|
185
|
+
set_attribute(path, Fragments::RUNINPUTVALUE % value, "application/xml")
|
186
|
+
rescue AttributeNotFoundError => e
|
187
|
+
if get_runs.has_key? uuid
|
188
|
+
raise e
|
146
189
|
else
|
147
|
-
|
190
|
+
raise RunNotFoundError.new(uuid)
|
148
191
|
end
|
149
192
|
end
|
150
193
|
|
194
|
+
# :call-seq:
|
195
|
+
# server.set_run_input_file(run, input, filename) -> bool
|
196
|
+
#
|
197
|
+
# Set the workflow input port _input_ on run _run_ to use the file at
|
198
|
+
# _filename_ for its input.
|
151
199
|
def set_run_input_file(run, input, filename)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
case response
|
159
|
-
when Net::HTTPOK
|
160
|
-
# Yay!
|
161
|
-
true
|
200
|
+
path = "#{@links[:runs]}/#{run.uuid}/#{run.inputs}/input/#{input}"
|
201
|
+
set_attribute(path, Fragments::RUNINPUTFILE % filename, "application/xml")
|
202
|
+
rescue AttributeNotFoundError => e
|
203
|
+
if get_runs.has_key? uuid
|
204
|
+
raise e
|
162
205
|
else
|
163
|
-
|
206
|
+
raise RunNotFoundError.new(uuid)
|
164
207
|
end
|
165
208
|
end
|
166
209
|
|
210
|
+
# :call-seq:
|
211
|
+
# server.make_run_dir(uuid, root, dir) -> bool
|
212
|
+
#
|
213
|
+
# Create a directory _dir_ within the directory _root_ on the run with
|
214
|
+
# identifier _uuid_. This is mainly for use by Run#mkdir.
|
167
215
|
def make_run_dir(uuid, root, dir)
|
168
|
-
|
216
|
+
raise AccessForbiddenError.new("subdirectories (#{dir})") if dir.include? ?/
|
217
|
+
request = Net::HTTP::Post.new("#{@links[:runs]}/#{uuid}/#{root}")
|
169
218
|
request.content_type = "application/xml"
|
170
|
-
|
171
|
-
|
219
|
+
begin
|
220
|
+
response = Net::HTTP.new(@host, @port).start do |http|
|
221
|
+
http.request(request, Fragments::MKDIR % dir)
|
222
|
+
end
|
223
|
+
rescue InternalHTTPError => e
|
224
|
+
raise ConnectionError.new(e)
|
172
225
|
end
|
173
|
-
|
226
|
+
|
174
227
|
case response
|
175
228
|
when Net::HTTPCreated
|
176
229
|
# OK, carry on...
|
177
230
|
true
|
178
|
-
when Net::HTTPForbidden
|
179
|
-
puts "Error!", response.body
|
180
|
-
false
|
181
231
|
when Net::HTTPNotFound
|
182
|
-
|
183
|
-
|
232
|
+
raise RunNotFoundError.new(uuid)
|
233
|
+
when Net::HTTPForbidden
|
234
|
+
raise AccessForbiddenError.new("#{dir} on run #{uuid}")
|
184
235
|
else
|
185
|
-
|
236
|
+
raise UnexpectedServerResponse.new(response)
|
186
237
|
end
|
187
238
|
end
|
188
|
-
|
239
|
+
|
240
|
+
# :call-seq:
|
241
|
+
# server.upload_run_file(uuid, filename, location, rename) -> string
|
242
|
+
#
|
243
|
+
# Upload a file to the run with identifier _uuid_. Mainly for internal use
|
244
|
+
# by Run#upload_file.
|
189
245
|
def upload_run_file(uuid, filename, location, rename)
|
190
246
|
contents = Base64.encode64(IO.read(filename))
|
191
247
|
rename = filename.split('/')[-1] if rename == ""
|
192
|
-
request = Net::HTTP::Post.new("#{@links[:runs]}/#{uuid}/#{location}"
|
248
|
+
request = Net::HTTP::Post.new("#{@links[:runs]}/#{uuid}/#{location}")
|
193
249
|
request.content_type = "application/xml"
|
194
|
-
|
195
|
-
|
250
|
+
begin
|
251
|
+
response = Net::HTTP.new(@host, @port).start do |http|
|
252
|
+
http.request(request, Fragments::UPLOAD % [rename, contents])
|
253
|
+
end
|
254
|
+
rescue InternalHTTPError => e
|
255
|
+
raise ConnectionError.new(e)
|
196
256
|
end
|
197
|
-
|
257
|
+
|
198
258
|
case response
|
199
259
|
when Net::HTTPCreated
|
200
260
|
# Success, return remote name of uploaded file
|
201
261
|
rename
|
262
|
+
when Net::HTTPNotFound
|
263
|
+
raise RunNotFoundError.new(uuid)
|
202
264
|
when Net::HTTPForbidden
|
203
|
-
|
265
|
+
raise AccessForbiddenError.new("run #{uuid}")
|
204
266
|
else
|
205
|
-
|
267
|
+
raise UnexpectedServerResponse.new(response)
|
206
268
|
end
|
207
269
|
end
|
208
270
|
|
271
|
+
# :call-seq:
|
272
|
+
# server.get_run_attribute(uuid, path) -> string
|
273
|
+
#
|
274
|
+
# Get the attribute at _path_ in the run with identifier _uuid_.
|
209
275
|
def get_run_attribute(uuid, path)
|
210
276
|
get_attribute("#{@links[:runs]}/#{uuid}/#{path}")
|
277
|
+
rescue AttributeNotFoundError => e
|
278
|
+
if get_runs.has_key? uuid
|
279
|
+
raise e
|
280
|
+
else
|
281
|
+
raise RunNotFoundError.new(uuid)
|
282
|
+
end
|
211
283
|
end
|
212
284
|
|
285
|
+
# :call-seq:
|
286
|
+
# server.set_run_attribute(uuid, path, value) -> bool
|
287
|
+
#
|
288
|
+
# Set the attribute at _path_ in the run with identifier _uuid_ to _value_.
|
213
289
|
def set_run_attribute(uuid, path, value)
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
case response
|
219
|
-
when Net::HTTPOK
|
220
|
-
# OK, so carry on
|
221
|
-
true
|
222
|
-
when Net::HTTPForbidden
|
223
|
-
puts "Error!"
|
224
|
-
puts response.body
|
225
|
-
false
|
226
|
-
when Net::HTTPNotFound
|
227
|
-
puts "Cannot find run #{uuid}."
|
228
|
-
false
|
290
|
+
set_attribute("#{@links[:runs]}/#{uuid}/#{path}", value, "text/plain")
|
291
|
+
rescue AttributeNotFoundError => e
|
292
|
+
if get_runs.has_key? uuid
|
293
|
+
raise e
|
229
294
|
else
|
230
|
-
|
295
|
+
raise RunNotFoundError.new(uuid)
|
231
296
|
end
|
232
297
|
end
|
233
|
-
|
298
|
+
|
234
299
|
private
|
235
300
|
def get_attribute(path)
|
236
|
-
request = Net::HTTP::Get.new(path
|
237
|
-
|
238
|
-
|
301
|
+
request = Net::HTTP::Get.new(path)
|
302
|
+
begin
|
303
|
+
response = Net::HTTP.new(@host, @port).start {|http| http.request(request)}
|
304
|
+
rescue InternalHTTPError => e
|
305
|
+
raise ConnectionError.new(e)
|
306
|
+
end
|
307
|
+
|
239
308
|
case response
|
240
309
|
when Net::HTTPOK
|
241
310
|
return response.body
|
242
311
|
when Net::HTTPNotFound
|
243
|
-
|
312
|
+
raise AttributeNotFoundError.new(path)
|
244
313
|
when Net::HTTPForbidden
|
245
|
-
|
314
|
+
raise AccessForbiddenError.new("attribute #{path}")
|
246
315
|
else
|
247
|
-
|
316
|
+
raise UnexpectedServerResponse.new(response)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def set_attribute(path, value, type)
|
321
|
+
request = Net::HTTP::Put.new(path)
|
322
|
+
request.content_type = type
|
323
|
+
begin
|
324
|
+
response = Net::HTTP.new(@host, @port).start {|http| http.request(request, value)}
|
325
|
+
rescue InternalHTTPError => e
|
326
|
+
raise ConnectionError.new(e)
|
327
|
+
end
|
328
|
+
|
329
|
+
case response
|
330
|
+
when Net::HTTPOK
|
331
|
+
# OK, so carry on
|
332
|
+
true
|
333
|
+
when Net::HTTPNotFound
|
334
|
+
raise AttributeNotFoundError.new(path)
|
335
|
+
when Net::HTTPForbidden
|
336
|
+
raise AccessForbiddenError.new("attribute #{path}")
|
337
|
+
else
|
338
|
+
raise UnexpectedServerResponse.new(response)
|
248
339
|
end
|
249
340
|
end
|
250
341
|
|
@@ -258,18 +349,7 @@ module T2Server
|
|
258
349
|
:permlisteners => URI.parse(XPath.first(doc, "//nsr:permittedListeners", nsmap).attributes["href"]).path
|
259
350
|
}
|
260
351
|
end
|
261
|
-
|
262
|
-
def response_error(response)
|
263
|
-
puts "Unnexpected response from Taverna Server!"
|
264
|
-
puts "Server is: #{@host}:#{@port}#{@base_path}"
|
265
|
-
puts "Response code is: #{response.code}"
|
266
|
-
if response.body
|
267
|
-
puts "Response body is: \n#{response.body}"
|
268
|
-
end
|
269
|
-
puts "\nRaw error is: \n#{response.error!}"
|
270
|
-
false
|
271
|
-
end
|
272
|
-
|
352
|
+
|
273
353
|
def get_runs
|
274
354
|
run_list = get_attribute("#{@links[:runs]}")
|
275
355
|
|
data/test/tc_paths.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Copyright (c) 2010, The University of Manchester, UK.
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# * Neither the names of The University of Manchester nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from this
|
17
|
+
# software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
# Author: Robert Haines
|
32
|
+
|
33
|
+
require 't2server'
|
34
|
+
require 'test/unit'
|
35
|
+
|
36
|
+
class TestFilePaths < Test::Unit::TestCase
|
37
|
+
def test_standard
|
38
|
+
assert_equal("dir/with/child", "dir/with/child".strip_path)
|
39
|
+
assert_equal("dir/with/child", "/dir/with/child".strip_path)
|
40
|
+
assert_equal("dir/with/child", "dir/with/child/".strip_path)
|
41
|
+
assert_equal("dir/with/child", "/dir/with/child/".strip_path)
|
42
|
+
|
43
|
+
# make sure it is not stripping in place
|
44
|
+
dir = "/dir/with/child/"
|
45
|
+
dir.strip_path
|
46
|
+
assert_equal("/dir/with/child/", dir)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_inplace
|
50
|
+
assert_nil("dir/with/child".strip_path!)
|
51
|
+
assert_not_nil("/dir/with/child".strip_path!)
|
52
|
+
assert_not_nil("dir/with/child/".strip_path!)
|
53
|
+
assert_not_nil("/dir/with/child/".strip_path!)
|
54
|
+
|
55
|
+
# make sure it is stripping in place
|
56
|
+
dir = "/dir/with/child/"
|
57
|
+
dir.strip_path!
|
58
|
+
assert_equal("dir/with/child", dir)
|
59
|
+
end
|
60
|
+
end
|