sidejob 3.0.1 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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