stoplight 5.6.0 → 5.7.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.
- checksums.yaml +4 -4
- data/lib/stoplight/admin/dependencies.rb +1 -1
- data/lib/stoplight/admin/helpers.rb +10 -5
- data/lib/stoplight/admin/lights_repository.rb +18 -15
- data/lib/stoplight/admin.rb +2 -1
- data/lib/stoplight/common/deprecations.rb +11 -0
- data/lib/stoplight/domain/config.rb +5 -1
- data/lib/stoplight/domain/data_store.rb +17 -1
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +120 -16
- data/lib/stoplight/domain/light.rb +31 -20
- data/lib/stoplight/domain/metrics.rb +6 -27
- data/lib/stoplight/domain/recovery_lock_token.rb +15 -0
- data/lib/stoplight/domain/storage/metrics.rb +42 -0
- data/lib/stoplight/domain/storage/recovery_lock.rb +56 -0
- data/lib/stoplight/domain/storage/state.rb +87 -0
- data/lib/stoplight/domain/strategies/run_strategy.rb +0 -5
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +58 -32
- data/lib/stoplight/domain/tracker/base.rb +0 -29
- data/lib/stoplight/domain/tracker/recovery_probe.rb +23 -22
- data/lib/stoplight/domain/tracker/request.rb +23 -19
- data/lib/stoplight/domain/traffic_recovery/base.rb +1 -2
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +2 -8
- data/lib/stoplight/domain/traffic_recovery.rb +0 -1
- data/lib/stoplight/infrastructure/data_store/fail_safe.rb +164 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_store.rb +54 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_token.rb +20 -0
- data/lib/stoplight/infrastructure/data_store/memory.rb +61 -32
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_failure.lua +27 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_success.lua +23 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/release_lock.lua +6 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_store.rb +73 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_token.rb +35 -0
- data/lib/stoplight/infrastructure/data_store/redis/scripting.rb +71 -0
- data/lib/stoplight/infrastructure/data_store/redis.rb +133 -162
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +62 -0
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +48 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +36 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +55 -0
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +55 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/data_store/base.rb +11 -0
- data/lib/stoplight/wiring/data_store/memory.rb +10 -0
- data/lib/stoplight/wiring/data_store/redis.rb +25 -0
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_configuration.rb +1 -1
- data/lib/stoplight/wiring/default_factory_builder.rb +1 -1
- data/lib/stoplight/wiring/light_builder.rb +185 -0
- data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +55 -0
- data/lib/stoplight/wiring/light_factory/config_normalizer.rb +71 -0
- data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +72 -0
- data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +26 -0
- data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +21 -0
- data/lib/stoplight/wiring/light_factory.rb +45 -132
- data/lib/stoplight/wiring/notifier_factory.rb +26 -0
- data/lib/stoplight/wiring/public_api.rb +3 -2
- data/lib/stoplight.rb +18 -3
- metadata +50 -15
- data/lib/stoplight/infrastructure/data_store/redis/lua.rb +0 -25
- data/lib/stoplight/infrastructure/dependency_injection/container.rb +0 -249
- data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +0 -13
- data/lib/stoplight/wiring/container.rb +0 -80
- data/lib/stoplight/wiring/fail_safe_data_store.rb +0 -147
- data/lib/stoplight/wiring/fail_safe_notifier.rb +0 -79
- data/lib/stoplight/wiring/system_container.rb +0 -9
- data/lib/stoplight/wiring/system_light_factory.rb +0 -17
- /data/lib/stoplight/infrastructure/data_store/redis/{get_metrics.lua → lua_scripts/get_metrics.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{record_failure.lua → lua_scripts/record_failure.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{record_success.lua → lua_scripts/record_success.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_green.lua → lua_scripts/transition_to_green.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_red.lua → lua_scripts/transition_to_red.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_yellow.lua → lua_scripts/transition_to_yellow.lua} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d96e1b5dcce81c642d059b07b2e274b8c7dbd7f246b86ee25ce40567eaba8418
|
|
4
|
+
data.tar.gz: e6b04f0ff592f89f345ff3f9ec9e26b86463ff0baeb56ef752cbd00b7f8952b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 01c5c2356f84eaeade94edba7113e700682911558d165b8b60186abbe0cda3d0f453427b8ab6e96f4ab51948e3ec5a109e53e1b51cc15c46c3fd79aad4865768
|
|
7
|
+
data.tar.gz: 94633b744ef88219f66289d9aae91bbb35b013bbea82c84f0f253abd15d4e5bd7ea5a123c39dffc36911479b1663cee71f786110be0ee15c3c8f56d035032e76
|
|
@@ -15,11 +15,16 @@ module Stoplight
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
private def data_store
|
|
18
|
-
settings.data_store.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if settings.data_store.is_a?(Stoplight::DataStore::Memory)
|
|
19
|
+
raise "Stoplight Admin requires a persistent data store, but the current data store is Memory. " \
|
|
20
|
+
"Please configure a different data store in your Stoplight configuration."
|
|
21
|
+
else
|
|
22
|
+
Stoplight::Wiring::LightBuilder.new(
|
|
23
|
+
{
|
|
24
|
+
data_store: settings.data_store,
|
|
25
|
+
config: Wiring::Light::DefaultConfig
|
|
26
|
+
}
|
|
27
|
+
).__send__(:data_store)
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
end
|
|
@@ -4,11 +4,11 @@ module Stoplight
|
|
|
4
4
|
class Admin
|
|
5
5
|
class LightsRepository
|
|
6
6
|
# @!attribute data_store
|
|
7
|
-
# @return [Stoplight::DataStore
|
|
7
|
+
# @return [Stoplight::Domain::DataStore]
|
|
8
8
|
attr_reader :data_store
|
|
9
9
|
private :data_store
|
|
10
10
|
|
|
11
|
-
# @param data_store [Stoplight::DataStore
|
|
11
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
12
12
|
def initialize(data_store:)
|
|
13
13
|
@data_store = data_store
|
|
14
14
|
end
|
|
@@ -37,46 +37,49 @@ module Stoplight
|
|
|
37
37
|
# color
|
|
38
38
|
# @return [void]
|
|
39
39
|
def lock(name, color = nil)
|
|
40
|
-
|
|
40
|
+
config = build_config(name)
|
|
41
|
+
color ||= data_store.get_state_snapshot(config).color
|
|
41
42
|
|
|
42
|
-
case color
|
|
43
|
+
case color
|
|
43
44
|
when Stoplight::Color::GREEN
|
|
44
|
-
|
|
45
|
+
data_store.set_state(config, Stoplight::State::LOCKED_GREEN)
|
|
45
46
|
else
|
|
46
|
-
|
|
47
|
+
data_store.set_state(config, Stoplight::State::LOCKED_RED)
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
# @param name [String] unlocks light by its name
|
|
51
52
|
# @return [void]
|
|
52
53
|
def unlock(name)
|
|
53
|
-
|
|
54
|
+
config = build_config(name)
|
|
55
|
+
data_store.set_state(config, Domain::State::UNLOCKED)
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
# @param name [String] removes light metadata by its name
|
|
57
59
|
# @return [void]
|
|
58
60
|
def remove(name)
|
|
59
|
-
|
|
61
|
+
config = build_config(name)
|
|
60
62
|
|
|
61
|
-
data_store.delete_light(
|
|
63
|
+
data_store.delete_light(config)
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
private def load_light(name)
|
|
65
|
-
|
|
67
|
+
config = build_config(name)
|
|
68
|
+
|
|
66
69
|
# failures, state
|
|
67
|
-
state_snapshot = data_store.get_state_snapshot(
|
|
68
|
-
metrics = data_store.get_metrics(
|
|
70
|
+
state_snapshot = data_store.get_state_snapshot(config)
|
|
71
|
+
metrics = data_store.get_metrics(config)
|
|
69
72
|
|
|
70
73
|
Light.new(
|
|
71
74
|
name: name,
|
|
72
|
-
color:
|
|
75
|
+
color: state_snapshot.color,
|
|
73
76
|
state: state_snapshot.locked_state,
|
|
74
77
|
failures: [metrics.last_error].compact
|
|
75
78
|
)
|
|
76
79
|
end
|
|
77
80
|
|
|
78
|
-
private def
|
|
79
|
-
|
|
81
|
+
private def build_config(name)
|
|
82
|
+
Wiring::Light::DefaultConfig.with(name:)
|
|
80
83
|
end
|
|
81
84
|
end
|
|
82
85
|
end
|
data/lib/stoplight/admin.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Stoplight
|
|
|
7
7
|
# # @!attribute [r] name
|
|
8
8
|
# @return [String]
|
|
9
9
|
#
|
|
10
|
-
# @!attribute [r] cool_off_time
|
|
10
|
+
# @!attribute [r] cool_off_time - cool-off time in seconds
|
|
11
11
|
# @return [Numeric]
|
|
12
12
|
#
|
|
13
13
|
# @!attribute [r] threshold
|
|
@@ -50,6 +50,10 @@ module Stoplight
|
|
|
50
50
|
|
|
51
51
|
!skip && track
|
|
52
52
|
end
|
|
53
|
+
|
|
54
|
+
def cool_off_time_in_milliseconds
|
|
55
|
+
cool_off_time * 1_000
|
|
56
|
+
end
|
|
53
57
|
end
|
|
54
58
|
end
|
|
55
59
|
end
|
|
@@ -44,7 +44,11 @@ module Stoplight
|
|
|
44
44
|
#
|
|
45
45
|
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
46
46
|
# @return [void]
|
|
47
|
-
def
|
|
47
|
+
def clear_metrics(config)
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clear_recovery_metrics(config)
|
|
48
52
|
raise NotImplementedError
|
|
49
53
|
end
|
|
50
54
|
|
|
@@ -91,6 +95,18 @@ module Stoplight
|
|
|
91
95
|
raise NotImplementedError
|
|
92
96
|
end
|
|
93
97
|
|
|
98
|
+
# Acquires recovery lock for serializing probe execution.
|
|
99
|
+
#
|
|
100
|
+
# @param config [Stoplight::Domain::Config]
|
|
101
|
+
# @return [Stoplight::Domain::LockToken, nil] Lock if acquired, nil if contended
|
|
102
|
+
def acquire_recovery_lock(config) = raise NotImplementedError
|
|
103
|
+
|
|
104
|
+
# Releases previously acquired lock.
|
|
105
|
+
#
|
|
106
|
+
# @param lock [Stoplight::Domain::RecoveryLockToken]
|
|
107
|
+
# @return [void]
|
|
108
|
+
def release_recovery_lock(lock) = raise NotImplementedError
|
|
109
|
+
|
|
94
110
|
# Transitions the Stoplight to the specified color.
|
|
95
111
|
#
|
|
96
112
|
# This method performs a color transition operation that works across distributed instances
|
|
@@ -15,9 +15,22 @@ module Stoplight
|
|
|
15
15
|
#
|
|
16
16
|
# @param data_store [DataStore::Base]
|
|
17
17
|
# @return [Stoplight::Light]
|
|
18
|
-
# @deprecated
|
|
18
|
+
# @deprecated
|
|
19
19
|
def with_data_store(data_store)
|
|
20
|
-
|
|
20
|
+
deprecate(<<~MSG)
|
|
21
|
+
Light#with_data_store is deprecated and will be removed in v6.0.0.
|
|
22
|
+
|
|
23
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
24
|
+
modifications.
|
|
25
|
+
|
|
26
|
+
Instead of:
|
|
27
|
+
light = Stoplight('api-call')
|
|
28
|
+
modified = light.with_data_store(data_stare)
|
|
29
|
+
|
|
30
|
+
Configure correctly from the start:
|
|
31
|
+
Stoplight('api-call', data_store:)
|
|
32
|
+
MSG
|
|
33
|
+
with_without_warning(data_store:)
|
|
21
34
|
end
|
|
22
35
|
|
|
23
36
|
# Configures cool off time. Stoplight automatically tries to recover
|
|
@@ -29,9 +42,22 @@ module Stoplight
|
|
|
29
42
|
#
|
|
30
43
|
# @param cool_off_time [Numeric] number of seconds
|
|
31
44
|
# @return [Stoplight::Light]
|
|
32
|
-
# @deprecated
|
|
45
|
+
# @deprecated
|
|
33
46
|
def with_cool_off_time(cool_off_time)
|
|
34
|
-
|
|
47
|
+
deprecate(<<~MSG)
|
|
48
|
+
Light#with_cool_off_time is deprecated and will be removed in v6.0.0.
|
|
49
|
+
|
|
50
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
51
|
+
modifications.
|
|
52
|
+
|
|
53
|
+
Instead of:
|
|
54
|
+
light = Stoplight('api-call')
|
|
55
|
+
modified = light.with_cool_off_time(cool_off_time)
|
|
56
|
+
|
|
57
|
+
Configure correctly from the start:
|
|
58
|
+
Stoplight('api-call', cool_off_time:)
|
|
59
|
+
MSG
|
|
60
|
+
with_without_warning(cool_off_time:)
|
|
35
61
|
end
|
|
36
62
|
|
|
37
63
|
# Configures custom threshold. After this number of failures Stoplight
|
|
@@ -43,9 +69,22 @@ module Stoplight
|
|
|
43
69
|
#
|
|
44
70
|
# @param threshold [Numeric]
|
|
45
71
|
# @return [Stoplight::Light]
|
|
46
|
-
# @deprecated
|
|
72
|
+
# @deprecated
|
|
47
73
|
def with_threshold(threshold)
|
|
48
|
-
|
|
74
|
+
deprecate(<<~MSG)
|
|
75
|
+
Light#with_threshold is deprecated and will be removed in v6.0.0.
|
|
76
|
+
|
|
77
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
78
|
+
modifications.
|
|
79
|
+
|
|
80
|
+
Instead of:
|
|
81
|
+
light = Stoplight('api-call')
|
|
82
|
+
modified = light.with_threshold(threshold)
|
|
83
|
+
|
|
84
|
+
Configure correctly from the start:
|
|
85
|
+
Stoplight('api-call', threshold:)
|
|
86
|
+
MSG
|
|
87
|
+
with_without_warning(threshold:)
|
|
49
88
|
end
|
|
50
89
|
|
|
51
90
|
# Configures custom window size which Stoplight uses to count failures. For example,
|
|
@@ -60,9 +99,22 @@ module Stoplight
|
|
|
60
99
|
#
|
|
61
100
|
# @param window_size [Numeric] number of seconds
|
|
62
101
|
# @return [Stoplight::Light]
|
|
63
|
-
# @deprecated
|
|
102
|
+
# @deprecated
|
|
64
103
|
def with_window_size(window_size)
|
|
65
|
-
|
|
104
|
+
deprecate(<<~MSG)
|
|
105
|
+
Light#with_window_size is deprecated and will be removed in v6.0.0.
|
|
106
|
+
|
|
107
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
108
|
+
modifications.
|
|
109
|
+
|
|
110
|
+
Instead of:
|
|
111
|
+
light = Stoplight('api-call')
|
|
112
|
+
modified = light.with_window_size(window_size)
|
|
113
|
+
|
|
114
|
+
Configure correctly from the start:
|
|
115
|
+
Stoplight('api-call', window_size:)
|
|
116
|
+
MSG
|
|
117
|
+
with_without_warning(window_size:)
|
|
66
118
|
end
|
|
67
119
|
|
|
68
120
|
# Configures custom notifier
|
|
@@ -75,17 +127,43 @@ module Stoplight
|
|
|
75
127
|
#
|
|
76
128
|
# @param notifiers [Array<Notifier::Base>]
|
|
77
129
|
# @return [Stoplight::Light]
|
|
78
|
-
# @deprecated
|
|
130
|
+
# @deprecated
|
|
79
131
|
def with_notifiers(notifiers)
|
|
80
|
-
|
|
132
|
+
deprecate(<<~MSG)
|
|
133
|
+
Light#with_notifiers is deprecated and will be removed in v6.0.0.
|
|
134
|
+
|
|
135
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
136
|
+
modifications.
|
|
137
|
+
|
|
138
|
+
Instead of:
|
|
139
|
+
light = Stoplight('api-call')
|
|
140
|
+
modified = light.with_notifiers(notifiers)
|
|
141
|
+
|
|
142
|
+
Configure correctly from the start:
|
|
143
|
+
Stoplight('api-call', notifiers:)
|
|
144
|
+
MSG
|
|
145
|
+
with_without_warning(notifiers:)
|
|
81
146
|
end
|
|
82
147
|
|
|
83
148
|
# @param error_notifier [Proc]
|
|
84
149
|
# @return [Stoplight::Light]
|
|
85
150
|
# @api private
|
|
86
|
-
# @deprecated
|
|
151
|
+
# @deprecated
|
|
87
152
|
def with_error_notifier(&error_notifier)
|
|
88
|
-
|
|
153
|
+
deprecate(<<~MSG)
|
|
154
|
+
Light#with_error_notifier is deprecated and will be removed in v6.0.0.
|
|
155
|
+
|
|
156
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
157
|
+
modifications.
|
|
158
|
+
|
|
159
|
+
Instead of:
|
|
160
|
+
light = Stoplight('api-call')
|
|
161
|
+
modified = light.with_error_notifier { |error| warn error }
|
|
162
|
+
|
|
163
|
+
Configure correctly from the start:
|
|
164
|
+
Stoplight('api-call', error_notifier: ->(error) { warn error })
|
|
165
|
+
MSG
|
|
166
|
+
with_without_warning(error_notifier: error_notifier)
|
|
89
167
|
end
|
|
90
168
|
|
|
91
169
|
# Configures a custom list of tracked errors that counts toward the threshold.
|
|
@@ -101,9 +179,22 @@ module Stoplight
|
|
|
101
179
|
#
|
|
102
180
|
# @param tracked_errors [Array<StandardError>]
|
|
103
181
|
# @return [Stoplight::Light]
|
|
104
|
-
# @deprecated
|
|
182
|
+
# @deprecated
|
|
105
183
|
def with_tracked_errors(*tracked_errors)
|
|
106
|
-
|
|
184
|
+
deprecate(<<~MSG)
|
|
185
|
+
Light#with_tracked_errors is deprecated and will be removed in v6.0.0.
|
|
186
|
+
|
|
187
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
188
|
+
modifications.
|
|
189
|
+
|
|
190
|
+
Instead of:
|
|
191
|
+
light = Stoplight('api-call')
|
|
192
|
+
modified = light.with_tracked_errors(TimeoutError, NetworkError)
|
|
193
|
+
|
|
194
|
+
Configure correctly from the start:
|
|
195
|
+
Stoplight('api-call', tracked_errors: [TimeoutError, NetworkError])
|
|
196
|
+
MSG
|
|
197
|
+
with_without_warning(tracked_errors:)
|
|
107
198
|
end
|
|
108
199
|
|
|
109
200
|
# Configures a custom list of skipped errors that do not count toward the threshold.
|
|
@@ -120,9 +211,22 @@ module Stoplight
|
|
|
120
211
|
#
|
|
121
212
|
# @param skipped_errors [Array<Exception>]
|
|
122
213
|
# @return [Stoplight::Light]
|
|
123
|
-
# @deprecated
|
|
214
|
+
# @deprecated
|
|
124
215
|
def with_skipped_errors(*skipped_errors)
|
|
125
|
-
|
|
216
|
+
deprecate(<<~MSG)
|
|
217
|
+
Light#with_skipped_errors is deprecated and will be removed in v6.0.0.
|
|
218
|
+
|
|
219
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
220
|
+
modifications.
|
|
221
|
+
|
|
222
|
+
Instead of:
|
|
223
|
+
light = Stoplight('api-call')
|
|
224
|
+
modified = light.with_skipped_errors(ActiveRecord::RecordNotFound)
|
|
225
|
+
|
|
226
|
+
Configure correctly from the start:
|
|
227
|
+
Stoplight('api-call', skipped_errors: [ActiveRecord::RecordNotFound])
|
|
228
|
+
MSG
|
|
229
|
+
with_without_warning(skipped_errors:)
|
|
126
230
|
end
|
|
127
231
|
end
|
|
128
232
|
end
|
|
@@ -8,6 +8,7 @@ module Stoplight
|
|
|
8
8
|
# @api private use +Stoplight()+ method instead
|
|
9
9
|
class Light
|
|
10
10
|
extend Forwardable
|
|
11
|
+
include Common::Deprecations
|
|
11
12
|
include ConfigurationBuilderInterface
|
|
12
13
|
|
|
13
14
|
# @!attribute [r] config
|
|
@@ -32,22 +33,22 @@ module Stoplight
|
|
|
32
33
|
# @return [Stoplight::Domain::Strategies::RedRunStrategy]
|
|
33
34
|
protected attr_reader :red_run_strategy
|
|
34
35
|
|
|
35
|
-
# @!attribute [r] data_store
|
|
36
|
-
# @return [Stoplight::Light::Base]
|
|
37
|
-
protected attr_reader :data_store
|
|
38
|
-
|
|
39
36
|
# @!attribute [r] factory
|
|
40
37
|
# @return [Stoplight::Domain::LightFactory]
|
|
41
38
|
protected attr_reader :factory
|
|
42
39
|
|
|
40
|
+
# @!attribute state_store
|
|
41
|
+
# @param [Stoplight::Domain::Storage::State]
|
|
42
|
+
protected attr_reader :state_store
|
|
43
|
+
|
|
43
44
|
# @param config [Stoplight::Domain::Config]
|
|
44
|
-
def initialize(config, green_run_strategy:, yellow_run_strategy:, red_run_strategy:,
|
|
45
|
+
def initialize(config, green_run_strategy:, yellow_run_strategy:, red_run_strategy:, factory:, state_store:)
|
|
45
46
|
@config = config
|
|
46
|
-
@data_store = data_store
|
|
47
47
|
@green_run_strategy = green_run_strategy
|
|
48
48
|
@yellow_run_strategy = yellow_run_strategy
|
|
49
49
|
@red_run_strategy = red_run_strategy
|
|
50
50
|
@factory = factory
|
|
51
|
+
@state_store = state_store
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# Returns the current state of the light:
|
|
@@ -56,9 +57,7 @@ module Stoplight
|
|
|
56
57
|
# * +Stoplight::State::UNLOCKED+ -- light is not locked and follow the configured rules
|
|
57
58
|
#
|
|
58
59
|
# @return [String]
|
|
59
|
-
def state
|
|
60
|
-
state_snapshot.locked_state
|
|
61
|
-
end
|
|
60
|
+
def state = state_snapshot.locked_state
|
|
62
61
|
|
|
63
62
|
# Returns current color:
|
|
64
63
|
# * +Stoplight::Color::GREEN+ -- circuit breaker is closed
|
|
@@ -70,9 +69,7 @@ module Stoplight
|
|
|
70
69
|
# light.color #=> Color::GREEN
|
|
71
70
|
#
|
|
72
71
|
# @return [String] returns current light color
|
|
73
|
-
def color
|
|
74
|
-
state_snapshot.color
|
|
75
|
-
end
|
|
72
|
+
def color = state_snapshot.color
|
|
76
73
|
|
|
77
74
|
# Runs the given block of code with this circuit breaker
|
|
78
75
|
#
|
|
@@ -112,7 +109,7 @@ module Stoplight
|
|
|
112
109
|
else raise Error::IncorrectColor
|
|
113
110
|
end
|
|
114
111
|
|
|
115
|
-
|
|
112
|
+
state_store.set_state(state)
|
|
116
113
|
|
|
117
114
|
self
|
|
118
115
|
end
|
|
@@ -126,7 +123,7 @@ module Stoplight
|
|
|
126
123
|
#
|
|
127
124
|
# @return [Stoplight::Light] returns unlocked light (circuit breaker)
|
|
128
125
|
def unlock
|
|
129
|
-
|
|
126
|
+
state_store.set_state(State::UNLOCKED)
|
|
130
127
|
|
|
131
128
|
self
|
|
132
129
|
end
|
|
@@ -136,9 +133,7 @@ module Stoplight
|
|
|
136
133
|
# @param other [any]
|
|
137
134
|
# @return [Boolean]
|
|
138
135
|
def ==(other)
|
|
139
|
-
other.is_a?(self.class) &&
|
|
140
|
-
green_run_strategy == other.green_run_strategy && yellow_run_strategy == other.yellow_run_strategy &&
|
|
141
|
-
red_run_strategy == other.red_run_strategy && factory == other.factory
|
|
136
|
+
other.is_a?(self.class) && factory == other.factory
|
|
142
137
|
end
|
|
143
138
|
|
|
144
139
|
# Reconfigures the light with updated settings and returns a new instance.
|
|
@@ -171,8 +166,26 @@ module Stoplight
|
|
|
171
166
|
# # Run the lights with their respective configurations
|
|
172
167
|
# invoices_light.run(->(error) { [] }) { call_invoices_api }
|
|
173
168
|
# payment_light.run(->(error) { nil }) { call_payment_api }
|
|
169
|
+
# @deprecated
|
|
174
170
|
# @see +Stoplight()+
|
|
175
171
|
def with(**settings)
|
|
172
|
+
deprecate(<<~MSG)
|
|
173
|
+
Light#with is deprecated and will be removed in v6.0.0.
|
|
174
|
+
|
|
175
|
+
Circuit breakers should be configured once at creation, not cloned with
|
|
176
|
+
modifications.
|
|
177
|
+
|
|
178
|
+
Instead of:
|
|
179
|
+
light = Stoplight('api-call', threshold: 5)
|
|
180
|
+
modified = light.with(threshold: 10)
|
|
181
|
+
|
|
182
|
+
Configure correctly from the start:
|
|
183
|
+
Stoplight('api-call', threshold: 10)
|
|
184
|
+
MSG
|
|
185
|
+
with_without_warning(**settings)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private def with_without_warning(**settings)
|
|
176
189
|
factory.build_with(**settings)
|
|
177
190
|
end
|
|
178
191
|
|
|
@@ -189,9 +202,7 @@ module Stoplight
|
|
|
189
202
|
end
|
|
190
203
|
end
|
|
191
204
|
|
|
192
|
-
def state_snapshot
|
|
193
|
-
data_store.get_state_snapshot(config)
|
|
194
|
-
end
|
|
205
|
+
def state_snapshot = state_store.state_snapshot
|
|
195
206
|
end
|
|
196
207
|
end
|
|
197
208
|
end
|
|
@@ -12,12 +12,12 @@ module Stoplight
|
|
|
12
12
|
# A number of errors withing requested window. Zero for non-windowed metrics
|
|
13
13
|
# @return [Integer]
|
|
14
14
|
#
|
|
15
|
-
# @!attribute
|
|
16
|
-
# A
|
|
15
|
+
# @!attribute consecutive_errors
|
|
16
|
+
# A number of consecutive errors
|
|
17
17
|
# @return [Integer]
|
|
18
18
|
#
|
|
19
|
-
# @!attribute
|
|
20
|
-
# A
|
|
19
|
+
# @!attribute consecutive_successes
|
|
20
|
+
# A number of consecutive successes
|
|
21
21
|
# @return [Integer]
|
|
22
22
|
#
|
|
23
23
|
# @!attribute last_error
|
|
@@ -30,32 +30,11 @@ module Stoplight
|
|
|
30
30
|
Metrics = Data.define(
|
|
31
31
|
:successes,
|
|
32
32
|
:errors,
|
|
33
|
-
:
|
|
34
|
-
:
|
|
33
|
+
:consecutive_errors,
|
|
34
|
+
:consecutive_successes,
|
|
35
35
|
:last_error,
|
|
36
36
|
:last_success_at
|
|
37
37
|
) do
|
|
38
|
-
# A number of consecutive errors withing requested window
|
|
39
|
-
#
|
|
40
|
-
# @return [Integer]
|
|
41
|
-
def consecutive_errors
|
|
42
|
-
if errors # we effectively check if this is windowed metrics
|
|
43
|
-
[total_consecutive_errors, errors].min
|
|
44
|
-
else
|
|
45
|
-
total_consecutive_errors
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# A number of consecutive successes withing requested window
|
|
50
|
-
#
|
|
51
|
-
def consecutive_successes
|
|
52
|
-
if successes # we effectively check if this is windowed metrics
|
|
53
|
-
[total_consecutive_successes, successes].min
|
|
54
|
-
else
|
|
55
|
-
total_consecutive_successes
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
38
|
# Calculates the error rate based on the number of successes and errors.
|
|
60
39
|
#
|
|
61
40
|
# @return [Float]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
# Token representing an acquired recovery lock.
|
|
6
|
+
#
|
|
7
|
+
# Returned by +DataStore#acquire_recovery_lock+ and passed to
|
|
8
|
+
# +DataStore#release_recovery_lock+ to identify which lock to release.
|
|
9
|
+
#
|
|
10
|
+
# The actual locking mechanism lives in DataStore implementations,
|
|
11
|
+
# not in these tokens.
|
|
12
|
+
class RecoveryLockToken
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
module Storage
|
|
6
|
+
# Encapsulates metrics storage for circuit breaker execution tracking.
|
|
7
|
+
#
|
|
8
|
+
# This abstraction isolates metrics collection and retrieval from the
|
|
9
|
+
# broader data store concerns, enabling:
|
|
10
|
+
# - Purpose-built implementations optimized for time-series data
|
|
11
|
+
# - Independent scaling and optimization of metrics vs. state storage
|
|
12
|
+
# - Clearer separation between "what happened" (metrics) and "what to do" (state)
|
|
13
|
+
#
|
|
14
|
+
# Lifecycle: A Metrics instance is scoped to a single circuit breaker
|
|
15
|
+
# configuration. Each circuit gets its own metrics store instance,
|
|
16
|
+
# allowing different circuits to use different storage strategies.
|
|
17
|
+
#
|
|
18
|
+
# @abstract
|
|
19
|
+
class Metrics
|
|
20
|
+
# Retrieves a snapshot of current metrics for decision-making.
|
|
21
|
+
#
|
|
22
|
+
# @return [Stoplight::Domain::Metrics]
|
|
23
|
+
def metrics_snapshot = raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
# Records a successful circuit breaker execution
|
|
26
|
+
#
|
|
27
|
+
# @return [void]
|
|
28
|
+
def record_success = raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
# Records a failed circuit breaker execution
|
|
31
|
+
#
|
|
32
|
+
# @param error [StandardError]
|
|
33
|
+
# @return [void]
|
|
34
|
+
def record_failure(error) = raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
# Clears all metrics for this circuit
|
|
37
|
+
# @return [void]
|
|
38
|
+
def clear = raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|