rbbt-rest 1.8.8 → 1.8.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cff29021a179c10bc6cc157b4b68c167f0d0cbce
4
- data.tar.gz: bd58e81210656647a893ce423b3afacd1959f36a
3
+ metadata.gz: 3efb056fd9e11310126a6cbec3dbdc9fea9ee093
4
+ data.tar.gz: e7fb374a165521846f5e10a2491f8dcceaaef8a5
5
5
  SHA512:
6
- metadata.gz: 3813b05fb8974c6b8ca3141bd0f2eb7108f8e979e68f932a888dc9ce8c9e505c978120037e11e2d9a4087bf2bcd1c21056aff5f95b941c9425b11c1de6d84d6a
7
- data.tar.gz: eb7519b674f9c552f771d6fcd7f4ac75c8a244820a28820c80b5c42742d0b7fb1b0138727fd3a5a23439a88a9430884ac1c62f68279f363f77a277e7eb95814f
6
+ metadata.gz: a0e7197f529598c35dc7f2f0f33931d48cd208205f8b068df899007eaa905cf7550de75255d2e1c052f54b409acc483f5781ddf92a2ecf83003c0dd8217168d9
7
+ data.tar.gz: d73a4246be17d0b5056c80b686ac24776c51192fdfbc4bcaccbbd0360ef0647a374a4810435d104c2dfabf44879554c703f1f30ff0a3e7f549a84ced1eb2c2a1
@@ -109,9 +109,10 @@ module RbbtRESTHelpers
109
109
 
