Commit ee2e5835 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Fair usage of Shared Runners

parent 6aefd3c3
...@@ -19,6 +19,7 @@ v 8.9.0 (unreleased) ...@@ -19,6 +19,7 @@ v 8.9.0 (unreleased)
- Added descriptions to notification settings dropdown - Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API - Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies - Reduce number of fog gem dependencies
- Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Redesign navigation for project pages - Redesign navigation for project pages
......
...@@ -7,15 +7,15 @@ module Ci ...@@ -7,15 +7,15 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
# don't run projects which have not enables shared runners # this returns builds that are ordered by number of running builds
builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }) # we prefer projects that don't use shared runners at all
builds.joins("JOIN (#{projects_with_builds_for_shared_runners.to_sql}) AS projects ON ci_builds.gl_project_id=projects.gl_project_id").
order('projects.running_builds ASC', 'ci_builds.id ASC')
else else
# do run projects which are only assigned to this runner # do run projects which are only assigned to this runner (FIFO)
builds.where(project: current_runner.projects.where(builds_enabled: true)) builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
end end
builds = builds.order('created_at ASC')
build = builds.find do |build| build = builds.find do |build|
build.can_be_served?(current_runner) build.can_be_served?(current_runner)
end end
...@@ -35,5 +35,18 @@ module Ci ...@@ -35,5 +35,18 @@ module Ci
rescue StateMachines::InvalidTransition rescue StateMachines::InvalidTransition
nil nil
end end
private
def projects_with_builds_for_shared_runners
Ci::Build.running_or_pending.
joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
group(:gl_project_id).
select(:gl_project_id, "count(case when status = 'running' AND runner_id = (#{shared_runners.to_sql}) then 1 end) as running_builds")
end
def shared_runners
Ci::Runner.shared.select(:id)
end
end end
end end
...@@ -50,6 +50,46 @@ module Ci ...@@ -50,6 +50,46 @@ module Ci
project.update(shared_runners_enabled: true) project.update(shared_runners_enabled: true)
end end
context 'for multiple builds' do
let!(:project2) { create :empty_project, shared_runners_enabled: true }
let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :empty_project, shared_runners_enabled: true }
let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_build }
let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
let!(:build2_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
let!(:build1_project3) { FactoryGirl.create :ci_build, pipeline: pipeline3 }
it 'prefers projects without builds first' do
# it gets for one build from each of the projects
expect(service.execute(shared_runner)).to eq(build1_project1)
expect(service.execute(shared_runner)).to eq(build1_project2)
expect(service.execute(shared_runner)).to eq(build1_project3)
# then it gets a second build from each of the projects
expect(service.execute(shared_runner)).to eq(build2_project1)
expect(service.execute(shared_runner)).to eq(build2_project2)
# in the end the third build
expect(service.execute(shared_runner)).to eq(build3_project1)
end
it 'equalises number of running builds' do
# after finishing the first build for project 1, get a second build from the same project
expect(service.execute(shared_runner)).to eq(build1_project1)
build1_project1.success
expect(service.execute(shared_runner)).to eq(build2_project1)
expect(service.execute(shared_runner)).to eq(build1_project2)
build1_project2.success
expect(service.execute(shared_runner)).to eq(build2_project2)
expect(service.execute(shared_runner)).to eq(build1_project3)
expect(service.execute(shared_runner)).to eq(build3_project1)
end
end
context 'shared runner' do context 'shared runner' do
let(:build) { service.execute(shared_runner) } let(:build) { service.execute(shared_runner) }
......
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