stoplight 5.3.8 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -1
  3. data/lib/generators/stoplight/install/templates/stoplight.rb.erb +2 -0
  4. data/lib/stoplight/admin/views/layout.erb +3 -3
  5. data/lib/stoplight/admin.rb +4 -4
  6. data/lib/stoplight/domain/color.rb +11 -0
  7. data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
  8. data/lib/stoplight/domain/config.rb +55 -0
  9. data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +17 -15
  10. data/lib/stoplight/domain/error.rb +42 -0
  11. data/lib/stoplight/domain/failure.rb +42 -0
  12. data/lib/stoplight/domain/light/configuration_builder_interface.rb +130 -0
  13. data/lib/stoplight/domain/light.rb +198 -0
  14. data/lib/stoplight/domain/light_factory.rb +75 -0
  15. data/lib/stoplight/domain/metadata.rb +65 -0
  16. data/lib/stoplight/domain/state.rb +11 -0
  17. data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
  18. data/lib/stoplight/domain/strategies/green_run_strategy.rb +69 -0
  19. data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
  20. data/lib/stoplight/domain/strategies/run_strategy.rb +27 -0
  21. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +98 -0
  22. data/lib/stoplight/domain/tracker/base.rb +41 -0
  23. data/lib/stoplight/domain/tracker/recovery_probe.rb +72 -0
  24. data/lib/stoplight/domain/tracker/request.rb +67 -0
  25. data/lib/stoplight/domain/traffic_control/base.rb +74 -0
  26. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +57 -0
  27. data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
  28. data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
  29. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +70 -0
  30. data/lib/stoplight/domain/traffic_recovery.rb +13 -0
  31. data/lib/stoplight/infrastructure/data_store/memory/sliding_window.rb +79 -0
  32. data/lib/stoplight/infrastructure/data_store/memory.rb +307 -0
  33. data/lib/stoplight/infrastructure/data_store/redis/lua.rb +25 -0
  34. data/lib/stoplight/infrastructure/data_store/redis.rb +478 -0
  35. data/lib/stoplight/infrastructure/dependency_injection/container.rb +249 -0
  36. data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +13 -0
  37. data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
  38. data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
  39. data/lib/stoplight/infrastructure/notifier/logger.rb +21 -0
  40. data/lib/stoplight/rspec/generic_notifier.rb +1 -1
  41. data/lib/stoplight/version.rb +1 -1
  42. data/lib/stoplight/wiring/container.rb +80 -0
  43. data/lib/stoplight/wiring/default.rb +28 -0
  44. data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
  45. data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
  46. data/lib/stoplight/wiring/fail_safe_data_store.rb +123 -0
  47. data/lib/stoplight/{notifier/fail_safe.rb → wiring/fail_safe_notifier.rb} +22 -13
  48. data/lib/stoplight/wiring/light/default_config.rb +18 -0
  49. data/lib/stoplight/wiring/light/system_config.rb +11 -0
  50. data/lib/stoplight/wiring/light_factory.rb +188 -0
  51. data/lib/stoplight/wiring/public_api.rb +28 -0
  52. data/lib/stoplight/wiring/system_container.rb +9 -0
  53. data/lib/stoplight/wiring/system_light_factory.rb +17 -0
  54. data/lib/stoplight.rb +38 -28
  55. metadata +53 -42
  56. data/lib/stoplight/color.rb +0 -9
  57. data/lib/stoplight/config/dsl.rb +0 -97
  58. data/lib/stoplight/config/library_default_config.rb +0 -21
  59. data/lib/stoplight/config/system_config.rb +0 -7
  60. data/lib/stoplight/data_store/fail_safe.rb +0 -113
  61. data/lib/stoplight/data_store/memory.rb +0 -311
  62. data/lib/stoplight/data_store/redis/lua.rb +0 -23
  63. data/lib/stoplight/data_store/redis.rb +0 -449
  64. data/lib/stoplight/data_store.rb +0 -6
  65. data/lib/stoplight/default.rb +0 -30
  66. data/lib/stoplight/error.rb +0 -10
  67. data/lib/stoplight/failure.rb +0 -71
  68. data/lib/stoplight/light/config.rb +0 -111
  69. data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
  70. data/lib/stoplight/light/green_run_strategy.rb +0 -54
  71. data/lib/stoplight/light/red_run_strategy.rb +0 -27
  72. data/lib/stoplight/light/run_strategy.rb +0 -32
  73. data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
  74. data/lib/stoplight/light.rb +0 -191
  75. data/lib/stoplight/metadata.rb +0 -99
  76. data/lib/stoplight/notifier/generic.rb +0 -79
  77. data/lib/stoplight/notifier/io.rb +0 -21
  78. data/lib/stoplight/notifier/logger.rb +0 -19
  79. data/lib/stoplight/state.rb +0 -9
  80. data/lib/stoplight/traffic_control/base.rb +0 -70
  81. data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
  82. data/lib/stoplight/traffic_control/error_rate.rb +0 -49
  83. data/lib/stoplight/traffic_recovery/base.rb +0 -75
  84. data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
  85. data/lib/stoplight/traffic_recovery.rb +0 -11
  86. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/get_metadata.lua +0 -0
  87. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_failure.lua +0 -0
  88. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_success.lua +0 -0
  89. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_green.lua +0 -0
  90. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_red.lua +0 -0
  91. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_yellow.lua +0 -0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stoplight
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.8
4
+ version: 5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Desautels
@@ -57,50 +57,61 @@ files:
57
57
  - lib/stoplight/admin/views/_card.erb
