summaryrefslogtreecommitdiff
path: root/spec/models/ci/runner_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci/runner_spec.rb')
-rw-r--r--spec/models/ci/runner_spec.rb449
1 files changed, 321 insertions, 128 deletions
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 6830a8daa3b..eb29db697a5 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Ci::Runner do
+ include StubGitlabCalls
+
it_behaves_like 'having unique enum values'
it_behaves_like 'it has loose foreign keys' do
@@ -23,6 +25,16 @@ RSpec.describe Ci::Runner do
end
end
+ describe 'projects association' do
+ let(:runner) { create(:ci_runner, :project) }
+
+ it 'does not create a cross-database query' do
+ with_cross_joins_prevented do
+ expect(runner.projects.count).to eq(1)
+ end
+ end
+ end
+
describe 'validation' do
it { is_expected.to validate_presence_of(:access_level) }
it { is_expected.to validate_presence_of(:runner_type) }
@@ -255,22 +267,89 @@ RSpec.describe Ci::Runner do
it_behaves_like '.belonging_to_parent_group_of_project'
end
- describe '.owned_or_instance_wide' do
- it 'returns a globally shared, a project specific and a group specific runner' do
- # group specific
- group = create(:group)
- project = create(:project, group: group)
- group_runner = create(:ci_runner, :group, groups: [group])
+ context 'with instance runners sharing enabled' do
+ # group specific
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- # project specific
- project_runner = create(:ci_runner, :project, projects: [project])
+ # project specific
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) }
- # globally shared
- shared_runner = create(:ci_runner, :instance)
+ # globally shared
+ let_it_be(:shared_runner) { create(:ci_runner, :instance) }
- expect(described_class.owned_or_instance_wide(project.id)).to contain_exactly(
- group_runner, project_runner, shared_runner
- )
+ describe '.owned_or_instance_wide' do
+ subject { described_class.owned_or_instance_wide(project.id) }
+
+ it 'returns a globally shared, a project specific and a group specific runner' do
+ is_expected.to contain_exactly(group_runner, project_runner, shared_runner)
+ end
+ end
+
+ describe '.group_or_instance_wide' do
+ subject { described_class.group_or_instance_wide(group) }
+
+ before do
+ # Ensure the project runner is instantiated
+ project_runner
+ end
+
+ it 'returns a globally shared and a group specific runner' do
+ is_expected.to contain_exactly(group_runner, shared_runner)
+ end
+ end
+ end
+
+ context 'with instance runners sharing disabled' do
+ # group specific
+ let_it_be(:group) { create(:group, shared_runners_enabled: false) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+
+ let(:group_runners_enabled) { true }
+ let(:project) { create(:project, group: group, shared_runners_enabled: false) }
+
+ # project specific
+ let(:project_runner) { create(:ci_runner, :project, projects: [project]) }
+
+ # globally shared
+ let_it_be(:shared_runner) { create(:ci_runner, :instance) }
+
+ before do
+ project.update!(group_runners_enabled: group_runners_enabled)
+ end
+
+ describe '.owned_or_instance_wide' do
+ subject { described_class.owned_or_instance_wide(project.id) }
+
+ context 'with group runners disabled' do
+ let(:group_runners_enabled) { false }
+
+ it 'returns only the project specific runner' do
+ is_expected.to contain_exactly(project_runner)
+ end
+ end
+
+ context 'with group runners enabled' do
+ let(:group_runners_enabled) { true }
+
+ it 'returns a project specific and a group specific runner' do
+ is_expected.to contain_exactly(group_runner, project_runner)
+ end
+ end
+ end
+
+ describe '.group_or_instance_wide' do
+ subject { described_class.group_or_instance_wide(group) }
+
+ before do
+ # Ensure the project runner is instantiated
+ project_runner
+ end
+
+ it 'returns a group specific runner' do
+ is_expected.to contain_exactly(group_runner)
+ end
end
end
@@ -291,6 +370,30 @@ RSpec.describe Ci::Runner do
end
end
+ describe '#only_for' do
+ let_it_be_with_reload(:runner) { create(:ci_runner, :project) }
+ let_it_be(:project) { runner.projects.first }
+
+ subject { runner.only_for?(project) }
+
+ context 'with matching project' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'without matching project' do
+ let_it_be(:project) { create(:project) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'with runner having multiple projects' do
+ let_it_be(:other_project) { create(:project) }
+ let_it_be(:runner_project) { create(:ci_runner_project, project: other_project, runner: runner) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#assign_to' do
let(:project) { create(:project) }
@@ -544,7 +647,7 @@ RSpec.describe Ci::Runner do
end
end
- describe '#can_pick?' do
+ describe '#matches_build?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:pipeline) { create(:ci_pipeline) }
@@ -555,31 +658,15 @@ RSpec.describe Ci::Runner do
let(:tag_list) { [] }
let(:run_untagged) { true }
- subject { runner.can_pick?(build) }
-
- context 'a different runner' do
- let(:other_project) { create(:project) }
- let(:other_runner) { create(:ci_runner, :project, projects: [other_project], tag_list: tag_list, run_untagged: run_untagged) }
-
- before do
- # `can_pick?` is not used outside the runners available for the project
- stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
- end
-
- it 'cannot handle builds' do
- expect(other_runner.can_pick?(build)).to be_falsey
- end
- end
+ subject { runner.matches_build?(build) }
context 'when runner does not have tags' do
- it 'can handle builds without tags' do
- expect(runner.can_pick?(build)).to be_truthy
- end
+ it { is_expected.to be_truthy }
it 'cannot handle build with tags' do
build.tag_list = ['aa']
- expect(runner.can_pick?(build)).to be_falsey
+ is_expected.to be_falsey
end
end
@@ -590,20 +677,18 @@ RSpec.describe Ci::Runner do
it 'can handle build with matching tags' do
build.tag_list = ['bb']
- expect(runner.can_pick?(build)).to be_truthy
+ is_expected.to be_truthy
end
it 'cannot handle build without matching tags' do
build.tag_list = ['aa']
- expect(runner.can_pick?(build)).to be_falsey
+ is_expected.to be_falsey
end
end
context 'when runner can pick untagged jobs' do
- it 'can handle builds without tags' do
- expect(runner.can_pick?(build)).to be_truthy
- end
+ it { is_expected.to be_truthy }
it_behaves_like 'tagged build picker'
end
@@ -611,9 +696,7 @@ RSpec.describe Ci::Runner do
context 'when runner cannot pick untagged jobs' do
let(:run_untagged) { false }
- it 'cannot handle builds without tags' do
- expect(runner.can_pick?(build)).to be_falsey
- end
+ it { is_expected.to be_falsey }
it_behaves_like 'tagged build picker'
end
@@ -622,64 +705,31 @@ RSpec.describe Ci::Runner do
context 'when runner is shared' do
let(:runner) { create(:ci_runner, :instance) }
- it 'can handle builds' do
- expect(runner.can_pick?(build)).to be_truthy
- end
+ it { is_expected.to be_truthy }
context 'when runner is locked' do
let(:runner) { create(:ci_runner, :instance, locked: true) }
- it 'can handle builds' do
- expect(runner.can_pick?(build)).to be_truthy
- end
+ it { is_expected.to be_truthy }
end
it 'does not query for owned or instance runners' do
expect(described_class).not_to receive(:owned_or_instance_wide)
- runner.can_pick?(build)
- end
-
- context 'when feature flag ci_runners_short_circuit_assignable_for is disabled' do
- before do
- stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
- end
-
- it 'does not query for owned or instance runners' do
- expect(described_class).to receive(:owned_or_instance_wide).and_call_original
-
- runner.can_pick?(build)
- end
+ subject
end
end
context 'when runner is not shared' do
- before do
- # `can_pick?` is not used outside the runners available for the project
- stub_feature_flags(ci_runners_short_circuit_assignable_for: false)
- end
-
context 'when runner is assigned to a project' do
- it 'can handle builds' do
- expect(runner.can_pick?(build)).to be_truthy
- end
- end
-
- context 'when runner is assigned to another project' do
- let(:runner_project) { create(:project) }
-
- it 'cannot handle builds' do
- expect(runner.can_pick?(build)).to be_falsey
- end
+ it { is_expected.to be_truthy }
end
context 'when runner is assigned to a group' do
let(:group) { create(:group, projects: [build.project]) }
let(:runner) { create(:ci_runner, :group, tag_list: tag_list, run_untagged: run_untagged, groups: [group]) }
- it 'can handle builds' do
- expect(runner.can_pick?(build)).to be_truthy
- end
+ it { is_expected.to be_truthy }
it 'knows namespace id it is assigned to' do
expect(runner.namespace_ids).to eq [group.id]
@@ -1220,14 +1270,6 @@ RSpec.describe Ci::Runner do
runner.pick_build!(build)
end
end
-
- context 'build picking improvement' do
- it 'does not check if the build is assignable to a runner' do
- expect(runner).not_to receive(:can_pick?)
-
- runner.pick_build!(build)
- end
- end
end
describe 'project runner without projects is destroyable' do
@@ -1259,6 +1301,20 @@ RSpec.describe Ci::Runner do
expect(runners).to eq([runner2, runner1])
end
+
+ it 'supports ordering by the token expiration' do
+ runner1 = create(:ci_runner)
+ runner1.update!(token_expires_at: 1.year.from_now)
+ runner2 = create(:ci_runner)
+ runner3 = create(:ci_runner)
+ runner3.update!(token_expires_at: 1.month.from_now)
+
+ runners = described_class.order_by('token_expires_at_asc')
+ expect(runners).to eq([runner3, runner1, runner2])
+
+ runners = described_class.order_by('token_expires_at_desc')
+ expect(runners).to eq([runner2, runner1, runner3])
+ end
end
describe '.runner_matchers' do
@@ -1386,47 +1442,6 @@ RSpec.describe Ci::Runner do
it { is_expected.to eq(contacted_at_stored) }
end
- describe '.legacy_belonging_to_group' do
- shared_examples 'returns group runners' do
- it 'returns the specific group runner' do
- group = create(:group)
- runner = create(:ci_runner, :group, groups: [group])
- unrelated_group = create(:group)
- create(:ci_runner, :group, groups: [unrelated_group])
-
- expect(described_class.legacy_belonging_to_group(group.id)).to contain_exactly(runner)
- end
-
- context 'runner belonging to parent group' do
- let_it_be(:parent_group) { create(:group) }
- let_it_be(:parent_runner) { create(:ci_runner, :group, groups: [parent_group]) }
- let_it_be(:group) { create(:group, parent: parent_group) }
-
- context 'when include_parent option is passed' do
- it 'returns the group runner from the parent group' do
- expect(described_class.legacy_belonging_to_group(group.id, include_ancestors: true)).to contain_exactly(parent_runner)
- end
- end
-
- context 'when include_parent option is not passed' do
- it 'does not return the group runner from the parent group' do
- expect(described_class.legacy_belonging_to_group(group.id)).to be_empty
- end
- end
- end
- end
-
- it_behaves_like 'returns group runners'
-
- context 'when feature flag :linear_runner_ancestor_scopes is disabled' do
- before do
- stub_feature_flags(linear_runner_ancestor_scopes: false)
- end
-
- it_behaves_like 'returns group runners'
- end
- end
-
describe '.belonging_to_group' do
it 'returns the specific group runner' do
group = create(:group)
@@ -1470,4 +1485,182 @@ RSpec.describe Ci::Runner do
)
end
end
+
+ describe '#token_expires_at', :freeze_time do
+ shared_examples 'expiring token' do |interval:|
+ it 'expires' do
+ expect(runner.token_expires_at).to eq(interval.from_now)
+ end
+ end
+
+ shared_examples 'non-expiring token' do
+ it 'does not expire' do
+ expect(runner.token_expires_at).to be_nil
+ end
+ end
+
+ context 'no expiration' do
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'system-wide shared expiration' do
+ before do
+ stub_application_setting(runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'expiring token', interval: 5.days
+ end
+
+ context 'system-wide group expiration' do
+ before do
+ stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'system-wide project expiration' do
+ before do
+ stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'group expiration' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 6.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 6.days
+ end
+
+ context 'human-readable group expiration' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval_human_readable: '7 days') }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 7.days
+ end
+
+ context 'project expiration' do
+ let(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 4.days
+ end
+
+ context 'human-readable project expiration' do
+ let(:project) { create(:project, runner_token_expiration_interval_human_readable: '5 days').tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 5.days
+ end
+
+ context 'multiple projects' do
+ let(:project1) { create(:project, runner_token_expiration_interval: 8.days.to_i).tap(&:save!) }
+ let(:project2) { create(:project, runner_token_expiration_interval: 7.days.to_i).tap(&:save!) }
+ let(:project3) { create(:project, runner_token_expiration_interval: 9.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2, project3]) }
+
+ it_behaves_like 'expiring token', interval: 7.days
+ end
+
+ context 'with project runner token expiring' do
+ let_it_be(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i).tap(&:save!) }
+
+ context 'project overrides system' do
+ before do
+ stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 4.days
+ end
+
+ context 'system overrides project' do
+ before do
+ stub_application_setting(project_runner_token_expiration_interval: 3.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 3.days
+ end
+ end
+
+ context 'with group runner token expiring' do
+ let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ context 'group overrides system' do
+ before do
+ stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 4.days
+ end
+
+ context 'system overrides group' do
+ before do
+ stub_application_setting(group_runner_token_expiration_interval: 3.days.to_i)
+ end
+
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 3.days
+ end
+ end
+
+ context "with group's project runner token expiring" do
+ let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 2.days.to_i) }
+ let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+
+ context 'parent group overrides subgroup' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 3.days.to_i) }
+ let(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 2.days
+ end
+
+ context 'subgroup overrides parent group' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 1.day.to_i) }
+ let(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'expiring token', interval: 1.day
+ end
+ end
+
+ context "with group's project runner token expiring" do
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 2.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ context 'group overrides project' do
+ let(:project) { create(:project, group: group, runner_token_expiration_interval: 3.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 2.days
+ end
+
+ context 'project overrides group' do
+ let(:project) { create(:project, group: group, runner_token_expiration_interval: 1.day.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+
+ it_behaves_like 'expiring token', interval: 1.day
+ end
+ end
+ end
end