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.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -5
- data/README.md +10 -14
- data/lib/sidejob.rb +29 -23
- data/lib/sidejob/job.rb +183 -213
- data/lib/sidejob/port.rb +112 -80
- data/lib/sidejob/server_middleware.rb +56 -50
- data/lib/sidejob/testing.rb +0 -2
- data/lib/sidejob/version.rb +1 -1
- data/lib/sidejob/worker.rb +28 -46
- data/spec/integration/fib_spec.rb +8 -4
- data/spec/integration/sum_spec.rb +0 -1
- data/spec/sidejob/job_spec.rb +323 -241
- data/spec/sidejob/port_spec.rb +152 -138
- data/spec/sidejob/server_middleware_spec.rb +27 -47
- data/spec/sidejob/worker_spec.rb +16 -84
- data/spec/sidejob_spec.rb +39 -16
- data/web/Gemfile +6 -0
- data/web/Gemfile.lock +43 -0
- data/web/app.rb +205 -0
- data/web/config.ru +14 -0
- metadata +6 -2
| @@ -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 | 
            -
                   | 
| 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 ' | 
| 88 | 
            -
                   | 
| 89 | 
            -
                   | 
| 90 | 
            -
                  process(@job) { @ | 
| 91 | 
            -
                  expect(@ | 
| 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 ' | 
| 96 | 
            -
                   | 
| 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. | 
| 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 ' | 
| 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) {  | 
| 115 | 
            -
                  }.to change {Sidekiq::Stats.new. | 
| 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:: | 
| 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:: | 
| 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  | 
| 161 | 
            +
                it 'does not set status to failed if status is terminating' do
         | 
| 180 162 | 
             
                  process(@job) do |worker|
         | 
| 181 | 
            -
                    worker. | 
| 163 | 
            +
                    worker.terminate
         | 
| 182 164 | 
             
                    raise 'oops'
         | 
| 183 165 | 
             
                  end
         | 
| 184 | 
            -
                  expect(@job.status).to eq ' | 
| 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 | 
            -
             | 
| 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
         | 
    
        data/spec/sidejob/worker_spec.rb
    CHANGED
    
    | @@ -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, ' | 
| 117 | 
            -
                                              {'timestamp' => SideJob.timestamp, ' | 
| 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  | 
| 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(: | 
| 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  | 
| 138 | 
            -
                  @ | 
| 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 ' | 
| 150 | 
            -
                  @job. | 
| 151 | 
            -
                   | 
| 152 | 
            -
             | 
| 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 ' | 
| 195 | 
            -
                  @worker. | 
| 196 | 
            -
                  @worker. | 
| 197 | 
            -
                   | 
| 198 | 
            -
                  expect { @worker. | 
| 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  | 
| 48 | 
            +
                  expect(job.id).to be 1
         | 
| 49 49 | 
             
                  job = SideJob.queue('testq', 'TestWorker')
         | 
| 50 | 
            -
                  expect(job.id).to  | 
| 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
    
    
    
        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
         |