58
58
  - lib/stoplight/admin/views/index.erb
59
59
  - lib/stoplight/admin/views/layout.erb
60
- - lib/stoplight/color.rb
61
- - lib/stoplight/config/compatibility_result.rb
62
- - lib/stoplight/config/dsl.rb
63
- - lib/stoplight/config/library_default_config.rb
64
- - lib/stoplight/config/system_config.rb
65
- - lib/stoplight/config/user_default_config.rb
66
- - lib/stoplight/data_store.rb
67
- - lib/stoplight/data_store/base.rb
68
- - lib/stoplight/data_store/fail_safe.rb
69
- - lib/stoplight/data_store/memory.rb
70
- - lib/stoplight/data_store/redis.rb
71
- - lib/stoplight/data_store/redis/get_metadata.lua
72
- - lib/stoplight/data_store/redis/lua.rb
73
- - lib/stoplight/data_store/redis/record_failure.lua
74
- - lib/stoplight/data_store/redis/record_success.lua
75
- - lib/stoplight/data_store/redis/transition_to_green.lua
76
- - lib/stoplight/data_store/redis/transition_to_red.lua
77
- - lib/stoplight/data_store/redis/transition_to_yellow.lua
78
- - lib/stoplight/default.rb
79
- - lib/stoplight/error.rb
80
- - lib/stoplight/failure.rb
81
- - lib/stoplight/light.rb
82
- - lib/stoplight/light/config.rb
83
- - lib/stoplight/light/configuration_builder_interface.rb
84
- - lib/stoplight/light/green_run_strategy.rb
85
- - lib/stoplight/light/red_run_strategy.rb
86
- - lib/stoplight/light/run_strategy.rb
87
- - lib/stoplight/light/yellow_run_strategy.rb
88
- - lib/stoplight/metadata.rb
89
- - lib/stoplight/notifier/base.rb
90
- - lib/stoplight/notifier/fail_safe.rb
91
- - lib/stoplight/notifier/generic.rb
92
- - lib/stoplight/notifier/io.rb
93
- - lib/stoplight/notifier/logger.rb
60
+ - lib/stoplight/domain/color.rb
61
+ - lib/stoplight/domain/compatibility_result.rb
62
+ - lib/stoplight/domain/config.rb
63
+ - lib/stoplight/domain/data_store.rb
64
+ - lib/stoplight/domain/error.rb
65
+ - lib/stoplight/domain/failure.rb
66
+ - lib/stoplight/domain/light.rb
67
+ - lib/stoplight/domain/light/configuration_builder_interface.rb
68
+ - lib/stoplight/domain/light_factory.rb
69
+ - lib/stoplight/domain/metadata.rb
70
+ - lib/stoplight/domain/state.rb
71
+ - lib/stoplight/domain/state_transition_notifier.rb
72
+ - lib/stoplight/domain/strategies/green_run_strategy.rb
73
+ - lib/stoplight/domain/strategies/red_run_strategy.rb
74
+ - lib/stoplight/domain/strategies/run_strategy.rb
75
+ - lib/stoplight/domain/strategies/yellow_run_strategy.rb
76
+ - lib/stoplight/domain/tracker/base.rb
77
+ - lib/stoplight/domain/tracker/recovery_probe.rb
78
+ - lib/stoplight/domain/tracker/request.rb
79
+ - lib/stoplight/domain/traffic_control/base.rb
80
+ - lib/stoplight/domain/traffic_control/consecutive_errors.rb
81
+ - lib/stoplight/domain/traffic_control/error_rate.rb
82
+ - lib/stoplight/domain/traffic_recovery.rb
83
+ - lib/stoplight/domain/traffic_recovery/base.rb
84
+ - lib/stoplight/domain/traffic_recovery/consecutive_successes.rb
85
+ - lib/stoplight/infrastructure/data_store/memory.rb
86
+ - lib/stoplight/infrastructure/data_store/memory/sliding_window.rb
87
+ - lib/stoplight/infrastructure/data_store/redis.rb
88
+ - lib/stoplight/infrastructure/data_store/redis/get_metadata.lua
89
+ - lib/stoplight/infrastructure/data_store/redis/lua.rb
90
+ - lib/stoplight/infrastructure/data_store/redis/record_failure.lua
91
+ - lib/stoplight/infrastructure/data_store/redis/record_success.lua
92
+ - lib/stoplight/infrastructure/data_store/redis/transition_to_green.lua
93
+ - lib/stoplight/infrastructure/data_store/redis/transition_to_red.lua
94
+ - lib/stoplight/infrastructure/data_store/redis/transition_to_yellow.lua
95
+ - lib/stoplight/infrastructure/dependency_injection/container.rb
96
+ - lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb
97
+ - lib/stoplight/infrastructure/notifier/generic.rb
98
+ - lib/stoplight/infrastructure/notifier/io.rb
99
+ - lib/stoplight/infrastructure/notifier/logger.rb
94
100
  - lib/stoplight/rspec.rb
