Commit 9bed8de9 authored by Alexis Reigel's avatar Alexis Reigel

simplify runner selection

don't differentiate between the different runner types, instead we rely
on the Runner model to provide the available projects.
scheduling is now applied to all runners equally.
parent 1acd8eb7
...@@ -94,6 +94,24 @@ module Ci ...@@ -94,6 +94,24 @@ module Ci
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
end end
def accessible_projects
accessible_projects =
if shared?
Project.with_shared_runners
elsif project?
projects
elsif group?
hierarchy_groups = Gitlab::GroupHierarchy.new(groups).base_and_descendants
Project.where(namespace_id: hierarchy_groups)
else
Project.none
end
accessible_projects
.with_builds_enabled
.without_deleted
end
def assign_to(project, current_user = nil) def assign_to(project, current_user = nil)
self.is_shared = false if shared? self.is_shared = false if shared?
self.save self.save
......
...@@ -14,14 +14,7 @@ module Ci ...@@ -14,14 +14,7 @@ module Ci
end end
def execute def execute
builds = builds = builds_for_runner
if runner.shared?
builds_for_shared_runner
elsif runner.group?
builds_for_group_runner
else
builds_for_specific_runner
end
valid = true valid = true
...@@ -70,62 +63,27 @@ module Ci ...@@ -70,62 +63,27 @@ module Ci
private private
def builds_for_shared_runner def builds_for_runner
builds_for_scheduled_runner( new_builds
running_builds_for_shared_runners, .joins("LEFT JOIN (#{running_projects.to_sql}) AS running_projects ON ci_builds.project_id=running_projects.project_id")
projects: { shared_runners_enabled: true } .order('COALESCE(running_projects.running_builds, 0) ASC', 'ci_builds.id ASC')
)
end
def builds_for_group_runner
builds_for_scheduled_runner(
running_builds_for_group_runners,
projects: { group_runners_enabled: true }
)
end
def builds_for_scheduled_runner(build_join, project_where)
project_where = project_where.deep_merge(projects: { pending_delete: false })
# don't run projects which have not enabled group runners and builds
builds = new_builds
.joins(:project).where(project_where)
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use group runners at all
builds
.joins("LEFT JOIN (#{build_join.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id")
.order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end
def builds_for_specific_runner
new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC')
end end
def running_builds_for_shared_runners # New builds from the accessible projects
running_builds_for_runners(Ci::Runner.shared) def new_builds
end filter_builds(Ci::Build.pending.unstarted)
def running_builds_for_group_runners
running_builds_for_runners(Ci::Runner.joins(:runner_groups))
end end
def running_builds_for_runners(runners) # Count running builds from the accessible projects
Ci::Build.running.where(runner: runners) def running_projects
filter_builds(Ci::Build.running)
.group(:project_id).select(:project_id, 'count(*) AS running_builds') .group(:project_id).select(:project_id, 'count(*) AS running_builds')
end end
def new_builds # Filter the builds from the accessible projects
builds = Ci::Build.pending.unstarted def filter_builds(builds)
builds = builds.ref_protected if runner.ref_protected? builds = builds.ref_protected if runner.ref_protected?
builds builds.where(project: runner.accessible_projects)
end
def shared_runner_build_limits_feature_enabled?
ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
end end
def register_failure def register_failure
......
...@@ -776,4 +776,71 @@ describe Ci::Runner do ...@@ -776,4 +776,71 @@ describe Ci::Runner do
expect(runner.project?).to be true expect(runner.project?).to be true
end end
end end
describe '#accessible_projects' do
let!(:shared_runner) { create(:ci_runner, :shared) }
let!(:shared_project) { create(:project, shared_runners_enabled: true) }
let!(:project_runner) { create(:ci_runner) }
let!(:project_project) { create(:project, runners: [project_runner], shared_runners_enabled: false) }
let!(:group_runner) { create(:ci_runner) }
let!(:parent_group) { create(:group) }
let!(:parent_group_project) do
create(:project, group: parent_group, shared_runners_enabled: false)
end
let!(:group) { create :group, runners: [group_runner], parent: parent_group }
let!(:group_project) do
create(:project, group: group, shared_runners_enabled: false)
end
let!(:nested_group_project) do
nested_group = create :group, parent: group
create(:project, group: nested_group, shared_runners_enabled: false)
end
it 'returns the project with a shared runner' do
expect(shared_runner.reload.accessible_projects).to eq [shared_project]
end
it 'returns the project with a project runner' do
expect(project_runner.reload.accessible_projects).to eq [project_project]
end
it 'returns the projects with a group and nested group runner' do
expect(group_runner.reload.accessible_projects).to eq [group_project, nested_group_project]
end
context 'deleted' do
before do
shared_project.update_attributes!(pending_delete: true)
project_project.update_attributes!(pending_delete: true)
group_project.update_attributes!(pending_delete: true)
nested_group_project.update_attributes!(pending_delete: true)
end
it 'returns no projects' do
expect(shared_runner.reload.accessible_projects).to be_empty
expect(project_runner.reload.accessible_projects).to be_empty
expect(group_runner.reload.accessible_projects).to be_empty
end
end
context 'builds disabled' do
before do
shared_project.update_attributes!(builds_enabled: false)
project_project.update_attributes!(builds_enabled: false)
group_project.update_attributes!(builds_enabled: false)
nested_group_project.update_attributes!(builds_enabled: false)
end
it 'returns no projects' do
expect(shared_runner.reload.accessible_projects).to be_empty
expect(project_runner.reload.accessible_projects).to be_empty
expect(group_runner.reload.accessible_projects).to be_empty
end
end
end
end end
...@@ -179,6 +179,7 @@ module Ci ...@@ -179,6 +179,7 @@ module Ci
let!(:pipeline2) { create :ci_pipeline, project: project2 } let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :project, group_runners_enabled: true, group: group } let!(:project3) { create :project, group_runners_enabled: true, group: group }
let!(:pipeline3) { create :ci_pipeline, project: project3 } let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_job } let!(:build1_project1) { pending_job }
let!(:build2_project1) { create :ci_build, pipeline: pipeline } let!(:build2_project1) { create :ci_build, pipeline: pipeline }
let!(:build3_project1) { create :ci_build, pipeline: pipeline } let!(:build3_project1) { create :ci_build, pipeline: pipeline }
...@@ -186,6 +187,36 @@ module Ci ...@@ -186,6 +187,36 @@ module Ci
let!(:build2_project2) { create :ci_build, pipeline: pipeline2 } let!(:build2_project2) { create :ci_build, pipeline: pipeline2 }
let!(:build1_project3) { create :ci_build, pipeline: pipeline3 } let!(:build1_project3) { create :ci_build, pipeline: pipeline3 }
# these shouldn't influence the scheduling
let!(:unrelated_group) { create :group }
let!(:unrelated_project) { create :project, group_runners_enabled: true, group: unrelated_group }
let!(:unrelated_pipeline) { create :ci_pipeline, project: unrelated_project }
let!(:build1_unrelated_project) { create :ci_build, pipeline: unrelated_pipeline }
let!(:unrelated_group_runner) { create :ci_runner, groups: [unrelated_group] }
it 'does not consider builds from other group runners' do
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 6
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 5
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 4
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 3
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 2
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 1
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_runner).count).to eq 0
expect(execute(group_runner)).to be_nil
end
it 'prefers projects without builds first' do it 'prefers projects without builds first' do
# it gets for one build from each of the projects # it gets for one build from each of the projects
expect(execute(group_runner)).to eq(build1_project1) expect(execute(group_runner)).to eq(build1_project1)
...@@ -198,6 +229,8 @@ module Ci ...@@ -198,6 +229,8 @@ module Ci
# in the end the third build # in the end the third build
expect(execute(group_runner)).to eq(build3_project1) expect(execute(group_runner)).to eq(build3_project1)
expect(execute(group_runner)).to be_nil
end end
it 'equalises number of running builds' do it 'equalises number of running builds' do
...@@ -211,6 +244,8 @@ module Ci ...@@ -211,6 +244,8 @@ module Ci
expect(execute(group_runner)).to eq(build2_project2) expect(execute(group_runner)).to eq(build2_project2)
expect(execute(group_runner)).to eq(build1_project3) expect(execute(group_runner)).to eq(build1_project3)
expect(execute(group_runner)).to eq(build3_project1) expect(execute(group_runner)).to eq(build3_project1)
expect(execute(group_runner)).to be_nil
end end
end end
...@@ -247,7 +282,7 @@ module Ci ...@@ -247,7 +282,7 @@ module Ci
let!(:other_build) { create :ci_build, pipeline: pipeline } let!(:other_build) { create :ci_build, pipeline: pipeline }
before do before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_runner)
.and_return(Ci::Build.where(id: [pending_job, other_build])) .and_return(Ci::Build.where(id: [pending_job, other_build]))
end end
...@@ -259,7 +294,7 @@ module Ci ...@@ -259,7 +294,7 @@ module Ci
context 'when single build is in queue' do context 'when single build is in queue' do
before do before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_runner)
.and_return(Ci::Build.where(id: pending_job)) .and_return(Ci::Build.where(id: pending_job))
end end
...@@ -270,7 +305,7 @@ module Ci ...@@ -270,7 +305,7 @@ module Ci
context 'when there is no build in queue' do context 'when there is no build in queue' do
before do before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner) allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_runner)
.and_return(Ci::Build.none) .and_return(Ci::Build.none)
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