summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-06-13 21:52:41 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2016-06-13 22:06:14 +0200
commitee2e583500360385c9b3f8d9231233223ab72b42 (patch)
tree1c490cd51bb77a2ddb94c8950342b6e8f95cd6af
parent6aefd3c3213277d80e0680809b45643ef19df4d0 (diff)
downloadgitlab-ce-ee2e583500360385c9b3f8d9231233223ab72b42.tar.gz
Fair usage of Shared Runners
-rw-r--r--CHANGELOG1
-rw-r--r--app/services/ci/register_build_service.rb25
-rw-r--r--spec/services/ci/register_build_service_spec.rb40
3 files changed, 60 insertions, 6 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 7a6a14919da..3c1a55d7771 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,6 +19,7 @@ v 8.9.0 (unreleased)
- Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies
+ - Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Redesign navigation for project pages
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 4ff268a6f06..54aceba1c87 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -7,15 +7,15 @@ module Ci
builds =
if current_runner.shared?
- # don't run projects which have not enables shared runners
- builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
+ # this returns builds that are ordered by number of running builds
+ # 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
- # do run projects which are only assigned to this runner
- builds.where(project: current_runner.projects.where(builds_enabled: true))
+ # do run projects which are only assigned to this runner (FIFO)
+ builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
end
- builds = builds.order('created_at ASC')
-
build = builds.find do |build|
build.can_be_served?(current_runner)
end
@@ -35,5 +35,18 @@ module Ci
rescue StateMachines::InvalidTransition
nil
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
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index d91fc574299..fa4c2fddeb8 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -50,6 +50,46 @@ module Ci
project.update(shared_runners_enabled: true)
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
let(:build) { service.execute(shared_runner) }