Module: WebHooks::AutoDisabling

Extended by:
ActiveSupport::Concern
Includes:
Gitlab::Loggable
Included in:
WebHook
Defined in:
app/models/concerns/web_hooks/auto_disabling.rb

Constant Summary collapse

ENABLED_HOOK_TYPES =
%w[ProjectHook].freeze
MAX_FAILURES =
100
FAILURE_THRESHOLD =
3
EXCEEDED_FAILURE_THRESHOLD =
FAILURE_THRESHOLD + 1
INITIAL_BACKOFF =
1.minute.freeze
MAX_BACKOFF =
1.day.freeze
BACKOFF_GROWTH_FACTOR =
2.0

Constants included from Gitlab::Loggable

Gitlab::Loggable::ANONYMOUS

Instance Method Summary collapse

Methods included from Gitlab::Loggable

#build_structured_payload

Instance Method Details

#alert_statusObject



141
142
143
144
145
146
147
148
149
150
151
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 141

def alert_status
  return :executable unless auto_disabling_enabled?

  if temporarily_disabled?
    :temporarily_disabled
  elsif permanently_disabled?
    :disabled
  else
    :executable
  end
end

#backoff!Object

Don’t actually back-off until FAILURE_THRESHOLD failures have been seen we mark the grace-period using the recent_failures counter



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 103

def backoff!
  return unless auto_disabling_enabled?
  return if permanently_disabled? || (backoff_count >= MAX_FAILURES && temporarily_disabled?)

  attrs = { recent_failures: next_failure_count }

  if recent_failures >= FAILURE_THRESHOLD
    attrs[:backoff_count] = next_backoff_count
    attrs[:disabled_until] = next_backoff.from_now
  end

  assign_attributes(attrs)

  return unless changed?

  logger.info(hook_id: id, action: 'backoff', **attrs)
  save(validate: false)
end

#enable!Object



90
91
92
93
94
95
96
97
98
99
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 90

def enable!
  return unless auto_disabling_enabled?
  return if recent_failures == 0 && disabled_until.nil? && backoff_count == 0

  attrs = { recent_failures: 0, disabled_until: nil, backoff_count: 0 }

  assign_attributes(attrs)
  logger.info(hook_id: id, action: 'enable', **attrs)
  save(validate: false)
end

#executable?Boolean

Returns:

  • (Boolean)


72
73
74
75
76
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 72

def executable?
  return true unless auto_disabling_enabled?

  !temporarily_disabled? && !permanently_disabled?
end

#failed!Object



122
123
124
125
126
127
128
129
130
131
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 122

def failed!
  return unless auto_disabling_enabled?
  return unless recent_failures < MAX_FAILURES

  attrs = { disabled_until: nil, backoff_count: 0, recent_failures: next_failure_count }

  assign_attributes(**attrs)
  logger.info(hook_id: id, action: 'disable', **attrs)
  save(validate: false)
end

#next_backoffObject



133
134
135
136
137
138
139
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 133

def next_backoff
  return MAX_BACKOFF if backoff_count >= 8 # optimization to prevent expensive exponentiation and possible overflows

  (INITIAL_BACKOFF * (BACKOFF_GROWTH_FACTOR**backoff_count))
    .clamp(INITIAL_BACKOFF, MAX_BACKOFF)
    .seconds
end

#permanently_disabled?Boolean

Returns:

  • (Boolean)


84
85
86
87
88
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 84

def permanently_disabled?
  return false unless auto_disabling_enabled?

  recent_failures > FAILURE_THRESHOLD && disabled_until.blank?
end

#temporarily_disabled?Boolean

Returns:

  • (Boolean)


78
79
80
81
82
# File 'app/models/concerns/web_hooks/auto_disabling.rb', line 78

def temporarily_disabled?
  return false unless auto_disabling_enabled?

  disabled_until.present? && disabled_until >= Time.current && recent_failures > FAILURE_THRESHOLD
end