diff options
author | Tomasz Maczukin <tomasz@maczukin.pl> | 2017-02-28 13:29:52 +0100 |
---|---|---|
committer | Tomasz Maczukin <tomasz@maczukin.pl> | 2017-03-02 17:45:46 +0100 |
commit | 7e46db0f5a5b6aa84ac653fa4826b70bf50d6909 (patch) | |
tree | 6aadb1adf61243d17c35fc954d4ac62206efe2ca | |
parent | d5f7e5421157dbd1be134247dfec318c0db546a8 (diff) | |
download | gitlab-ce-7e46db0f5a5b6aa84ac653fa4826b70bf50d6909.tar.gz |
Add job patch trace API
-rw-r--r-- | lib/api/helpers/runner.rb | 13 | ||||
-rw-r--r-- | lib/api/runner.rb | 32 | ||||
-rw-r--r-- | spec/requests/api/runner_spec.rb | 134 |
3 files changed, 178 insertions, 1 deletions
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 15eb6b932ed..e71895c091e 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -1,6 +1,8 @@ module API module Helpers module Runner + JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN' + JOB_TOKEN_PARAM = :token UPDATE_RUNNER_EVERY = 10 * 60 def runner_registration_token_valid? @@ -55,6 +57,17 @@ module API forbidden!('Project has been deleted!') unless job.project forbidden!('Job has been erased!') if job.erased? end + + def authenticate_job!(job) + validate_job!(job) do + forbidden! unless job_token_valid?(job) + end + end + + def job_token_valid?(job) + token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s + token && job.valid_token?(token) + end end end end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index b57fbdabab9..2b636847dba 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -93,7 +93,7 @@ module API http_codes [[200, 'Job was updated'], [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Job's authentication token) + requires :token, type: String, desc: %q(Runners's authentication token) requires :id, type: Fixnum, desc: %q(Job's ID) optional :trace, type: String, desc: %q(Job's full trace) optional :state, type: String, desc: %q(Job's status: success, failed) @@ -114,6 +114,36 @@ module API job.drop end end + + desc 'Appends a patch to the job.trace' do + http_codes [[202, 'Trace was patched'], + [400, 'Missing Content-Range header'], + [403, 'Forbidden'], + [416, 'Range not satisfiable']] + end + params do + requires :id, type: Fixnum, desc: %q(Job's ID) + optional :token, type: String, desc: %q(Job's authentication token) + end + patch '/:id/trace' do + job = Ci::Build.find_by_id(params[:id]) + authenticate_job!(job) + + error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range') + content_range = request.headers['Content-Range'] + content_range = content_range.split('-') + + current_length = job.trace_length + unless current_length == content_range[0].to_i + return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{current_length}" }) + end + + job.append_trace(request.body.read, content_range[0].to_i) + + status 202 + header 'Job-Status', job.status + header 'Range', "0-#{job.trace_length}" + end end end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 21f2d7635ff..c5844810cdd 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -480,5 +480,139 @@ describe API::Runner do put api("/jobs/#{job.id}"), new_params end end + + describe 'PATCH /api/v4/jobs/:id/trace' do + let(:job) { create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) } + let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } } + let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } + let(:update_interval) { 10.seconds.to_i } + + before { initial_patch_the_trace } + + context 'when request is valid' do + it 'gets correct response' do + expect(response.status).to eq 202 + expect(job.reload.trace).to eq 'BUILD TRACE appended' + expect(response.header).to have_key 'Range' + expect(response.header).to have_key 'Job-Status' + end + + context 'when job has been updated recently' do + it { expect{ patch_the_trace }.not_to change { job.updated_at }} + + it "changes the job's trace" do + patch_the_trace + expect(job.reload.trace).to eq 'BUILD TRACE appended appended' + end + + context 'when Runner makes a force-patch' do + it { expect{ force_patch_the_trace }.not_to change { job.updated_at }} + + it "doesn't change the build.trace" do + force_patch_the_trace + expect(job.reload.trace).to eq 'BUILD TRACE appended' + end + end + end + + context 'when job was not updated recently' do + let(:update_interval) { 15.minutes.to_i } + + it { expect { patch_the_trace }.to change { job.updated_at } } + + it 'changes the job.trace' do + patch_the_trace + expect(job.reload.trace).to eq 'BUILD TRACE appended appended' + end + + context 'when Runner makes a force-patch' do + it { expect { force_patch_the_trace }.to change { job.updated_at } } + + it "doesn't change the job.trace" do + force_patch_the_trace + expect(job.reload.trace).to eq 'BUILD TRACE appended' + end + end + end + + context 'when project for the build has been deleted' do + let(:job) do + create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) do |job| + job.project.update(pending_delete: true) + end + end + + it 'responds with forbidden' do + expect(response.status).to eq(403) + end + end + end + + context 'when Runner makes a force-patch' do + before do + force_patch_the_trace + end + + it 'gets correct response' do + expect(response.status).to eq 202 + expect(job.reload.trace).to eq 'BUILD TRACE appended' + expect(response.header).to have_key 'Range' + expect(response.header).to have_key 'Job-Status' + end + end + + context 'when content-range start is too big' do + let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) } + + it 'gets 416 error response with range headers' do + expect(response.status).to eq 416 + expect(response.header).to have_key 'Range' + expect(response.header['Range']).to eq '0-11' + end + end + + context 'when content-range start is too small' do + let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) } + + it 'gets 416 error response with range headers' do + expect(response.status).to eq 416 + expect(response.header).to have_key 'Range' + expect(response.header['Range']).to eq '0-11' + end + end + + context 'when Content-Range header is missing' do + let(:headers_with_range) { headers } + + it { expect(response.status).to eq 400 } + end + + context 'when job has been errased' do + let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) } + + it { expect(response.status).to eq 403 } + end + + def patch_the_trace(content = ' appended', request_headers = nil) + unless request_headers + offset = job.trace_length + limit = offset + content.length - 1 + request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" }) + end + + Timecop.travel(job.updated_at + update_interval) do + patch api("/jobs/#{job.id}/trace"), content, request_headers + job.reload + end + end + + def initial_patch_the_trace + patch_the_trace(' appended', headers_with_range) + end + + def force_patch_the_trace + 2.times { patch_the_trace('') } + end + end end end |