iron_worker 2.1.0

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.
@@ -0,0 +1,80 @@
1
+ # This is the file that gets executed on the server.
2
+
3
+ def init_database_connection(sw_config)
4
+ if sw_config
5
+ db_config = sw_config['database']
6
+ if db_config
7
+ #@logger.info "Connecting to database using ActiveRecord..."
8
+ require 'active_record'
9
+ ActiveRecord::Base.establish_connection(db_config)
10
+ end
11
+ end
12
+ end
13
+
14
+ def init_mailer(sw_config)
15
+ if sw_config
16
+ mailer_config = sw_config['mailer']
17
+ if mailer_config
18
+ require 'action_mailer'
19
+ ActionMailer::Base.raise_delivery_errors = true
20
+ ActionMailer::Base.smtp_settings = mailer_config
21
+ ActionMailer::Base.delivery_method = :smtp
22
+ end
23
+ end
24
+ end
25
+
26
+ def get_class_to_run(class_name)
27
+ runner_class = constantize(class_name)
28
+ return runner_class
29
+ end
30
+
31
+ # File activesupport/lib/active_support/inflector/methods.rb, line 107
32
+ # Shoutout to the MIT License
33
+ def constantize(camel_cased_word)
34
+ names = camel_cased_word.split('::')
35
+ names.shift if names.empty? || names.first.empty?
36
+
37
+ constant = Object
38
+ names.each do |name|
39
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
40
+ end
41
+ constant
42
+ end
43
+
44
+ def init_runner(runner_class, job_data, user_dir, task_id)
45
+ # ensure initialize takes no arguments
46
+ init_arity = runner_class.instance_method(:initialize).arity
47
+ if init_arity == 0 || init_arity == -1
48
+ # good. -1 can be if it's not defined at all
49
+ else
50
+ raise IronWorker::InvalidWorkerError, "Worker initialize method must accept zero arguments."
51
+ end
52
+ runner = runner_class.new
53
+ runner.instance_variable_set(:@task_id, task_id)
54
+ runner.instance_variable_set(:@job_data, job_data)
55
+ runner.instance_variable_set(:@sw_config, job_data['sw_config'])
56
+ runner.instance_variable_set(:@user_dir, user_dir)
57
+ runner.sw_set_data(job_data)
58
+ runner
59
+ end
60
+
61
+ def init_worker_service_for_runner(job_data)
62
+ IronWorker.configure do |config|
63
+ sw_config = job_data['sw_config']
64
+ config.token = sw_config['token']
65
+ config.project_id = sw_config['project_id']
66
+ config.scheme = sw_config['scheme'] if sw_config['scheme']
67
+ config.host = sw_config['host'] if sw_config['host']
68
+ config.port = sw_config['port'] if sw_config['port']
69
+ db_config = sw_config['database']
70
+ if db_config
71
+ config.database = db_config
72
+ end
73
+ mailer_config = sw_config['mailer']
74
+ if mailer_config && config.respond_to?(:mailer)
75
+ config.mailer = mailer_config
76
+ end
77
+ config.global_attributes = sw_config['global_attributes'] if sw_config['global_attributes']
78
+ end
79
+ end
80
+
@@ -0,0 +1,553 @@
1
+ require 'base64'
2
+ require 'logger'
3
+ require 'zip'
4
+ require 'rest_client'
5
+ require 'json'
6
+
7
+ require_relative 'api'
8
+
9
+ module IronWorker
10
+
11
+ class Service < IronWorker::Api::Client
12
+
13
+ attr_accessor :config
14
+
15
+ def initialize(token, options={})
16
+ if options[:config]
17
+ self.config = options[:config]
18
+ else
19
+ c = IronWorker::Config.new unless self.config
20
+ c.token = token
21
+ self.config = c
22
+ end
23
+ options[:version] = IronWorker.api_version
24
+ options[:logger] = IronWorker.logger
25
+ super("worker-aws-us-east-1.iron.io", token, options)
26
+ self.host = self.config.host if self.config && self.config.host
27
+ # automerge simple worker gem and dependenices
28
+ self.config.merge_gem('rest-client')
29
+ self.config.merge_gem('iron_worker')
30
+ IronWorker.logger.info 'IronWorker initialized.'
31
+ IronWorker.logger.debug ' host = ' + self.host.inspect
32
+ end
33
+
34
+ # Options:
35
+ # - :callback_url
36
+ # - :merge => array of files to merge in with this file
37
+ def upload(filename, class_name, options={})
38
+ name = options[:name] || class_name
39
+ project_id = get_project_id(options)
40
+ tmp = Dir.tmpdir()
41
+ md5file = "iron_worker_#{class_name.gsub("::", ".")}_#{token[0, 8]}.md5"
42
+ existing_md5 = nil
43
+ md5_f = File.join(tmp, md5file)
44
+ if File.exists?(md5_f)
45
+ existing_md5 = IO.read(md5_f)
46
+ end
47
+
48
+ # Check for code changes.
49
+ md5 = Digest::MD5.hexdigest(File.read(filename))
50
+ new_code = false
51
+ if self.config.force_upload || md5 != existing_md5
52
+ IronWorker.logger.info "Uploading #{class_name}, code modified."
53
+ File.open(md5_f, 'w') { |f| f.write(md5) }
54
+ new_code = true
55
+ # todo: delete md5 file if error occurs during upload process
56
+ else
57
+ # puts "#{class_name}: same code, not uploading"
58
+ return
59
+ end
60
+
61
+ begin
62
+
63
+ zip_filename = build_merged_file(filename, options[:merge], options[:unmerge], options[:merged_gems], options[:merged_mailers], options[:merged_folders])
64
+
65
+ if new_code
66
+ upload_code(name, zip_filename, 'runner.rb', :runtime=>'ruby')
67
+ end
68
+
69
+ rescue Exception => ex
70
+ # if it errors, let's delete md5 since it wouldn't have uploaded.
71
+ File.delete(md5_f)
72
+ raise ex
73
+ end
74
+ end
75
+
76
+ def get_server_gems
77
+ hash = get("gems/list")
78
+ JSON.parse(hash["gems"])
79
+ end
80
+
81
+ def logger
82
+ IronWorker.logger
83
+ end
84
+
85
+ def self.get_gem_path(gem_info)
86
+ # gem_name =(gem_info[:require] || gem_info[:name].match(/^[a-zA-Z0-9\-_]+/)[0])
87
+ gem_name =(gem_info[:name].match(/^[a-zA-Z0-9\-_]+/)[0])
88
+ #puts "Searching for #{gem_name}..."
89
+ gems= Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem_name) : Gem::GemPathSearcher.new.find_all(gem_name)
90
+ if (!Gem::GemPathSearcher.private_instance_methods.include?(:_deprecated_initialize)) && (!gems || gems.empty?)
91
+ gems = Gem::GemPathSearcher.new.init_gemspecs.select { |gem| gem.name==gem_name }
92
+ end
93
+ IronWorker.logger.debug 'gems found=' + gems.inspect
94
+ gems = gems.select { |g| g.version.version==gem_info[:version] } if gem_info[:version]
95
+ if !gems.empty?
96
+ gem = gems.last
97
+ gem.full_gem_path
98
+ else
99
+ return nil
100
+ end
101
+ end
102
+
103
+ def build_merged_file(filename, merged, unmerge, merged_gems, merged_mailers, merged_folders)
104
+
105
+ merge = IronWorker.config.merged.dup
106
+ merge.merge!(merged) if merged
107
+ if unmerge
108
+ unmerge.each_pair do |x, y|
109
+ deleted = merge.delete x
110
+ IronWorker.logger.debug "Unmerging #{x}. Success? #{deleted}"
111
+ end
112
+ end
113
+ merged = merge
114
+ IronWorker.logger.debug 'merged=' + merged.inspect
115
+
116
+ merged_gems = merged_gems.merge(IronWorker.config.merged_gems)
117
+ IronWorker.logger.debug 'merged_gems=' + merged_gems.inspect
118
+ IronWorker.config.unmerged_gems.each_pair do |k, v|
119
+ IronWorker.logger.debug 'unmerging gem=' + k.inspect
120
+ merged_gems.delete(k)
121
+ end
122
+ IronWorker.logger.debug 'merged_gems_after=' + merged_gems.inspect
123
+
124
+ merged_mailers ||= {}
125
+ merged_mailers = merged_mailers.merge(IronWorker.config.mailers) if IronWorker.config.mailers
126
+
127
+ #tmp_file = File.join(Dir.tmpdir(), File.basename(filename))
128
+ tmp_file = File.join(Dir.tmpdir(), 'runner.rb')
129
+ File.open(tmp_file, "w") do |f|
130
+
131
+ f.write("
132
+ # Find environment (-e)
133
+ dirname = ''
134
+ i = 0
135
+ task_data_file = nil
136
+ task_id = nil
137
+ #puts \"args for single file=\" + ARGV.inspect
138
+ ARGV.each do |arg|
139
+ if arg == \"-d\"
140
+ # the user's writable directory
141
+ dirname = ARGV[i+1]
142
+ end
143
+ if arg == \"-id\"
144
+ # task_id
145
+ task_id = ARGV[i+1]
146
+ end
147
+ if arg == \"-payload\"
148
+ # path to job data
149
+ task_data_file = ARGV[i+1]
150
+ end
151
+ i+=1
152
+ end
153
+ require 'json'
154
+ ")
155
+ # require merged gems
156
+ merged_gems.each_pair do |k, gem|
157
+ IronWorker.logger.debug "Bundling gem #{gem[:name]}..."
158
+ f.write "$LOAD_PATH << File.join(File.dirname(__FILE__), '/gems/#{gem[:name]}/lib')\n"
159
+ IronWorker.logger.debug 'writing requires: ' + gem[:require].inspect
160
+ if gem[:require].nil?
161
+ gem[:require] = []
162
+ elsif gem[:require].is_a?(String)
163
+ gem[:require] = [gem[:require]]
164
+ end
165
+ IronWorker.logger.debug "gem[:require]: " + gem[:require].inspect
166
+ gem[:require].each do |r|
167
+ IronWorker.logger.debug 'adding require to file ' + r.to_s
168
+ f.write "require '#{r}'\n"
169
+ end
170
+ end
171
+
172
+ File.open(File.join(File.dirname(__FILE__), 'server', 'overrides.rb'), 'r') do |fr|
173
+ while line = fr.gets
174
+ f.write line
175
+ end
176
+ end
177
+
178
+ # Now we must disable queuing while loading up classes. This is from the overrides.rb file
179
+ f.write("
180
+ IronWorker.disable_queueing()
181
+ ")
182
+
183
+
184
+ File.open(File.join(File.dirname(__FILE__), "server", 'runner.rb'), 'r') do |fr|
185
+ while line = fr.gets
186
+ f.write line
187
+ end
188
+ end
189
+
190
+ # load job data
191
+ f.write("
192
+ # Change to user directory
193
+ #puts 'dirname=' + dirname.inspect
194
+ Dir.chdir(dirname)
195
+ # Load in job data
196
+ job_data = JSON.load(File.open(task_data_file))
197
+ puts 'job_data=' + job_data.inspect
198
+ sw_config = job_data['sw_config']
199
+ IronWorker.task_data = job_data
200
+
201
+ if IronWorker.task_data['rails']
202
+ module ::Rails
203
+ def self.version
204
+ IronWorker.task_data['rails']['version']
205
+ end
206
+ def self.env
207
+ IronWorker.task_data['rails']['env']
208
+ end
209
+ end
210
+ end
211
+ ")
212
+
213
+ if IronWorker.config.extra_requires
214
+ IronWorker.config.extra_requires.each do |r|
215
+ f.write "require '#{r}'\n"
216
+ end
217
+ end
218
+ if merged_mailers && !merged_mailers.empty?
219
+ # todo: isn't 'action_mailer already required in railtie?
220
+ f.write "require 'action_mailer'\n"
221
+ f.write "init_mailer(sw_config)\n"
222
+ f.write "ActionMailer::Base.prepend_view_path('templates')\n"
223
+ end
224
+ f.write "init_database_connection(sw_config)\n"
225
+
226
+ merged.each_pair do |k, v|
227
+ if v[:extname] == ".rb"
228
+ f.write "require_relative '#{File.basename(v[:path])}'\n"
229
+ end
230
+ end
231
+ merged_mailers.each_pair do |k, mailer|
232
+ f.write "require_relative '#{mailer[:name]}'\n"
233
+ end
234
+ #end
235
+ #f.write File.open(filename, 'r') { |mo| mo.read }
236
+ f.write("require_relative '#{File.basename(filename)}'\n")
237
+
238
+ f.write("
239
+ runner_class = get_class_to_run(job_data['class_name'])
240
+ IronWorker.running_class = runner_class
241
+ runner = init_runner(runner_class, job_data, dirname, task_id)
242
+ init_worker_service_for_runner(job_data)
243
+
244
+ # Now reenable after loading
245
+ IronWorker.enable_queueing()
246
+
247
+ # Let's run it!
248
+ runner_return_data = runner.run
249
+ ")
250
+
251
+ end
252
+ #puts 'funner.rb=' + tmp_file
253
+ merge['runner.rb'] = {:path=>tmp_file}
254
+ #puts 'filename=' + filename
255
+ merge[File.basename(filename)] = {:path=>filename}
256
+ #puts "merge before uniq! " + merge.inspect
257
+ # puts "merge after uniq! " + merge.inspect
258
+
259
+ fname2 = tmp_file + ".zip"
260
+ # puts 'fname2=' + fname2
261
+ # puts 'merged_file_array=' + merge.inspect
262
+ #File.open(fname2, "w") do |f|
263
+ File.delete(fname2) if File.exist?(fname2)
264
+ Zip::ZipFile.open(fname2, 'w') do |f|
265
+ if merged_gems && merged_gems.size > 0
266
+ merged_gems.each_pair do |k, gem|
267
+ next unless gem[:merge]
268
+ # puts 'gem=' + gem.inspect
269
+ path = gem[:path]
270
+ if path
271
+ IronWorker.logger.debug "Collecting gem #{path}"
272
+ paths_to_use = ["#{path}/*", "#{path}/lib/**/**"]
273
+ if gem[:include_dirs]
274
+ IronWorker.logger.debug "including extra dirs: " + gem[:include_dirs].inspect
275
+ gem[:include_dirs].each do |dir|
276
+ paths_to_use << "#{path}/#{dir}/**/**"
277
+ end
278
+ end
279
+ IronWorker.logger.debug 'paths_to_use: ' + paths_to_use.inspect
280
+ Dir.glob(paths_to_use).each do |file|
281
+ # todo: could check if directory and it not lib, skip it
282
+ IronWorker.logger.debug 'file for gem=' + file.inspect
283
+ # puts 'gem2=' + gem.inspect
284
+ zdest = "gems/#{gem[:name]}/#{file.sub(path+'/', '')}"
285
+ # puts 'gem file=' + file.to_s
286
+ IronWorker.logger.debug 'zip dest=' + zdest
287
+ f.add(zdest, file)
288
+ end
289
+ else
290
+ if gem[:auto_merged]
291
+ # todo: should only continue if the gem was auto merged.
292
+ IronWorker.logger.warn "Gem #{gem[:name]} #{gem[:version]} was not found, continuing anyways."
293
+ else
294
+ raise "Gem #{gem[:name]} #{gem[:version]} was not found. This will occour when gem_name.gemspec is not the same as the gems primary require."
295
+ end
296
+
297
+ end
298
+ end
299
+ end
300
+ if merged_folders && merged_folders.size > 0
301
+ merged_folders.each do |folder, files|
302
+ IronWorker.logger.debug "Collecting folder #{folder}"
303
+ if files and files.size>0
304
+ files.each do |file|
305
+ zdest = "#{Digest::MD5.hexdigest(folder)}/#{file.sub(':', '_').sub('/', '_')}"
306
+ IronWorker.logger.debug 'put file to=' + zdest
307
+ f.add(zdest, file)
308
+ end
309
+ end
310
+ end
311
+ end
312
+
313
+ IronWorker.logger.debug "merge=" + merge.inspect
314
+ merge.each_pair do |k, v|
315
+ IronWorker.logger.debug "merging k=#{k.inspect} v=#{v.inspect} into #{filename}"
316
+ f.add(File.basename(v[:path]), v[:path])
317
+ end
318
+ if merged_mailers && merged_mailers.size > 0
319
+ # puts " MERGED MAILERS" + merged_mailers.inspect
320
+ merged_mailers.each_pair do |k, mailer|
321
+ IronWorker.logger.debug "Collecting mailer #{mailer[:name]}"
322
+ f.add(File.basename(mailer[:filename]), mailer[:filename])
323
+ path = mailer[:path_to_templates]
324
+ Dir["#{path}/**/**"].each do |file|
325
+ zdest = "templates/#{mailer[:name]}/#{file.sub(path+'/', '')}"
326
+ f.add(zdest, file)
327
+ end
328
+ end
329
+ end
330
+ end
331
+ fname2
332
+ end
333
+
334
+ # This will package up files into a zip file ready for uploading.
335
+ def package_code(files)
336
+ fname2 = "package.zip"
337
+ File.delete(fname2) if File.exist?(fname2)
338
+ Zip::ZipFile.open(fname2, 'w') do |f|
339
+ files.each do |file|
340
+ f.add(file, file)
341
+ end
342
+ end
343
+ fname2
344
+ end
345
+
346
+ # options:
347
+ # :runtime => 'ruby', 'python', 'node', 'java', 'go'
348
+ def upload_code(name, package_file, exec_file, options={})
349
+ IronWorker.logger.info 'file size to upload: ' + File.size(package_file).to_s
350
+ options = {
351
+ "name"=>name,
352
+ "standalone"=>true,
353
+ "runtime"=>options[:runtime] || "ruby",
354
+ "file_name"=> exec_file # File.basename(filename)
355
+ }
356
+ #puts 'options for upload=' + options.inspect
357
+ IronWorker.logger.info "Uploading now..."
358
+ ret = post_file("#{project_url_prefix(get_project_id(options))}codes", File.new(package_file), options)
359
+ IronWorker.logger.info "Done uploading."
360
+ return ret
361
+ end
362
+
363
+ def project_url_prefix(project_id = 0)
364
+ # IronWorker.logger.info "project_url_prefix, project_id = " + project_id.inspect
365
+ if project_id == 0
366
+ return false
367
+ project_id = config.project_id
368
+ end
369
+ "projects/#{project_id}/"
370
+ end
371
+
372
+ def wait_until_complete(task_id, options={})
373
+ tries = 0
374
+ status = nil
375
+ sleep 1
376
+ while tries < 100
377
+ status = status(task_id)
378
+ puts "Waiting... status=" + status["status"]
379
+ if status["status"] != "queued" && status["status"] != "running"
380
+ break
381
+ end
382
+ sleep 2
383
+ end
384
+ status
385
+ end
386
+
387
+ def add_sw_params(hash_to_send)
388
+ # todo: remove secret key?? Can use worker service from within a worker without it now
389
+ hash_to_send["oauth"] = self.token
390
+ hash_to_send["api_version"] = IronWorker.api_version
391
+ end
392
+
393
+ def check_config
394
+ if self.config.nil? || self.config.token.nil? || self.config.project_id.nil?
395
+ raise "Invalid IronWorker configuration, token and project_id required."
396
+ end
397
+ end
398
+
399
+ def enqueue(name, data={}, options={})
400
+ queue(name, data, options)
401
+ end
402
+
403
+ # name: The name of previously upload worker code, eg: MySuperWorker
404
+ # data: Arbitrary hash of your own data that your task will need to run.
405
+ def queue(name, data={}, options={})
406
+ puts "Queuing #{name}..."
407
+ check_config
408
+ if !data.is_a?(Array)
409
+ data = [data]
410
+ end
411
+ # Now we need to add class_name to the payload
412
+ tasks = []
413
+ data.each do |d|
414
+ d['class_name'] = name unless d['class_name']
415
+ task = {}
416
+ task["payload"] = d.to_json
417
+ task["code_name"] = name
418
+ task["priority"] = options[:priority] if options[:priority]
419
+ task["timeout"] = options[:timeout] if options[:timeout]
420
+ tasks << task
421
+ end
422
+ name = options[:name] || name
423
+ hash_to_send = {}
424
+ hash_to_send["options"] = options
425
+ hash_to_send["tasks"] = tasks
426
+ add_sw_params(hash_to_send)
427
+ if defined?(RAILS_ENV)
428
+ # todo: REMOVE THIS
429
+ hash_to_send["rails_env"] = RAILS_ENV
430
+ end
431
+ return queue_raw(name, hash_to_send, options)
432
+ end
433
+
434
+ def queue_raw(name, data={}, options={})
435
+ params = nil
436
+ hash_to_send = data
437
+ #hash_to_send["class_name"] = name unless hash_to_send["class_name"]
438
+ hash_to_send["name"] = name unless hash_to_send["name"]
439
+ uri = project_url_prefix(get_project_id(options)) + "tasks"
440
+ IronWorker.logger.debug 'queue_raw , uri = ' + uri
441
+ ret = post(uri, hash_to_send)
442
+ ret
443
+ end
444
+
445
+ #
446
+ # schedule: hash of scheduling options that can include:
447
+ # Required:
448
+ # - start_at: Time of first run - DateTime or Time object.
449
+ # Optional:
450
+ # - run_every: Time in seconds between runs. If ommitted, task will only run once.
451
+ # - delay_type: Fixed Rate or Fixed Delay. Default is fixed_delay.
452
+ # - end_at: Scheduled task will stop running after this date (optional, if ommitted, runs forever or until cancelled)
453
+ # - run_times: Task will run exactly :run_times. For instance if :run_times is 5, then the task will run 5 times.
454
+ #
455
+ def schedule(name, data, schedule)
456
+ puts "Scheduling #{name}..."
457
+ raise "Schedule must be a hash." if !schedule.is_a? Hash
458
+ hash_to_send = {}
459
+ schedules = []
460
+ schedule["payload"] = data.to_json
461
+ schedule["name"] = name unless schedule["name"]
462
+ schedule["code_name"] = name unless schedule["code_name"]
463
+ schedules << schedule
464
+ hash_to_send["schedules"] = schedules
465
+ add_sw_params(hash_to_send)
466
+ # puts ' about to send ' + hash_to_send.inspect
467
+ uri = project_url_prefix(get_project_id(data)) + "schedules"
468
+ ret = post(uri, hash_to_send)
469
+ ret
470
+ end
471
+
472
+ def cancel_schedule(scheduled_task_id, options={})
473
+ raise "Must include a schedule id." if scheduled_task_id.blank?
474
+ hash_to_send = {}
475
+ hash_to_send["schedule_id"] = scheduled_task_id
476
+ uri = "#{project_url_prefix(get_project_id(options))}schedules/#{scheduled_task_id}"
477
+ delete(uri, hash_to_send)
478
+ end
479
+
480
+ def get_projects()
481
+ hash_to_send = {}
482
+ ret = get("projects", hash_to_send)
483
+ ret
484
+ end
485
+
486
+ def get_project_id(options={})
487
+ options[:project_id] || config.project_id
488
+ end
489
+
490
+ def get_project(options={})
491
+ hash_to_send = {}
492
+
493
+ ret = get("projects/"+ get_project_id(options) +"/", hash_to_send)
494
+ #uri = project_url_prefix(id)
495
+ #puts "get_project, uri = " + uri
496
+ #ret = get(uri, hash_to_send)
497
+ ret
498
+ end
499
+
500
+ def get_codes(options={})
501
+ hash_to_send = {}
502
+ uri = "projects/" + get_project_id(options) + "/codes/"
503
+ ret = get(uri, hash_to_send)
504
+ ret
505
+ end
506
+
507
+ def get_schedules(options={})
508
+ hash_to_send = {}
509
+ uri = "projects/" + get_project_id(options) + "/schedules/"
510
+ ret = get(uri, hash_to_send)
511
+ ret
512
+ end
513
+
514
+ def get_jobs(options={})
515
+ hash_to_send = {}
516
+ uri = "projects/" + get_project_id(options) + "/tasks/"
517
+ ret = get(uri, hash_to_send)
518
+ ret
519
+ end
520
+
521
+ def get_log(job_id, options={})
522
+ log(job_id, options)
523
+ end
524
+
525
+ def log(task_id, options={})
526
+ data = {}
527
+ ret = get("#{project_url_prefix(get_project_id(options))}tasks/#{task_id}/log", data, :parse=>false)
528
+ ret
529
+ end
530
+
531
+
532
+ def status(task_id, options={})
533
+ data = {"task_id"=>task_id}
534
+ ret = get("#{project_url_prefix(get_project_id(options))}tasks/#{task_id}", data)
535
+ ret
536
+ end
537
+
538
+ def schedule_status(schedule_id, options={})
539
+ data = {"schedule_id"=>schedule_id}
540
+ ret = get("#{project_url_prefix(get_project_id(options))}schedules/#{schedule_id}", data)
541
+ ret
542
+ end
543
+
544
+ # data is a hash, should include 'percent' and 'msg'
545
+ def set_progress(task_id, options={})
546
+ #data={"data"=>data, "task_id"=>task_id}
547
+ post("#{project_url_prefix(get_project_id(options))}tasks/#{task_id}/progress", options)
548
+ end
549
+
550
+
551
+ end
552
+
553
+ end