stoplight 4.1.0 → 5.0.1
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/README.md +289 -350
- data/lib/stoplight/admin/actions/action.rb +24 -0
- data/lib/stoplight/admin/actions/lock.rb +23 -0
- data/lib/stoplight/admin/actions/lock_all_green.rb +18 -0
- data/lib/stoplight/admin/actions/lock_green.rb +23 -0
- data/lib/stoplight/admin/actions/lock_red.rb +23 -0
- data/lib/stoplight/admin/actions/stats.rb +27 -0
- data/lib/stoplight/admin/actions/unlock.rb +23 -0
- data/lib/stoplight/admin/dependencies.rb +50 -0
- data/lib/stoplight/admin/helpers.rb +27 -0
- data/lib/stoplight/admin/lights_repository/light.rb +155 -0
- data/lib/stoplight/admin/lights_repository.rb +74 -0
- data/lib/stoplight/admin/lights_stats.rb +77 -0
- data/lib/stoplight/admin/views/_card.erb +120 -0
- data/lib/stoplight/admin/views/index.erb +36 -0
- data/lib/stoplight/admin/views/layout.erb +66 -0
- data/lib/stoplight/admin.rb +68 -0
- data/lib/stoplight/color.rb +3 -3
- data/lib/stoplight/config/config_provider.rb +62 -0
- data/lib/stoplight/config/library_default_config.rb +29 -0
- data/lib/stoplight/config/user_default_config.rb +83 -0
- data/lib/stoplight/data_store/base.rb +59 -33
- data/lib/stoplight/data_store/fail_safe.rb +105 -0
- data/lib/stoplight/data_store/memory.rb +257 -50
- data/lib/stoplight/data_store/redis/get_metadata.lua +38 -0
- data/lib/stoplight/data_store/redis/lua.rb +23 -0
- data/lib/stoplight/data_store/redis/record_failure.lua +36 -0
- data/lib/stoplight/data_store/redis/record_success.lua +35 -0
- data/lib/stoplight/data_store/redis/transition_to_green.lua +10 -0
- data/lib/stoplight/data_store/redis/transition_to_red.lua +10 -0
- data/lib/stoplight/data_store/redis/transition_to_yellow.lua +9 -0
- data/lib/stoplight/data_store/redis.rb +345 -106
- data/lib/stoplight/default.rb +11 -9
- data/lib/stoplight/error.rb +1 -13
- data/lib/stoplight/failure.rb +14 -13
- data/lib/stoplight/light/config.rb +118 -0
- data/lib/stoplight/light/configuration_builder_interface.rb +128 -0
- data/lib/stoplight/light/green_run_strategy.rb +53 -0
- data/lib/stoplight/light/red_run_strategy.rb +26 -0
- data/lib/stoplight/light/run_strategy.rb +30 -0
- data/lib/stoplight/light/yellow_run_strategy.rb +78 -0
- data/lib/stoplight/light.rb +164 -84
- data/lib/stoplight/metadata.rb +71 -0
- data/lib/stoplight/notifier/base.rb +14 -7
- data/lib/stoplight/notifier/fail_safe.rb +67 -0
- data/lib/stoplight/notifier/generic.rb +54 -5
- data/lib/stoplight/rspec/generic_notifier.rb +11 -12
- data/lib/stoplight/rspec.rb +1 -1
- data/lib/stoplight/state.rb +3 -3
- data/lib/stoplight/traffic_control/base.rb +35 -0
- data/lib/stoplight/traffic_control/consecutive_failures.rb +43 -0
- data/lib/stoplight/traffic_recovery/base.rb +51 -0
- data/lib/stoplight/traffic_recovery/single_success.rb +35 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight.rb +111 -51
- metadata +49 -98
- data/lib/stoplight/builder.rb +0 -70
- data/lib/stoplight/circuit_breaker.rb +0 -102
- data/lib/stoplight/configurable.rb +0 -95
- data/lib/stoplight/configuration.rb +0 -126
- data/lib/stoplight/light/deprecated.rb +0 -44
- data/lib/stoplight/light/lockable.rb +0 -45
- data/lib/stoplight/light/runnable.rb +0 -127
- data/lib/stoplight/notifier.rb +0 -6
- data/spec/spec_helper.rb +0 -22
- data/spec/stoplight/builder_spec.rb +0 -165
- data/spec/stoplight/circuit_breaker_spec.rb +0 -43
- data/spec/stoplight/color_spec.rb +0 -39
- data/spec/stoplight/configurable_spec.rb +0 -25
- data/spec/stoplight/data_store/base_spec.rb +0 -71
- data/spec/stoplight/data_store/memory_spec.rb +0 -22
- data/spec/stoplight/data_store/redis_spec.rb +0 -45
- data/spec/stoplight/data_store_spec.rb +0 -9
- data/spec/stoplight/default_spec.rb +0 -80
- data/spec/stoplight/error_spec.rb +0 -39
- data/spec/stoplight/failure_spec.rb +0 -108
- data/spec/stoplight/light/lockable_spec.rb +0 -93
- data/spec/stoplight/light/runnable_spec.rb +0 -38
- data/spec/stoplight/light_spec.rb +0 -156
- data/spec/stoplight/notifier/base_spec.rb +0 -18
- data/spec/stoplight/notifier/generic_spec.rb +0 -50
- data/spec/stoplight/notifier/io_spec.rb +0 -41
- data/spec/stoplight/notifier/logger_spec.rb +0 -75
- data/spec/stoplight/notifier_spec.rb +0 -9
- data/spec/stoplight/state_spec.rb +0 -39
- data/spec/stoplight/version_spec.rb +0 -9
- data/spec/stoplight_spec.rb +0 -32
- data/spec/support/configurable.rb +0 -69
- data/spec/support/data_store/base/clear_failures.rb +0 -18
- data/spec/support/data_store/base/clear_state.rb +0 -20
- data/spec/support/data_store/base/get_all.rb +0 -44
- data/spec/support/data_store/base/get_failures.rb +0 -30
- data/spec/support/data_store/base/get_state.rb +0 -7
- data/spec/support/data_store/base/names.rb +0 -29
- data/spec/support/data_store/base/record_failures.rb +0 -70
- data/spec/support/data_store/base/set_state.rb +0 -15
- data/spec/support/data_store/base/with_notification_lock.rb +0 -27
- data/spec/support/data_store/base.rb +0 -21
- data/spec/support/database_cleaner.rb +0 -26
- data/spec/support/exception_helpers.rb +0 -9
- data/spec/support/light/runnable/color.rb +0 -79
- data/spec/support/light/runnable/run.rb +0 -247
- data/spec/support/light/runnable/state.rb +0 -31
- data/spec/support/light/runnable.rb +0 -5
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Stoplight
|
4
|
-
# @api private
|
5
|
-
# @abstract include the module and define +#reconfigure+ method
|
6
|
-
module Configurable
|
7
|
-
# @!attribute [r] configuration
|
8
|
-
# @return [Stoplight::Configuration]
|
9
|
-
# @api private
|
10
|
-
attr_reader :configuration
|
11
|
-
|
12
|
-
# Configures data store to be used with this circuit breaker
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# Stoplight('example')
|
16
|
-
# .with_data_store(Stoplight::DataStore::Memory.new)
|
17
|
-
#
|
18
|
-
# @param data_store [DataStore::Base]
|
19
|
-
# @return [Stoplight::CircuitBreaker]
|
20
|
-
def with_data_store(data_store)
|
21
|
-
reconfigure(configuration.with(data_store: data_store))
|
22
|
-
end
|
23
|
-
|
24
|
-
# Configures cool off time. Stoplight automatically tries to recover
|
25
|
-
# from the red state after the cool off time.
|
26
|
-
#
|
27
|
-
# @example
|
28
|
-
# Stoplight('example')
|
29
|
-
# .cool_off_time(60)
|
30
|
-
#
|
31
|
-
# @param cool_off_time [Numeric] number of seconds
|
32
|
-
# @return [Stoplight::CircuitBreaker]
|
33
|
-
def with_cool_off_time(cool_off_time)
|
34
|
-
reconfigure(configuration.with(cool_off_time: cool_off_time))
|
35
|
-
end
|
36
|
-
|
37
|
-
# Configures custom threshold. After this number of failures Stoplight
|
38
|
-
# switches to the red state:
|
39
|
-
#
|
40
|
-
# @example
|
41
|
-
# Stoplight('example')
|
42
|
-
# .with_threshold(5)
|
43
|
-
#
|
44
|
-
# @param threshold [Numeric]
|
45
|
-
# @return [Stoplight::CircuitBreaker]
|
46
|
-
def with_threshold(threshold)
|
47
|
-
reconfigure(configuration.with(threshold: threshold))
|
48
|
-
end
|
49
|
-
|
50
|
-
# Configures custom window size which Stoplight uses to count failures. For example,
|
51
|
-
#
|
52
|
-
# @example
|
53
|
-
# Stoplight('example')
|
54
|
-
# .with_threshold(5)
|
55
|
-
# .with_window_size(60)
|
56
|
-
#
|
57
|
-
# The above example will turn to red light only when 5 errors happen
|
58
|
-
# within 60 seconds period.
|
59
|
-
#
|
60
|
-
# @param window_size [Numeric] number of seconds
|
61
|
-
# @return [Stoplight::CircuitBreaker]
|
62
|
-
def with_window_size(window_size)
|
63
|
-
reconfigure(configuration.with(window_size: window_size))
|
64
|
-
end
|
65
|
-
|
66
|
-
# Configures custom notifier
|
67
|
-
#
|
68
|
-
# @example
|
69
|
-
# io = StringIO.new
|
70
|
-
# notifier = Stoplight::Notifier::IO.new(io)
|
71
|
-
# Stoplight('example')
|
72
|
-
# .with_notifiers([notifier])
|
73
|
-
#
|
74
|
-
# @param notifiers [Array<Notifier::Base>]
|
75
|
-
# @return [Stoplight::CircuitBreaker]
|
76
|
-
def with_notifiers(notifiers)
|
77
|
-
reconfigure(configuration.with(notifiers: notifiers))
|
78
|
-
end
|
79
|
-
|
80
|
-
# @param error_notifier [Proc]
|
81
|
-
# @return [Stoplight::CircuitBreaker]
|
82
|
-
# @api private
|
83
|
-
def with_error_notifier(&error_notifier)
|
84
|
-
reconfigure(configuration.with(error_notifier: error_notifier))
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
# @param [Stoplight::Configuration]
|
90
|
-
# @return [Stoplight::CircuitBreaker]
|
91
|
-
def reconfigure(_configuration)
|
92
|
-
raise NotImplementedError, "#{self.class.name}#reconfigure is not implemented"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
@@ -1,126 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Stoplight
|
4
|
-
# A +Stoplight::Light+ configuration object.
|
5
|
-
class Configuration
|
6
|
-
class << self
|
7
|
-
alias __new_without_defaults__ new
|
8
|
-
|
9
|
-
# It overrides the +Configuration.new+ to inject default settings
|
10
|
-
# @see +Stoplight::Configuration#initialize+
|
11
|
-
def new(**settings)
|
12
|
-
__new_without_defaults__(
|
13
|
-
**default_settings.merge(settings)
|
14
|
-
)
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
# @return [Hash]
|
20
|
-
def default_settings
|
21
|
-
{
|
22
|
-
cool_off_time: Default::COOL_OFF_TIME,
|
23
|
-
data_store: Stoplight.default_data_store,
|
24
|
-
error_notifier: Stoplight.default_error_notifier,
|
25
|
-
notifiers: Stoplight.default_notifiers,
|
26
|
-
threshold: Default::THRESHOLD,
|
27
|
-
window_size: Default::WINDOW_SIZE
|
28
|
-
}
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# @!attribute [r] name
|
33
|
-
# @return [String]
|
34
|
-
attr_reader :name
|
35
|
-
|
36
|
-
# @!attribute [r] cool_off_time
|
37
|
-
# @return [Numeric]
|
38
|
-
attr_reader :cool_off_time
|
39
|
-
|
40
|
-
# @!attribute [r] data_store
|
41
|
-
# @return [Stoplight::DataStore::Base]
|
42
|
-
attr_reader :data_store
|
43
|
-
|
44
|
-
# @!attribute [r] error_notifier
|
45
|
-
# # @return [StandardError => void]
|
46
|
-
attr_reader :error_notifier
|
47
|
-
|
48
|
-
# @!attribute [r] notifiers
|
49
|
-
# # @return [Array<Notifier::Base>]
|
50
|
-
attr_reader :notifiers
|
51
|
-
|
52
|
-
# @!attribute [r] threshold
|
53
|
-
# @return [Numeric]
|
54
|
-
attr_reader :threshold
|
55
|
-
|
56
|
-
# @!attribute [r] window_size
|
57
|
-
# @return [Numeric]
|
58
|
-
attr_reader :window_size
|
59
|
-
|
60
|
-
# @param name [String]
|
61
|
-
# @param cool_off_time [Numeric]
|
62
|
-
# @param data_store [Stoplight::DataStore::Base]
|
63
|
-
# @param error_notifier [Proc]
|
64
|
-
# @param notifiers [Stoplight::Notifier::Base]
|
65
|
-
# @param threshold [Numeric]
|
66
|
-
# @param window_size [Numeric]
|
67
|
-
def initialize(name:, cool_off_time:, data_store:, error_notifier:, notifiers:, threshold:, window_size:)
|
68
|
-
@name = name
|
69
|
-
@cool_off_time = cool_off_time
|
70
|
-
@data_store = data_store
|
71
|
-
@error_notifier = error_notifier
|
72
|
-
@notifiers = notifiers
|
73
|
-
@threshold = threshold
|
74
|
-
@window_size = window_size
|
75
|
-
end
|
76
|
-
|
77
|
-
# @param other [any]
|
78
|
-
# @return [Boolean]
|
79
|
-
def ==(other)
|
80
|
-
other.is_a?(self.class) && settings == other.settings
|
81
|
-
end
|
82
|
-
|
83
|
-
# @param cool_off_time [Numeric]
|
84
|
-
# @param data_store [Stoplight::DataStore::Base]
|
85
|
-
# @param error_notifier [Proc]
|
86
|
-
# @param name [String]
|
87
|
-
# @param notifiers [Stoplight::Notifier::Base]
|
88
|
-
# @param threshold [Numeric]
|
89
|
-
# @param window_size [Numeric]
|
90
|
-
# @return [Stoplight::Configuration]
|
91
|
-
def with(
|
92
|
-
cool_off_time: self.cool_off_time,
|
93
|
-
data_store: self.data_store,
|
94
|
-
error_notifier: self.error_notifier,
|
95
|
-
name: self.name,
|
96
|
-
notifiers: self.notifiers,
|
97
|
-
threshold: self.threshold,
|
98
|
-
window_size: self.window_size
|
99
|
-
)
|
100
|
-
Configuration.new(
|
101
|
-
cool_off_time: cool_off_time,
|
102
|
-
data_store: data_store,
|
103
|
-
error_notifier: error_notifier,
|
104
|
-
name: name,
|
105
|
-
notifiers: notifiers,
|
106
|
-
threshold: threshold,
|
107
|
-
window_size: window_size
|
108
|
-
)
|
109
|
-
end
|
110
|
-
|
111
|
-
protected
|
112
|
-
|
113
|
-
# @return [Hash]
|
114
|
-
def settings
|
115
|
-
{
|
116
|
-
cool_off_time: cool_off_time,
|
117
|
-
data_store: data_store,
|
118
|
-
error_notifier: error_notifier,
|
119
|
-
name: name,
|
120
|
-
notifiers: notifiers,
|
121
|
-
threshold: threshold,
|
122
|
-
window_size: window_size
|
123
|
-
}
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Stoplight
|
4
|
-
class Light
|
5
|
-
# @api private
|
6
|
-
module Deprecated
|
7
|
-
def default_data_store
|
8
|
-
warn '[DEPRECATED] `Stoplight::Light.default_data_store` is deprecated. ' \
|
9
|
-
'Please use `Stoplight.default_data_store` instead.'
|
10
|
-
Stoplight.default_data_store
|
11
|
-
end
|
12
|
-
|
13
|
-
def default_data_store=(value)
|
14
|
-
warn '[DEPRECATED] `Stoplight::Light.default_data_store=` is deprecated. ' \
|
15
|
-
'Please use `Stoplight.default_data_store=` instead.'
|
16
|
-
Stoplight.default_data_store = value
|
17
|
-
end
|
18
|
-
|
19
|
-
def default_notifiers
|
20
|
-
warn '[DEPRECATED] `Stoplight::Light.default_notifiers` is deprecated. ' \
|
21
|
-
'Please use `Stoplight.default_notifiers` instead.'
|
22
|
-
Stoplight.default_notifiers
|
23
|
-
end
|
24
|
-
|
25
|
-
def default_notifiers=(value)
|
26
|
-
warn '[DEPRECATED] `Stoplight::Light.default_notifiers=` is deprecated. ' \
|
27
|
-
'Please use `Stoplight.default_notifiers=` instead.'
|
28
|
-
Stoplight.default_notifiers = value
|
29
|
-
end
|
30
|
-
|
31
|
-
def default_error_notifier
|
32
|
-
warn '[DEPRECATED] `Stoplight::Light.default_error_notifier` is deprecated. ' \
|
33
|
-
'Please use `Stoplight.default_error_notifier` instead.'
|
34
|
-
Stoplight.default_error_notifier
|
35
|
-
end
|
36
|
-
|
37
|
-
def default_error_notifier=(value)
|
38
|
-
warn '[DEPRECATED] `Stoplight::Light.default_error_notifier=` is deprecated. ' \
|
39
|
-
'Please use `Stoplight.default_error_notifier=` instead.'
|
40
|
-
Stoplight.default_error_notifier = value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Stoplight
|
4
|
-
class Light
|
5
|
-
# The Lockable module implements the behavior of locking and unlocking the light.
|
6
|
-
# Light can be locked in either a State::LOCKED_RED or State::LOCKED_GREEN state.
|
7
|
-
# By locking the light, you force it always to run code with the chosen light color.
|
8
|
-
#
|
9
|
-
# @example
|
10
|
-
# light = Stoplight('example-locked') { true }
|
11
|
-
# # => #<Stoplight::Light:..>
|
12
|
-
# light.run
|
13
|
-
# # => true
|
14
|
-
# light.lock(Stoplight::Color::RED)
|
15
|
-
# # => #<Stoplight::Light:..>
|
16
|
-
# light.run
|
17
|
-
# # => Stoplight::Error::RedLight: example-locked
|
18
|
-
# light.unlock
|
19
|
-
# # => #<Stoplight::Light:..>
|
20
|
-
# light.run
|
21
|
-
# # => true
|
22
|
-
module Lockable
|
23
|
-
# @param color [String] should be either Color::RED or Color::GREEN
|
24
|
-
# @return [Stoplight::Light] returns locked light
|
25
|
-
def lock(color)
|
26
|
-
state = case color
|
27
|
-
when Color::RED then State::LOCKED_RED
|
28
|
-
when Color::GREEN then State::LOCKED_GREEN
|
29
|
-
else raise Error::IncorrectColor
|
30
|
-
end
|
31
|
-
|
32
|
-
safely { data_store.set_state(self, state) }
|
33
|
-
|
34
|
-
self
|
35
|
-
end
|
36
|
-
|
37
|
-
# @return [Stoplight::Light] returns unlocked light
|
38
|
-
def unlock
|
39
|
-
safely { data_store.set_state(self, Stoplight::State::UNLOCKED) }
|
40
|
-
|
41
|
-
self
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Stoplight
|
4
|
-
class Light
|
5
|
-
module Runnable # rubocop:disable Style/Documentation
|
6
|
-
# @return [String]
|
7
|
-
def state
|
8
|
-
_, state = failures_and_state
|
9
|
-
state
|
10
|
-
end
|
11
|
-
|
12
|
-
# @return [String]
|
13
|
-
def color
|
14
|
-
failures, state = failures_and_state
|
15
|
-
failure = failures.first
|
16
|
-
|
17
|
-
if state == State::LOCKED_GREEN then Color::GREEN
|
18
|
-
elsif state == State::LOCKED_RED then Color::RED
|
19
|
-
elsif failures.size < threshold then Color::GREEN
|
20
|
-
elsif failure && Time.now - failure.time >= cool_off_time
|
21
|
-
Color::YELLOW
|
22
|
-
else
|
23
|
-
Color::RED
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# @raise [Error::RedLight]
|
28
|
-
def run(&code)
|
29
|
-
code = validate_code(&code)
|
30
|
-
case color
|
31
|
-
when Color::GREEN then run_green(&code)
|
32
|
-
when Color::YELLOW then run_yellow(&code)
|
33
|
-
else run_red
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def validate_code(&code)
|
40
|
-
raise ArgumentError, <<~ERROR if block_given? && self.code
|
41
|
-
passing code block into both `Light.new` and `Light#run` is not allowed
|
42
|
-
ERROR
|
43
|
-
|
44
|
-
raise ArgumentError, <<~ERROR unless block_given? || self.code
|
45
|
-
nothing to run. Please, pass a block into `Light#run`
|
46
|
-
ERROR
|
47
|
-
|
48
|
-
code || self.code
|
49
|
-
end
|
50
|
-
|
51
|
-
def run_green(&code)
|
52
|
-
on_failure = lambda do |size, error|
|
53
|
-
notify(Color::GREEN, Color::RED, error) if failures_threshold_breached?(size, threshold)
|
54
|
-
end
|
55
|
-
run_code(nil, on_failure, &code)
|
56
|
-
end
|
57
|
-
|
58
|
-
def failures_threshold_breached?(current_failures_count, max_errors_threshold)
|
59
|
-
current_failures_count == max_errors_threshold
|
60
|
-
end
|
61
|
-
|
62
|
-
def run_yellow(&code)
|
63
|
-
on_success = lambda do |failures|
|
64
|
-
notify(Color::RED, Color::GREEN) unless failures.empty?
|
65
|
-
end
|
66
|
-
run_code(on_success, nil, &code)
|
67
|
-
end
|
68
|
-
|
69
|
-
def run_red
|
70
|
-
raise Error::RedLight, name unless fallback
|
71
|
-
|
72
|
-
fallback.call(nil)
|
73
|
-
end
|
74
|
-
|
75
|
-
def run_code(on_success, on_failure, &code)
|
76
|
-
result = code.call
|
77
|
-
failures = clear_failures
|
78
|
-
on_success&.call(failures)
|
79
|
-
result
|
80
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
81
|
-
handle_error(e, on_failure)
|
82
|
-
end
|
83
|
-
|
84
|
-
def handle_error(error, on_failure)
|
85
|
-
error_handler.call(error, Error::HANDLER)
|
86
|
-
size = record_failure(error)
|
87
|
-
on_failure&.call(size, error)
|
88
|
-
raise error unless fallback
|
89
|
-
|
90
|
-
fallback.call(error)
|
91
|
-
end
|
92
|
-
|
93
|
-
def clear_failures
|
94
|
-
safely([]) { data_store.clear_failures(self) }
|
95
|
-
end
|
96
|
-
|
97
|
-
def failures_and_state
|
98
|
-
safely([[], State::UNLOCKED]) { data_store.get_all(self) }
|
99
|
-
end
|
100
|
-
|
101
|
-
def notify(from_color, to_color, error = nil)
|
102
|
-
data_store.with_notification_lock(self, from_color, to_color) do
|
103
|
-
notifiers.each do |notifier|
|
104
|
-
safely { notifier.notify(self, from_color, to_color, error) }
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def record_failure(error)
|
110
|
-
failure = Failure.from_error(error)
|
111
|
-
safely(0) { data_store.record_failure(self, failure) }
|
112
|
-
end
|
113
|
-
|
114
|
-
def safely(default = nil, &code)
|
115
|
-
return yield if data_store == Default::DATA_STORE
|
116
|
-
|
117
|
-
Stoplight("#{name}-safely")
|
118
|
-
.with_data_store(Default::DATA_STORE)
|
119
|
-
.with_fallback do |error|
|
120
|
-
error_notifier.call(error) if error
|
121
|
-
default
|
122
|
-
end
|
123
|
-
.run(&code)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
data/lib/stoplight/notifier.rb
DELETED
data/spec/spec_helper.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'simplecov'
|
4
|
-
|
5
|
-
require 'stoplight'
|
6
|
-
require 'timecop'
|
7
|
-
require_relative 'support/data_store/base'
|
8
|
-
require_relative 'support/light/runnable'
|
9
|
-
require_relative 'support/configurable'
|
10
|
-
require_relative 'support/database_cleaner'
|
11
|
-
require_relative 'support/exception_helpers'
|
12
|
-
|
13
|
-
Timecop.safe_mode = true
|
14
|
-
|
15
|
-
RSpec.configure do |rspec|
|
16
|
-
rspec.include ExceptionHelpers
|
17
|
-
|
18
|
-
rspec.color = true
|
19
|
-
rspec.disable_monkey_patching!
|
20
|
-
rspec.order = :random
|
21
|
-
rspec.warnings = true
|
22
|
-
end
|
@@ -1,165 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'securerandom'
|
5
|
-
|
6
|
-
RSpec.describe Stoplight::Builder do
|
7
|
-
let(:name) { SecureRandom.uuid }
|
8
|
-
|
9
|
-
describe '.with' do
|
10
|
-
context 'with only name' do
|
11
|
-
subject(:configuration) { builder.configuration }
|
12
|
-
|
13
|
-
let(:builder) { described_class.with(name: name) }
|
14
|
-
|
15
|
-
it 'sets configuration to default values' do
|
16
|
-
expect(configuration).to have_attributes(
|
17
|
-
name: name,
|
18
|
-
data_store: Stoplight.default_data_store,
|
19
|
-
notifiers: Stoplight.default_notifiers,
|
20
|
-
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
21
|
-
threshold: Stoplight::Default::THRESHOLD,
|
22
|
-
window_size: Stoplight::Default::WINDOW_SIZE
|
23
|
-
)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
context 'with configured parameters' do
|
28
|
-
subject(:configuration) { builder.configuration }
|
29
|
-
|
30
|
-
let(:builder) { described_class.with(**configured_parameters) }
|
31
|
-
let(:configured_parameters) do
|
32
|
-
{
|
33
|
-
name: name,
|
34
|
-
data_store: 42,
|
35
|
-
notifiers: [43],
|
36
|
-
cool_off_time: 44,
|
37
|
-
threshold: 45,
|
38
|
-
window_size: 46
|
39
|
-
}
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'sets configuration parameters' do
|
43
|
-
expect(configuration).to have_attributes(**configured_parameters)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe '.build' do
|
49
|
-
let(:builder) { described_class.new(configuration) }
|
50
|
-
let(:configuration) { instance_double(Stoplight::Configuration, name: name) }
|
51
|
-
|
52
|
-
context 'with code' do
|
53
|
-
subject(:light) { builder.build(&code) }
|
54
|
-
|
55
|
-
let(:code) { -> { 42 } }
|
56
|
-
|
57
|
-
it 'builds new light' do
|
58
|
-
expect(light.configuration).to eq(configuration)
|
59
|
-
expect(light.code).to eq(code)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
context 'without code' do
|
64
|
-
subject(:light) { builder.build }
|
65
|
-
|
66
|
-
it 'builds new light' do
|
67
|
-
expect(light.configuration).to eq(configuration)
|
68
|
-
expect(light.code).to eq(nil)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
context 'methods building an instance of light' do
|
74
|
-
let(:builder) { described_class.new(configuration) }
|
75
|
-
let(:configuration) do
|
76
|
-
Stoplight::Configuration.new(
|
77
|
-
name: name,
|
78
|
-
data_store: Stoplight.default_data_store,
|
79
|
-
notifiers: Stoplight.default_notifiers,
|
80
|
-
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
81
|
-
threshold: Stoplight::Default::THRESHOLD,
|
82
|
-
window_size: Stoplight::Default::WINDOW_SIZE,
|
83
|
-
error_notifier: Stoplight.default_error_notifier
|
84
|
-
)
|
85
|
-
end
|
86
|
-
|
87
|
-
describe '#with_error_handler' do
|
88
|
-
subject(:light) { builder.with_error_handler(&error_handler) }
|
89
|
-
|
90
|
-
let(:error_handler) { ->(error, handle) {} }
|
91
|
-
|
92
|
-
it 'returns an instance of the Light class with this configuration set' do
|
93
|
-
expect(light.configuration).to be(configuration)
|
94
|
-
expect(light.error_handler).to eq(error_handler)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
describe '#with_fallback' do
|
99
|
-
subject(:light) { builder.with_fallback(&fallback) }
|
100
|
-
|
101
|
-
let(:fallback) { ->(error) {} }
|
102
|
-
|
103
|
-
it 'returns an instance of the Light class with this configuration set' do
|
104
|
-
expect(light.configuration).to be(configuration)
|
105
|
-
expect(light.fallback).to eq(fallback)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe '#run' do
|
110
|
-
it 'yields the block' do
|
111
|
-
expect do |code|
|
112
|
-
builder.run(&code)
|
113
|
-
end.to yield_control
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
describe '#lock' do
|
118
|
-
context 'when the light is not locked' do
|
119
|
-
it 'locks the light' do
|
120
|
-
expect { builder.lock(Stoplight::Color::RED) }
|
121
|
-
.to change(builder, :color)
|
122
|
-
.from(Stoplight::Color::GREEN)
|
123
|
-
.to(Stoplight::Color::RED)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
context 'when the light is locked' do
|
128
|
-
before do
|
129
|
-
builder.lock(Stoplight::Color::RED)
|
130
|
-
end
|
131
|
-
|
132
|
-
it 'does not change the light' do
|
133
|
-
expect { builder.lock(Stoplight::Color::RED) }
|
134
|
-
.not_to change(builder, :color)
|
135
|
-
.from(Stoplight::Color::RED)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe '#unlock' do
|
141
|
-
context 'when the light is not locked' do
|
142
|
-
it 'does nothing' do
|
143
|
-
expect { builder.unlock }
|
144
|
-
.not_to change(builder, :color)
|
145
|
-
.from(Stoplight::Color::GREEN)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
context 'when the light is locked' do
|
150
|
-
before do
|
151
|
-
builder.lock(Stoplight::Color::RED)
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'unlocks the light' do
|
155
|
-
expect { builder.unlock }
|
156
|
-
.to change(builder, :color)
|
157
|
-
.from(Stoplight::Color::RED)
|
158
|
-
.to(Stoplight::Color::GREEN)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
it_behaves_like Stoplight::Configurable
|
165
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
RSpec.describe Stoplight::CircuitBreaker do
|
6
|
-
subject(:circuit_breaker) { klass.new }
|
7
|
-
|
8
|
-
let(:klass) do
|
9
|
-
Class.new { include Stoplight::CircuitBreaker }
|
10
|
-
end
|
11
|
-
|
12
|
-
specify '#with_error_handler' do
|
13
|
-
expect { circuit_breaker.with_error_handler {} }.to raise_error(NotImplementedError)
|
14
|
-
end
|
15
|
-
|
16
|
-
specify '#with_fallback' do
|
17
|
-
expect { circuit_breaker.with_fallback {} }.to raise_error(NotImplementedError)
|
18
|
-
end
|
19
|
-
|
20
|
-
specify '#color' do
|
21
|
-
expect { circuit_breaker.color }.to raise_error(NotImplementedError)
|
22
|
-
end
|
23
|
-
|
24
|
-
specify '#state' do
|
25
|
-
expect { circuit_breaker.state }.to raise_error(NotImplementedError)
|
26
|
-
end
|
27
|
-
|
28
|
-
specify '#name' do
|
29
|
-
expect { circuit_breaker.name }.to raise_error(NotImplementedError)
|
30
|
-
end
|
31
|
-
|
32
|
-
specify '#run' do
|
33
|
-
expect { circuit_breaker.run {} }.to raise_error(NotImplementedError)
|
34
|
-
end
|
35
|
-
|
36
|
-
specify '#lock' do
|
37
|
-
expect { circuit_breaker.lock('red') }.to raise_error(NotImplementedError)
|
38
|
-
end
|
39
|
-
|
40
|
-
specify '#unlock' do
|
41
|
-
expect { circuit_breaker.unlock }.to raise_error(NotImplementedError)
|
42
|
-
end
|
43
|
-
end
|