sidejob 3.0.1 → 4.0.1

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.
@@ -34,7 +34,6 @@ describe SideJob::ServerMiddleware do
34
34
  worker = msg.klass.constantize.new
35
35
  worker.jid = job.id
36
36
  chain.invoke(worker, msg, @queue) { yield worker }
37
- job.reload
38
37
  worker
39
38
  end
40
39
 
@@ -67,10 +66,7 @@ describe SideJob::ServerMiddleware do
67
66
  @job.status = 'suspended'
68
67
  child = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
69
68
  expect(@job.status).to eq 'suspended'
70
- expect {
71
- child.run_inline
72
- }.to change {Sidekiq::Stats.new.enqueued}.by(1)
73
- @job.reload
69
+ child.run_inline
74
70
  expect(@job.status).to eq 'queued'
75
71
  end
76
72
 
@@ -84,22 +80,27 @@ describe SideJob::ServerMiddleware do
84
80
  end
85
81
 
86
82
  describe 'prevents multiple threads running the same job' do
87
- it 'sets the job lock to the current time' do
88
- now = Time.now
89
- allow(Time).to receive(:now) { now }
90
- process(@job) { @lock = SideJob.redis.get("#{@job.redis_key}:lock").to_f }
91
- expect(@lock).to eq(now.to_f)
83
+ it 'does not run if the worker lock is set' do
84
+ SideJob.redis.set "#{@job.redis_key}:lock:worker", 1
85
+ @run = false
86
+ process(@job) { @run = true }
87
+ expect(@run).to be false
88
+ expect(SideJob.redis.exists("#{@job.redis_key}:lock:worker"))
89
+ end
90
+
91
+ it 'obtains and releases a lock' do
92
+ process(@job) { @lock = SideJob.redis.get("#{@job.redis_key}:lock") }
93
+ expect(@lock).to_not be nil
92
94
  expect(SideJob.redis.exists("#{@job.redis_key}:lock")).to be false
93
95
  end
94
96
 
95
- it 'sets the job lock to the current time and does not run if already locked' do
96
- now = Time.now
97
- allow(Time).to receive(:now) { now }
97
+ it 'does not run if the job is locked' do
98
+ token = @job.lock(100)
98
99
  @run = false
99
- SideJob.redis.set "#{@job.redis_key}:lock", (now-10).to_f
100
100
  process(@job) { @run = true }
101
101
  expect(@run).to be false
102
- expect(SideJob.redis.get("#{@job.redis_key}:lock").to_f).to eq now.to_f
102
+ expect(SideJob.redis.exists("#{@job.redis_key}:lock"))
103
+ expect(@job.unlock(token)).to be true
103
104
  end
104
105
 
105
106
  it 'does not restart the worker unless another worker was locked out during the run' do
@@ -109,10 +110,11 @@ describe SideJob::ServerMiddleware do
109
110
  expect(@job.status).to eq 'completed'
110
111
  end
111
112
 
112
- it 'restarts the worker if another worker was locked out during the run' do
113
+ it 'requeues the worker if it was locked out during the run' do
114
+ token = @job.lock(100)
113
115
  expect {
114
- process(@job) { SideJob.redis.set "#{@job.redis_key}:lock", Time.now.to_f }
115
- }.to change {Sidekiq::Stats.new.enqueued}.by(1)
116
+ process(@job) { }
117
+ }.to change {Sidekiq::Stats.new.scheduled_size}.by(1)
116
118
  expect(@job.status).to eq 'queued'
117
119
  end
118
120
  end
@@ -122,7 +124,7 @@ describe SideJob::ServerMiddleware do
122
124
  now = Time.now
123
125
  allow(Time).to receive(:now) { now }
124
126
  key = "#{@job.redis_key}:rate:#{Time.now.to_i/60}"
125
- SideJob.redis.set key, SideJob::ServerMiddleware::CONFIGURATION[:max_runs_per_minute]
127
+ SideJob.redis.set key, SideJob::CONFIGURATION[:max_runs_per_minute]
126
128
  @run = false
127
129
  process(@job) { @run = true }
128
130
  expect(@run).to be false
@@ -133,27 +135,7 @@ describe SideJob::ServerMiddleware do
133
135
  now = Time.now
134
136
  allow(Time).to receive(:now) { now }
135
137
  key = "#{@job.redis_key}:rate:#{Time.now.to_i/60}"