95
101
  - lib/stoplight/rspec/generic_notifier.rb
96
- - lib/stoplight/state.rb
97
- - lib/stoplight/traffic_control/base.rb
98
- - lib/stoplight/traffic_control/consecutive_errors.rb
99
- - lib/stoplight/traffic_control/error_rate.rb
100
- - lib/stoplight/traffic_recovery.rb
101
- - lib/stoplight/traffic_recovery/base.rb
102
- - lib/stoplight/traffic_recovery/consecutive_successes.rb
103
102
  - lib/stoplight/version.rb
103
+ - lib/stoplight/wiring/container.rb
104
+ - lib/stoplight/wiring/default.rb
105
+ - lib/stoplight/wiring/default_configuration.rb
106
+ - lib/stoplight/wiring/default_factory_builder.rb
107
+ - lib/stoplight/wiring/fail_safe_data_store.rb
108
+ - lib/stoplight/wiring/fail_safe_notifier.rb
109
+ - lib/stoplight/wiring/light/default_config.rb
110
+ - lib/stoplight/wiring/light/system_config.rb
111
+ - lib/stoplight/wiring/light_factory.rb
112
+ - lib/stoplight/wiring/public_api.rb
113
+ - lib/stoplight/wiring/system_container.rb
114
+ - lib/stoplight/wiring/system_light_factory.rb
104
115
  homepage: https://github.com/bolshakov/stoplight
105
116
  licenses:
