summaryrefslogtreecommitdiff
path: root/app/services/ci/update_build_state_service.rb
blob: 61e4c77c1e5508e7211b341a835e5a58a8d08f40 (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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# frozen_string_literal: true

module Ci
  class UpdateBuildStateService
    Result = Struct.new(:status, keyword_init: true)

    ACCEPT_TIMEOUT = 5.minutes.freeze

    attr_reader :build, :params, :metrics

    def initialize(build, params, metrics = ::Gitlab::Ci::Trace::Metrics.new)
      @build = build
      @params = params
      @metrics = metrics
    end

    def execute
      overwrite_trace! if has_trace?

      if accept_request?
        accept_build_state!
      else
        check_migration_state
        update_build_state!
      end
    end

    private

    def accept_build_state!
      if Time.current - ensure_pending_state.created_at > ACCEPT_TIMEOUT
        metrics.increment_trace_operation(operation: :discarded)

        return update_build_state!
      end

      build.trace_chunks.live.find_each do |chunk|
        chunk.schedule_to_persist!
      end

      metrics.increment_trace_operation(operation: :accepted)

      Result.new(status: 202)
    end

    def overwrite_trace!
      metrics.increment_trace_operation(operation: :overwrite)

      build.trace.set(params[:trace]) if Gitlab::Ci::Features.trace_overwrite?
    end

    def check_migration_state
      return unless accept_available?

      if has_chunks? && !live_chunks_pending?
        metrics.increment_trace_operation(operation: :finalized)
      end
    end

    def update_build_state!
      case build_state
      when 'running'
        build.touch if build.needs_touch?

        Result.new(status: 200)
      when 'success'
        build.success!

        Result.new(status: 200)
      when 'failed'
        build.drop!(params[:failure_reason] || :unknown_failure)

        Result.new(status: 200)
      else
        Result.new(status: 400)
      end
    end

    def accept_available?
      !build_running? && has_checksum? && chunks_migration_enabled?
    end

    def accept_request?
      accept_available? && live_chunks_pending?
    end

    def build_state
      params.dig(:state).to_s
    end

    def has_trace?
      params.dig(:trace).present?
    end

    def has_checksum?
      params.dig(:checksum).present?
    end

    def has_chunks?
      build.trace_chunks.any?
    end

    def live_chunks_pending?
      build.trace_chunks.live.any?
    end

    def build_running?
      build_state == 'running'
    end

    def ensure_pending_state
      Ci::BuildPendingState.create_or_find_by!(
        build_id: build.id,
        state: params.fetch(:state),
        trace_checksum: params.fetch(:checksum),
        failure_reason: params.dig(:failure_reason)
      )
    rescue ActiveRecord::RecordNotFound
      metrics.increment_trace_operation(operation: :conflict)

      build.pending_state
    end

    def chunks_migration_enabled?
      ::Gitlab::Ci::Features.accept_trace?(build.project)
    end
  end
end