136
- SideJob.redis.set key, SideJob::ServerMiddleware::CONFIGURATION[:max_runs_per_minute]-1
137
- @run = false
138
- process(@job) { @run = true }
139
- expect(@run).to be true
140
- expect(@job.status).to eq 'completed'
141
- end
142
-
143
- it 'does not run if job is too deep' do
144
- (SideJob::ServerMiddleware::CONFIGURATION[:max_depth]+1).times do |i|
145
- @job = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
146
- end
147
- @run = false
148
- process(@job) { @run = true }
149
- expect(@run).to be false
150
- expect(@job.status).to eq 'terminating'
151
- end
152
-
153
- it 'does run if job is not too deep' do
154
- SideJob::ServerMiddleware::CONFIGURATION[:max_depth].times do |i|
155
- @job = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
156
- end
138
+ SideJob.redis.set key, SideJob::CONFIGURATION[:max_runs_per_minute]-1
157
139
  @run = false
158
140
  process(@job) { @run = true }
159
141
  expect(@run).to be true
@@ -176,21 +158,19 @@ describe SideJob::ServerMiddleware do
176
158
  expect(log[0]['backtrace']).to_not match(/sidekiq/)
177
159
  end
178
160
 
179
- it 'does not set status to failed if status is not running' do
161
+ it 'does not set status to failed if status is terminating' do
180
162
  process(@job) do |worker|
181
- worker.run
163
+ worker.terminate
182
164
  raise 'oops'
183
165
  end
184
- expect(@job.status).to eq 'queued'
166
+ expect(@job.status).to eq 'terminating'
185
167
  end
186
168
 
187
169
  it 'runs the parent job' do
188
170
  @job.status = 'suspended'
189
171
  child = SideJob.queue(@queue, 'TestWorker', parent: @job, name: 'child')
190
- expect {
191
- process(child) { raise 'oops' }
192
- }.to change {Sidekiq::Stats.new.enqueued}.by(1)
193
- @job.reload
172
+ expect(@job.status).to eq 'suspended'
173
+ process(child) { raise 'oops' }
194
174
  expect(@job.status).to eq 'queued'
195
175
  end
196
176
  end
@@ -5,7 +5,6 @@ describe SideJob::Worker do
5
5
  @job = SideJob.queue('testq', 'TestWorker', inports: {
6
6
  in1: {},
7
7
  in2: {},
8
- memory: { mode: :memory },
9
8
  default: { default: 'default' },
10
9
  default_null: { default: nil },
11
10
  }, outports: {out1: {}})
@@ -62,33 +61,6 @@ describe SideJob::Worker do
62
61
  expect { @worker.suspend }.to raise_error(SideJob::Worker::Suspended)
63
62
  end
64
63
 
65
- describe '#queue' do
66
- it 'can queue child jobs' do
67
- expect(SideJob).to receive(:queue).with('testq', 'TestWorker', args: [1,2], inports: {'myport' => {'mode' => 'memory'}}, parent: @job, name: 'child', by: "job:#{@worker.id}").and_call_original
68
- expect {
69
- child = @worker.queue('testq', 'TestWorker', args: [1,2], inports: {'myport' => {'mode' => 'memory'}}, name: 'child')
70
- expect(child.parent).to eq(@job)
71
- expect(@job.children).to eq('child' => child)
72
- }.to change {Sidekiq::Stats.new.enqueued}.by(1)
73
- end
74
-
75
- it 'queues with by string set to self' do
76
- child = @worker.queue('testq', 'TestWorker', name: 'child')
77
- expect(child.get(:created_by)).to eq "job:#{@worker.id}"
78
- end
79
-
80
- it 'groups initial port data' do
81
- now = Time.now
82
- allow(Time).to receive(:now) { now }
83
- child = @worker.queue('testq', 'TestWorker', name: 'child', inports: {'inport1' => {data: [1,2]}}, outports: {'outport1' => {data: [3,4]}})
84
- expect(SideJob.logs).to eq [{'timestamp' => SideJob.timestamp, 'job' => @worker.id,
85
- 'read' => [],
86
- 'write' => [{'job' => child.id, 'inport' => 'inport1', 'data' => [1,2]},
87
- {'job' => child.id, 'outport' => 'outport1', 'data' => [3,4]},
88
- ]}]
89
- end
90
- end
91
-
92
64
  describe '#for_inputs' do