106
117
  - MIT
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Color
5
- GREEN = "green"
6
- YELLOW = "yellow"
7
- RED = "red"
8
- end
9
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Config
5
- # This is a DSL for configuring Stoplight settings. It is responsible for
6
- # transforming the provided settings into a format that can be used by Stoplight.
7
- #
8
- # @api private
9
- class DSL
10
- def transform(settings)
11
- if settings.has_key?(:data_store)
12
- settings[:data_store] = build_data_store(settings[:data_store])
13
- end
14
-
15
- if settings.has_key?(:notifiers)
16
- settings[:notifiers] = build_notifiers(settings[:notifiers])
17
- end
18
-
19
- if settings.has_key?(:tracked_errors)
20
- settings[:tracked_errors] = build_tracked_errors(settings[:tracked_errors])
21
- end
22
-
23
- if settings.has_key?(:skipped_errors)
24
- settings[:skipped_errors] = build_skipped_errors(settings[:skipped_errors])
25
- end
26
-
27
- if settings.has_key?(:cool_off_time)
28
- settings[:cool_off_time] = build_cool_off_time(settings[:cool_off_time])
29
- end
30
-
31
- if settings.has_key?(:traffic_control)
32
- settings[:traffic_control] = build_traffic_control(settings[:traffic_control])
33
- end
34
-
35
- if settings.has_key?(:traffic_recovery)
36
- settings[:traffic_recovery] = build_traffic_recovery(settings[:traffic_recovery])
37
- end
38
- settings
39
- end
40
-
41
- private
42
-
43
- def build_data_store(data_store)
44
- DataStore::FailSafe.wrap(data_store)
45
- end
46
-
47
- def build_notifiers(notifiers)
48
- notifiers.map { |notifier| Notifier::FailSafe.wrap(notifier) }
49
- end
50
-
51
- def build_tracked_errors(tracked_error)
52
- Array(tracked_error)
53
- end
54
-
55
- def build_skipped_errors(skipped_errors)
56
- Array(skipped_errors)
57
- end
58
-
59
- def build_cool_off_time(cool_off_time)
60
- cool_off_time.to_i
61
- end
62
-
63
- def build_traffic_control(traffic_control)
64
- case traffic_control
65
- in Stoplight::TrafficControl::Base
66
- traffic_control
67
- in :consecutive_errors
68
- Stoplight::TrafficControl::ConsecutiveErrors.new
69
- in :error_rate
70
- Stoplight::TrafficControl::ErrorRate.new
71
- in {error_rate: error_rate_settings}
72
- Stoplight::TrafficControl::ErrorRate.new(**error_rate_settings)
73
- else
74
- raise Error::ConfigurationError, <<~ERROR
75
- unsupported traffic_control strategy provided (`#{traffic_control}`). Supported options:
76
- * Stoplight::TrafficControl::ConsecutiveErrors
77
- * Stoplight::TrafficControl::ErrorRate
78
- ERROR
79
- end
80
- end
81
-
82
- def build_traffic_recovery(traffic_recovery)
83
- case traffic_recovery
84
- in Stoplight::TrafficRecovery::Base
85
- traffic_recovery
86
- in :consecutive_successes
87
- Stoplight::TrafficRecovery::ConsecutiveSuccesses.new
88
- else
89
- raise Error::ConfigurationError, <<~ERROR
90
- unsupported traffic_recovery strategy provided (`#{traffic_recovery}`). Supported options:
91
- * Stoplight::TrafficRecovery::ConsecutiveSuccesses
92
- ERROR
93
- end
94
- end
95
- end
96
- end
97
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Config
5
- # Provides default settings for the Stoplight library.
6
- # @api private
7
- LibraryDefaultConfig = Light::Config.empty.with(
8
- cool_off_time: Stoplight::Default::COOL_OFF_TIME,
9
- data_store: Stoplight::Default::DATA_STORE,
10
- error_notifier: Stoplight::Default::ERROR_NOTIFIER,
11
- notifiers: Stoplight::Default::NOTIFIERS,
12
- threshold: Stoplight::Default::THRESHOLD,
13
- recovery_threshold: Stoplight::Default::RECOVERY_THRESHOLD,
14
- window_size: Stoplight::Default::WINDOW_SIZE,
15
- tracked_errors: Stoplight::Default::TRACKED_ERRORS,
16
- skipped_errors: Stoplight::Default::SKIPPED_ERRORS,
17
- traffic_control: Stoplight::Default::TRAFFIC_CONTROL,
18
- traffic_recovery: Stoplight::Default::TRAFFIC_RECOVERY
19
- )
20
- end
21
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Config
5
- SystemConfig = LibraryDefaultConfig
6
- end
7
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module Stoplight
6
- module DataStore
7
- # A wrapper around a data store that provides fail-safe mechanisms using a
8
- # circuit breaker. It ensures that operations on the data store can gracefully
9
- # handle failures by falling back to default values when necessary.
10
- #
11
- # @api private
12
- class FailSafe < Base
13
- # @!attribute [r] data_store
14
- # @return [Stoplight::DataStore::Base] The underlying data store being wrapped.
15
- protected attr_reader :data_store
16
-
17
- class << self
18
- # Wraps a data store with fail-safe mechanisms.
19
- #
20
- # @param data_store [Stoplight::DataStore::Base] The data store to wrap.
21
- # @return [Stoplight::DataStore::Base, FailSafe] The original data store if it is already
22
- # a +Memory+ or +FailSafe+ instance, otherwise a new +FailSafe+ instance.
23
- def wrap(data_store)
24
- case data_store
25
- when Memory, FailSafe
26
- data_store
27
- else
28
- new(data_store)
29
- end
30
- end
31
- end
32
-
33
- # @param data_store [Stoplight::DataStore::Base]
34
- def initialize(data_store)
35
- @data_store = data_store
36
- @circuit_breaker = Stoplight(
37
- "stoplight:data_store:fail_safe:#{data_store.class.name}",
38
- data_store: Default::DATA_STORE,
39
- traffic_control: TrafficControl::ConsecutiveErrors.new,
40
- threshold: Default::THRESHOLD
41
- )
42
- end
43
-
44
- def names
45
- with_fallback([]) do
46
- data_store.names
47
- end
48
- end
49
-
50
- def get_metadata(config)
51
- with_fallback(Metadata.new, config) do
52
- data_store.get_metadata(config)
53
- end
54
- end
55
-
56
- def record_failure(config, failure)
57
- with_fallback(Metadata.new, config) do
58
- data_store.record_failure(config, failure)
59
- end
60
- end
61
-
62
- def record_success(config, **args)
63
- with_fallback(nil, config) do
64
- data_store.record_success(config, **args)
65
- end
66
- end
67
-
68
- def record_recovery_probe_success(config, **args)
69
- with_fallback(Metadata.new, config) do
70
- data_store.record_recovery_probe_success(config, **args)
71
- end
72
- end
73
-
74
- def record_recovery_probe_failure(config, failure)
75
- with_fallback(Metadata.new, config) do
76
- data_store.record_recovery_probe_failure(config, failure)
77
- end
78
- end
79
-
80
- def set_state(config, state)
81
- with_fallback(State::UNLOCKED, config) do
82
- data_store.set_state(config, state)
83
- end
84
- end
85
-
86
- def transition_to_color(config, color)
87
- with_fallback(false, config) do
88
- data_store.transition_to_color(config, color)
89
- end
90
- end
91
-
92
- def ==(other)
93
- other.is_a?(self.class) && other.data_store == data_store
94
- end
95
-
96
- # @param default [Object, nil]
97
- # @param config [Stoplight::Light::Config]
98
- private def with_fallback(default = nil, config = nil, &code)
99
- fallback = proc do |error|
100
- config.error_notifier.call(error) if config && error
101
- default
102
- end
103
-
104
- circuit_breaker.run(fallback, &code)
105
- end
106
-
107
- # @return [Stoplight::Light] The circuit breaker used to handle failures.
108
- private def circuit_breaker
109
- @circuit_breaker ||= Stoplight.system_light("stoplight:data_store:fail_safe:#{data_store.class.name}")
110
- end
111
- end
112
- end
113
- end
@@ -1,311 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "monitor"
4
-
5
- module Stoplight
6
- module DataStore
7
- # @see Base
8
- class Memory < Base
9
- include MonitorMixin
10
-
11
- KEY_SEPARATOR = ":"
12
-
13
- def initialize
14
- @errors = Hash.new { |h, k| h[k] = [] }
15
- @successes = Hash.new { |h, k| h[k] = [] }
16
-
17
- @recovery_probe_errors = Hash.new { |h, k| h[k] = [] }
18
- @recovery_probe_successes = Hash.new { |h, k| h[k] = [] }
19
-
20
- @metadata = Hash.new { |h, k| h[k] = Metadata.new }
21
- super # MonitorMixin
22
- end
23
-
24
- # @return [Array<String>]
25
- def names
26
- synchronize { @metadata.keys }
27
- end
28
-
29
- # @param config [Stoplight::Light::Config]
30
- # @return [Stoplight::Metadata]
31
- def get_metadata(config)
32
- light_name = config.name
33
-
34
- synchronize do
35
- current_time = Time.now
36
- recovery_window = (current_time - config.cool_off_time)..current_time
37
- recovered_at = @metadata[light_name].recovered_at
38
- window = if config.window_size
39
- window_start = [recovered_at, (current_time - config.window_size)].compact.max
40
- (window_start..current_time)
41
- else
42
- (..current_time)
43
- end
44
-
45
- errors = @errors[config.name].count do |request_time|
46
- window.cover?(request_time)
47
- end
48
-
49
- successes = @successes[config.name].count do |request_time|
50
- window.cover?(request_time)
51
- end
52
-
53
- recovery_probe_errors = @recovery_probe_errors[config.name].count do |request_time|
54
- recovery_window.cover?(request_time)
55
- end
56
- recovery_probe_successes = @recovery_probe_successes[config.name].count do |request_time|
57
- recovery_window.cover?(request_time)
58
- end
59
-
60
- @metadata[light_name].with(
61
- current_time:,
62
- errors:,
63
- successes:,
64
- recovery_probe_errors:,
65
- recovery_probe_successes:
66
- )
67
- end
68
- end
69
-
70
- # @param metrics [<Time>]
71
- # @param window_size [Numeric, nil]
72
- # @return [void]
73
- def cleanup(metrics, window_size:)
74
- min_age = Time.now - [window_size&.*(3), METRICS_RETENTION_TIME].compact.min
75
-
76
- metrics.reject! { _1 < min_age }
77
- end
78
-
79
- # @param config [Stoplight::Light::Config]
80
- # @param failure [Stoplight::Failure]
81
- # @return [Stoplight::Metadata]
82
- def record_failure(config, failure)
83
- light_name = config.name
84
-
85
- synchronize do
86
- @errors[light_name].unshift(failure.time) if config.window_size
87
-
88
- cleanup(@errors[light_name], window_size: config.window_size)
89
-
90
- metadata = @metadata[light_name]
91
- @metadata[light_name] = if metadata.last_error_at.nil? || failure.time > metadata.last_error_at
92
- metadata.with(
93
- last_error_at: failure.time,
94
- last_error: failure,
95
- consecutive_errors: metadata.consecutive_errors.succ,
96
- consecutive_successes: 0
97
- )
98
- else
99
- metadata.with(
100
- consecutive_errors: metadata.consecutive_errors.succ,
101
- consecutive_successes: 0
102
- )
103
- end
104
- get_metadata(config)
105
- end
106
- end
107
-
108
- # @param config [Stoplight::Light::Config]
109
- # @param request_id [String]
110
- # @param request_time [Time]
111
- # @return [void]
112
- def record_success(config, request_time: Time.now, request_id: SecureRandom.hex(12))
113
- light_name = config.name
114
-
115
- synchronize do
116
- @successes[light_name].unshift(request_time) if config.window_size
117
- cleanup(@successes[light_name], window_size: config.window_size)
118
-
119
- metadata = @metadata[light_name]
120
- @metadata[light_name] = if metadata.last_success_at.nil? || request_time > metadata.last_success_at
121
- metadata.with(
122
- last_success_at: request_time,
123
- consecutive_errors: 0,
124
- consecutive_successes: metadata.consecutive_successes.succ
125
- )
126
- else
127
- metadata.with(
128
- consecutive_errors: 0,
129
- consecutive_successes: metadata.consecutive_successes.succ
130
- )
131
- end
132
- end
133
- end
134
-
135
- # @param config [Stoplight::Light::Config]
136
- # @param failure [Stoplight::Failure]
137
- # @return [Stoplight::Metadata]
138
- def record_recovery_probe_failure(config, failure)
139
- light_name = config.name
140
-
141
- synchronize do
142
- @recovery_probe_errors[light_name].unshift(failure.time)
143
- cleanup(@recovery_probe_errors[light_name], window_size: config.cool_off_time)
144
-
145
- metadata = @metadata[light_name]
146
- @metadata[light_name] = if metadata.last_error_at.nil? || failure.time > metadata.last_error_at
147
- metadata.with(
148
- last_error_at: failure.time,
149
- last_error: failure,
150
- consecutive_errors: metadata.consecutive_errors.succ,
151
- consecutive_successes: 0
152
- )
153
- else
154
- metadata.with(
155
- consecutive_errors: metadata.consecutive_errors.succ,
156
- consecutive_successes: 0
157
- )
158
- end
159
- get_metadata(config)
160
- end
161
- end
162
-
163
- # @param config [Stoplight::Light::Config]
164
- # @param request_id [String]
165
- # @param request_time [Time]
166
- # @return [Stoplight::Metadata]
167
- def record_recovery_probe_success(config, request_time: Time.now, request_id: SecureRandom.hex(12))
168
- light_name = config.name
169
-
170
- synchronize do
171
- @recovery_probe_successes[light_name].unshift(request_time)
172
- cleanup(@recovery_probe_successes[light_name], window_size: config.cool_off_time)
173
-
174
- metadata = @metadata[light_name]
175
- @metadata[light_name] = if metadata.last_success_at.nil? || request_time > metadata.last_success_at
176
- metadata.with(
177
- last_success_at: request_time,
178
- consecutive_errors: 0,
179
- consecutive_successes: metadata.consecutive_successes.succ
180
- )
181
- else
182
- metadata.with(
183
- consecutive_errors: 0,
184
- consecutive_successes: metadata.consecutive_successes.succ
185
- )
186
- end
187
- get_metadata(config)
188
- end
189
- end
190
-
191
- # @param config [Stoplight::Light::Config]
192
- # @param state [String]
193
- # @return [String]
194
- def set_state(config, state)
195
- light_name = config.name
196
-
197
- synchronize do
198
- metadata = @metadata[light_name]
199
- @metadata[light_name] = metadata.with(locked_state: state)
200
- end
201
- state
202
- end
203
-
204
- # @return [String]
205
- def inspect
206
- "#<#{self.class.name}>"
207
- end
208
-
209
- # Combined method that performs the state transition based on color
210
- #
211
- # @param config [Stoplight::Light::Config] The light configuration
212
- # @param color [String] The color to transition to ("GREEN", "YELLOW", or "RED")
213
- # @param current_time [Time]
214
- # @return [Boolean] true if this is the first instance to detect this transition
215
- def transition_to_color(config, color, current_time: Time.now)
216
- case color
217
- when Color::GREEN
218
- transition_to_green(config)
219
- when Color::YELLOW
220
- transition_to_yellow(config, current_time:)
221
- when Color::RED
222
- transition_to_red(config, current_time:)
223
- else
224
- raise ArgumentError, "Invalid color: #{color}"
225
- end
226
- end
227
-
228
- # Transitions to GREEN state and ensures only one notification
229
- #
230
- # @param config [Stoplight::Light::Config] The light configuration
231
- # @return [Boolean] true if this is the first instance to detect this transition
232
- private def transition_to_green(config, current_time: Time.now)
233
- light_name = config.name
234
-
235
- synchronize do
236
- metadata = @metadata[light_name]
237
- if metadata.recovered_at
238
- false
239
- else
240
- @metadata[light_name] = metadata.with(
241
- recovered_at: current_time,
242
- recovery_started_at: nil,
243
- breached_at: nil,
244
- recovery_scheduled_after: nil
245
- )
246
- true
247
- end
248
- end
249
- end
250
-
251
- # Transitions to YELLOW (recovery) state and ensures only one notification
252
- #
253
- # @param config [Stoplight::Light::Config] The light configuration
254
- # @param current_time [Time]
255
- # @return [Boolean] true if this is the first instance to detect this transition
256
- private def transition_to_yellow(config, current_time: Time.now)
257
- light_name = config.name
258
-
259
- synchronize do
260
- metadata = @metadata[light_name]
261
- if metadata.recovery_started_at.nil?
262
- @metadata[light_name] = metadata.with(
263
- recovery_started_at: current_time,
264
- recovery_scheduled_after: nil,
265
- recovered_at: nil,
266
- breached_at: nil
267
- )
268
- true
269
- else
270
- @metadata[light_name] = metadata.with(
271
- recovery_scheduled_after: nil,
272
- recovered_at: nil,
273
- breached_at: nil
274
- )
275
- false
276
- end
277
- end
278
- end
279
-
280
- # Transitions to RED state and ensures only one notification
281
- #
282
- # @param config [Stoplight::Light::Config] The light configuration
283
- # @param current_time [Time]
284
- # @return [Boolean] true if this is the first instance to detect this transition
285
- private def transition_to_red(config, current_time: Time.now)
286
- light_name = config.name
287
- recovery_scheduled_after = current_time + config.cool_off_time
288
-
289
- synchronize do
290
- metadata = @metadata[light_name]
291
- if metadata.breached_at
292
- @metadata[light_name] = metadata.with(
293
- recovery_scheduled_after: recovery_scheduled_after,
294
- recovery_started_at: nil,
295
- recovered_at: nil
296
- )
297
- false
298
- else
299
- @metadata[light_name] = metadata.with(
300
- breached_at: current_time,
301
- recovery_scheduled_after: recovery_scheduled_after,
302
- recovery_started_at: nil,
303
- recovered_at: nil
304
- )
305
- true
306
- end
307
- end
308
- end
309
- end
310
- end
311
- end