stoplight 5.3.8 → 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/README.md +17 -2
- data/lib/generators/stoplight/install/templates/stoplight.rb.erb +2 -0
- data/lib/stoplight/admin/actions/remove.rb +23 -0
- data/lib/stoplight/admin/dependencies.rb +6 -1
- data/lib/stoplight/admin/helpers.rb +10 -5
- data/lib/stoplight/admin/lights_repository.rb +26 -14
- data/lib/stoplight/admin/views/_card.erb +13 -1
- data/lib/stoplight/admin/views/layout.erb +3 -3
- data/lib/stoplight/admin.rb +13 -4
- data/lib/stoplight/common/deprecations.rb +11 -0
- data/lib/stoplight/domain/color.rb +11 -0
- data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
- data/lib/stoplight/domain/config.rb +59 -0
- data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +71 -17
- data/lib/stoplight/domain/error.rb +42 -0
- data/lib/stoplight/domain/failure.rb +44 -0
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +234 -0
- data/lib/stoplight/domain/light.rb +208 -0
- data/lib/stoplight/domain/light_factory.rb +75 -0
- data/lib/stoplight/domain/metrics.rb +64 -0
- data/lib/stoplight/domain/recovery_lock_token.rb +15 -0
- data/lib/stoplight/domain/state.rb +11 -0
- data/lib/stoplight/domain/state_snapshot.rb +57 -0
- data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
- 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/green_run_strategy.rb +69 -0
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
- data/lib/stoplight/domain/strategies/run_strategy.rb +22 -0
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +125 -0
- data/lib/stoplight/domain/tracker/base.rb +12 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +76 -0
- data/lib/stoplight/domain/tracker/request.rb +72 -0
- data/lib/stoplight/domain/traffic_control/base.rb +74 -0
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +53 -0
- data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
- data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +66 -0
- data/lib/stoplight/domain/traffic_recovery.rb +12 -0
- data/lib/stoplight/infrastructure/data_store/fail_safe.rb +164 -0
- data/lib/stoplight/infrastructure/data_store/memory/metrics.rb +27 -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/sliding_window.rb +79 -0
- data/lib/stoplight/infrastructure/data_store/memory/state.rb +21 -0
- data/lib/stoplight/infrastructure/data_store/memory.rb +338 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/get_metrics.lua +26 -0
- 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 +524 -0
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +62 -0
- data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
- data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
- data/lib/stoplight/infrastructure/notifier/logger.rb +21 -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/rspec/generic_notifier.rb +1 -1
- 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 +28 -0
- data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
- data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
- data/lib/stoplight/wiring/light/default_config.rb +18 -0
- data/lib/stoplight/wiring/light/system_config.rb +11 -0
- 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 +101 -0
- data/lib/stoplight/wiring/notifier_factory.rb +26 -0
- data/lib/stoplight/wiring/public_api.rb +29 -0
- data/lib/stoplight.rb +55 -30
- metadata +92 -42
- data/lib/stoplight/color.rb +0 -9
- data/lib/stoplight/config/dsl.rb +0 -97
- data/lib/stoplight/config/library_default_config.rb +0 -21
- data/lib/stoplight/config/system_config.rb +0 -7
- data/lib/stoplight/data_store/fail_safe.rb +0 -113
- data/lib/stoplight/data_store/memory.rb +0 -311
- data/lib/stoplight/data_store/redis/get_metadata.lua +0 -38
- data/lib/stoplight/data_store/redis/lua.rb +0 -23
- data/lib/stoplight/data_store/redis.rb +0 -449
- data/lib/stoplight/data_store.rb +0 -6
- data/lib/stoplight/default.rb +0 -30
- data/lib/stoplight/error.rb +0 -10
- data/lib/stoplight/failure.rb +0 -71
- data/lib/stoplight/light/config.rb +0 -111
- data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
- data/lib/stoplight/light/green_run_strategy.rb +0 -54
- data/lib/stoplight/light/red_run_strategy.rb +0 -27
- data/lib/stoplight/light/run_strategy.rb +0 -32
- data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
- data/lib/stoplight/light.rb +0 -191
- data/lib/stoplight/metadata.rb +0 -99
- data/lib/stoplight/notifier/fail_safe.rb +0 -70
- data/lib/stoplight/notifier/generic.rb +0 -79
- data/lib/stoplight/notifier/io.rb +0 -21
- data/lib/stoplight/notifier/logger.rb +0 -19
- data/lib/stoplight/state.rb +0 -9
- data/lib/stoplight/traffic_control/base.rb +0 -70
- data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
- data/lib/stoplight/traffic_control/error_rate.rb +0 -49
- data/lib/stoplight/traffic_recovery/base.rb +0 -75
- data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
- data/lib/stoplight/traffic_recovery.rb +0 -11
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/record_failure.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/record_success.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_green.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_red.lua +0 -0
- /data/lib/stoplight/{data_store/redis → infrastructure/data_store/redis/lua_scripts}/transition_to_yellow.lua +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Notifier
|
|
6
|
+
# @see Base
|
|
7
|
+
class Logger < Domain::StateTransitionNotifier
|
|
8
|
+
include Generic
|
|
9
|
+
|
|
10
|
+
# @return [::Logger]
|
|
11
|
+
def logger
|
|
12
|
+
@object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def put(message)
|
|
16
|
+
logger.warn(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Storage
|
|
6
|
+
# Temporary adapter that bridges Domain::Storage::Metrics to existing DataStore.
|
|
7
|
+
#
|
|
8
|
+
# This compatibility layer allows the metrics abstraction to be introduced
|
|
9
|
+
# without breaking existing data store implementations. It delegates all
|
|
10
|
+
# operations to the data store's original methods.
|
|
11
|
+
#
|
|
12
|
+
# This class will be removed in a future versions once all data stores
|
|
13
|
+
# have native metrics implementations.
|
|
14
|
+
#
|
|
15
|
+
# @example Creating metrics for a circuit
|
|
16
|
+
# metrics = CompatibilityMetrics.new(
|
|
17
|
+
# data_store: redis_store,
|
|
18
|
+
# config: config
|
|
19
|
+
# )
|
|
20
|
+
# metrics.record_success
|
|
21
|
+
#
|
|
22
|
+
# @see Stoplight::Domain::Storage::Metrics
|
|
23
|
+
class CompatibilityMetrics < Domain::Storage::Metrics
|
|
24
|
+
private attr_reader :data_store
|
|
25
|
+
private attr_reader :config
|
|
26
|
+
|
|
27
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
28
|
+
# @param config [Stoplight::Domain::Config]
|
|
29
|
+
def initialize(data_store:, config:)
|
|
30
|
+
@data_store = data_store
|
|
31
|
+
@config = config
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def metrics_snapshot = data_store.get_metrics(config)
|
|
35
|
+
|
|
36
|
+
# @return [void]
|
|
37
|
+
def record_success = data_store.record_success(config)
|
|
38
|
+
|
|
39
|
+
# @param error [StandardError]
|
|
40
|
+
# @return [void]
|
|
41
|
+
def record_failure(error) = data_store.record_failure(config, error)
|
|
42
|
+
|
|
43
|
+
# @return [void]
|
|
44
|
+
def clear = data_store.clear_metrics(config)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Storage
|
|
6
|
+
# Temporary adapter that bridges +Domain::Storage::RecoveryLock+ to existing DataStore.
|
|
7
|
+
#
|
|
8
|
+
# This compatibility layer allows the recovery lock abstraction to be
|
|
9
|
+
# introduced without breaking existing data store implementations. It
|
|
10
|
+
# delegates all lock operations to the data store's original methods.
|
|
11
|
+
#
|
|
12
|
+
# This adapter will be removed in a future versions once all
|
|
13
|
+
# data stores have native recovery lock implementations.
|
|
14
|
+
#
|
|
15
|
+
# @see Stoplight::Domain::Storage::RecoveryLock
|
|
16
|
+
class CompatibilityRecoveryLock < Domain::Storage::RecoveryLock
|
|
17
|
+
private attr_reader :data_store
|
|
18
|
+
private attr_reader :config
|
|
19
|
+
|
|
20
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
21
|
+
# @param config [Stoplight::Domain::Config]
|
|
22
|
+
def initialize(data_store:, config:)
|
|
23
|
+
@data_store = data_store
|
|
24
|
+
@config = config
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Stoplight::Domain::RecoveryLockToken, nil]
|
|
28
|
+
def acquire_lock = data_store.acquire_recovery_lock(config)
|
|
29
|
+
|
|
30
|
+
# @param lock [Stoplight::Domain::LockToken]
|
|
31
|
+
# @return [void]
|
|
32
|
+
def release_lock(lock) = data_store.release_recovery_lock(lock)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Storage
|
|
6
|
+
# When a circuit is RED (open), Stoplight periodically sends "recovery probes"
|
|
7
|
+
# to test whether the protected service has recovered. These test requests have
|
|
8
|
+
# different semantics than normal requests and their metrics are tracked separately.
|
|
9
|
+
#
|
|
10
|
+
# Like +CompatibilityMetrics+, this adapter will be replaced with purpose-built
|
|
11
|
+
# recovery metrics implementations (e.g., +ConsecutiveSuccessMetrics+) once the
|
|
12
|
+
# metrics extraction is complete.
|
|
13
|
+
#
|
|
14
|
+
# @example Recovery probe flow
|
|
15
|
+
# # Circuit is RED, start probing
|
|
16
|
+
# recovery_metrics = CompatibilityRecoveryMetrics.new(
|
|
17
|
+
# data_store: redis_store,
|
|
18
|
+
# config: circuit_config
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# recovery_metrics.record_success
|
|
22
|
+
# recovery_metrics.metrics_snapshot # => 1 success, 0 failures
|
|
23
|
+
#
|
|
24
|
+
# @see Stoplight::Domain::Storage::Metrics
|
|
25
|
+
class CompatibilityRecoveryMetrics < Domain::Storage::Metrics
|
|
26
|
+
private attr_reader :data_store
|
|
27
|
+
private attr_reader :config
|
|
28
|
+
|
|
29
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
30
|
+
# @param config [Stoplight::Domain::Config]
|
|
31
|
+
def initialize(data_store:, config:)
|
|
32
|
+
@data_store = data_store
|
|
33
|
+
@config = config
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def metrics_snapshot = data_store.get_recovery_metrics(config)
|
|
37
|
+
|
|
38
|
+
# Tracks successful circuit breaker execution
|
|
39
|
+
#
|
|
40
|
+
# @return [void]
|
|
41
|
+
def record_success = data_store.record_recovery_probe_success(config)
|
|
42
|
+
|
|
43
|
+
# Tracks failed circuit breaker execution
|
|
44
|
+
#
|
|
45
|
+
# @param error [StandardError]
|
|
46
|
+
# @return [void]
|
|
47
|
+
def record_failure(error) = data_store.record_recovery_probe_failure(config, error)
|
|
48
|
+
|
|
49
|
+
# Clears metrics
|
|
50
|
+
# @return [void]
|
|
51
|
+
def clear = data_store.clear_recovery_metrics(config)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Storage
|
|
6
|
+
# Temporary adapter that bridges Domain::Storage::State to existing DataStore.
|
|
7
|
+
#
|
|
8
|
+
# This compatibility layer allows the state abstraction to be introduced
|
|
9
|
+
# without breaking existing data store implementations. It delegates all
|
|
10
|
+
# state operations to the data store's original methods.
|
|
11
|
+
#
|
|
12
|
+
# This adapter will be removed in a future versions once all
|
|
13
|
+
# data stores have native state storage implementations.
|
|
14
|
+
#
|
|
15
|
+
# @example Creating state storage for a circuit
|
|
16
|
+
# state = CompatibilityState.new(
|
|
17
|
+
# data_store: redis_store,
|
|
18
|
+
# config: circuit_config
|
|
19
|
+
# )
|
|
20
|
+
# state.set_state(State::LOCKED_RED)
|
|
21
|
+
# snapshot = state.state_snapshot
|
|
22
|
+
#
|
|
23
|
+
class CompatibilityState < Domain::Storage::State
|
|
24
|
+
# @!attribute data_store
|
|
25
|
+
# @return [Stoplight::Domain::DataStore]
|
|
26
|
+
private attr_reader :data_store
|
|
27
|
+
|
|
28
|
+
# @!attribute config
|
|
29
|
+
# @return [Stoplight::Domain::Config]
|
|
30
|
+
private attr_reader :config
|
|
31
|
+
|
|
32
|
+
# @param data_store [Stoplight::Domain::DataStore]
|
|
33
|
+
# @param config [Stoplight::Domain::Config]
|
|
34
|
+
def initialize(data_store:, config:)
|
|
35
|
+
@data_store = data_store
|
|
36
|
+
@config = config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Stoplight::Domain::StateSnapshot]
|
|
40
|
+
def state_snapshot = data_store.get_state_snapshot(config)
|
|
41
|
+
|
|
42
|
+
# @param state [String]
|
|
43
|
+
# @return [String]
|
|
44
|
+
def set_state(state) = data_store.set_state(config, state)
|
|
45
|
+
|
|
46
|
+
# @param color [String]
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
def transition_to_color(color) = data_store.transition_to_color(config, color)
|
|
49
|
+
|
|
50
|
+
# @return [void]
|
|
51
|
+
def clear = data_store.delete_light(config)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -9,7 +9,7 @@ RSpec.shared_examples "a generic notifier" do
|
|
|
9
9
|
it "is initially the default" do
|
|
10
10
|
formatter = nil
|
|
11
11
|
expect(described_class.new(nil, formatter).formatter)
|
|
12
|
-
.to eql(Stoplight::Default::FORMATTER)
|
|
12
|
+
.to eql(Stoplight::Wiring::Default::FORMATTER)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it "reads the formatter" do
|
data/lib/stoplight/version.rb
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
module DataStore
|
|
6
|
+
class Redis < Base
|
|
7
|
+
# @!attribute redis
|
|
8
|
+
# @return [::Redis, ConnectionPool<::Redis>]
|
|
9
|
+
attr_reader :redis
|
|
10
|
+
|
|
11
|
+
# @!attribute warn_on_clock_skew
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
attr_reader :warn_on_clock_skew
|
|
14
|
+
|
|
15
|
+
# @param redis [::Redis, ConnectionPool<::Redis>]
|
|
16
|
+
# @param warn_on_clock_skew [Boolean] (true) Whether to warn about clock skew between Redis and
|
|
17
|
+
# the application server
|
|
18
|
+
def initialize(redis, warn_on_clock_skew: true)
|
|
19
|
+
@warn_on_clock_skew = warn_on_clock_skew
|
|
20
|
+
@redis = redis
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
module Default
|
|
6
|
+
COOL_OFF_TIME = 60.0
|
|
7
|
+
|
|
8
|
+
DATA_STORE = Stoplight::DataStore::Memory.new
|
|
9
|
+
|
|
10
|
+
ERROR_NOTIFIER = ->(error) { warn error }
|
|
11
|
+
|
|
12
|
+
FORMATTER = Infrastructure::Notifier::Generic::DEFAULT_FORMATTER
|
|
13
|
+
|
|
14
|
+
NOTIFIERS = [Infrastructure::Notifier::IO.new($stderr)].freeze
|
|
15
|
+
|
|
16
|
+
THRESHOLD = 3
|
|
17
|
+
RECOVERY_THRESHOLD = 1
|
|
18
|
+
|
|
19
|
+
WINDOW_SIZE = nil
|
|
20
|
+
|
|
21
|
+
TRACKED_ERRORS = [StandardError].freeze
|
|
22
|
+
SKIPPED_ERRORS = [].freeze
|
|
23
|
+
|
|
24
|
+
TRAFFIC_CONTROL = Domain::TrafficControl::ConsecutiveErrors.new
|
|
25
|
+
TRAFFIC_RECOVERY = Domain::TrafficRecovery::ConsecutiveSuccesses.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "forwardable"
|
|
4
|
-
|
|
5
3
|
module Stoplight
|
|
6
|
-
module
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
# This class allows users to define default settings for various Stoplight
|
|
10
|
-
# parameters, such as cool-off time, data store, error notifier, and more.
|
|
11
|
-
# TODO: add evaluation/recovery strategy support
|
|
12
|
-
class UserDefaultConfig
|
|
13
|
-
extend Forwardable
|
|
14
|
-
|
|
4
|
+
module Wiring
|
|
5
|
+
# User-facing configuration interface
|
|
6
|
+
class DefaultConfiguration
|
|
15
7
|
# @!attribute [w] cool_off_time
|
|
16
8
|
# @return [Integer, nil] The default cool-off time in seconds.
|
|
17
9
|
attr_writer :cool_off_time
|
|
18
10
|
|
|
19
|
-
# @!attribute [w] error_notifier
|
|
20
|
-
# @return [Proc, nil] The default error notifier (callable object).
|
|
21
|
-
attr_writer :error_notifier
|
|
22
|
-
|
|
23
|
-
# @!attribute [rw] notifiers
|
|
24
|
-
# @return [Array<Stoplight::Notifier::Base>] The default list of notifiers.
|
|
25
|
-
attr_accessor :notifiers
|
|
26
|
-
|
|
27
11
|
# @!attribute [w] threshold
|
|
28
12
|
# @return [Integer, Float, nil] The default failure threshold to trip the circuit breaker.
|
|
29
13
|
attr_writer :threshold
|
|
@@ -44,14 +28,26 @@ module Stoplight
|
|
|
44
28
|
# @return [Array<Class>, nil] The default list of errors to skip.
|
|
45
29
|
attr_writer :skipped_errors
|
|
46
30
|
|
|
47
|
-
# @!attribute [w]
|
|
48
|
-
# @return [
|
|
49
|
-
attr_writer :
|
|
31
|
+
# @!attribute [w] error_notifier
|
|
32
|
+
# @return [Proc, nil] The default error notifier (callable object).
|
|
33
|
+
attr_writer :error_notifier
|
|
34
|
+
|
|
35
|
+
# @!attribute [rw] notifiers
|
|
36
|
+
# @return [Array<Stoplight::Domain::StateTransitionNotifier>] The default list of notifiers.
|
|
37
|
+
attr_accessor :notifiers
|
|
38
|
+
|
|
39
|
+
# @!attribute [rw] data_store
|
|
40
|
+
# @return [Stoplight::Wiring::DataStore::Base] The default data store instance.
|
|
41
|
+
attr_accessor :data_store
|
|
50
42
|
|
|
51
43
|
# @!attribute [w] traffic_control
|
|
52
|
-
# @return [Stoplight::TrafficControl::Base
|
|
44
|
+
# @return [Stoplight::Domain::TrafficControl::Base] The traffic control strategy.
|
|
53
45
|
attr_writer :traffic_control
|
|
54
46
|
|
|
47
|
+
# @!attribute [w] traffic_recovery
|
|
48
|
+
# @return [Stoplight::Domain::TrafficRecovery::Base] The traffic recovery strategy.
|
|
49
|
+
attr_writer :traffic_recovery
|
|
50
|
+
|
|
55
51
|
def initialize
|
|
56
52
|
# This allows users appending notifiers to the default list,
|
|
57
53
|
# while still allowing them to override the default list.
|
|
@@ -65,21 +61,18 @@ module Stoplight
|
|
|
65
61
|
def to_h
|
|
66
62
|
{
|
|
67
63
|
cool_off_time: @cool_off_time,
|
|
68
|
-
data_store: @data_store,
|
|
69
|
-
error_notifier: @error_notifier,
|
|
70
|
-
notifiers: @notifiers,
|
|
71
64
|
threshold: @threshold,
|
|
72
65
|
recovery_threshold: @recovery_threshold,
|
|
73
66
|
window_size: @window_size,
|
|
74
67
|
tracked_errors: @tracked_errors,
|
|
75
68
|
skipped_errors: @skipped_errors,
|
|
76
|
-
|
|
69
|
+
data_store: @data_store,
|
|
70
|
+
error_notifier: @error_notifier,
|
|
71
|
+
notifiers: @notifiers,
|
|
72
|
+
traffic_control: @traffic_control,
|
|
73
|
+
traffic_recovery: @traffic_recovery
|
|
77
74
|
}.compact
|
|
78
75
|
end
|
|
79
|
-
|
|
80
|
-
# @return [Boolean] True if the configuration hash is not empty, false otherwise.
|
|
81
|
-
# @api private
|
|
82
|
-
def_delegator :to_h, :any?
|
|
83
76
|
end
|
|
84
77
|
end
|
|
85
78
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
# Builds the default LightFactory from user-provided configuration which is
|
|
6
|
+
# used as the basis for all circuit breakers.
|
|
7
|
+
#
|
|
8
|
+
class DefaultFactoryBuilder
|
|
9
|
+
# @!attribute [r] configuration
|
|
10
|
+
# @return [Stoplight::Wiring::DefaultConfiguration]
|
|
11
|
+
#
|
|
12
|
+
attr_reader :configuration
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@configuration = DefaultConfiguration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Stoplight::Wiring::LightFactory]
|
|
19
|
+
# @api private the method is used internally by Stoplight
|
|
20
|
+
def build
|
|
21
|
+
LightFactory.new(configuration.to_h)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
module Light
|
|
6
|
+
# Provides default settings for the Stoplight library.
|
|
7
|
+
# @api private
|
|
8
|
+
DefaultConfig = Domain::Config.empty.with(
|
|
9
|
+
cool_off_time: Default::COOL_OFF_TIME,
|
|
10
|
+
threshold: Default::THRESHOLD,
|
|
11
|
+
recovery_threshold: Default::RECOVERY_THRESHOLD,
|
|
12
|
+
window_size: Default::WINDOW_SIZE,
|
|
13
|
+
tracked_errors: Default::TRACKED_ERRORS,
|
|
14
|
+
skipped_errors: Default::SKIPPED_ERRORS
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
module Wiring
|
|
7
|
+
# Constructs a fully-wired Light instance from validated configuration.
|
|
8
|
+
#
|
|
9
|
+
# LightBuilder is the final assembly step in the Light creation pipeline.
|
|
10
|
+
# It receives validated config and dependencies from ConfigurationPipeline
|
|
11
|
+
# and wires together all infrastructure components (data stores, trackers,
|
|
12
|
+
# strategies) needed for a functioning circuit breaker.
|
|
13
|
+
#
|
|
14
|
+
# LightBuilder maintains a global registry (MEMORY_REGISTRY) that ensures
|
|
15
|
+
# the same Memory data store config object always produces the same
|
|
16
|
+
# data store instance:
|
|
17
|
+
#
|
|
18
|
+
# data_store = Stoplight::DataStore::Memory.new
|
|
19
|
+
# light1 = Stoplight("foo", data_store: data_store)
|
|
20
|
+
# light2 = Stoplight("bar", data_store: data_store)
|
|
21
|
+
# # light1 and light2 share the same underlying memory store
|
|
22
|
+
#
|
|
23
|
+
# light3 = Stoplight("baz", data_store: Stoplight::DataStore::Memory.new)
|
|
24
|
+
# # light3 has its own independent store
|
|
25
|
+
#
|
|
26
|
+
# This singleton behavior is keyed by config object identity (object_id),
|
|
27
|
+
# not by value equality.
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
class LightBuilder
|
|
31
|
+
FAILOVER_DATA_STORE_CONFIG = Stoplight::DataStore::Memory.new
|
|
32
|
+
private_constant :FAILOVER_DATA_STORE_CONFIG
|
|
33
|
+
|
|
34
|
+
MEMORY_REGISTRY = Concurrent::Map.new
|
|
35
|
+
private_constant :MEMORY_REGISTRY
|
|
36
|
+
|
|
37
|
+
# @!attribute data_store_config
|
|
38
|
+
# @return [Stoplight::DataStore::Bose]
|
|
39
|
+
private attr_reader :data_store_config
|
|
40
|
+
|
|
41
|
+
# @!attribute error_notifier
|
|
42
|
+
# @return [Proc]
|
|
43
|
+
private attr_reader :error_notifier
|
|
44
|
+
|
|
45
|
+
# @!attribute traffic_recovery
|
|
46
|
+
# @return [Stoplight::Domain::TrafficRecovery::Base]
|
|
47
|
+
private attr_reader :traffic_recovery
|
|
48
|
+
|
|
49
|
+
# @!attribute traffic_control
|
|
50
|
+
# @return [Stoplight::Domain::TrafficControl::Base]
|
|
51
|
+
private attr_reader :traffic_control
|
|
52
|
+
|
|
53
|
+
# @!attribute config
|
|
54
|
+
# @return [Stoplight::Domain::Config]
|
|
55
|
+
private attr_reader :config
|
|
56
|
+
|
|
57
|
+
# @!attribute factory
|
|
58
|
+
# @return [Stoplight::Domain::LightFactory]
|
|
59
|
+
private attr_reader :factory
|
|
60
|
+
|
|
61
|
+
def initialize(settings)
|
|
62
|
+
@notifiers = settings[:notifiers]
|
|
63
|
+
@data_store_config = settings[:data_store]
|
|
64
|
+
@error_notifier = settings[:error_notifier]
|
|
65
|
+
@traffic_recovery = settings[:traffic_recovery]
|
|
66
|
+
@traffic_control = settings[:traffic_control]
|
|
67
|
+
@config = settings[:config]
|
|
68
|
+
@factory = settings[:factory]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build
|
|
72
|
+
Stoplight::Domain::Light.new(
|
|
73
|
+
config,
|
|
74
|
+
state_store:,
|
|
75
|
+
green_run_strategy:,
|
|
76
|
+
yellow_run_strategy:,
|
|
77
|
+
red_run_strategy:,
|
|
78
|
+
factory:
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def state_store = Stoplight::Infrastructure::Storage::CompatibilityState.new(config:, data_store:)
|
|
83
|
+
|
|
84
|
+
# @return [<Stoplight::Notifier::Base>]
|
|
85
|
+
private def notifiers
|
|
86
|
+
Array(@notifiers).map do |notifier|
|
|
87
|
+
Infrastructure::Notifier::FailSafe.new(notifier:, error_notifier:)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def redis_recovery_lock_store
|
|
92
|
+
Infrastructure::DataStore::Redis::RecoveryLockStore.new(
|
|
93
|
+
redis: data_store_config.redis,
|
|
94
|
+
lock_timeout: config.cool_off_time_in_milliseconds,
|
|
95
|
+
scripting:
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private def scripting = Infrastructure::DataStore::Redis::Scripting.new(redis: data_store_config.redis)
|
|
100
|
+
|
|
101
|
+
private def memory_recovery_lock_store
|
|
102
|
+
Infrastructure::DataStore::Memory::RecoveryLockStore.new
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private def failover_data_store
|
|
106
|
+
create_data_store(FAILOVER_DATA_STORE_CONFIG)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private def data_store
|
|
110
|
+
create_data_store(data_store_config)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private def metrics_store
|
|
114
|
+
Stoplight::Infrastructure::Storage::CompatibilityMetrics.new(config:, data_store:)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private def recovery_lock_store
|
|
118
|
+
Stoplight::Infrastructure::Storage::CompatibilityRecoveryLock.new(config:, data_store:)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private def request_tracker
|
|
122
|
+
Domain::Tracker::Request.new(traffic_control:, notifiers:, config:, metrics_store:, state_store:)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private def recovery_probe_tracker
|
|
126
|
+
Domain::Tracker::RecoveryProbe.new(
|
|
127
|
+
traffic_recovery:,
|
|
128
|
+
notifiers:,
|
|
129
|
+
config:,
|
|
130
|
+
metrics_store: recovery_metrics_store,
|
|
131
|
+
state_store:
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private def recovery_metrics_store
|
|
136
|
+
Stoplight::Infrastructure::Storage::CompatibilityRecoveryMetrics.new(config:, data_store:)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private def green_run_strategy
|
|
140
|
+
Domain::Strategies::GreenRunStrategy.new(config:, request_tracker:)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private def yellow_run_strategy
|
|
144
|
+
Domain::Strategies::YellowRunStrategy.new(
|
|
145
|
+
config:,
|
|
146
|
+
notifiers:,
|
|
147
|
+
request_tracker: recovery_probe_tracker,
|
|
148
|
+
red_run_strategy:,
|
|
149
|
+
state_store:,
|
|
150
|
+
metrics_store:,
|
|
151
|
+
recovery_lock_store:
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private def red_run_strategy
|
|
156
|
+
Domain::Strategies::RedRunStrategy.new(config:)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private def create_data_store(data_store_config)
|
|
160
|
+
case data_store_config
|
|
161
|
+
in Stoplight::DataStore::Memory
|
|
162
|
+
memory_registry.compute_if_absent(data_store_config.object_id) do
|
|
163
|
+
Infrastructure::DataStore::Memory.new(
|
|
164
|
+
recovery_lock_store: memory_recovery_lock_store
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
in Stoplight::DataStore::Redis
|
|
168
|
+
Infrastructure::DataStore::FailSafe.new(
|
|
169
|
+
data_store: Stoplight::Infrastructure::DataStore::Redis.new(
|
|
170
|
+
redis: data_store_config.redis,
|
|
171
|
+
warn_on_clock_skew: data_store_config.warn_on_clock_skew,
|
|
172
|
+
recovery_lock_store: redis_recovery_lock_store,
|
|
173
|
+
scripting:
|
|
174
|
+
),
|
|
175
|
+
error_notifier:,
|
|
176
|
+
failover_data_store:,
|
|
177
|
+
circuit_breaker: Stoplight.system_light("data_store:fail_safe:redis")
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private def memory_registry = MEMORY_REGISTRY
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|