93
65
  it 'does nothing if no ports provided' do
94
66
  expect {|block| @worker.for_inputs(&block)}.not_to yield_control
@@ -113,8 +85,8 @@ describe SideJob::Worker do
113
85
  @worker.for_inputs(:in1, :in2) do |in1, in2|
114
86
  @worker.output(:out1).write [in1, in2[0]]
115
87
  end
116
- expect(SideJob.logs).to eq([{'timestamp' => SideJob.timestamp, 'job' => @job.id, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [1]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['a', 'b']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[1, 'a']]}]},
117
- {'timestamp' => SideJob.timestamp, 'job' => @job.id, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [2]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['c', 'd']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[2, 'c']]}]},
88
+ expect(SideJob.logs).to eq([{'timestamp' => SideJob.timestamp, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [1]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['a', 'b']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[1, 'a']]}]},
89
+ {'timestamp' => SideJob.timestamp, 'read' => [{'job' => @job.id, 'inport' => 'in1', 'data' => [2]}, {'job' => @job.id, 'inport' => 'in2', 'data' => [['c', 'd']]}], 'write' => [{'job' => @job.id, 'outport' => 'out1', 'data' => [[2, 'c']]}]},
118
90
  ])
119
91
  end
120
92
 
@@ -127,16 +99,14 @@ describe SideJob::Worker do
127
99
  }.to raise_error(SideJob::Worker::Suspended)
128
100
  end
129
101
 
130
- it 'returns data from memory input ports' do
131
- @job.input(:memory).write 1
102
+ it 'returns default values from ports' do
132
103
  @job.input(:in2).write [2, 3]
133
104
  @job.input(:in2).write 3
134
- expect {|block| @worker.for_inputs(:memory, :in2, &block)}.to yield_successive_args([1, [2,3]], [1, 3])
105
+ expect {|block| @worker.for_inputs(:default, :in2, &block)}.to yield_successive_args(['default', [2,3]], ['default', 3])
135
106
  end
136
107
 
137
- it 'does not suspend if there is only data on memory port' do
138
- @job.input(:memory).write 1
139
- expect {|block| @worker.for_inputs(:memory, :in2, &block)}.not_to yield_control
108
+ it 'does not yield if there are only default values' do
109
+ expect {|block| @worker.for_inputs(:default, :in2, &block)}.not_to yield_control
140
110
  end
141
111
 
142
112
  it 'allows for null default values' do
@@ -146,56 +116,18 @@ describe SideJob::Worker do
146
116
  expect {|block| @worker.for_inputs(:default_null, :in2, &block)}.to yield_successive_args([1, [2,3]], [nil, 3])
147
117
  end
148
118
 
149
- it 'raises error if all ports have defaults' do
150
- @job.input(:memory).write true
151
- expect {|block| @worker.for_inputs(:memory, :default, &block)}.to raise_error
152
- end
153
- end
154
-
155
- describe '#set' do
156
- it 'can save state in redis' do
157
- @worker.set(test: 'data', test2: 123)
158
- state = JSON.parse(SideJob.redis.hget('jobs', @worker.id))
159
- expect(state['test']).to eq 'data'
160
- expect(state['test2']).to eq 123
161
-
162
- # test updating
163
- @worker.set(test: 'data2')
164
- state = JSON.parse(SideJob.redis.hget('jobs', @worker.id))
165
- expect(state['test']).to eq 'data2'
166
- end
167
-
168
- it 'can update values' do
169
- 3.times do |i|
170
- @worker.set key: i
171
- @worker.reload
172
- expect(@worker.get(:key)).to eq i
173
- state = JSON.parse(SideJob.redis.hget('jobs', @worker.id))
174
- expect(state['key']).to eq i
175
- end
176
- end
177
-
178
- it 'raises error if job no longer exists' do
179
- @worker.status = 'terminated'
180
- SideJob.find(@worker.id).delete
181
- expect { @worker.set key: 123 }.to raise_error
182
- end
183
- end
184
-
185
- describe '#unset' do
186
- it 'unsets fields' do
187
- @worker.set(a: 123, b: 456, c: 789)
188
- @worker.unset('a', :b)
189
- expect(@worker.get(:a)).to eq nil
190
- expect(@worker.get(:b)).to eq nil
191
- expect(@worker.get(:c)).to eq 789
119
+ it 'sets output default value for ports written in block if all inputs are default' do
120
+ expect(@job.output(:out1).default).to be SideJob::Port::None
121
+ @worker.for_inputs(:default, :default_null) { @job.output(:out1).write 1234 }
122
+ expect(@job.output(:out1).default).to eq 1234
192
123
  end
