Commit 1b486454 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'split-runner-specs' into 'master'

Split api/ci/runner_spec.rb into separate files

See merge request gitlab-org/gitlab!38187
parents 239f2bc2 500b5e84
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/jobs' do
let(:root_namespace) { create(:namespace) }
let(:namespace) { create(:namespace, parent: root_namespace) }
let(:project) { create(:project, namespace: namespace, shared_runners_enabled: false) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:user) { create(:user) }
let(:job) do
create(:ci_build, :artifacts, :extended_options,
pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
end
describe 'PUT /api/v4/jobs/:id' do
let(:job) do
create(:ci_build, :pending, :trace_live, pipeline: pipeline, project: project, user: user, runner_id: runner.id)
end
before do
job.run!
end
it_behaves_like 'API::CI::Runner application context metadata', '/api/:version/jobs/:id' do
let(:send_request) { update_job(state: 'success') }
end
it 'updates runner info' do
expect { update_job(state: 'success') }.to change { runner.reload.contacted_at }
end
context 'when status is given' do
it 'mark job as succeeded' do
update_job(state: 'success')
job.reload
expect(job).to be_success
end
it 'mark job as failed' do
update_job(state: 'failed')
job.reload
expect(job).to be_failed
expect(job).to be_unknown_failure
end
context 'when failure_reason is script_failure' do
before do
update_job(state: 'failed', failure_reason: 'script_failure')
job.reload
end
it { expect(job).to be_script_failure }
end
context 'when failure_reason is runner_system_failure' do
before do
update_job(state: 'failed', failure_reason: 'runner_system_failure')
job.reload
end
it { expect(job).to be_runner_system_failure }
end
context 'when failure_reason is unrecognized value' do
before do
update_job(state: 'failed', failure_reason: 'what_is_this')
job.reload
end
it { expect(job).to be_unknown_failure }
end
context 'when failure_reason is job_execution_timeout' do
before do
update_job(state: 'failed', failure_reason: 'job_execution_timeout')
job.reload
end
it { expect(job).to be_job_execution_timeout }
end
context 'when failure_reason is unmet_prerequisites' do
before do
update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
job.reload
end
it { expect(job).to be_unmet_prerequisites }
end
end
context 'when trace is given' do
it 'creates a trace artifact' do
allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
ArchiveTraceWorker.new.perform(job.id)
end
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
job.reload
expect(response).to have_gitlab_http_status(:ok)
expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
end
context 'when concurrent update of trace is happening' do
before do
job.trace.write('wb') do
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
end
end
it 'returns that operation conflicts' do
expect(response).to have_gitlab_http_status(:conflict)
end
end
end
context 'when no trace is given' do
it 'does not override trace information' do
update_job
expect(job.reload.trace.raw).to eq 'BUILD TRACE'
end
context 'when running state is sent' do
it 'updates update_at value' do
expect { update_job_after_time }.to change { job.reload.updated_at }
end
end
context 'when other state is sent' do
it "doesn't update update_at value" do
expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
end
end
end
context 'when job has been erased' do
let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
it 'responds with forbidden' do
update_job
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when job has already been finished' do
before do
job.trace.set('Job failed')
job.drop!(:script_failure)
end
it 'does not update job status and job trace' do
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
job.reload
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.header['Job-Status']).to eq 'failed'
expect(job.trace.raw).to eq 'Job failed'
expect(job).to be_failed
end
end
def update_job(token = job.token, **params)
new_params = params.merge(token: token)
put api("/jobs/#{job.id}"), params: new_params
end
def update_job_after_time(update_interval = 20.minutes, state = 'running')
Timecop.travel(job.updated_at + update_interval) do
update_job(job.token, state: state)
end
end
end
end
end
This diff is collapsed.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/jobs' do
let(:root_namespace) { create(:namespace) }
let(:namespace) { create(:namespace, parent: root_namespace) }
let(:project) { create(:project, namespace: namespace, shared_runners_enabled: false) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:user) { create(:user) }
let(:job) do
create(:ci_build, :artifacts, :extended_options,
pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
end
describe 'PATCH /api/v4/jobs/:id/trace' do
let(:job) do
create(:ci_build, :running, :trace_live,
project: project, user: user, runner_id: runner.id, pipeline: pipeline)
end
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 do
initial_patch_the_trace
end
it_behaves_like 'API::CI::Runner application context metadata', '/api/:version/jobs/:id/trace' do
let(:send_request) { patch_the_trace }
end
it 'updates runner info' do
runner.update!(contacted_at: 1.year.ago)
expect { patch_the_trace }.to change { runner.reload.contacted_at }
end
context 'when request is valid' do
it 'gets correct response' do
expect(response).to have_gitlab_http_status(:accepted)
expect(job.reload.trace.raw).to eq 'BUILD TRACE appended'
expect(response.header).to have_key 'Range'
expect(response.header).to have_key 'Job-Status'
expect(response.header).to have_key 'X-GitLab-Trace-Update-Interval'
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.raw).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.raw).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.raw).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.raw).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_live, runner_id: runner.id, pipeline: pipeline) do |job|
job.project.update!(pending_delete: true)
end
end
it 'responds with forbidden' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when trace is patched' do
before do
patch_the_trace
end
it 'has valid trace' do
expect(response).to have_gitlab_http_status(:accepted)
expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended'
end
context 'when job is cancelled' do
before do
job.cancel
end
context 'when trace is patched' do
before do
patch_the_trace
end
it 'returns Forbidden ' do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'when redis data are flushed' do
before do
redis_shared_state_cleanup!
end
it 'has empty trace' do
expect(job.reload.trace.raw).to eq ''
end
context 'when we perform partial patch' do
before do
patch_the_trace('hello', headers.merge({ 'Content-Range' => "28-32/5" }))
end
it 'returns an error' do
expect(response).to have_gitlab_http_status(:range_not_satisfiable)
expect(response.header['Range']).to eq('0-0')
end
end
context 'when we resend full trace' do
before do
patch_the_trace('BUILD TRACE appended appended hello', headers.merge({ 'Content-Range' => "0-34/35" }))
end
it 'succeeds with updating trace' do
expect(response).to have_gitlab_http_status(:accepted)
expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended hello'
end
end
end
end
context 'when concurrent update of trace is happening' do
before do
job.trace.write('wb') do
patch_the_trace
end
end
it 'returns that operation conflicts' do
expect(response).to have_gitlab_http_status(:conflict)
end
end
context 'when the job is canceled' do
before do
job.cancel
patch_the_trace
end
it 'receives status in header' do
expect(response.header['Job-Status']).to eq 'canceled'
end
end
context 'when build trace is being watched' do
before do
job.trace.being_watched!
end
it 'returns X-GitLab-Trace-Update-Interval as 3' do
patch_the_trace
expect(response).to have_gitlab_http_status(:accepted)
expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('3')
end
end
context 'when build trace is not being watched' do
it 'returns X-GitLab-Trace-Update-Interval as 30' do
patch_the_trace
expect(response).to have_gitlab_http_status(:accepted)
expect(response.header['X-GitLab-Trace-Update-Interval']).to eq('30')
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).to have_gitlab_http_status(:accepted)
expect(job.reload.trace.raw).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/6' }) }
it 'gets 416 error response with range headers' do
expect(response).to have_gitlab_http_status(:range_not_satisfiable)
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/13' }) }
it 'gets 416 error response with range headers' do
expect(response).to have_gitlab_http_status(:range_not_satisfiable)
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).to have_gitlab_http_status(:bad_request) }
end
context 'when job has been errased' do
let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
it { expect(response).to have_gitlab_http_status(:forbidden) }
end
def patch_the_trace(content = ' appended', request_headers = nil)
unless request_headers
job.trace.read do |stream|
offset = stream.size
limit = offset + content.length - 1
request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" })
end
end
Timecop.travel(job.updated_at + update_interval) do
patch api("/jobs/#{job.id}/trace"), params: content, headers: 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
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
describe 'DELETE /api/v4/runners' do
context 'when no token is provided' do
it 'returns 400 error' do
delete api('/runners')
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when invalid token is provided' do
it 'returns 403 error' do
delete api('/runners'), params: { token: 'invalid' }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when valid token is provided' do
let(:runner) { create(:ci_runner) }
it 'deletes Runner' do
delete api('/runners'), params: { token: runner.token }
expect(response).to have_gitlab_http_status(:no_content)
expect(::Ci::Runner.count).to eq(0)
end
it_behaves_like '412 response' do
let(:request) { api('/runners') }
let(:params) { { token: runner.token } }
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
describe 'POST /api/v4/runners' do
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners')
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when invalid token is provided' do
it 'returns 403 error' do
post api('/runners'), params: { token: 'invalid' }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when valid token is provided' do
it 'creates runner with default values' do
post api('/runners'), params: { token: registration_token }
runner = ::Ci::Runner.first
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(runner.id)
expect(json_response['token']).to eq(runner.token)
expect(runner.run_untagged).to be true
expect(runner.active).to be true
expect(runner.token).not_to eq(registration_token)
expect(runner).to be_instance_type
end
context 'when project token is used' do
let(:project) { create(:project) }
it 'creates project runner' do
post api('/runners'), params: { token: project.runners_token }
expect(response).to have_gitlab_http_status(:created)
expect(project.runners.size).to eq(1)
runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
end
end
context 'when group token is used' do
let(:group) { create(:group) }
it 'creates a group runner' do
post api('/runners'), params: { token: group.runners_token }
expect(response).to have_gitlab_http_status(:created)
expect(group.runners.reload.size).to eq(1)
runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
end
end
end
context 'when runner description is provided' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
description: 'server.hostname'
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.description).to eq('server.hostname')
end
end
context 'when runner tags are provided' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
tag_list: 'tag1, tag2'
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
context 'when option for running untagged jobs is provided' do
context 'when tags are provided' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
run_untagged: false,
tag_list: ['tag']
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.run_untagged).to be false
expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
end
end
context 'when tags are not provided' do
it 'returns 400 error' do
post api('/runners'), params: {
token: registration_token,
run_untagged: false
}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to include(
'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
end
end
end
context 'when option for locking Runner is provided' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
locked: true
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.locked).to be true
end
end
context 'when option for activating a Runner is provided' do
context 'when active is set to true' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
active: true
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.active).to be true
end
end
context 'when active is set to false' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
active: false
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.active).to be false
end
end
end
context 'when access_level is provided for Runner' do
context 'when access_level is set to ref_protected' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
access_level: 'ref_protected'
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.ref_protected?).to be true
end
end
context 'when access_level is set to not_protected' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
access_level: 'not_protected'
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.ref_protected?).to be false
end
end
end
context 'when maximum job timeout is specified' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
maximum_timeout: 9000
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
end
context 'when maximum job timeout is empty' do
it 'creates runner' do
post api('/runners'), params: {
token: registration_token,
maximum_timeout: ''
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.maximum_timeout).to be_nil
end
end
end
%w(name version revision platform architecture).each do |param|
context "when info parameter '#{param}' info is present" do
let(:value) { "#{param}_value" }
it "updates provided Runner's parameter" do
post api('/runners'), params: {
token: registration_token,
info: { param => value }
}
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
end
end
end
it "sets the runner's ip_address" do
post api('/runners'),
params: { token: registration_token },
headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
before do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
describe 'POST /api/v4/runners/verify' do
let(:runner) { create(:ci_runner) }
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners/verify')
expect(response).to have_gitlab_http_status :bad_request
end
end
context 'when invalid token is provided' do
it 'returns 403 error' do
post api('/runners/verify'), params: { token: 'invalid-token' }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when valid token is provided' do
it 'verifies Runner credentials' do
post api('/runners/verify'), params: { token: runner.token }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'API::CI::Runner application context metadata' do |api_route|
it 'contains correct context metadata' do
# Avoids popping the context from the thread so we can
# check its content after the request.
allow(Labkit::Context).to receive(:pop)
send_request
Labkit::Context.with_context do |context|
expected_context = {
'meta.caller_id' => api_route,
'meta.user' => job.user.username,
'meta.project' => job.project.full_path,
'meta.root_namespace' => job.project.full_path_components.first
}
expect(context.to_h).to include(expected_context)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment