Commit 47e6f1d6 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Refactor builds queue builder to avoid mutations

parent e9368df2
...@@ -105,6 +105,7 @@ module Ci ...@@ -105,6 +105,7 @@ module Ci
def each_build(params, &blk) def each_build(params, &blk)
queue = ::Gitlab::Ci::Queue::Builder.new(runner) queue = ::Gitlab::Ci::Queue::Builder.new(runner)
builds = begin
if runner.instance_type? if runner.instance_type?
queue.builds_for_shared_runner queue.builds_for_shared_runner
elsif runner.group_type? elsif runner.group_type?
...@@ -112,21 +113,22 @@ module Ci ...@@ -112,21 +113,22 @@ module Ci
else else
queue.builds_for_project_runner queue.builds_for_project_runner
end end
end
# pick builds that does not have other tags than runner's one # pick builds that does not have other tags than runner's one
queue.builds_matching_tag_ids(runner.tags.ids) builds = queue.builds_matching_tag_ids(builds, runner.tags.ids)
# pick builds that have at least one tag # pick builds that have at least one tag
unless runner.run_untagged? unless runner.run_untagged?
queue.builds_with_any_tags builds = queue.builds_with_any_tags(builds)
end end
# pick builds that older than specified age # pick builds that older than specified age
if params.key?(:job_age) if params.key?(:job_age)
queue.builds_queued_before(params[:job_age].seconds.ago) builds = queue.builds_queued_before(builds, params[:job_age].seconds.ago)
end end
build_ids = retrieve_queue(-> { queue.build_ids }) build_ids = retrieve_queue(-> { queue.build_ids(builds) })
@metrics.observe_queue_size(-> { build_ids.size }, @runner.runner_type) @metrics.observe_queue_size(-> { build_ids.size }, @runner.runner_type)
......
...@@ -4,89 +4,102 @@ module Gitlab ...@@ -4,89 +4,102 @@ module Gitlab
module Ci module Ci
module Queue module Queue
class Builder < SimpleDelegator class Builder < SimpleDelegator
attr_reader :runner
def initialize(runner) def initialize(runner)
@runner = runner
@strategy = begin
if ::Feature.enabled?(:ci_pending_builds_queue_source, runner, default_enabled: :yaml) if ::Feature.enabled?(:ci_pending_builds_queue_source, runner, default_enabled: :yaml)
super(PendingBuildsTableStrategy.new(runner)) PendingBuildsTableStrategy.new(runner)
else else
super(BuildsTableStrategy.new(runner)) BuildsTableStrategy.new(runner)
end
end
super(@strategy)
end end
##
# This is overridden in EE
#
def builds_for_shared_runner
@strategy.builds_for_shared_runner
end end
# rubocop:disable CodeReuse/ActiveRecord # rubocop:disable CodeReuse/ActiveRecord
def builds_for_group_runner
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::ObjectHierarchy
.new(groups, options: { use_distinct: ::Feature.enabled?(:use_distinct_in_register_job_object_hierarchy) })
.base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled
.with_builds_enabled
.without_deleted
relation = @strategy.new_builds.where(project: projects)
@strategy.order(relation)
end
def builds_for_project_runner
relation = @strategy.new_builds
.where(project: runner.projects.without_deleted.with_builds_enabled)
@strategy.order(relation)
end
def builds_queued_before(relation, time)
relation.queued_before(time)
end
class BuildsTableStrategy class BuildsTableStrategy
attr_reader :runner attr_reader :runner, :common
def initialize(runner) def initialize(runner)
@runner = runner @runner = runner
@relation = new_builds
end end
def builds_for_shared_runner def builds_for_shared_runner
@relation = new_builds relation = new_builds
# don't run projects which have not enabled shared runners and builds # don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_builds.project_id = projects.id') .joins('INNER JOIN projects ON ci_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false }) .where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id') .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') .where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
@relation = begin
if Feature.enabled?(:ci_queueing_disaster_recovery, runner, type: :ops, default_enabled: :yaml) if Feature.enabled?(:ci_queueing_disaster_recovery, runner, type: :ops, default_enabled: :yaml)
# if disaster recovery is enabled, we fallback to FIFO scheduling # if disaster recovery is enabled, we fallback to FIFO scheduling
@relation.order('ci_builds.id ASC') relation.order('ci_builds.id ASC')
else else
# Implement fair scheduling # Implement fair scheduling
# this returns builds that are ordered by number of running builds # this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all # we prefer projects that don't use shared runners at all
relation relation
.joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id") .joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id = project_builds.project_id")
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_builds.id ASC') .order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_builds.id ASC')
end end
end end
end
def builds_for_project_runner def builds_matching_tag_ids(relation, ids)
@relation = new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('id ASC')
end
def builds_for_group_runner
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
hierarchy_groups = Gitlab::ObjectHierarchy
.new(groups, options: { use_distinct: ::Feature.enabled?(:use_distinct_in_register_job_object_hierarchy) })
.base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled
.with_builds_enabled
.without_deleted
@relation = new_builds.where(project: projects).order('id ASC')
end
def builds_matching_tag_ids(ids)
# pick builds that does not have other tags than runner's one # pick builds that does not have other tags than runner's one
@relation = @relation.matches_tag_ids(ids) relation.matches_tag_ids(ids)
end end
def builds_with_any_tags def builds_with_any_tags(relation)
# pick builds that have at least one tag # pick builds that have at least one tag
@relation = @relation.with_any_tags relation.with_any_tags
end end
def builds_queued_before(time) def order(relation)
@relation = @relation.queued_before(time) relation.order('id ASC')
end end
def build_ids def build_ids(relation)
@relation.pluck(:id) relation.pluck(:id)
end
private
def all_builds
::Ci::Build.pending.unstarted
end end
def new_builds def new_builds
...@@ -97,6 +110,12 @@ module Gitlab ...@@ -97,6 +110,12 @@ module Gitlab
end end
end end
private
def all_builds
::Ci::Build.pending.unstarted
end
def running_builds_for_shared_runners def running_builds_for_shared_runners
::Ci::Build.running ::Ci::Build.running
.where(runner: ::Ci::Runner.instance_type) .where(runner: ::Ci::Runner.instance_type)
...@@ -110,34 +129,31 @@ module Gitlab ...@@ -110,34 +129,31 @@ module Gitlab
def initialize(runner) def initialize(runner)
@runner = runner @runner = runner
@relation = new_builds
end end
def builds_for_shared_runner def builds_for_shared_runner
@relation = new_builds relation = new_builds
# don't run projects which have not enabled shared runners and builds # don't run projects which have not enabled shared runners and builds
.joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id') .joins('INNER JOIN projects ON ci_pending_builds.project_id = projects.id')
.where(projects: { shared_runners_enabled: true, pending_delete: false }) .where(projects: { shared_runners_enabled: true, pending_delete: false })
.joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id') .joins('LEFT JOIN project_features ON ci_pending_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') .where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0')
@relation = begin
if Feature.enabled?(:ci_queueing_disaster_recovery, runner, type: :ops, default_enabled: :yaml) if Feature.enabled?(:ci_queueing_disaster_recovery, runner, type: :ops, default_enabled: :yaml)
# if disaster recovery is enabled, we fallback to FIFO scheduling # if disaster recovery is enabled, we fallback to FIFO scheduling
@relation.order('ci_pending_builds.build_id ASC') relation.order('ci_pending_builds.build_id ASC')
else else
# Implement fair scheduling # Implement fair scheduling
# this returns builds that are ordered by number of running builds # this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all # we prefer projects that don't use shared runners at all
@relation relation
.joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_pending_builds.project_id=project_builds.project_id") .joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_pending_builds.project_id=project_builds.project_id")
.order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC') .order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_pending_builds.build_id ASC')
end end
end end
end
def builds_for_project_runner def builds_for_project_runner
@relation = new_builds new_builds
.where(project: runner.projects.without_deleted.with_builds_enabled) .where(project: runner.projects.without_deleted.with_builds_enabled)
.order('build_id ASC') .order('build_id ASC')
end end
...@@ -155,29 +171,27 @@ module Gitlab ...@@ -155,29 +171,27 @@ module Gitlab
.with_builds_enabled .with_builds_enabled
.without_deleted .without_deleted
@relation = new_builds.where(project: projects).order('build_id ASC') new_builds.where(project: projects).order('build_id ASC')
end end
def builds_matching_tag_ids(ids) def builds_matching_tag_ids(relation, ids)
@relation = @relation.merge(CommitStatus.matches_tag_ids(ids, on: 'ci_pending_builds.build_id')) relation.merge(CommitStatus.matches_tag_ids(ids, on: 'ci_pending_builds.build_id'))
end end
def builds_with_any_tags def builds_with_any_tags(relation)
@relation = @relation.merge(CommitStatus.with_any_tags(on: 'ci_pending_builds.build_id')) relation.merge(CommitStatus.with_any_tags(on: 'ci_pending_builds.build_id'))
end end
def builds_queued_before(time) def builds_queued_before(relation, time)
@relation = @relation.queued_before(time) relation.queued_before(time)
end end
def build_ids def order(relation)
@relation.pluck(:build_id) relation.order('build_id ASC')
end end
private def build_ids(relation)
relation.pluck(:build_id)
def all_builds
::Ci::PendingBuild.all
end end
def new_builds def new_builds
...@@ -188,13 +202,18 @@ module Gitlab ...@@ -188,13 +202,18 @@ module Gitlab
end end
end end
private
def all_builds
::Ci::PendingBuild.all
end
def running_builds_for_shared_runners def running_builds_for_shared_runners
::Ci::RunningBuild ::Ci::RunningBuild
.where(runner: ::Ci::Runner.instance_type) .where(runner: ::Ci::Runner.instance_type)
.group(:project_id) .group(:project_id)
.select(:project_id, 'count(*) AS running_builds') .select(:project_id, 'count(*) AS running_builds')
end end
# rubocop:enable CodeReuse/ActiveRecord # rubocop:enable CodeReuse/ActiveRecord
end 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