193
124
 
194
- it 'raises error if job no longer exists' do
195
- @worker.status = 'terminated'
196
- @worker.set a: 123
197
- SideJob.find(@worker.id).delete
198
- expect { @worker.unset(:a) }.to raise_error
125
+ it 'yields exactly once until port defaults change' do
126
+ expect {|block| @worker.for_inputs(:default, :default_null, &block)}.to yield_successive_args(['default', nil])
127
+ expect {|block| @worker.for_inputs(:default, :default_null, &block)}.not_to yield_control
128
+ @job.input(:default).default = 'new'
129
+ expect {|block| @worker.for_inputs(:default, :default_null, &block)}.to yield_successive_args(['new', nil])
130
+ expect {|block| @worker.for_inputs(:default, :default_null, &block)}.not_to yield_control
199
131
  end
200
132
  end
201
133
  end
data/spec/sidejob_spec.rb CHANGED
@@ -45,9 +45,9 @@ describe SideJob do
45
45
 
46
46
  it 'generates an incrementing job id from 1' do
47
47
  job = SideJob.queue('testq', 'TestWorker')
48
- expect(job.id).to eq('1')
48
+ expect(job.id).to be 1
49
49
  job = SideJob.queue('testq', 'TestWorker')
50
- expect(job.id).to eq('2')
50
+ expect(job.id).to be 2
51
51
  end
52
52
 
53
53
  it 'stores created at timestamp' do
@@ -72,7 +72,6 @@ describe SideJob do
72
72
  expect(job.status).to eq 'queued'
73
73
  expect(job.parent).to eq(parent)
74
74
  expect(parent.child('child1')).to eq job
75
- expect(SideJob.redis.lrange("#{job.redis_key}:ancestors", 0, -1)).to eq([parent.id])
76
75
  }.to change {Sidekiq::Stats.new.enqueued}.by(2)
77
76
  end
78
77
 
@@ -87,25 +86,12 @@ describe SideJob do
87
86
  expect { SideJob.queue('testq', 'TestWorker', parent: parent, name: 'child') }.to raise_error
88
87
  end
89
88
 
90
- it 'sets ancestor tree correctly' do
91
- j1 = SideJob.queue('testq', 'TestWorker')
92
- j2 = SideJob.queue('testq', 'TestWorker', parent: j1, name: 'child1')
93
- j3 = SideJob.queue('testq', 'TestWorker', parent: j2, name: 'child1')
94
- expect(SideJob.redis.lrange("#{j3.redis_key}:ancestors", 0, -1)).to eq([j2.id, j1.id])
95
- end
96
-
97
89
  it 'can add a port via inports configuration' do
98
90
  job = SideJob.queue('testq', 'TestWorker', inports: {myport: {default: [1,2]}})
99
91
  expect(job.status).to eq 'queued'
100
92
  expect(job.input(:myport).read).to eq [1, 2]
101
93
  end
102
94
 
103
- it 'can set the port mode via inports configuration' do
104
- job = SideJob.queue('testq', 'TestWorker', inports: {queue: {mode: 'queue'}})
105
- expect(job.status).to eq 'queued'
106
- expect(job.input(:queue).mode).to be :queue
107
- end
108
-
109
95
  it 'can add a port via outports configuration' do
110
96
  job = SideJob.queue('testq', 'TestWorker', outports: {myport: {}})
111
97
  expect(job.status).to eq 'queued'
@@ -179,4 +165,41 @@ describe SideJob do
179
165
  {'xyz' => 456, 'timestamp' => SideJob.timestamp},])
180
166
  end
181
167
  end
