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 CHANGED
@@ -1,7 +1,7 @@
1
1
  = Taverna[http://www.taverna.org.uk/] 2 Server Interaction Gem
2
2
 
3
3
  Authors:: Robert Haines
4
- Gem Version:: 0.0.4
4
+ Gem Version:: 0.1.0
5
5
  API Version:: 2.2a1
6
6
  Contact:: mailto:rhaines@manchester.ac.uk
7
7
  URL:: http://taverna.sourceforge.net/
@@ -29,7 +29,14 @@ http://github.com/myGrid/t2-server-gem
29
29
 
30
30
  == Usage
31
31
 
32
- As well as the library there are also a couple of example scripts which
32
+ There are two entry points for the T2Server API:
33
+ * T2Server::Run - Use this for running single jobs on a server.
34
+ * T2Server::Server - Use this if you are providing a web interface to one or
35
+ more Taverna 2 Server instances.
36
+
37
+ See the rdoc for more information.
38
+
39
+ As well as rdoc there are also a couple of example scripts which
33
40
  demonstrate good use of the T2Server API. These are available in the
34
41
  <tt>bin</tt> directory:
35
42
  * run_workflow
data/bin/delete_all_runs CHANGED
@@ -62,4 +62,9 @@ if ARGV[0] == nil
62
62
  end
63
63
 
64
64
  # connect and delete them all!
65
- T2Server::Server.connect(ARGV[0]).delete_all_runs
65
+ begin
66
+ T2Server::Server.connect(ARGV[0]).delete_all_runs
67
+ rescue T2Server::T2ServerError => e
68
+ puts e
69
+ exit 1
70
+ end
data/bin/run_workflow CHANGED
@@ -47,8 +47,8 @@ def get_outputs(run, pout, dir="")
47
47
  if pout
48
48
  p data
49
49
  else
50
- # remove leading slash
51
- filename = "#{dir[1..-1]}/#{out}".gsub('/', '-')
50
+ # strip path and convert remove /'s for file output
51
+ filename = "#{dir.strip_path}/#{out}".gsub('/', '-')
52
52
  File.open(filename, "w") do |file|
53
53
  file.syswrite(data)
54
54
  end
@@ -106,11 +106,17 @@ else
106
106
  wkf = IO.read(wkf_file)
107
107
  end
108
108
 
109
- # create run and set inputs
110
- run = T2Server::Run.create(uri, wkf)
109
+ # create run
110
+ begin
111
+ run = T2Server::Run.create(uri, wkf)
112
+ rescue T2Server::T2ServerError => e
113
+ puts e
114
+ exit 1
115
+ end
111
116
  puts "Created run with uuid: #{run.uuid}"
112
117
  puts "Created at #{run.create_time}"
113
118
 
119
+ # set inputs
114
120
  inputs.each do |input, value|
115
121
  puts "Set input '#{input}' to #{value}"
116
122
  run.set_input(input, value)
@@ -126,12 +132,15 @@ puts "Finished at #{run.finish_time}"
126
132
  # get outputs
127
133
  stdout = run.stdout
128
134
  stderr = run.stderr
129
- puts "Exitcode: #{run.exitcode}"
130
- if stdout != "" then puts "Stdout:\n #{stdout}" end
131
- if stderr != "" then puts "Stderr:\n #{stderr}" end
135
+ exitcd = run.exitcode
136
+ puts "Exitcode: #{exitcd}"
137
+ if stdout != "" then puts "Stdout:\n#{stdout}" end
138
+ if stderr != "" then puts "Stderr:\n#{stderr}" end
132
139
 
133
- puts "Outputs:"
134
- get_outputs(run, print_output)
140
+ if exitcd == 0
141
+ puts "Outputs:"
142
+ get_outputs(run, print_output)
143
+ end
135
144
 
136
145
  # delete run
137
146
  run.delete
data/bin/server_info CHANGED
@@ -62,14 +62,19 @@ if uri == nil
62
62
  end
63
63
 
64
64
  # connect to server and output information
65
- server = T2Server::Server.connect(uri)
66
- print " Server: #{uri}\n"
67
- print " Run limit: #{server.run_limit}\n"
68
- runs = server.runs
69
- print "No. of runs: #{runs.length}\n"
70
- if runs.length > 0
71
- print " Run list: #{runs[0].uuid} - #{runs[0].expiry}\n"
72
- runs[1..-1].each do |run|
73
- print " #{run.uuid} - #{run.expiry}\n"
65
+ begin
66
+ server = T2Server::Server.connect(uri)
67
+ print " Server: #{uri}\n"
68
+ print " Run limit: #{server.run_limit}\n"
69
+ runs = server.runs
70
+ print "No. of runs: #{runs.length}\n"
71
+ if runs.length > 0
72
+ print " Run list: #{runs[0].uuid} - #{runs[0].expiry}\n"
73
+ runs[1..-1].each do |run|
74
+ print " #{run.uuid} - #{run.expiry}\n"
75
+ end
74
76
  end
77
+ rescue T2Server::T2ServerError => e
78
+ puts e
79
+ exit 1
75
80
  end
data/lib/t2server.rb CHANGED
@@ -31,10 +31,41 @@
31
31
  # Author: Robert Haines
32
32
 
33
33
  require 't2server/xml'
34
+ require 't2server/exceptions'
34
35
  require 't2server/server'
35
36
  require 't2server/run'
36
37
 
38
+ # This is a Ruby library to interface with the Taverna 2 Server REST API.
39
+ #
40
+ # There are two API entry points:
41
+ # * T2Server::Run - Use this for running single jobs on a server.
42
+ # * T2Server::Server - Use this if you are providing a web interface to a
43
+ # Taverna 2 Server instance.
37
44
  module T2Server
45
+ # The version of this library
38
46
  GEM_VERSION = "0.0.4"
47
+ # The version of the Taverna 2 Server API that this library can interface with
39
48
  API_VERSION = "2.2a1"
40
49
  end
50
+
51
+ # Add methods to the String class to operate on file paths.
52
+ class String
53
+ # :call-seq:
54
+ # str.strip_path -> string
55
+ #
56
+ # Returns a new String with one leading and one trailing slash
57
+ # removed from the ends of _str_ (if present).
58
+ def strip_path
59
+ self.gsub(/^\//, "").chomp("/")
60
+ end
61
+
62
+ # :call-seq:
63
+ # str.strip_path! -> str or nil
64
+ #
65
+ # Modifies _str_ in place as described for String#strip_path,
66
+ # returning _str_, or returning +nil+ if no modifications were made.
67
+ def strip_path!
68
+ g = self.gsub!(/^\//, "")
69
+ self.chomp!("/") || g
70
+ end
71
+ end
@@ -0,0 +1,153 @@
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 'net/http'
34
+
35
+ module T2Server
36
+ # An internal module to collect all the exceptions that we
37
+ # can't really do anything about ourselves, such as
38
+ # timeouts and lost connections. This is further wrapped
39
+ # and exposed in the API as T2Server::ConnectionError below.
40
+ module InternalHTTPError #:nodoc:
41
+ end
42
+
43
+ # These are the HTTP errors we want to catch.
44
+ # Add the above exception as an ancestor to them all.
45
+ [
46
+ EOFError,
47
+ SocketError,
48
+ Timeout::Error,
49
+ Errno::EINVAL,
50
+ Errno::ETIMEDOUT,
51
+ Errno::ECONNRESET,
52
+ Errno::ECONNREFUSED,
53
+ Net::HTTPBadResponse,
54
+ Net::HTTPHeaderSyntaxError,
55
+ Net::ProtocolError
56
+ ].each {|err| err.send(:include, InternalHTTPError)}
57
+
58
+ # This is a superclass for all T2Server exceptions. It is provided as a
59
+ # useful catch-all for all the internally raised/thrown exceptions.
60
+ class T2ServerError < RuntimeError
61
+ end
62
+
63
+ # Raised when there is an error with the connection to the server in some
64
+ # way. This could be due to the server not accepting the connection, the
65
+ # connection being dropped unexpectedly or a timeout of some sort.
66
+ class ConnectionError < T2ServerError
67
+ attr_reader :cause
68
+
69
+ # Create a new ConnectionError with the specified cause. The cause to be
70
+ # passed in should be the exception object that caused the connection
71
+ # error.
72
+ def initialize(cause)
73
+ @cause = cause
74
+ super "Connection error (#{@cause.class.name}): #{@cause.message}"
75
+ end
76
+ end
77
+
78
+ # Raised when there is an unexpected response from the server. This does
79
+ # not necessarily indicate a problem with the server.
80
+ class UnexpectedServerResponse < T2ServerError
81
+
82
+ # Create a new UnexpectedServerResponse with the specified unexpected
83
+ # response. The response to be passed in is that which was returned by a
84
+ # call to Net::HTTP#request.
85
+ def initialize(response)
86
+ body = "\n#{response.body}" if response.body
87
+ super "Unexpected server response: #{response.code}\n#{response.error!}#{body}"
88
+ end
89
+ end
90
+
91
+ # Raised when the run that is being operated on cannot be found. If the
92
+ # expectation is that the run exists then it could have been destroyed by
93
+ # a timeout or another user.
94
+ class RunNotFoundError < T2ServerError
95
+ attr_reader :uuid
96
+
97
+ # Create a new RunNotFoundError with the specified UUID.
98
+ def initialize(uuid)
99
+ @uuid = uuid
100
+ super "Could not find run #{@uuid}"
101
+ end
102
+ end
103
+
104
+ # Indicates that the attribute that the user is trying to read/change does
105
+ # not exist. The attribute could be a server or run attribute.
106
+ class AttributeNotFoundError < T2ServerError
107
+ attr_reader :path
108
+
109
+ # Create a new AttributeNotFoundError with the path to the erroneous
110
+ # attribute.
111
+ def initialize(path)
112
+ @path = path
113
+ super "Could not find attribute at #{@path}"
114
+ end
115
+ end
116
+
117
+ # The server is at capacity and cannot accept anymore runs at this time.
118
+ class ServerAtCapacityError < T2ServerError
119
+ attr_reader :limit
120
+
121
+ # Create a new ServerAtCapacityError with the specified limit for
122
+ # information.
123
+ def initialize(limit)
124
+ @limit = limit
125
+ super "The server is already running its configured limit of concurrent workflows (#{@limit})"
126
+ end
127
+ end
128
+
129
+ # Access to the entity (run or attribute) is denied. The credentials
130
+ # supplied are not sufficient or the server does not allow the operation.
131
+ class AccessForbiddenError < T2ServerError
132
+ attr_reader :path
133
+
134
+ # Create a new AccessForbiddenError with the path to the restricted
135
+ # attribute.
136
+ def initialize(path)
137
+ @path = path
138
+ super "Access to #{@path} is forbidden. Either you do not have the required credentials or the server does not allow the requested operation"
139
+ end
140
+ end
141
+
142
+ # Raised if an operation is performed on a run when it is in the wrong
143
+ # state. Trying to start a run if it is the finished state would cause this
144
+ # exception to be raised.
145
+ class RunStateError < T2ServerError
146
+
147
+ # Create a new RunStateError specifying both the current state and that
148
+ # which is needed to run the operation.
149
+ def initialize(current, need)
150
+ super "The run is in the wrong state (#{current}); it should be '#{need}' to perform that action"
151
+ end
152
+ end
153
+ end
data/lib/t2server/run.rb CHANGED
@@ -34,19 +34,29 @@ require 'rexml/document'
34
34
  include REXML
35
35
 
36
36
  module T2Server
37
-
37
+
38
+ # An interface for easily running jobs on a Taverna 2 Server with minimal
39
+ # setup and configuration required.
40
+ #
41
+ # A run can be in one of three states:
42
+ # * Initialized: The run has been accepted by the server. It may not yet be
43
+ # ready to run though as its input port may not have been set.
44
+ # * Running: The run is being run by the server.
45
+ # * Finished: The run has finished running and its outputs are available for
46
+ # download.
38
47
  class Run
39
-
48
+ private_class_method :new
49
+ attr_reader :uuid
50
+
51
+ # :stopdoc:
40
52
  STATE = {
41
53
  :initialized => "Initialized",
42
54
  :running => "Operating",
43
55
  :finished => "Finished",
44
56
  :stopped => "Stopped"
45
57
  }
46
-
47
- private_class_method :new
48
- attr_reader :uuid
49
-
58
+
59
+ # New is private but rdoc does not get it right! Hence :stopdoc: section.
50
60
  def initialize(server, uuid)
51
61
  @server = server
52
62
  @uuid = uuid
@@ -56,7 +66,16 @@ module T2Server
56
66
  @links = get_attributes(@server.get_run_attribute(uuid, ""))
57
67
  #@links.each {|key, val| puts "#{key}: #{val}"}
58
68
  end
69
+ # :startdoc:
59
70
 
71
+ # :call-seq:
72
+ # Run.create(server, workflow) -> run
73
+ #
74
+ # Create a new run in the +Initialized+ state. The run will be created on
75
+ # the server with address supplied by _server_. This can either be a
76
+ # String of the form <tt>http://example.com:8888/blah</tt> or an already
77
+ # created instance of T2Server::Server. The _workflow_ must also be
78
+ # supplied as a string in t2flow or scufl format.
60
79
  def Run.create(server, workflow, uuid="")
61
80
  if server.class == String
62
81
  server = Server.connect(server)
@@ -67,55 +86,130 @@ module T2Server
67
86
  new(server, uuid)
68
87
  end
69
88
  end
70
-
89
+
90
+ # :call-seq:
91
+ # run.delete
92
+ #
93
+ # Delete this run from the server.
71
94
  def delete
72
95
  @server.delete_run uuid
73
96
  end
74
-
97
+
98
+ # :call-seq:
99
+ # run.inputs -> string
100
+ #
101
+ # Return the path to the input ports of this run on the server.
75
102
  def inputs
76
103
  @links[:inputs]
77
104
  end
78
-
105
+
106
+ # :call-seq:
107
+ # run.set_input(input, value) -> bool
108
+ #
109
+ # Set the workflow input port _input_ to _value_.
110
+ #
111
+ # Raises RunStateError if the run is not in the +Initialized+ state.
79
112
  def set_input(input, value)
113
+ state = status
114
+ raise RunStateError.new(state, STATE[:initialized]) if state != STATE[:initialized]
115
+
80
116
  @server.set_run_input(self, input, value)
81
117
  end
82
-
118
+
119
+ # :call-seq:
120
+ # run.set_input_file(input, filename) -> bool
121
+ #
122
+ # Set the workflow input port _input_ to use the file at _filename_ as its
123
+ # input data.
124
+ #
125
+ # Raises RunStateError if the run is not in the +Initialized+ state.
83
126
  def set_input_file(input, filename)
127
+ state = status
128
+ raise RunStateError.new(state, STATE[:initialized]) if state != STATE[:initialized]
129
+
84
130
  @server.set_run_input_file(self, input, filename)
85
131
  end
86
-
132
+
133
+ # :call-seq:
134
+ # run.get_output(output) -> string
135
+ #
136
+ # Return the value of the workflow output port _output_.
137
+ #
138
+ # Raises RunStateError if the run is not in the +Finished+ state.
87
139
  def get_output(output, type="text/plain")
88
- return unless finished? ### raise exception?
140
+ state = status
141
+ raise RunStateError.new(state, STATE[:finished]) if state != STATE[:finished]
142
+
143
+ output.strip_path!
89
144
  doc = @server.get_run_attribute(@uuid, "#{@links[:wdir]}/out/#{output}")
90
145
  doc
91
146
  end
92
-
147
+
148
+ # :call-seq:
149
+ # run.expiry -> string
150
+ #
151
+ # Return the expiry time of this run. It is formatted as an ISO-8601
152
+ # timestamp.
93
153
  def expiry
94
154
  @server.get_run_attribute(@uuid, @links[:expiry])
95
155
  end
96
-
156
+
157
+ # :call-seq:
158
+ # run.expiry=(time) -> bool
159
+ #
160
+ # Set the expiry time of this run to _time_. The format of _time_ should
161
+ # be an ISO-8601 timestamp.
97
162
  def expiry=(date)
98
163
  @server.set_run_attribute(@uuid, @links[:expiry], date)
99
164
  end
100
165
 
166
+ # :call-seq:
167
+ # run.workflow -> string
168
+ #
169
+ # Get the workflow that this run represents.
101
170
  def workflow
102
171
  if @workflow == ""
103
172
  @workflow = @server.get_run_attribute(@uuid, @links[:workflow])
104
173
  end
105
174
  @workflow
106
175
  end
107
-
176
+
177
+ # :call-seq:
178
+ # run.status -> string
179
+ #
180
+ # Get the status of this run.
108
181
  def status
109
182
  @server.get_run_attribute(@uuid, @links[:status])
110
183
  end
111
-
184
+
185
+ # :call-seq:
186
+ # run.start
187
+ #
188
+ # Start this run on the server.
189
+ #
190
+ # Raises RunStateError if the run is not in the +Initialized+ state.
112
191
  def start
192
+ state = status
193
+ raise RunStateError.new(state, STATE[:initialized]) if state != STATE[:initialized]
194
+
113
195
  @server.set_run_attribute(@uuid, @links[:status], STATE[:running])
114
196
  end
115
-
197
+
198
+ # :call-seq:
199
+ # run.wait(params={})
200
+ #
201
+ # Wait (block) for this run to finish. Possible values that can be passed
202
+ # in via _params_ are:
203
+ # * :interval - How often (in seconds) to test for run completion.
204
+ # Default +1+.
205
+ # * :progress - Print a dot (.) each interval to show that something is
206
+ # actually happening. Default +false+.
207
+ #
208
+ # Raises RunStateError if the run is not in the +Running+ state.
116
209
  def wait(params={})
117
- return unless running?
118
-
210
+ state = status
211
+ raise RunStateError.new(state, STATE[:running]) if state != STATE[:running]
212
+
119
213
  interval = params[:interval] || 1
120
214
  progress = params[:progress] || false
121
215
  keepalive = params[:keepalive] || false ### TODO maybe move out of params
@@ -132,42 +226,108 @@ module T2Server
132
226
  # tidy up output if there is any
133
227
  puts if progress
134
228
  end
135
-
229
+
230
+ # :call-seq:
231
+ # run.exitcode -> integer
232
+ #
233
+ # Get the return code of the run. Zero indicates success.
136
234
  def exitcode
137
235
  @server.get_run_attribute(@uuid, @links[:exitcode]).to_i
138
236
  end
139
-
237
+
238
+ # :call-seq:
239
+ # run.stdout -> string
240
+ #
241
+ # Get anything that the run printed to the standard out stream.
140
242
  def stdout
141
243
  @server.get_run_attribute(@uuid, @links[:stdout])
142
244
  end
143
-
245
+
246
+ # :call-seq:
247
+ # run.stderr -> string
248
+ #
249
+ # Get anything that the run printed to the standard error stream.
144
250
  def stderr
145
251
  @server.get_run_attribute(@uuid, @links[:stderr])
146
252
  end
147
-
253
+
254
+ # :call-seq:
255
+ # run.mkdir(dir) -> bool
256
+ #
257
+ # Create a directory in the run's working directory on the server. This
258
+ # could be used to store input data.
148
259
  def mkdir(dir)
149
- @server.make_run_dir(@uuid, @links[:wdir], dir)
260
+ dir.strip_path!
261
+ if dir.include? ?/
262
+ # if a path is given then separate the leaf from the
263
+ # end and add the rest of the path to the wdir link
264
+ leaf = dir.split("/")[-1]
265
+ path = dir[0...-(leaf.length + 1)]
266
+ @server.make_run_dir(@uuid, "#{@links[:wdir]}/#{path}", leaf)
267
+ else
268
+ @server.make_run_dir(@uuid, @links[:wdir], dir)
269
+ end
150
270
  end
151
-
271
+
272
+ # :call-seq:
273
+ # run.upload_file(filename, params={}) -> string
274
+ #
275
+ # Upload a file, with name _filename_, to the server. Possible values that
276
+ # can be passed in via _params_ are:
277
+ # * :dir - The directory to upload to. If this is not left blank the
278
+ # corresponding directory will need to have been created by Run#mkdir.
279
+ # * :rename - Save the file on the server with a different name.
280
+ #
281
+ # The name of the file on the server is returned.
152
282
  def upload_file(filename, params={})
153
283
  location = params[:dir] || ""
154
284
  location = "#{@links[:wdir]}/#{location}"
155
285
  rename = params[:rename] || ""
156
286
  @server.upload_run_file(@uuid, filename, location, rename)
157
287
  end
158
-
288
+
289
+ # :call-seq:
290
+ # run.upload_input_file(input, filename, params={}) -> string
291
+ #
292
+ # Upload a file, with name _filename_, to the server and set it as the
293
+ # input data for input port _input_. Possible values that can be passed
294
+ # in via _params_ are:
295
+ # * :dir - The directory to upload to. If this is not left blank the
296
+ # corresponding directory will need to have been created by Run#mkdir.
297
+ # * :rename - Save the file on the server with a different name.
298
+ #
299
+ # The name of the file on the server is returned.
300
+ #
301
+ # Raises RunStateError if the run is not in the +Initialized+ state.
159
302
  def upload_input_file(input, filename, params={})
303
+ state = status
304
+ raise RunStateError.new(state, STATE[:initialized]) if state != STATE[:initialized]
305
+
160
306
  file = upload_file(filename, params)
161
307
  set_input_file(input, file)
162
308
  end
163
-
309
+
310
+ # :call-seq:
311
+ # run.upload_baclava_file(filename) -> bool
312
+ #
313
+ # Upload a baclava file to be used for the workflow inputs.
164
314
  def upload_baclava_file(filename)
315
+ state = status
316
+ raise RunStateError.new(state, STATE[:initialized]) if state != STATE[:initialized]
317
+
165
318
  @baclava = true
166
319
  rename = upload_file(filename)
167
320
  @server.set_run_attribute(@uuid, @links[:baclava], rename)
168
321
  end
169
322
 
323
+ # :call-seq:
324
+ # run.ls(dir="") -> [[dirs], [objects]]
325
+ #
326
+ # List a directory in the run's workspace on the server. If _dir_ is left
327
+ # blank then / is listed. The contents of a directory are returned as a
328
+ # list of two lists, directories and "objects" respectively.
170
329
  def ls(dir="")
330
+ dir.strip_path!
171
331
  dir_list = @server.get_run_attribute(@uuid, "#{@links[:wdir]}/#{dir}")
172
332
  doc = Document.new(dir_list)
173
333
 
@@ -180,26 +340,50 @@ module T2Server
180
340
  [dirs, files]
181
341
  end
182
342
 
343
+ # :call-seq:
344
+ # run.initialized? -> bool
345
+ #
346
+ # Is this run in the +Initialized+ state?
183
347
  def initialized?
184
348
  status == STATE[:initialized]
185
349
  end
186
-
350
+
351
+ # :call-seq:
352
+ # run.running? -> bool
353
+ #
354
+ # Is this run in the +Running+ state?
187
355
  def running?
188
356
  status == STATE[:running]
189
357
  end
190
-
358
+
359
+ # :call-seq:
360
+ # run.finished? -> bool
361
+ #
362
+ # Is this run in the +Finished+ state?
191
363
  def finished?
192
364
  status == STATE[:finished]
193
365
  end
194
-
366
+
367
+ # :call-seq:
368
+ # run.create_time -> string
369
+ #
370
+ # Get the creation time of this run formatted as an ISO-8601 timestamp.
195
371
  def create_time
196
372
  @server.get_run_attribute(@uuid, @links[:createtime])
197
373
  end
198
-
374
+
375
+ # :call-seq:
376
+ # run.start_time -> string
377
+ #
378
+ # Get the start time of this run formatted as an ISO-8601 timestamp.
199
379
  def start_time
200
380
  @server.get_run_attribute(@uuid, @links[:starttime])
201
381
  end
202
382
 
383
+ # :call-seq:
384
+ # run.finish_time -> string
385
+ #
386
+ # Get the finish time of this run formatted as an ISO-8601 timestamp.
203
387
  def finish_time
204
388
  @server.get_run_attribute(@uuid, @links[:finishtime])
205
389
  end