summaryrefslogtreecommitdiff
path: root/app/services/ci/queue/pending_builds_strategy.rb
blob: 7a913e47df4f29e37de00c4071e82a8f90474952 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# frozen_string_literal: true

module Ci
  module Queue
    class PendingBuildsStrategy
      attr_reader :runner

      def initialize(runner)
        @runner = runner
      end

      # rubocop:disable CodeReuse/ActiveRecord
      def builds_for_shared_runner
        shared_builds = builds_available_for_shared_runners

        builds_ordered_for_shared_runners(shared_builds)
      end

      def builds_for_group_runner
        return new_builds.none if runner.namespace_ids.empty?

        new_builds.where('ci_pending_builds.namespace_traversal_ids && ARRAY[?]::int[]', runner.namespace_ids)
      end

      def builds_matching_tag_ids(relation, ids)
        if ::Feature.enabled?(:ci_queueing_denormalize_tags_information, runner, default_enabled: :yaml)
          relation.for_tags(runner.tags_ids)
        else
          relation.merge(CommitStatus.matches_tag_ids(ids, table: 'ci_pending_builds', column: 'build_id'))
        end
      end

      def builds_with_any_tags(relation)
        if ::Feature.enabled?(:ci_queueing_denormalize_tags_information, runner, default_enabled: :yaml)
          relation.where('cardinality(tag_ids) > 0')
        else
          relation.merge(CommitStatus.with_any_tags(table: 'ci_pending_builds', column: 'build_id'))
        end
      end

      def order(relation)
        relation.order('build_id ASC')
      end

      def new_builds
        ::Ci::PendingBuild.all
      end

      def build_ids(relation)
        relation.pluck(:build_id)
      end

      def use_denormalized_shared_runners_data?
        ::Feature.enabled?(:ci_queueing_denormalize_shared_runners_information, runner, type: :development, default_enabled: :yaml)
      end

      def use_denormalized_minutes_data?
        ::Feature.enabled?(:ci_queueing_denormalize_ci_minutes_information, runner, type: :development, default_enabled: :yaml)
      end

      def use_denormalized_namespace_traversal_ids?
        ::Feature.enabled?(:ci_queueing_denormalize_namespace_traversal_ids, runner, type: :development, default_enabled: :yaml)
      end

      private

      def builds_available_for_shared_runners
        if use_denormalized_shared_runners_data?
          new_builds.with_instance_runners
        else
          new_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')
            .where(projects: { shared_runners_enabled: true, pending_delete: false })
            .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')
        end
      end

      def builds_ordered_for_shared_runners(relation)
        if Feature.enabled?(:ci_queueing_disaster_recovery_disable_fair_scheduling, runner, type: :ops, default_enabled: :yaml)
          # if disaster recovery is enabled, we fallback to FIFO scheduling
          relation.order('ci_pending_builds.build_id ASC')
        else
          # Implement fair scheduling
          # this returns builds that are ordered by number of running builds
          # we prefer projects that don't use shared runners at all
          relation
            .with(running_builds_for_shared_runners_cte.to_arel)
            .joins("LEFT JOIN 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')
        end
      end

      def running_builds_for_shared_runners_cte
        running_builds = ::Ci::RunningBuild
          .instance_type
          .group(:project_id)
          .select(:project_id, 'COUNT(*) AS running_builds')

        ::Gitlab::SQL::CTE
          .new(:project_builds, running_builds, materialized: true)
      end
      # rubocop:enable CodeReuse/ActiveRecord
    end
  end
end

Ci::Queue::PendingBuildsStrategy.prepend_mod_with('Ci::Queue::PendingBuildsStrategy')