168
+
169
+ describe '.log_context' do
170
+ before do
171
+ now = Time.now
172
+ allow(Time).to receive(:now) { now }
173
+ end
174
+
175
+ it 'adds metadata to logs within the group' do
176
+ SideJob.log_context(data1: 1, data2: 2) do
177
+ SideJob.log({abc: 123})
178
+ expect(SideJob.logs).to eq([{'data1' => 1, 'data2' => 2, 'abc' => 123, 'timestamp' => SideJob.timestamp}])
179
+ end
180
+ end
181
+
182
+ it 'does not add metadata to logs outside of the group' do
183
+ SideJob.log_context(data1: 1, data2: 2) {}
184
+ SideJob.log({abc: 123})
185
+ expect(SideJob.logs).to eq([{'abc' => 123, 'timestamp' => SideJob.timestamp}])
186
+ end
187
+
188
+ it 'can be nested' do
189
+ SideJob.log_context(data1: 1) do
190
+ SideJob.log({x: 1})
191
+ SideJob.log_context(data2: 2) do
192
+ SideJob.log({x: 2})
193
+ end
194
+ SideJob.log({x: 3})
195
+ end
196
+ SideJob.log({x: 4})
197
+
198
+ expect(SideJob.logs).to eq([{'data1' => 1, 'timestamp' => SideJob.timestamp, 'x' => 1},
199
+ {'data1' => 1, 'data2' => 2, 'timestamp' => SideJob.timestamp, 'x' => 2},
200
+ {'data1' => 1, 'timestamp' => SideJob.timestamp, 'x' => 3},
201
+ {'timestamp' => SideJob.timestamp, 'x' => 4},
202
+ ])
203
+ end
204
+ end
182
205
  end