110
110
  pid = @step.child{
111
111
  begin
112
- res = capture_haml &block
112
+ Log.low("Fragment started: #{ fragment_file } - #{Process.pid}")
113
+ res = capture_haml fragment_code, &block
113
114
  Open.write(fragment_file, res)
114
- Log.low("Fragment done: #{ fragment_file }")
115
+ Log.low("Fragment done: #{ fragment_file } - #{Process.pid}")
115
116
  rescue Exception
116
117
  Open.write(fragment_file + '.error', [$!.class.to_s, $!.message] * ": ")
117
118
  Open.write(fragment_file + '.backtrace', $!.backtrace * "\n") if $!.backtrace
@@ -129,7 +130,11 @@ module RbbtRESTHelpers
129
130
  if link.nil?
130
131
  html_tag('a', "", :href => fragment_url, :class => 'fragment', "data-text" => text)
131
132
  else
132
- if link =~ / href=/
133
+ if FalseClass === link
134
+ return fragment_code
135
+ elsif TrueClass === link
136
+ return fragment_url
137
+ elsif link =~ / href=/
133
138
  link = link.sub(/ href=('|")/," href='#{fragment_url}'")
134
139
  else
135
140
  link = link.sub(/<a /,"<a href='#{fragment_url}' ")
@@ -1,6 +1,5 @@
1
1
  require 'rbbt'
2
2
  require 'sinatra/base'
3
- require 'sinatra/streaming'
4
3
 
5
4
 
6
5
  module Sinatra
@@ -8,7 +7,6 @@ module Sinatra
8
7
 
9
8
  def self.registered(base)
10
9
  base.module_eval do
11
- helpers Sinatra::Streaming
12
10
 
13
11
  get '/resource/:resource/get_directory' do
14
12
  directory, resource, create = params.values_at :directory, :resource, :create
@@ -8,8 +8,11 @@ require 'sinatra/base'
8
8
  require 'sinatra/cross_origin'
9
9
  require "sinatra/multi_route"
10
10
  require "sinatra/cookies"
11
+ require 'sinatra/streaming'
11
12
  require 'json'
12
13
 
14
+ require 'nakayoshi_fork'
15
+
13
16
  module Sinatra
14
17
  module RbbtRESTMain
15
18
 
@@ -46,6 +49,7 @@ module Sinatra
46
49
  helpers RbbtRESTHelpers
47
50
  register Sinatra::RbbtAuth
48
51
  helpers Sinatra::Cookies
52
+ helpers Sinatra::Streaming
49
53
 
50
54
  add_sass_load_path Rbbt.share.views.compass.find(:lib)
51
55
 
@@ -66,7 +70,7 @@ module Sinatra
66
70
  if production?
67
71
  set :haml, { :ugly => true }
68
72
  set :clean_trace, true
69
- set :static_cache_control , [:public, {:max_age => 360000}]
73
+ set :static_cache_control , [:public, {:max_age => 360_000}]
70
74
  else
71
75
  set :static_cache_control , [:public, {:max_age => 0}]
72
76
  end
@@ -122,6 +126,7 @@ module Sinatra
122
126
  printer.print(:path => dir, :profile => 'profile')
123
127
  Log.info{ "Profile saved at #{ dir }: #{request.env["REQUEST_URI"]}" }
124
128
  end
129
+
125
130
  response.header["URI"] = request.env["REQUEST_URI"]
126
131
  end
127
132
 
@@ -239,14 +244,11 @@ module Sinatra
239
244
  end
240
245
 
241
246
  require 'rbbt/rest/monitor'
247
+
242
248
  register Sinatra::RbbtRESTMonitor
243
249
  end
244
250
  end
245
251
 
246
252
  end
247
-
248
-
249
- #require 'rack/stream'
250
- #use Rack::Stream
251
253
  end
252
254
 
@@ -46,7 +46,9 @@ module Sinatra
46
46
  workflow_render('tasks', workflow, nil, @clean_params)
47
47
  when :json
48
48
  content_type "application/json"
49
- {:exec => workflow.exec_exports, :synchronous => workflow.synchronous_exports, :asynchronous => workflow.asynchronous_exports}.to_json
49
+
50
+ @can_stream = ENV["RBBT_WORKFLOW_TASK_STREAM"] == 'true'
51
+ {:exec => workflow.exec_exports, :synchronous => workflow.synchronous_exports, :asynchronous => workflow.asynchronous_exports, :can_stream => !!@can_stream}.to_json
50
52
  else
51
53
  raise "Unsupported format specified: #{ format }"
52
54
  end
@@ -63,6 +65,7 @@ module Sinatra
63
65
  raise "Unsupported format specified: #{ format }"
64
66
  end
65
67
  end
68
+
66
69
  get "/#{workflow.to_s}/:task/info" do
67
70
  task = consume_parameter(:task)
68
71
 
@@ -157,7 +160,7 @@ module Sinatra
157
160
  raise RbbtRESTHelpers::Retry
158
161
  end
159
162
  else
160
- halt 404, "Job not found"
163
+ halt 404, "Job not found: #{job.status}"
161
164
  end
162
165
  end
163
166
  rescue RbbtRESTHelpers::Retry
@@ -264,6 +267,15 @@ module Sinatra
264
267
  def self.registered(base)
265
268
  base.module_eval do
266
269
  helpers WorkflowRESTHelpers
270
+
271
+ if ENV["RBBT_WORKFLOW_TASK_STREAM"] == 'true'
272
+ require 'rbbt/rest/workflow/stream_task'
273
+ Log.info "Preparing server for streaming workflow tasks"
274
+ use StreamWorkflowTask if not @can_stream
275
+ @can_stream = true
276
+ else
277
+ @can_stream = false
278
+ end
267
279
  end
268
280
  end
269
281
  end
@@ -77,17 +77,20 @@ module WorkflowRESTHelpers
77
77
  halt 200, result.to_json
78
78
  when :tsv
79
79
  content_type "text/tab-separated-values"
80
- halt 200, result.to_s
80
+ result = result.to_s unless String === result or result.respond_to? :gets
81
+ halt 200, result
81
82
  when :literal, :raw
82
83
  content_type "text/plain"
83
84
  case workflow.task_info(task)[:result_type]
84
85
  when :array
85
86
  halt 200, result * "\n"
86
87
  else
87
- halt 200, result.to_s
88
+ result = result.to_s unless String === result or result.respond_to? :gets
89
+ halt 200, result
88
90
  end
89
91
  when :binary
90
92
  content_type "application/octet-stream"
93
+ result = result.to_s unless String === result or result.respond_to? :gets
91
94
  halt 200, result.to_s
92
95
  when :jobname
93
96
  halt 200, nil
@@ -146,6 +149,21 @@ module WorkflowRESTHelpers
146
149
  end
147
150
  end
148
151
 
152
+ def stream_job(job)
153
+ unless job.started? or job.done?
154
+ job.run(:stream)
155
+ job.soft_grace
156
+ end
157
+
158
+ s = TSV.get_stream job
159
+
160
+ sout, sin = Misc.pipe
161
+
162
+ Misc.consume_stream(s, true, sin)
163
+
164
+ halt 200, sout
165
+ end
166
+
149
167
  def issue_job(workflow, task, jobname = nil, params = {})
150
168
  inputs = prepare_job_inputs(workflow, task, params)
151
169
  job = workflow.job(task, jobname, inputs)
@@ -157,7 +175,15 @@ module WorkflowRESTHelpers
157
175
 
158
176
  case execution_type.to_sym
159
177
  when :exec
160
- show_exec_result job.exec, workflow, task
178
+ show_exec_result job.exec(:stream), workflow, task
179
+ when :stream
180
+ if update == :reload
181
+ job.abort
182
+ job.clean
183
+ end
184
+
185
+ stream_job(job)
186
+
161
187
  when :synchronous, :sync
162
188
  if update == :reload
163
189
  job.abort
@@ -169,11 +195,10 @@ module WorkflowRESTHelpers
169
195
  job_url += "?_format=#{@format}" if @format
170
196
 
171
197
  if not job.started?
172
- job.run true
198
+ job.run
173
199
  job.join
174
200
  end
175
201
 
176
- #halt 200, job.name if format == :jobname
177
202
  if format == :jobname
178
203
  job.name
179
204
  else
@@ -22,7 +22,7 @@ module WorkflowRESTHelpers
22
22
  end
23
23
 
24
24
  if job
25
- locals[:job] = job
25
+ locals[:job] = job
26
26
  @step = job
27
27
  cache_type = execution_type(workflow, task)
28
28
  cache_file = job.file('html')
@@ -0,0 +1,169 @@
1
+ class StreamWorkflowTask
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def read_normal_inputs(io, boundary, stream_input)
7
+ content = ""
8
+ content_start = false
9
+ variable = nil
10
+ filename = nil
11
+ inputs = {}
12
+
13
+ while line = io.gets
14
+ line.chomp!
15
+
16
+ if line == "--" + boundary
17
+ if variable
18
+ inputs[variable] = content
19
+ end
20
+ content_start = false
21
+ content = ""
22
+ elsif line =~ /^Content.* name="([^\s;"]*)"/
23
+ variable = $1
24
+ filename = line.match(/filename="([^"]+)"/)[1] if line =~ /filename/
25
+ content = ""
26
+ elsif line.empty?
27
+ content_start = true
28
+ break if variable == stream_input
29
+ else
30
+ content << line if content_start
31
+ end
32
+ end
33
+
34
+ [inputs, filename]
35
+ end
36
+
37
+ def parse_uri(env)
38
+ uri = env["REQUEST_URI"]
39
+ _n, workflow, task = uri.split("/")
40
+ workflow = begin
41
+ Kernel.const_get(workflow)
42
+ rescue
43
+ raise "Could not accept task for workflow: #{ workflow }"
44
+ end
45
+ [workflow, task]
46
+ end
47
+
48
+ EOL = "\r\n"
49
+ def read_chunk(sin, rest = "")
50
+ parts = []
51
+ c = sin.gets
52
+ c = rest << c unless rest.empty?
53
+ c = c[2..-1] if c[0..1] == EOL
54
+ index = c.index EOL
55
+ while index
56
+ part = c[0..index-1]
57
+ parts << part
58
+ c = c[index+2..-1]
59
+ index = c.index EOL
60
+ end
61
+ rest = c
62
+ [parts, rest]
63
+ end
64
+
65
+
66
+ def copy_chunked_stream(sin, sout, boundary)
67
+
68
+ rest = ""
69
+ done = false
70
+ content = true
71
+
72
+ while not done
73
+ parts, rest = read_chunk(sin, rest)
74
+ while parts.any?
75
+ part = parts.shift
76
+ if content
77
+ part.split("\n").each do |line|
78
+ sout.puts line
79
+ if line.include? boundary
80
+ done = true
81
+ break
82
+ end
83
+ end
84
+ content = false
85
+ else
86
+ content = true
87
+ end
88
+ end
89
+ end
90
+
91
+ sout.write rest
92
+ sout.close
93
+ end
94
+
95
+ def call(env)
96
+ if env["REQUEST_METHOD"] == "POST" and env["rack.hijack"] and env["CONTENT_TYPE"] and env["CONTENT_TYPE"].include? "Rbbt_Param_Stream" and env["HTTP_TRANSFER_ENCODING"] == 'chunked'
97
+ Log.high "Hijacking post data"
98
+ inputs = {}
99
+ content_type = env["CONTENT_TYPE"]
100
+ boundary = content_type.match(/boundary=([^\s;]*)/)[1]
101
+ stream_input = content_type.match(/stream=([^\s;]*)/)[1]
102
+ post_stream = env["rack.hijack"].call
103
+ begin
104
+ inputs, filename = read_normal_inputs(post_stream, boundary, stream_input)
105
+
106
+ input_stream_out, input_stream_in = Misc.pipe
107
+ Misc.add_stream_filename(input_stream_out, filename) if filename
108
+ inputs[stream_input] = input_stream_out
109
+
110
+ workflow, task = parse_uri(env)
111
+ name = inputs.delete "jobname"
112
+ job = workflow.job(task, name, inputs)
113
+
114
+ task = task.to_sym
115
+ execution_type = case
116
+ when workflow.exec_exports.include?(task)
117
+ "exec"
118
+ when workflow.synchronous_exports.include?(task)
119
+ "synchronous"
120
+ when workflow.asynchronous_exports.include?(task)
121
+ "asynchronous"
122
+ else
123
+ raise "No known export type for #{ workflow } #{ task }. Accesses denied"
124
+ end
125
+
126
+ execution_type = "exec" if inputs["_cache_type"] == 'exec'
127
+ Log.info "Streaming task with execution_type: #{ execution_type }"
128
+
129
+ case execution_type
130
+ when "exec", nil
131
+ job.exec(:stream)
132
+ when "sync", "synchronous", "async", "asynchronous"
133
+ job.run(:stream)
134
+ else
135
+ raise "Unknown execution_type: #{execution_type}"
136
+ end
137
+
138
+ t_in = Thread.new do
139
+ begin
140
+ copy_chunked_stream(post_stream, input_stream_in, boundary)
141
+ rescue
142
+ Log.exception $!
143
+ end
144
+ end
145
+
146
+ job_output = TSV.get_stream job
147
+ t_out = Thread.new do
148
+ begin
149
+ post_stream.write "HTTP/1.1 200\r\n\r\n"
150
+ while c = job_output.read(1024)
151
+ post_stream.write c
152
+ end
153
+ job_output.join if job_output.respond_to? :join
154
+ post_stream.close
155
+ rescue
156
+ Log.exception $!
157
+ job.abort
158
+ end
159
+ end
160
+
161
+ end
162
+ [200, {}, nil]
163
+ else
164
+ Log.high "NOT Hijacking post data"
165
+ @app.call(env)
166
+ end
167
+ end
168
+ end
169
+
@@ -7,10 +7,10 @@
7
7
 
