arvados-cli 0.1.pre.20130710100340

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/arv +198 -0
  3. data/bin/wh-run-pipeline-instance +538 -0
  4. metadata +131 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffeaddc17c759549a44729941c40ed19396d8633
4
+ data.tar.gz: 5225f2e77dac7c297bbf184497fb142266f5c402
5
+ SHA512:
6
+ metadata.gz: f6b86d6ced3521ea24ccab2d84c26b3615e6a592a2dcbd531e4176e9e391bbea687357348ae9de6006021d9b33838fa00da3c3bde451e66c9b4b2ea57375e16b
7
+ data.tar.gz: eb33bd729c869e18e16ce80b68015499f5b87b2166cd2fb7f357ab94b92ccacd4b7c4d2b0040ee940522a2059ac31dbb86a2d14f6a8d2a728dafce3dc3fe54e9
data/bin/arv ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Arvados cli client
4
+ #
5
+ # Ward Vandewege <ward@clinicalfuture.com>
6
+
7
+ if RUBY_VERSION < '1.9.3' then
8
+ abort <<-EOS
9
+ #{$0.gsub(/^\.\//,'')} requires Ruby version 1.9.3 or higher.
10
+ EOS
11
+ end
12
+
13
+ ENV['ARVADOS_API_VERSION'] ||= 'v1'
14
+
15
+ if not ENV.include?('ARVADOS_API_HOST') or not ENV.include?('ARVADOS_API_TOKEN') then
16
+ abort <<-EOS
17
+ ARVADOS_API_HOST and ARVADOS_API_TOKEN need to be defined as environment variables.
18
+ EOS
19
+ end
20
+
21
+ begin
22
+ require 'rubygems'
23
+ require 'google/api_client'
24
+ require 'json'
25
+ require 'pp'
26
+ require 'trollop'
27
+ require 'andand'
28
+ require 'oj'
29
+ require 'active_support/inflector'
30
+ rescue LoadError
31
+ abort <<-EOS
32
+
33
+ Please install all required gems:
34
+
35
+ gem install google-api-client json trollop andand oj activesupport
36
+
37
+ EOS
38
+ end
39
+
40
+ ActiveSupport::Inflector.inflections do |inflect|
41
+ inflect.irregular 'specimen', 'specimens'
42
+ inflect.irregular 'human', 'humans'
43
+ end
44
+
45
+ module Kernel
46
+ def suppress_warnings
47
+ original_verbosity = $VERBOSE
48
+ $VERBOSE = nil
49
+ result = yield
50
+ $VERBOSE = original_verbosity
51
+ return result
52
+ end
53
+ end
54
+
55
+ # do this if you're testing with a dev server and you don't care about SSL certificate checks:
56
+ if ENV['ARVADOS_API_HOST_INSECURE']
57
+ suppress_warnings { OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE }
58
+ end
59
+
60
+ class Google::APIClient
61
+ def discovery_document(api, version)
62
+ api = api.to_s
63
+ return @discovery_documents["#{api}:#{version}"] ||= (begin
64
+ response = self.execute!(
65
+ :http_method => :get,
66
+ :uri => self.discovery_uri(api, version),
67
+ :authenticated => false
68
+ )
69
+ response.body.class == String ? JSON.parse(response.body) : response.body
70
+ end)
71
+ end
72
+ end
73
+
74
+ client = Google::APIClient.new(:host => ENV['ARVADOS_API_HOST'], :application_name => 'arvados-cli', :application_version => '1.0')
75
+ arvados = client.discovered_api('arvados', ENV['ARVADOS_API_VERSION'])
76
+
77
+ def to_boolean(s)
78
+ !!(s =~ /^(true|t|yes|y|1)$/i)
79
+ end
80
+
81
+ def parse_arguments(discovery_document)
82
+ resource_types = Array.new()
83
+ resource_types << '--help'
84
+ discovery_document["resources"].each do |k,v|
85
+ resource_types << k.singularize
86
+ end
87
+
88
+ global_opts = Trollop::options do
89
+ banner "arvados cli client"
90
+ opt :dry_run, "Don't actually do anything", :short => "-n"
91
+ opt :verbose, "Print some things on stderr", :short => "-v"
92
+ opt :uuid, "Return the UUIDs of the objects in the response, one per line (default)", :short => nil
93
+ opt :json, "Return the entire response received from the API server, as a JSON object", :short => "-j"
94
+ opt :human, "Return the response received from the API server, as a JSON object with whitespace added for human consumption", :short => "-h"
95
+ opt :pretty, "Synonym of --human", :short => nil
96
+ stop_on resource_types
97
+ end
98
+
99
+ # get the subcommand
100
+ resource_arg = ARGV.shift
101
+ if resource_types.include?(resource_arg) and resource_arg != '--help' then
102
+ # subcommand exists
103
+ # Now see if the method supplied exists
104
+ method = ARGV.shift
105
+ if discovery_document["resources"][resource_arg.pluralize]["methods"].include?(method) then
106
+ # method exists. Collect arguments.
107
+ discovered_params = discovery_document["resources"][resource_arg.pluralize]["methods"][method]["parameters"]
108
+ method_opts = Trollop::options do
109
+ discovered_params.each do |k,v|
110
+ opts = Hash.new()
111
+ opts[:type] = v["type"].to_sym if v.include?("type")
112
+ if [:datetime, :text, :object].index opts[:type]
113
+ opts[:type] = :string # else trollop bork
114
+ end
115
+ opts[:default] = v["default"] if v.include?("default")
116
+ opts[:default] = v["default"].to_i if opts[:type] == :integer
117
+ opts[:default] = to_boolean(v["default"]) if opts[:type] == :boolean
118
+ opts[:required] = true if v.include?("required") and v["required"]
119
+ description = ''
120
+ description = ' ' + v["description"] if v.include?("description")
121
+ opt k.to_sym, description, opts
122
+ end
123
+ end
124
+ discovered_params.each do |k,v|
125
+ if v["type"] == "object" and method_opts.has_key? k
126
+ method_opts[k] = JSON.parse method_opts[k]
127
+ end
128
+ end
129
+ else
130
+ banner = "\nThis resource type supports the following methods:\n\n"
131
+ discovery_document["resources"][resource_arg.pluralize]["methods"].each do |k,v|
132
+ description = ''
133
+ description = ' ' + v["description"] if v.include?("description")
134
+ banner += " #{sprintf("%20s",k)}#{description}\n"
135
+ end
136
+ banner += "\n"
137
+
138
+ STDERR.puts banner
139
+
140
+ if not method.nil? and method != '--help' then
141
+ Trollop::die "Unknown method #{method.to_s} for command #{resource_arg.to_s}"
142
+ else
143
+ exit 255
144
+ end
145
+
146
+ end
147
+
148
+ else
149
+ banner = "\nThis Arvados instance supports the following resource types:\n\n"
150
+ discovery_document["resources"].each do |k,v|
151
+ description = ''
152
+ if discovery_document["schemas"].include?(k.singularize.capitalize) and
153
+ discovery_document["schemas"][k.singularize.capitalize].include?('description') then
154
+ description = ' ' + discovery_document["schemas"][k.singularize.capitalize]["description"]
155
+ end
156
+ banner += " #{sprintf("%30s",k.singularize)}#{description}\n"
157
+ end
158
+ banner += "\n"
159
+
160
+ STDERR.puts banner
161
+
162
+ if not resource_arg.nil? and resource_arg != '--help' then
163
+ Trollop::die "Unknown resource type #{resource_arg.inspect}"
164
+ else
165
+ exit 255
166
+ end
167
+ end
168
+
169
+ return resource_arg.pluralize, method, method_opts, global_opts, ARGV
170
+ end
171
+
172
+ controller, method, method_opts, global_opts, remaining_opts = parse_arguments(arvados.discovery_document)
173
+
174
+ api_method = 'arvados.' + controller + '.' + method
175
+
176
+ if global_opts[:dry_run]
177
+ if global_opts[:verbose]
178
+ $stderr.puts "#{api_method} #{method_opts.inspect}"
179
+ end
180
+ exit
181
+ end
182
+
183
+ result = client.execute :api_method => eval(api_method), :parameters => { :api_token => ENV['ARVADOS_API_TOKEN'] }.merge(method_opts), :authenticated => false
184
+ results = JSON.parse result.body
185
+
186
+ if results["errors"] then
187
+ abort "Error: #{results["errors"][0]}"
188
+ end
189
+
190
+ if global_opts[:human] or global_opts[:pretty] then
191
+ puts Oj.dump(results, :indent => 1)
192
+ elsif global_opts[:json] then
193
+ puts Oj.dump(results)
194
+ elsif results["items"] and results["kind"].match /list$/i
195
+ results['items'].each do |i| puts i['uuid'] end
196
+ else
197
+ puts results['uuid']
198
+ end
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # == Synopsis
4
+ #
5
+ # wh-run-pipeline-instance --template pipeline-template-uuid [options] [--] [parameters]
6
+ # wh-run-pipeline-instance --instance pipeline-instance-uuid [options]
7
+ #
8
+ # Satisfy a pipeline template by finding or submitting a mapreduce job
9
+ # for each pipeline component.
10
+ #
11
+ # == Options
12
+ #
13
+ # [--template uuid] Use the specified pipeline template.
14
+ #
15
+ # [--instance uuid] Use the specified pipeline instance.
16
+ #
17
+ # [-n, --dry-run] Do not start any new jobs or wait for existing jobs
18
+ # to finish. Just find out whether jobs are finished,
19
+ # queued, or running for each component
20
+ #
21
+ # [--create-only] Do not try to satisfy any components. Just create an
22
+ # instance, print its UUID to stdout, and exit.
23
+ #
24
+ # [--no-wait] Make only as much progress as possible without entering
25
+ # a sleep/poll loop.
26
+ #
27
+ # [--debug] Print extra debugging information on stderr.
28
+ #
29
+ # [--debug-level N] Increase amount of debugging information. Default
30
+ # 1, possible range 0..3.
31
+ #
32
+ # [--status-text path] Print plain text status report to a file or
33
+ # fifo. Default: /dev/stdout
34
+ #
35
+ # [--status-json path] Print JSON status report to a file or
36
+ # fifo. Default: /dev/null
37
+ #
38
+ # == Parameters
39
+ #
40
+ # [param_name=param_value]
41
+ #
42
+ # [param_name param_value] Set (or override) the default value for
43
+ # every parameter with the given name.
44
+ #
45
+ # [component_name::param_name=param_value]
46
+ # [component_name::param_name param_value]
47
+ # [--component_name::param_name=param_value]
48
+ # [--component_name::param_name param_value] Set the value of a
49
+ # parameter for a single
50
+ # component.
51
+ #
52
+ class WhRunPipelineInstance
53
+ end
54
+
55
+ $application_version = 1.0
56
+
57
+ if RUBY_VERSION < '1.9.3' then
58
+ abort <<-EOS
59
+ #{$0.gsub(/^\.\//,'')} requires Ruby version 1.9.3 or higher.
60
+ EOS
61
+ end
62
+
63
+ $arvados_api_version = ENV['ARVADOS_API_VERSION'] || 'v1'
64
+ $arvados_api_host = ENV['ARVADOS_API_HOST'] or
65
+ abort "#{$0}: fatal: ARVADOS_API_HOST environment variable not set."
66
+ $arvados_api_token = ENV['ARVADOS_API_TOKEN'] or
67
+ abort "#{$0}: fatal: ARVADOS_API_TOKEN environment variable not set."
68
+
69
+ begin
70
+ require 'rubygems'
71
+ require 'google/api_client'
72
+ require 'json'
73
+ require 'pp'
74
+ require 'trollop'
75
+ rescue LoadError
76
+ abort <<-EOS
77
+ #{$0}: fatal: some runtime dependencies are missing.
78
+ Try: gem install pp google-api-client json trollop
79
+ EOS
80
+ end
81
+
82
+ def debuglog(message, verbosity=1)
83
+ $stderr.puts "#{File.split($0).last} #{$$}: #{message}" if $debuglevel >= verbosity
84
+ end
85
+
86
+ module Kernel
87
+ def suppress_warnings
88
+ original_verbosity = $VERBOSE
89
+ $VERBOSE = nil
90
+ result = yield
91
+ $VERBOSE = original_verbosity
92
+ return result
93
+ end
94
+ end
95
+
96
+ if $arvados_api_host.match /local/
97
+ # You probably don't care about SSL certificate checks if you're
98
+ # testing with a dev server.
99
+ suppress_warnings { OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE }
100
+ end
101
+
102
+ class Google::APIClient
103
+ def discovery_document(api, version)
104
+ api = api.to_s
105
+ return @discovery_documents["#{api}:#{version}"] ||=
106
+ begin
107
+ response = self.execute!(
108
+ :http_method => :get,
109
+ :uri => self.discovery_uri(api, version),
110
+ :authenticated => false
111
+ )
112
+ response.body.class == String ? JSON.parse(response.body) : response.body
113
+ end
114
+ end
115
+ end
116
+
117
+
118
+ # Parse command line options (the kind that control the behavior of
119
+ # this program, that is, not the pipeline component parameters).
120
+
121
+ p = Trollop::Parser.new do
122
+ opt(:dry_run,
123
+ "Do not start any new jobs or wait for existing jobs to finish. Just find out whether jobs are finished, queued, or running for each component.",
124
+ :type => :boolean,
125
+ :short => :n)
126
+ opt(:status_text,
127
+ "Store plain text status in given file.",
128
+ :short => :none,
129
+ :type => :string,
130
+ :default => '/dev/stdout')
131
+ opt(:status_json,
132
+ "Store json-formatted pipeline in given file.",
133
+ :short => :none,
134
+ :type => :string,
135
+ :default => '/dev/null')
136
+ opt(:no_wait,
137
+ "Do not wait for jobs to finish. Just look up status, submit new jobs if needed, and exit.",
138
+ :short => :none,
139
+ :type => :boolean)
140
+ opt(:debug,
141
+ "Print extra debugging information on stderr.",
142
+ :type => :boolean)
143
+ opt(:debug_level,
144
+ "Set debug verbosity level.",
145
+ :short => :none,
146
+ :type => :integer)
147
+ opt(:template,
148
+ "UUID of pipeline template.",
149
+ :short => :none,
150
+ :type => :string)
151
+ opt(:instance,
152
+ "UUID of pipeline instance.",
153
+ :short => :none,
154
+ :type => :string)
155
+ opt(:create_only,
156
+ "Do not try to satisfy any components. Just create a pipeline instance and output its UUID.",
157
+ :short => :none,
158
+ :type => :boolean)
159
+ stop_on [:'--']
160
+ end
161
+ $options = Trollop::with_standard_exception_handling p do
162
+ p.parse ARGV
163
+ end
164
+ $debuglevel = $options[:debug_level] || ($options[:debug] && 1) || 0
165
+
166
+ if $options[:instance]
167
+ if $options[:template] or $options[:create_only]
168
+ abort "#{$0}: syntax error: --instance cannot be combined with --template or --create-only."
169
+ end
170
+ elsif not $options[:template]
171
+ abort "#{$0}: syntax error: you must supply a --template or --instance."
172
+ end
173
+
174
+ # Set up the API client.
175
+
176
+ $client ||= Google::APIClient.
177
+ new(:host => $arvados_api_host,
178
+ :application_name => File.split($0).last,
179
+ :application_version => $application_version.to_s)
180
+ $arvados = $client.discovered_api('arvados', $arvados_api_version)
181
+
182
+
183
+ class PipelineInstance
184
+ def self.find(uuid)
185
+ result = $client.execute(:api_method => $arvados.pipeline_instances.get,
186
+ :parameters => {
187
+ :api_token => ENV['ARVADOS_API_TOKEN'],
188
+ :uuid => uuid
189
+ },
190
+ :authenticated => false)
191
+ j = JSON.parse result.body, :symbolize_names => true
192
+ unless j.is_a? Hash and j[:uuid]
193
+ debuglog "Failed to get pipeline_instance: #{j[:errors] rescue nil}", 0
194
+ nil
195
+ else
196
+ debuglog "Retrieved pipeline_instance #{j[:uuid]}"
197
+ self.new(j)
198
+ end
199
+ end
200
+ def self.create(attributes)
201
+ result = $client.execute(:api_method => $arvados.pipeline_instances.create,
202
+ :parameters => {
203
+ :api_token => ENV['ARVADOS_API_TOKEN'],
204
+ :pipeline_instance => attributes.to_json
205
+ },
206
+ :authenticated => false)
207
+ j = JSON.parse result.body, :symbolize_names => true
208
+ unless j.is_a? Hash and j[:uuid]
209
+ abort "Failed to create pipeline_instance: #{j[:errors] rescue nil} #{j.inspect}"
210
+ end
211
+ debuglog "Created pipeline instance: #{j[:uuid]}"
212
+ self.new(j)
213
+ end
214
+ def save
215
+ result = $client.execute(:api_method => $arvados.pipeline_instances.update,
216
+ :parameters => {
217
+ :api_token => ENV['ARVADOS_API_TOKEN'],
218
+ :uuid => @pi[:uuid],
219
+ :pipeline_instance => @attributes_to_update.to_json
220
+ },
221
+ :authenticated => false)
222
+ j = JSON.parse result.body, :symbolize_names => true
223
+ unless j.is_a? Hash and j[:uuid]
224
+ debuglog "Failed to save pipeline_instance: #{j[:errors] rescue nil}", 0
225
+ nil
226
+ else
227
+ @attributes_to_update = {}
228
+ @pi = j
229
+ end
230
+ end
231
+ def []=(x,y)
232
+ @attributes_to_update[x] = y
233
+ @pi[x] = y
234
+ end
235
+ def [](x)
236
+ @pi[x]
237
+ end
238
+ protected
239
+ def initialize(j)
240
+ @attributes_to_update = {}
241
+ @pi = j
242
+ end
243
+ end
244
+
245
+ class JobCache
246
+ def self.get(uuid)
247
+ @cache ||= {}
248
+ result = $client.execute(:api_method => $arvados.jobs.get,
249
+ :parameters => {
250
+ :api_token => ENV['ARVADOS_API_TOKEN'],
251
+ :uuid => uuid
252
+ },
253
+ :authenticated => false)
254
+ @cache[uuid] = JSON.parse result.body, :symbolize_names => true
255
+ end
256
+ def self.where(conditions)
257
+ result = $client.execute(:api_method => $arvados.jobs.list,
258
+ :parameters => {
259
+ :api_token => ENV['ARVADOS_API_TOKEN'],
260
+ :limit => 10000,
261
+ :where => conditions.to_json
262
+ },
263
+ :authenticated => false)
264
+ list = JSON.parse result.body, :symbolize_names => true
265
+ if list and list[:items].is_a? Array
266
+ list[:items]
267
+ else
268
+ []
269
+ end
270
+ end
271
+ def self.create(attributes)
272
+ @cache ||= {}
273
+ result = $client.execute(:api_method => $arvados.jobs.create,
274
+ :parameters => {
275
+ :api_token => ENV['ARVADOS_API_TOKEN'],
276
+ :job => attributes.to_json
277
+ },
278
+ :authenticated => false)
279
+ j = JSON.parse result.body, :symbolize_names => true
280
+ if j.is_a? Hash and j[:uuid]
281
+ @cache[j[:uuid]] = j
282
+ else
283
+ debuglog "create job: #{j[:errors] rescue nil}"
284
+ nil
285
+ end
286
+ end
287
+ end
288
+
289
+ class WhRunPipelineInstance
290
+ attr_reader :instance
291
+
292
+ def initialize(_options)
293
+ @options = _options
294
+ end
295
+
296
+ def fetch_template(template_uuid)
297
+ result = $client.execute(:api_method => $arvados.pipeline_templates.get,
298
+ :parameters => {
299
+ :api_token => ENV['ARVADOS_API_TOKEN'],
300
+ :uuid => template_uuid
301
+ },
302
+ :authenticated => false)
303
+ @template = JSON.parse result.body, :symbolize_names => true
304
+ if !@template[:uuid]
305
+ abort "#{$0}: fatal: failed to retrieve pipeline template #{template_uuid} #{@template[:errors].inspect rescue nil}"
306
+ end
307
+ self
308
+ end
309
+
310
+ def fetch_instance(instance_uuid)
311
+ @instance = PipelineInstance.find(instance_uuid)
312
+ @template = @instance
313
+ self
314
+ end
315
+
316
+ def apply_parameters(params_args)
317
+ params_args.shift if params_args[0] == '--'
318
+ params = {}
319
+ while !params_args.empty?
320
+ if (re = params_args[0].match /^(--)?([^-].*?)=(.)/)
321
+ params[re[2]] = re[3]
322
+ params_args.shift
323
+ elsif params_args.size > 1
324
+ param = params_args.shift.sub /^--/, ''
325
+ params[param] = params_args.shift
326
+ else
327
+ abort "Syntax error: I do not know what to do with arg \"#{params_args[0]}\""
328
+ end
329
+ end
330
+
331
+ @components = @template[:components].dup
332
+
333
+ errors = []
334
+ @components.each do |componentname, component|
335
+ component[:script_parameters].each do |parametername, parameter|
336
+ parameter = { :value => parameter } unless parameter.is_a? Hash
337
+ value =
338
+ (params["#{componentname}::#{parametername}"] ||
339
+ parameter[:value] ||
340
+ (parameter[:output_of].nil? &&
341
+ (params[parametername.to_s] ||
342
+ parameter[:default])) ||
343
+ nil)
344
+ if value.nil? and
345
+ ![false,'false',0,'0'].index parameter[:required]
346
+ if parameter[:output_of]
347
+ next
348
+ end
349
+ errors << [componentname, parametername, "required parameter is missing"]
350
+ end
351
+ debuglog "parameter #{componentname}::#{parametername} == #{value}"
352
+ component[:script_parameters][parametername] = value
353
+ end
354
+ end
355
+ if !errors.empty?
356
+ abort "Errors:\n#{errors.collect { |c,p,e| "#{c}::#{p} - #{e}\n" }.join ""}"
357
+ end
358
+ debuglog "options=" + @options.pretty_inspect
359
+ self
360
+ end
361
+
362
+ def setup_instance
363
+ @instance ||= PipelineInstance.
364
+ create(:components => @components,
365
+ :pipeline_template_uuid => @template[:uuid],
366
+ :active => true)
367
+ self
368
+ end
369
+
370
+ def run
371
+ moretodo = true
372
+ while moretodo
373
+ moretodo = false
374
+ @components.each do |cname, c|
375
+ job = nil
376
+ if !c[:job] and
377
+ c[:script_parameters].select { |pname, p| p.is_a? Hash }.empty?
378
+ # Job is fully specified (all parameter values are present) but
379
+ # no particular job has been found.
380
+
381
+ debuglog "component #{cname} ready to satisfy."
382
+
383
+ c.delete :wait
384
+ second_place_job = nil # satisfies component, but not finished yet
385
+ JobCache.where(:script => c[:script],
386
+ :script_parameters => c[:script_parameters],
387
+ :script_version_descends_from => c[:script_version_descends_from]).
388
+ each do |candidate_job|
389
+ candidate_params_downcase = Hash[candidate_job[:script_parameters].
390
+ map { |k,v| [k.downcase,v] }]
391
+ c_params_downcase = Hash[c[:script_parameters].
392
+ map { |k,v| [k.downcase,v] }]
393
+
394
+ debuglog "component #{cname} considering job #{candidate_job[:uuid]} version #{candidate_job[:script_version]} parameters #{candidate_params_downcase.inspect}", 3
395
+
396
+ unless candidate_params_downcase == c_params_downcase
397
+ next
398
+ end
399
+
400
+ unless candidate_job[:success] || candidate_job[:running] ||
401
+ (!candidate_job[:started_at] && !candidate_job[:cancelled_at])
402
+ debuglog "component #{cname} would be satisfied by job #{candidate_job[:uuid]} if it were running or successful.", 2
403
+ next
404
+ end
405
+
406
+ if candidate_job[:success]
407
+ job = candidate_job
408
+ debuglog "component #{cname} satisfied by job #{job[:uuid]} version #{job[:script_version]}"
409
+ c[:job] = job
410
+ else
411
+ second_place_job ||= candidate_job
412
+ end
413
+ break
414
+ end
415
+ if not c[:job] and second_place_job
416
+ job = second_place_job
417
+ debuglog "component #{cname} satisfied by job #{job[:uuid]} version #{job[:script_version]}"
418
+ c[:job] = job
419
+ end
420
+ if not c[:job]
421
+ debuglog "component #{cname} not satisfied by any existing job."
422
+ if !@options[:dry_run]
423
+ debuglog "component #{cname} new job."
424
+ job = JobCache.create(:script => c[:script],
425
+ :script_parameters => c[:script_parameters],
426
+ :resource_limits => c[:resource_limits] || {},
427
+ :script_version => c[:script_version] || 'master')
428
+ if job
429
+ debuglog "component #{cname} new job #{job[:uuid]}"
430
+ c[:job] = job
431
+ else
432
+ debuglog "component #{cname} new job failed: #{job[:errors]}"
433
+ end
434
+ end
435
+ end
436
+ else
437
+ c[:wait] = true
438
+ end
439
+ if c[:job] and c[:job][:uuid]
440
+ if not c[:job][:finished_at] and not c[:job][:cancelled_at]
441
+ c[:job] = JobCache.get(c[:job][:uuid])
442
+ end
443
+ if c[:job][:success]
444
+ # Populate script_parameters of other components waiting for
445
+ # this job
446
+ @components.each do |c2name, c2|
447
+ c2[:script_parameters].each do |pname, p|
448
+ if p.is_a? Hash and p[:output_of] == cname.to_s
449
+ debuglog "parameter #{c2name}::#{pname} == #{c[:job][:output]}"
450
+ c2[:script_parameters][pname] = c[:job][:output]
451
+ end
452
+ end
453
+ end
454
+ elsif c[:job][:running] ||
455
+ (!c[:job][:started_at] && !c[:job][:cancelled_at])
456
+ moretodo ||= !@options[:no_wait]
457
+ elsif c[:job][:cancelled_at]
458
+ debuglog "component #{cname} job #{c[:job][:uuid]} cancelled."
459
+ end
460
+ end
461
+ end
462
+ @instance[:components] = @components
463
+ @instance[:active] = moretodo
464
+ report_status
465
+ sleep 10 if moretodo
466
+ end
467
+ @instance[:success] = @components.reject { |cname,c| c[:job] and c[:job][:success] }.empty?
468
+ @instance.save
469
+ end
470
+
471
+ def cleanup
472
+ if @instance
473
+ @instance[:active] = false
474
+ @instance.save
475
+ end
476
+ end
477
+
478
+ def uuid
479
+ @instance[:uuid]
480
+ end
481
+
482
+ protected
483
+
484
+ def report_status
485
+ @instance.save
486
+
487
+ if @options[:status_json] != '/dev/null'
488
+ File.open(@options[:status_json], 'w') do |f|
489
+ f.puts @components.pretty_inspect
490
+ end
491
+ end
492
+
493
+ if @options[:status_text] != '/dev/null'
494
+ File.open(@options[:status_text], 'w') do |f|
495
+ f.puts "#{Time.now} -- pipeline_instance #{@instance[:uuid]}"
496
+ namewidth = @components.collect { |cname, c| cname.size }.max
497
+ @components.each do |cname, c|
498
+ jstatus = if !c[:job]
499
+ "-"
500
+ elsif c[:job][:running]
501
+ "#{c[:job][:tasks_summary].inspect}"
502
+ elsif c[:job][:success]
503
+ c[:job][:output]
504
+ elsif c[:job][:cancelled_at]
505
+ "cancelled #{c[:job][:cancelled_at]}"
506
+ elsif c[:job][:finished_at]
507
+ "failed #{c[:job][:finished_at]}"
508
+ elsif c[:job][:started_at]
509
+ "started #{c[:job][:started_at]}"
510
+ else
511
+ "queued #{c[:job][:created_at]}"
512
+ end
513
+ f.puts "#{cname.to_s.ljust namewidth} #{c[:job] ? c[:job][:uuid] : '-'.ljust(27)} #{jstatus}"
514
+ end
515
+ end
516
+ end
517
+ end
518
+ end
519
+
520
+ runner = WhRunPipelineInstance.new($options)
521
+ begin
522
+ if $options[:template]
523
+ runner.fetch_template($options[:template])
524
+ else
525
+ runner.fetch_instance($options[:instance])
526
+ end
527
+ runner.apply_parameters(p.leftovers)
528
+ runner.setup_instance
529
+ if $options[:create_only]
530
+ runner.instance.save
531
+ puts runner.instance[:uuid]
532
+ else
533
+ runner.run
534
+ end
535
+ rescue Exception => e
536
+ runner.cleanup
537
+ raise e
538
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arvados-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.pre.20130710100340
5
+ platform: ruby
6
+ authors:
7
+ - Arvados Authors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: google-api-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.13
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.13
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.7.7
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.7.7
55
+ - !ruby/object:Gem::Dependency
56
+ name: trollop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: andand
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.3
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: oj
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.3
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 2.0.3
97
+ description: This is the Arvados SDK CLI gem, git revision b10a80d5726f25e7284bf4d5f039f1e3fef83b34
98
+ email: gem-dev@clinicalfuture.com
99
+ executables:
100
+ - arv
101
+ - wh-run-pipeline-instance
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - bin/arv
106
+ - bin/wh-run-pipeline-instance
107
+ homepage: http://arvados.org
108
+ licenses:
109
+ - Apache License, Version 2.0
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>'
123
+ - !ruby/object:Gem::Version
124
+ version: 1.3.1
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.0.0
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Arvados SDK CLI
131
+ test_files: []