data/web/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sidejob', path: '..'
4
+ gem 'sinatra'
5
+ gem 'rack-cors'
6
+ gem 'puma'
data/web/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ sidejob (4.0.1)
5
+ sidekiq (~> 3.2.5)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ celluloid (0.15.2)
11
+ timers (~> 1.1.0)
12
+ connection_pool (2.2.0)
13
+ json (1.8.2)
14
+ puma (2.10.2)
15
+ rack (>= 1.1, < 2.0)
16
+ rack (1.6.0)
17
+ rack-cors (0.2.9)
18
+ rack-protection (1.5.3)
19
+ rack
20
+ redis (3.2.1)
21
+ redis-namespace (1.5.2)
22
+ redis (~> 3.0, >= 3.0.4)
23
+ sidekiq (3.2.6)
24
+ celluloid (= 0.15.2)
25
+ connection_pool (>= 2.0.0)
26
+ json
27
+ redis (>= 3.0.6)
28
+ redis-namespace (>= 1.3.1)
29
+ sinatra (1.4.5)
30
+ rack (~> 1.4)
31
+ rack-protection (~> 1.4)
32
+ tilt (~> 1.3, >= 1.3.4)
33
+ tilt (1.4.1)
34
+ timers (1.1.0)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ puma
41
+ rack-cors
42
+ sidejob!
43
+ sinatra
data/web/app.rb ADDED
@@ -0,0 +1,205 @@
1
+ require 'sinatra'
2
+ require 'sidejob'
3
+
4
+ # Provide a limited web API to SideJob methods
5
+ class SideJob::Web < Sinatra::Base
6
+ before do
7
+ content_type :json
8
+ end
9
+
10
+ # for CORS
11
+ options '/' do
12
+ 200
13
+ end
14
+
15
+ # provide some limited info for now
16
+ get '/' do
17
+ { version: '1.0' }.to_json
18
+ end
19
+
20
+ # queue a new job
21
+ post '/jobs' do
22
+ api_call do |params|
23
+ queue = params['queue'] or return { error: 'Missing queue' }
24
+ klass = params['class'] or return { error: 'Missing class' }
25
+
26
+ job = SideJob.queue(queue, klass, args: params['args'],
27
+ parent: SideJob.find(params['parent']), name: params['name'],
28
+ by: params['by'], inports: params['inports'], outports: params['outports'])
29
+ { job: job.id }
30
+ end
31
+ end
32
+
33
+ # gets job state and port info
34
+ get '/jobs/:job' do
35
+ job_api do |job, params|
36
+ { job: job.id, state: job.state, inports: ports_info(job.inports), outports: ports_info(job.outports) }
37
+ end
38
+ end
39
+
40
+ # sets job state
41
+ post '/jobs/:job/state' do
42
+ job_api do |job, params|
43
+ job.set(params)
44
+ { job: job.id, state: job.state }
45
+ end
46
+ end
47
+
48
+ # delete a job
49
+ delete '/jobs/:job' do
50
+ job_api do |job, params|
51
+ { job: job.id, delete: job.delete }
52
+ end
53
+ end
54
+
55
+ # set job ports
56
+ post '/jobs/:job/ports' do
57
+ job_api do |job, params|
58
+ job.inports = params['inports'] if params['inports']
59
+ job.outports = params['outports'] if params['outports']
60
+ nil
61
+ end
62
+ end
63
+
64
+ # port operations
65
+ post '/jobs/:job/*ports/:port/:operation' do |job_id, type, port_name, operation|
66
+ job_api do |job, params|
67
+ case type
68
+ when 'in'
69
+ port = job.input(port_name)
70
+ when 'out'
71
+ port = job.output(port_name)
72
+ else
73
+ raise 'Invalid port type'
74
+ end
75
+
76
+ case operation
77
+ # read one value
78
+ when 'read'
79
+ data = port.read
80
+ if data == SideJob::Port::None
81
+ {}
82
+ else
83
+ { data: data }
84
+ end
85
+
86
+ # read all and return an array of data
87
+ when 'entries'
88
+ { entries: port.entries }
89
+
90
+ # port write
91
+ when 'write'
92
+ if params['list']
93
+ list = params['list']
94
+ else
95
+ list = [params['data']]
96
+ end
97
+ list.each {|x| port.write(x)}
98
+ nil
99
+ end
100
+ end
101
+ end
102
+
103
+ # run a job
104
+ post '/jobs/:job/run' do
105
+ job_api do |job, params|
106
+ job.run(force: params['force'], at: params['at'], wait: params['wait'])
107
+ nil
108
+ end
109
+ end
110
+
111
+ # terminate a job
112
+ post '/jobs/:job/terminate' do
113
+ job_api do |job, params|
114
+ job.terminate(recursive: params['recursive'])
115
+ nil
116
+ end
117
+ end
118
+
119
+ # lock job
120
+ post '/jobs/:job/lock' do
121
+ job_api do |job, params|
122
+ { token: job.lock(params['ttl']) }
123
+ end
124
+ end
125
+
126
+ # refresh lock
127
+ post '/jobs/:job/refresh_lock' do
128
+ job_api do |job, params|
129
+ { refresh: job.refresh_lock(params['ttl']) }
130
+ end
131
+ end
132
+
133
+ # unlock job
134
+ post '/jobs/:job/unlock' do
135
+ job_api do |job, params|
136
+ { unlock: job.unlock(params['token']) }
137
+ end
138
+ end
139
+
140
+ # adopt another job
141
+ post '/jobs/:job/adopt' do
142
+ job_api do |job, params|
143
+ orphan = SideJob.find(params[:child])
144
+ raise 'Child job does not exist' unless orphan
145
+ job.adopt(orphan, params['name'])
146
+ nil
147
+ end
148
+ end
149
+
150
+ # disown a job
151
+ post '/jobs/:job/disown' do
152
+ job_api do |job, params|
153
+ job.disown(params['name'])
154
+ nil
155
+ end
156
+ end
157
+
158
+ # return all logs
159
+ get '/logs' do
160
+ api_call do |params|
161
+ SideJob.logs(clear: params['clear'] || false)
162
+ end
163
+ end
164
+
165
+ # add a log entry
166
+ post '/logs' do
167
+ api_call do |params|
168
+ SideJob.log(params['entry'])
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def job_api(&block)
175
+ job = SideJob.find(params['job'])
176
+ halt 422, { error: "Job #{params['job']} does not exist" }.to_json unless job
177
+ api_call do |params|
178
+ result = yield(job, params)
179
+ job.run(parent: params['run']['parent'], force: params['run']['force']) if params['run']
180
+ result
181
+ end
182
+ end
183
+
184
+ def api_call(&block)
185
+ begin
186
+ request.body.rewind
187
+ params = JSON.parse(request.body.read) rescue {}
188
+ SideJob.log_context(params['log_context'] || {}) do
189
+ (yield params).to_json
190
+ end
191
+ rescue => e
192
+ puts e.inspect
193
+ puts e.backtrace
194
+ halt 422, { error: e.to_s }.to_json
195
+ end
196
+ end
197
+
198
+ # @param ports [Array<SideJob::Port>]
199
+ def ports_info(ports)
200
+ ports.each_with_object({}) do |port, hash|
201
+ hash[port.name] = {size: port.size}
202
+ hash[port.name]['default'] = port.default if port.default?
203
+ end
204
+ end
205
+ end