8
8
  - if defined? job.info_file and File.exists? job.info_file and request.env["REQUEST_URI"].include? job.name
9
9
  - clean_url = add_GET_param(request.env["REQUEST_URI"], "_update", "clean")
10
- %a.ui.button.blue(href=clean_url) Clean
10
+ %a.ui.button.blue.clean(href=clean_url) Clean
11
11
  - else
12
12
  - clean_url = add_GET_param(request.env["REQUEST_URI"], "_update", "reload")
13
- %a.ui.blue.button(href=clean_url) Reload
13
+ %a.ui.blue.button.reload(href=clean_url) Reload
14
14
 
15
15
  - backtrace = job.info[:backtrace]
16
16
  - if backtrace and backtrace.any?
@@ -1 +1,18 @@
1
- = hash2dl(info)
1
+ - new_info = {}
2
+ - info.each{|k,v| new_info[k] = v unless k.to_s == 'dependencies'}
3
+ = hash2dl(new_info)
4
+
5
+ - if info[:dependencies] and info[:dependencies].any?
6
+ %h3 Dependencies
7
+ %ul
8
+
9
+ - task_exports = workflow.synchronous_exports + workflow.asynchronous_exports
10
+ - task_exports = task_exports.collect{|t| t.to_s}
11
+ - info[:dependencies].each do |task,name,path|
12
+ - url = "/" + [workflow, task, name] * "/"
13
+ %li
14
+ (#{workflow} #{task})
15
+ - if task_exports.include? task.to_s
16
+ %a(href=url) #{ name }
17
+ - else
18
+ = name
@@ -6,7 +6,7 @@
6
6
  %a.ui.small.button(href="/#{[workflow.to_s, task, jobname, "info"] * "/"}") job info
7
7
  %a.ui.small.button(href="/#{[workflow.to_s, task, jobname] * "/"}?_format=raw") get raw result file
8
8
 
9
- - files = job.files - ["html", "html.info"]
9
+ - files = job.files - ["html", "html.files", "html.info", "html.pid"]
10
10
  - if files.any?
11
11
  %dt.ui.header Files
12
12
  %dd.job_files
@@ -115,7 +115,8 @@ function replace_object(object, href, embedd, complete){
115
115
  error_message = error.sub(/\n\n.*/,'')
116
116
 
117
117
  error_span = $('<span>').html(error_message).addClass('error')
118
- error = $('<div>').append(error_span)
118
+ error = $('<div>') //.append(error_span)
119
+ error.append(req.responseText)
119
120
  object.removeClass(_reloading_class).addClass("error").css('height', 0).html(error).css('height', 'auto').attr('target-href', clean_href);
120
121
  },
121
122
 
@@ -287,6 +287,10 @@ $.widget("rbbt.action_controller", {
287
287
  action_controller.addClass('loading');
288
288
 
289
289
  replace_object(action_div, href, true, this.options.complete);
290
+ var tool = this
291
+ action_div.on('click', '.reload', function(e){
292
+ update_embedded(action_div, true, tool.options.complete); return false
293
+ });
290
294
 
291
295
  return false
292
296
  }
@@ -1,5 +1,8 @@
1
1
  - @reload_page = true
2
2
 
3
+ :sass
4
+ span.progress_time
5
+ color: grey
3
6
  .wait.ui.segment.info.message
4
7
  %h3.ui.header Waiting on #{format_name File.basename(job.name)}
5
8
 
@@ -10,10 +13,29 @@
10
13
  .footer.ui.basic.segment
11
14
  - progress = job.file(:progress).yaml
12
15
  - if progress[:last_percent]
16
+ - start = progress[:start]
17
+ - last_time = progress[:last_time]
18
+ - if progress[:last_percent].to_i > 0
19
+ - ellapsed = last_time - start
20
+ - if progress[:mean]
21
+ - missing = progress[:max].to_f - progress[:ticks].to_f
22
+ - eta = missing / progress[:mean].to_f
23
+ - else
24
+ - ratio = progress[:ticks].to_f/progress[:max].to_f
25
+ - eta = ellapsed * (1 - ratio)
26
+ - eta = Misc.format_seconds(eta)
27
+ - ellapsed = Misc.format_seconds(ellapsed)
28
+ - else
29
+ - ellapsed = '?'
30
+ - eta = '?'
13
31
  .ui.teal.step.percent.progress.indicating.meta(data-percent="#{progress[:last_percent]}" data-total=100)
14
32
  .bar
15
33
  .progress
16
- .label= progress[:desc]
34
+ .label
35
+ = progress[:desc]
36
+ - if progress[:last_percent].to_i > 0
37
+ %span.progress_time
38
+ (#{ eta } &#8658; #{ellapsed})
17
39
  :deferjs
18
40
  $('.step.progress').progress()
19
41
  - else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbbt-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.8
4
+ version: 1.8.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-22 00:00:00.000000000 Z
11
+ date: 2016-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -196,6 +196,7 @@ files:
196
196
  - lib/rbbt/rest/workflow/jobs.rb
197
197
  - lib/rbbt/rest/workflow/locate.rb
198
198
  - lib/rbbt/rest/workflow/render.rb
199
+ - lib/rbbt/rest/workflow/stream_task.rb
199
200
  - share/views/association/Default.haml
200
201
  - share/views/compass/app.sass
201
202
  - share/views/compass/card_templates.sass