Read about our upcoming Code of Conduct on this issue

process_pipeline_service.rb 4.1 KB
Newer Older
1
2
# frozen_string_literal: true

3
4
module Ci
  class ProcessPipelineService < BaseService
5
6
    include Gitlab::Utils::StrongMemoize

7
8
    attr_reader :pipeline

9
    def execute(pipeline, trigger_build_ids = nil)
10
11
      @pipeline = pipeline

12
13
      update_retried

14
      success = process_stages_without_needs
Kamil Trzciński's avatar
Kamil Trzciński committed
15
16
17

      # we evaluate dependent needs,
      # only when the another job has finished
18
      success = process_builds_with_needs(trigger_build_ids) || success
19

Kamil Trzcinski's avatar
Kamil Trzcinski committed
20
      @pipeline.update_status
21

Kamil Trzciński's avatar
Kamil Trzciński committed
22
      success
23
24
25
26
    end

    private

27
28
29
30
31
32
33
    def process_stages_without_needs
      stage_indexes_of_created_processables_without_needs.flat_map do |index|
        process_stage_without_needs(index)
      end.any?
    end

    def process_stage_without_needs(index)
34
35
      current_status = status_for_prior_stages(index)

36
      return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
37

38
      created_processables_in_stage_without_needs(index).find_each.select do |build|
39
        process_build(build, current_status)
40
      end.any?
41
42
    end

43
44
    def process_builds_with_needs(trigger_build_ids)
      return false unless trigger_build_ids.present?
45
      return false unless Feature.enabled?(:ci_dag_support, project, default_enabled: true)
Kamil Trzciński's avatar
Kamil Trzciński committed
46

Kamil Trzciński's avatar
Kamil Trzciński committed
47
48
49
50
      # we find processables that are dependent:
      # 1. because of current dependency,
      trigger_build_names = pipeline.processables.latest
        .for_ids(trigger_build_ids).names
51

Kamil Trzciński's avatar
Kamil Trzciński committed
52
53
54
55
56
      # 2. does not have builds that not yet complete
      incomplete_build_names = pipeline.processables.latest
        .incomplete.names

      # Each found processable is guaranteed here to have completed status
Kamil Trzciński's avatar
Kamil Trzciński committed
57
      created_processables
58
        .with_needs(trigger_build_names)
Kamil Trzciński's avatar
Kamil Trzciński committed
59
        .without_needs(incomplete_build_names)
Kamil Trzciński's avatar
Kamil Trzciński committed
60
61
62
63
64
65
66
67
68
69
        .find_each
        .map(&method(:process_build_with_needs))
        .any?
    end

    def process_build_with_needs(build)
      current_status = status_for_build_needs(build.needs.map(&:name))

      return unless HasStatus::COMPLETED_STATUSES.include?(current_status)

70
71
72
73
      process_build(build, current_status)
    end

    def process_build(build, current_status)
Kamil Trzciński's avatar
Kamil Trzciński committed
74
75
76
77
78
79
      Gitlab::OptimisticLocking.retry_lock(build) do |subject|
        Ci::ProcessBuildService.new(project, @user)
          .execute(subject, current_status)
      end
    end

80
    def status_for_prior_stages(index)
Kamil Trzciński's avatar
Kamil Trzciński committed
81
      pipeline.processables.status_for_prior_stages(index)
82
83
    end

Kamil Trzciński's avatar
Kamil Trzciński committed
84
    def status_for_build_needs(needs)
Kamil Trzciński's avatar
Kamil Trzciński committed
85
      pipeline.processables.status_for_names(needs)
Kamil Trzciński's avatar
Kamil Trzciński committed
86
87
    end

88
    # rubocop: disable CodeReuse/ActiveRecord
89
90
91
    def stage_indexes_of_created_processables_without_needs
      created_processables_without_needs.order(:stage_idx)
        .pluck(Arel.sql('DISTINCT stage_idx'))
92
    end
93
    # rubocop: enable CodeReuse/ActiveRecord
94

95
96
    def created_processables_in_stage_without_needs(index)
      created_processables_without_needs
97
        .with_preloads
Kamil Trzciński's avatar
Kamil Trzciński committed
98
        .for_stage(index)
99
100
    end

101
    def created_processables_without_needs
102
      if Feature.enabled?(:ci_dag_support, project, default_enabled: true)
103
104
105
106
107
108
        pipeline.processables.created.without_needs
      else
        pipeline.processables.created
      end
    end

109
    def created_processables
110
      pipeline.processables.created
111
    end
112
113
114
115
116

    # This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
    # This replicates what is db/post_migrate/20170416103934_upate_retried_for_ci_build.rb
    # and ensures that functionality will not be broken before migration is run
    # this updates only when there are data that needs to be updated, there are two groups with no retried flag
117
    # rubocop: disable CodeReuse/ActiveRecord
118
119
120
121
122
    def update_retried
      # find the latest builds for each name
      latest_statuses = pipeline.statuses.latest
        .group(:name)
        .having('count(*) > 1')
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
123
        .pluck(Arel.sql('MAX(id)'), 'name')
124
125
126
127
128
129
130

      # mark builds that are retried
      pipeline.statuses.latest
        .where(name: latest_statuses.map(&:second))
        .where.not(id: latest_statuses.map(&:first))
        .update_all(retried: true) if latest_statuses.any?
    end
131
    # rubocop: enable CodeReuse/ActiveRecord
132
133
  end
end