stoplight 5.8.3 → 6.0.0.rc2
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/CHANGELOG.md +1 -1
- data/LICENSE.md +1 -0
- data/README.md +132 -79
- data/UPGRADING.md +265 -6
- data/lib/stoplight/admin/actions/action.rb +4 -12
- data/lib/stoplight/admin/actions/lock.rb +12 -9
- data/lib/stoplight/admin/actions/lock_all.rb +27 -0
- data/lib/stoplight/admin/actions/remove.rb +12 -9
- data/lib/stoplight/admin/actions/stats.rb +15 -12
- data/lib/stoplight/admin/actions/unlock.rb +10 -9
- data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
- data/lib/stoplight/admin/assets/favicon.ico +0 -0
- data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
- data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
- data/lib/stoplight/admin/assets/icon.svg +7 -0
- data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
- data/lib/stoplight/admin/config_registry.rb +40 -0
- data/lib/stoplight/admin/dependencies.rb +29 -28
- data/lib/stoplight/admin/helpers.rb +50 -17
- data/lib/stoplight/admin/light_view.rb +184 -0
- data/lib/stoplight/admin/lights_stats.rb +7 -9
- data/lib/stoplight/admin/views/_card.erb +19 -21
- data/lib/stoplight/admin/views/index.erb +9 -3
- data/lib/stoplight/admin/views/layout.erb +45 -13
- data/lib/stoplight/admin.rb +110 -21
- data/lib/stoplight/data_store.rb +18 -8
- data/lib/stoplight/domain/config.rb +4 -1
- data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
- data/lib/stoplight/domain/failure.rb +4 -0
- data/lib/stoplight/domain/id.rb +13 -0
- data/lib/stoplight/domain/light.rb +35 -77
- data/lib/stoplight/domain/lock_control.rb +55 -0
- data/lib/stoplight/domain/matcher_validator.rb +24 -0
- data/lib/stoplight/domain/state_snapshot.rb +2 -6
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
- data/lib/stoplight/domain/telemetry/bus.rb +90 -0
- data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
- data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
- data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
- data/lib/stoplight/domain/telemetry/failure.rb +13 -0
- data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
- data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
- data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
- data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
- data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
- data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
- data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
- data/lib/stoplight/domain/telemetry/settings.rb +21 -0
- data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
- data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
- data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
- data/lib/stoplight/domain/tracker/request.rb +17 -8
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
- data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
- data/lib/stoplight/error.rb +10 -1
- data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
- data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
- data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
- data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
- data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
- data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
- data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
- data/lib/stoplight/infrastructure/redis/key.rb +25 -0
- data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
- data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
- data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
- data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
- data/lib/stoplight/infrastructure/system_clock.rb +4 -0
- data/lib/stoplight/telemetry.rb +19 -0
- data/lib/stoplight/types.rb +15 -2
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_config.rb +2 -1
- data/lib/stoplight/wiring/default_configuration.rb +3 -2
- data/lib/stoplight/wiring/external_caller.rb +27 -0
- data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
- data/lib/stoplight/wiring/global_state.rb +93 -0
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
- data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
- data/lib/stoplight/wiring/light_factory.rb +154 -97
- data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
- data/lib/stoplight/wiring/redis/backend.rb +1 -1
- data/lib/stoplight/wiring/system/storage.rb +69 -0
- data/lib/stoplight/wiring/system.rb +125 -56
- data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
- data/lib/stoplight.rb +50 -194
- data/sig/sinatra/base.rbs +6 -0
- data/sig/stdlib/securerandom.rbs +4 -0
- data/sig/stoplight/admin/actions/action.rbs +10 -0
- data/sig/stoplight/admin/actions/lock.rbs +17 -0
- data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
- data/sig/stoplight/admin/actions/remove.rbs +22 -0
- data/sig/stoplight/admin/actions/stats.rbs +28 -0
- data/sig/stoplight/admin/actions/unlock.rbs +17 -0
- data/sig/stoplight/admin/config_registry.rbs +20 -0
- data/sig/stoplight/admin/dependencies.rbs +16 -0
- data/sig/stoplight/admin/helpers.rbs +17 -0
- data/sig/stoplight/admin/light_view.rbs +39 -0
- data/sig/stoplight/admin/lights_stats.rbs +30 -0
- data/sig/stoplight/admin.rbs +14 -0
- data/sig/stoplight/color.rbs +3 -3
- data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
- data/sig/stoplight/data_store/memory.rbs +9 -0
- data/sig/stoplight/data_store/redis.rbs +17 -0
- data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
- data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
- data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
- data/sig/stoplight/domain/id.rbs +7 -0
- data/sig/stoplight/domain/light.rbs +39 -0
- data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
- data/sig/stoplight/domain/lock_control.rbs +16 -0
- data/sig/stoplight/domain/matcher_validator.rbs +11 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
- data/sig/stoplight/domain/ports/data_store.rbs +9 -0
- data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
- data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
- data/sig/stoplight/domain/ports/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
- data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
- data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
- data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
- data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
- data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
- data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
- data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
- data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
- data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
- data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
- data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
- data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
- data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
- data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
- data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
- data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
- data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
- data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
- data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
- data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
- data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
- data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
- data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
- data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
- data/sig/stoplight/domain/telemetry/types.rbs +32 -0
- data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
- data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
- data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
- data/sig/stoplight/domain/types.rbs +25 -0
- data/sig/stoplight/error.rbs +8 -0
- data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
- data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
- data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
- data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
- data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
- data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
- data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
- data/sig/stoplight/infrastructure/types.rbs +6 -0
- data/sig/stoplight/notifier.rbs +1 -1
- data/sig/stoplight/ports/system.rbs +16 -10
- data/sig/stoplight/state.rbs +3 -3
- data/sig/stoplight/telemetry.rbs +17 -0
- data/sig/stoplight/types.rbs +9 -0
- data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
- data/sig/stoplight/wiring/default.rbs +26 -0
- data/sig/stoplight/wiring/default_configuration.rbs +39 -0
- data/sig/stoplight/wiring/external_caller.rbs +13 -0
- data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
- data/sig/stoplight/wiring/global_state.rbs +36 -0
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
- data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
- data/sig/stoplight/wiring/light_factory.rbs +78 -0
- data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
- data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
- data/sig/stoplight/wiring/system/storage.rbs +35 -0
- data/sig/stoplight/wiring/system.rbs +30 -0
- data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
- data/sig/stoplight/wiring/types.rbs +6 -0
- data/sig/stoplight.rbs +37 -54
- metadata +190 -143
- data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
- data/lib/stoplight/admin/actions/lock_green.rb +0 -23
- data/lib/stoplight/admin/actions/lock_red.rb +0 -23
- data/lib/stoplight/admin/lights_repository/light.rb +0 -171
- data/lib/stoplight/admin/lights_repository.rb +0 -87
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
- data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
- data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
- data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
- data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
- data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
- data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
- data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
- data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
- data/lib/stoplight/wiring/light_builder.rb +0 -198
- data/lib/stoplight/wiring/notifier_factory.rb +0 -26
- data/lib/stoplight/wiring/system/light_builder.rb +0 -47
- data/lib/stoplight/wiring/system/light_factory.rb +0 -64
- data/sig/_private/stoplight/domain/light.rbs +0 -25
- data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
- data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
- data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
- data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
- data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
- data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
- data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
- data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
- data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
- data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
- data/sig/_private/stoplight/system/light_builder.rbs +0 -23
- data/sig/_private/stoplight/system/light_factory.rbs +0 -17
- data/sig/_private/stoplight/types.rbs +0 -6
- data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
- data/sig/_private/stoplight/wiring/default.rbs +0 -26
- data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
- data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
- data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
- data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
- data/sig/_private/stoplight/wiring/system.rbs +0 -15
- data/sig/_private/stoplight.rbs +0 -48
- data/sig/stoplight/data_store.rbs +0 -19
- data/sig/stoplight/ports/configuration.rbs +0 -19
- data/sig/stoplight/ports/exception_matcher.rbs +0 -8
- data/sig/stoplight/ports/light.rbs +0 -12
- data/sig/stoplight/ports/light_info.rbs +0 -5
- data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
- /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
- /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
local meta_key = KEYS[1]
|
|
2
|
-
local current_ts = tonumber(ARGV[1])
|
|
3
|
-
|
|
4
|
-
-- HSETNX returns 1 if field is new and was set, 0 if field already exists
|
|
5
|
-
local became_yellow = redis.call('HSETNX', meta_key, 'recovery_started_at', current_ts)
|
|
6
|
-
if became_yellow == 1 then
|
|
7
|
-
redis.call('HDEL', meta_key, 'recovery_scheduled_after', 'breached_at', 'recovered_at')
|
|
8
|
-
end
|
|
9
|
-
return became_yellow
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "securerandom"
|
|
4
|
-
require "forwardable"
|
|
5
|
-
|
|
6
|
-
module Stoplight
|
|
7
|
-
module Infrastructure
|
|
8
|
-
module Redis
|
|
9
|
-
class DataStore
|
|
10
|
-
# Distributed recovery lock using Redis SET NX (set-if-not-exists).
|
|
11
|
-
#
|
|
12
|
-
# Lock Acquisition:
|
|
13
|
-
# - Uses unique UUID token to prevent accidental release of others' locks
|
|
14
|
-
# - Atomic SET with NX flag ensures only one process acquires recovery_lock
|
|
15
|
-
# - TTL (px: lock_timeout) auto-releases recovery_lock if process crashes
|
|
16
|
-
#
|
|
17
|
-
# Lock Release:
|
|
18
|
-
# - Lua script ensures only token holder can release (token comparison)
|
|
19
|
-
# - Best-effort release; TTL cleanup handles failures
|
|
20
|
-
#
|
|
21
|
-
# Failure Modes:
|
|
22
|
-
# - Lock contention: Returns false, caller should skip probe
|
|
23
|
-
# - Redis unavailable: raises an error and let caller decide
|
|
24
|
-
# - Crashed holder: raises an error and let caller decide. Lock auto-expires after lock_timeout
|
|
25
|
-
# - Release failure: Lock auto-expires after lock_timeout
|
|
26
|
-
#
|
|
27
|
-
class RecoveryLockStore
|
|
28
|
-
def initialize(redis:, lock_timeout:, scripting:)
|
|
29
|
-
@redis = redis
|
|
30
|
-
@lock_timeout = lock_timeout
|
|
31
|
-
@scripting = scripting
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def acquire_lock(light_name)
|
|
35
|
-
recovery_lock = RecoveryLockToken.new(light_name:)
|
|
36
|
-
|
|
37
|
-
acquired = !!redis.then do |client|
|
|
38
|
-
client.set(recovery_lock.lock_key, recovery_lock.token, nx: true, px: lock_timeout)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
recovery_lock if acquired
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def release_lock(recovery_lock)
|
|
45
|
-
scripting.call(
|
|
46
|
-
:release_lock,
|
|
47
|
-
keys: [recovery_lock.lock_key], args: [recovery_lock.token]
|
|
48
|
-
)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
protected
|
|
52
|
-
|
|
53
|
-
attr_reader :redis
|
|
54
|
-
attr_reader :lock_timeout
|
|
55
|
-
attr_reader :scripting
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "securerandom"
|
|
4
|
-
|
|
5
|
-
module Stoplight
|
|
6
|
-
module Infrastructure
|
|
7
|
-
module Redis
|
|
8
|
-
class DataStore
|
|
9
|
-
class RecoveryLockToken
|
|
10
|
-
attr_reader :light_name
|
|
11
|
-
attr_reader :token
|
|
12
|
-
|
|
13
|
-
# @param light_name [String]
|
|
14
|
-
def initialize(light_name:)
|
|
15
|
-
@light_name = light_name
|
|
16
|
-
@token = SecureRandom.uuid
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def lock_key = key(:locks, :recovery, light_name)
|
|
20
|
-
|
|
21
|
-
private def key(*parts)
|
|
22
|
-
Stoplight::Infrastructure::Redis::DataStore.key(*parts)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Stoplight
|
|
4
|
-
module Infrastructure
|
|
5
|
-
module Redis
|
|
6
|
-
class DataStore
|
|
7
|
-
# Manages Lua scripts for Redis operations.
|
|
8
|
-
#
|
|
9
|
-
# This class provides execution of Lua scripts by caching their SHA digests
|
|
10
|
-
# and automatically reloading scripts if they're evicted from Redis memory.
|
|
11
|
-
#
|
|
12
|
-
# @example
|
|
13
|
-
# script_manager = ScriptManager.new(redis: redis_client)
|
|
14
|
-
# script_manager.call(:increment_counter, keys: ["counter:1"], args: [5])
|
|
15
|
-
#
|
|
16
|
-
# @note Scripts are loaded lazily on first use and cached in memory
|
|
17
|
-
# @note Script files must be named `<script_name>.lua` and located in scripts_root
|
|
18
|
-
class Scripting
|
|
19
|
-
current_dir = __dir__ or raise "Cannot determine script directory"
|
|
20
|
-
SCRIPTS_ROOT = File.join(current_dir, "lua_scripts")
|
|
21
|
-
private_constant :SCRIPTS_ROOT
|
|
22
|
-
|
|
23
|
-
class << self
|
|
24
|
-
def default_scripts_root = SCRIPTS_ROOT
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def initialize(redis:, scripts_root: self.class.default_scripts_root)
|
|
28
|
-
@scripts_root = scripts_root
|
|
29
|
-
@redis = redis
|
|
30
|
-
@shas = {}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def call(script_name, keys: [], args: [])
|
|
34
|
-
redis.then do |client|
|
|
35
|
-
client.evalsha(script_sha(script_name), keys: keys.map(&:to_s), argv: args.map(&:to_s))
|
|
36
|
-
end
|
|
37
|
-
rescue ::Redis::CommandError => error
|
|
38
|
-
if error.message.include?("NOSCRIPT")
|
|
39
|
-
reload_script(script_name)
|
|
40
|
-
retry
|
|
41
|
-
else
|
|
42
|
-
raise
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
protected
|
|
47
|
-
|
|
48
|
-
attr_reader :scripts_root
|
|
49
|
-
attr_reader :redis
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
attr_reader :shas
|
|
54
|
-
|
|
55
|
-
def reload_script(script_name)
|
|
56
|
-
shas.delete(script_name)
|
|
57
|
-
script_sha(script_name)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def script_sha(script_name)
|
|
61
|
-
if shas.key?(script_name)
|
|
62
|
-
shas[script_name]
|
|
63
|
-
else
|
|
64
|
-
script = File.read(File.join(scripts_root, "#{script_name}.lua"))
|
|
65
|
-
|
|
66
|
-
shas[script_name] = redis.then { |client| client.script(:load, script) }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "forwardable"
|
|
4
|
-
|
|
5
|
-
module Stoplight
|
|
6
|
-
module Infrastructure
|
|
7
|
-
module Redis
|
|
8
|
-
# == Errors
|
|
9
|
-
# All errors are stored in the sorted set where keys are serialized errors and
|
|
10
|
-
# values (Redis uses "score" term) contain integer representations of the time
|
|
11
|
-
# when an error happened.
|
|
12
|
-
#
|
|
13
|
-
# This data structure enables us to query errors that happened within a specific
|
|
14
|
-
# period. We use this feature to support +window_size+ option.
|
|
15
|
-
#
|
|
16
|
-
# To avoid uncontrolled memory consumption, we keep at most +config.threshold+ number
|
|
17
|
-
# of errors happened within last +config.window_size+ seconds (by default infinity).
|
|
18
|
-
#
|
|
19
|
-
# @see Base
|
|
20
|
-
# steep:ignore:start
|
|
21
|
-
class DataStore
|
|
22
|
-
extend Forwardable
|
|
23
|
-
|
|
24
|
-
class << self
|
|
25
|
-
# Generates a Redis key by joining the prefix with the provided pieces.
|
|
26
|
-
#
|
|
27
|
-
# @param pieces [Array<String, Integer>] Parts of the key to be joined.
|
|
28
|
-
# @return [String] The generated Redis key.
|
|
29
|
-
# @api private
|
|
30
|
-
def key(*pieces)
|
|
31
|
-
[KEY_PREFIX, *pieces].join(KEY_SEPARATOR)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
METRICS_RETENTION_TIME = 60 * 60 * 24 # 1 day
|
|
35
|
-
|
|
36
|
-
# Retrieves the list of Redis bucket keys required to cover a specific time window.
|
|
37
|
-
#
|
|
38
|
-
# @param light_name [String] The name of the light (used as part of the Redis key).
|
|
39
|
-
# @param metric [String] The metric type (e.g., "errors").
|
|
40
|
-
# @param window_end [Time, Numeric] The end time of the window (can be a Time object or a numeric timestamp).
|
|
41
|
-
# @param window_size [Numeric] The size of the time window in seconds.
|
|
42
|
-
# @return [Array<String>] A list of Redis keys for the buckets that cover the time window.
|
|
43
|
-
# @api private
|
|
44
|
-
def buckets_for_window(light_name, metric:, window_end:, window_size:)
|
|
45
|
-
window_end_ts = window_end.to_i
|
|
46
|
-
window_start_ts = window_end_ts - [window_size, METRICS_RETENTION_TIME].compact.min.to_i
|
|
47
|
-
|
|
48
|
-
# Find bucket timestamps that contain any part of the window
|
|
49
|
-
start_bucket = (window_start_ts / bucket_size) * bucket_size
|
|
50
|
-
|
|
51
|
-
# End bucket is the last bucket that contains data within our window
|
|
52
|
-
end_bucket = ((window_end_ts - 1) / bucket_size) * bucket_size
|
|
53
|
-
|
|
54
|
-
(start_bucket..end_bucket).step(bucket_size).map do |bucket_start|
|
|
55
|
-
bucket_key(light_name, metric: metric, time: bucket_start)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Generates a Redis key for a specific metric and time.
|
|
60
|
-
#
|
|
61
|
-
# @param light_name [String] The name of the light.
|
|
62
|
-
# @param metric [String] The metric type (e.g., "errors").
|
|
63
|
-
# @param time [Time, Numeric] The time for which to generate the key.
|
|
64
|
-
# @return [String] The generated Redis key.
|
|
65
|
-
def bucket_key(light_name, metric:, time:)
|
|
66
|
-
key("metrics", light_name, metric, (time.to_i / bucket_size) * bucket_size)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
BUCKET_SIZE = 3600 # 1h
|
|
70
|
-
private_constant :BUCKET_SIZE
|
|
71
|
-
|
|
72
|
-
private def bucket_size
|
|
73
|
-
BUCKET_SIZE
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
KEY_SEPARATOR = ":"
|
|
78
|
-
KEY_PREFIX = %w[stoplight v5].join(KEY_SEPARATOR)
|
|
79
|
-
|
|
80
|
-
# @!attribute recovery_lock_store
|
|
81
|
-
# @return [Stoplight::Infrastructure::Redis::DataStore::RecoveryLockStore]
|
|
82
|
-
protected attr_reader :recovery_lock_store
|
|
83
|
-
|
|
84
|
-
# @!attribute scripting
|
|
85
|
-
# @return [Stoplight::Infrastructure::Redis::DataStore::Scripting]
|
|
86
|
-
protected attr_reader :scripting
|
|
87
|
-
|
|
88
|
-
# @!attribute redis
|
|
89
|
-
# @return [::Redis | ConnectionPool<::Redis>]
|
|
90
|
-
protected attr_reader :redis
|
|
91
|
-
|
|
92
|
-
# @!attribute warn_on_clock_skew
|
|
93
|
-
# @return [Boolean]
|
|
94
|
-
protected attr_reader :warn_on_clock_skew
|
|
95
|
-
|
|
96
|
-
# @!attribute clock
|
|
97
|
-
# @return [Stoplight::Domain::_Clock]
|
|
98
|
-
private attr_reader :clock
|
|
99
|
-
|
|
100
|
-
# @param redis [::Redis, ConnectionPool<::Redis>]
|
|
101
|
-
# @param recovery_lock_store [Stoplight::Infrastructure::Redis::DataStore::RecoveryLockStore]
|
|
102
|
-
# @param warn_on_clock_skew [Boolean] (true) Whether to warn about clock skew between Redis and
|
|
103
|
-
# @param scripting [Stoplight::Infrastructure::Redis::DataStore::Scripting]
|
|
104
|
-
# @param clock [Stoplight::Domain::_Clock]
|
|
105
|
-
# the application server
|
|
106
|
-
def initialize(redis:, recovery_lock_store:, scripting:, clock:, warn_on_clock_skew: true)
|
|
107
|
-
@clock = clock
|
|
108
|
-
@warn_on_clock_skew = warn_on_clock_skew
|
|
109
|
-
@redis = redis
|
|
110
|
-
@recovery_lock_store = recovery_lock_store
|
|
111
|
-
@scripting = scripting
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def names
|
|
115
|
-
metadata, recovery_metrics = @redis.then do |client|
|
|
116
|
-
[
|
|
117
|
-
[key("metadata", "*"), /^#{key("metadata", "")}/],
|
|
118
|
-
[key("recovery_metrics", "*"), /^#{key("recovery_metrics", "")}/]
|
|
119
|
-
].map do |(pattern, prefix_regex)|
|
|
120
|
-
client.scan_each(match: pattern).to_a.map do |key|
|
|
121
|
-
key.sub(prefix_regex, "")
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
metadata + recovery_metrics
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# @param config [Stoplight::Domain::Config]
|
|
129
|
-
# @return [Stoplight::Domain::MetricsSnapshot]
|
|
130
|
-
def get_metrics(config)
|
|
131
|
-
config.name
|
|
132
|
-
|
|
133
|
-
window_end_ts = clock.current_time.to_f
|
|
134
|
-
window_start_ts = window_end_ts - config.window_size.to_i
|
|
135
|
-
|
|
136
|
-
if config.window_size
|
|
137
|
-
failure_keys = failure_bucket_keys(config, window_end: window_end_ts)
|
|
138
|
-
success_keys = success_bucket_keys(config, window_end: window_end_ts)
|
|
139
|
-
else
|
|
140
|
-
failure_keys = []
|
|
141
|
-
success_keys = []
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
successes, errors, last_success_at, last_error_json, consecutive_errors, consecutive_successes = scripting.call(
|
|
145
|
-
:get_metrics,
|
|
146
|
-
args: [
|
|
147
|
-
failure_keys.count,
|
|
148
|
-
window_start_ts,
|
|
149
|
-
window_end_ts,
|
|
150
|
-
"last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes"
|
|
151
|
-
],
|
|
152
|
-
keys: [
|
|
153
|
-
metadata_key(config),
|
|
154
|
-
*success_keys,
|
|
155
|
-
*failure_keys
|
|
156
|
-
]
|
|
157
|
-
)
|
|
158
|
-
consecutive_errors = config.window_size ? [consecutive_errors.to_i, errors].min : consecutive_errors.to_i
|
|
159
|
-
consecutive_successes = config.window_size ? [consecutive_successes.to_i, successes].min : consecutive_successes.to_i
|
|
160
|
-
|
|
161
|
-
Domain::MetricsSnapshot.new(
|
|
162
|
-
successes: (successes if config.window_size),
|
|
163
|
-
errors: (errors if config.window_size),
|
|
164
|
-
consecutive_errors:,
|
|
165
|
-
consecutive_successes:,
|
|
166
|
-
last_error: deserialize_failure(last_error_json),
|
|
167
|
-
last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
|
|
168
|
-
)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# @param config [Stoplight::Domain::Config]
|
|
172
|
-
# @return [Stoplight::Domain::MetricsSnapshot]
|
|
173
|
-
def get_recovery_metrics(config)
|
|
174
|
-
last_success_at, last_error_json, consecutive_errors, consecutive_successes = @redis.with do |client|
|
|
175
|
-
client.hmget(
|
|
176
|
-
recovery_metrics_key(config),
|
|
177
|
-
"last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes"
|
|
178
|
-
)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
Domain::MetricsSnapshot.new(
|
|
182
|
-
successes: nil, errors: nil,
|
|
183
|
-
consecutive_errors: consecutive_errors.to_i,
|
|
184
|
-
consecutive_successes: consecutive_successes.to_i,
|
|
185
|
-
last_error: deserialize_failure(last_error_json),
|
|
186
|
-
last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
|
|
187
|
-
)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# @return [Stoplight::Domain::StateSnapshot]
|
|
191
|
-
def get_state_snapshot(config)
|
|
192
|
-
detect_clock_skew
|
|
193
|
-
|
|
194
|
-
breached_at_raw, locked_state, recovery_scheduled_after_raw, recovery_started_at_raw = @redis.with do |client|
|
|
195
|
-
client.hmget(metadata_key(config), :breached_at, :locked_state, :recovery_scheduled_after, :recovery_started_at)
|
|
196
|
-
end
|
|
197
|
-
breached_at = breached_at_raw&.to_f
|
|
198
|
-
recovery_scheduled_after = recovery_scheduled_after_raw&.to_f
|
|
199
|
-
recovery_started_at = recovery_started_at_raw&.to_f
|
|
200
|
-
|
|
201
|
-
Domain::StateSnapshot.new(
|
|
202
|
-
breached_at: (clock.at(breached_at) if breached_at),
|
|
203
|
-
locked_state: locked_state || State::UNLOCKED,
|
|
204
|
-
recovery_scheduled_after: (clock.at(recovery_scheduled_after) if recovery_scheduled_after),
|
|
205
|
-
recovery_started_at: (clock.at(recovery_started_at) if recovery_started_at),
|
|
206
|
-
time: clock.current_time
|
|
207
|
-
)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def clear_metrics(config)
|
|
211
|
-
if config.window_size
|
|
212
|
-
window_end_ts = clock.current_time.to_i
|
|
213
|
-
@redis.with do |client|
|
|
214
|
-
client.multi do |tx|
|
|
215
|
-
tx.unlink(
|
|
216
|
-
*failure_bucket_keys(config, window_end: window_end_ts),
|
|
217
|
-
*success_bucket_keys(config, window_end: window_end_ts)
|
|
218
|
-
)
|
|
219
|
-
tx.hdel(metadata_key(config), "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes")
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
else
|
|
223
|
-
@redis.with do |client|
|
|
224
|
-
client.hdel(metadata_key(config), "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes")
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def clear_recovery_metrics(config)
|
|
230
|
-
@redis.with do |client|
|
|
231
|
-
client.del(recovery_metrics_key(config))
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
236
|
-
# @param exception [Exception]
|
|
237
|
-
# @return [void]
|
|
238
|
-
def record_failure(config, exception)
|
|
239
|
-
current_time = clock.current_time
|
|
240
|
-
current_ts = clock.current_time.to_f
|
|
241
|
-
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
242
|
-
|
|
243
|
-
scripting.call(
|
|
244
|
-
:record_failure,
|
|
245
|
-
args: [current_ts, SecureRandom.hex(12), serialize_failure(failure), metrics_ttl(config), metadata_ttl],
|
|
246
|
-
keys: [
|
|
247
|
-
metadata_key(config),
|
|
248
|
-
config.window_size && errors_key(config, time: current_ts)
|
|
249
|
-
].compact
|
|
250
|
-
)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def record_success(config, request_id: SecureRandom.hex(12))
|
|
254
|
-
current_ts = clock.current_time.to_f
|
|
255
|
-
|
|
256
|
-
scripting.call(
|
|
257
|
-
:record_success,
|
|
258
|
-
args: [current_ts, request_id, metrics_ttl(config), metadata_ttl],
|
|
259
|
-
keys: [
|
|
260
|
-
metadata_key(config),
|
|
261
|
-
config.window_size && successes_key(config, time: current_ts)
|
|
262
|
-
].compact
|
|
263
|
-
)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Records a failed recovery probe for a specific light configuration.
|
|
267
|
-
#
|
|
268
|
-
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
269
|
-
# @param exception [Exception]
|
|
270
|
-
# @return [void]
|
|
271
|
-
def record_recovery_probe_failure(config, exception)
|
|
272
|
-
current_time = clock.current_time
|
|
273
|
-
current_ts = clock.current_time.to_f
|
|
274
|
-
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
275
|
-
|
|
276
|
-
scripting.call(
|
|
277
|
-
:record_recovery_probe_failure,
|
|
278
|
-
args: [current_ts, serialize_failure(failure)],
|
|
279
|
-
keys: [recovery_metrics_key(config)]
|
|
280
|
-
)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Records a successful recovery probe for a specific light configuration.
|
|
284
|
-
#
|
|
285
|
-
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
286
|
-
# @return [void]
|
|
287
|
-
def record_recovery_probe_success(config)
|
|
288
|
-
current_ts = clock.current_time.to_f
|
|
289
|
-
|
|
290
|
-
scripting.call(
|
|
291
|
-
:record_recovery_probe_success,
|
|
292
|
-
args: [current_ts],
|
|
293
|
-
keys: [recovery_metrics_key(config)]
|
|
294
|
-
)
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def set_state(config, state)
|
|
298
|
-
@redis.then do |client|
|
|
299
|
-
client.hset(metadata_key(config), "locked_state", state)
|
|
300
|
-
end
|
|
301
|
-
state
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def inspect
|
|
305
|
-
"#<#{self.class.name} redis=#{@redis.inspect}>"
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
# Combined method that performs the state transition based on color
|
|
309
|
-
#
|
|
310
|
-
# @param config [Stoplight::Domain::Config] The light configuration
|
|
311
|
-
# @param color [String] The color to transition to ("green", "yellow", or "red")
|
|
312
|
-
# @return [Boolean] true if this is the first instance to detect this transition
|
|
313
|
-
def transition_to_color(config, color)
|
|
314
|
-
case color
|
|
315
|
-
when Color::GREEN
|
|
316
|
-
transition_to_green(config)
|
|
317
|
-
when Color::YELLOW
|
|
318
|
-
transition_to_yellow(config)
|
|
319
|
-
when Color::RED
|
|
320
|
-
transition_to_red(config)
|
|
321
|
-
else
|
|
322
|
-
raise ArgumentError, "Invalid color: #{color}"
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# @param config [Stoplight::Domain::Config]
|
|
327
|
-
# @return [Stoplight::Infrastructure::Redis::DataStore::RecoveryLockToken, nil]
|
|
328
|
-
def acquire_recovery_lock(config)
|
|
329
|
-
recovery_lock_store.acquire_lock(config.name)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# @param lock [Stoplight::Infrastructure::Redis::DataStore::RecoveryLockToken]
|
|
333
|
-
# @return [void]
|
|
334
|
-
def release_recovery_lock(lock)
|
|
335
|
-
recovery_lock_store.release_lock(lock)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Transitions to GREEN state and ensures only one notification
|
|
339
|
-
#
|
|
340
|
-
# @param config [Stoplight::Domain::Config] The light configuration
|
|
341
|
-
# @return [Boolean] true if this is the first instance to detect this transition
|
|
342
|
-
private def transition_to_green(config)
|
|
343
|
-
current_ts = clock.current_time.to_f
|
|
344
|
-
meta_key = metadata_key(config)
|
|
345
|
-
|
|
346
|
-
became_green = scripting.call(
|
|
347
|
-
:transition_to_green,
|
|
348
|
-
args: [current_ts],
|
|
349
|
-
keys: [meta_key]
|
|
350
|
-
)
|
|
351
|
-
became_green == 1
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# Transitions to YELLOW (recovery) state and ensures only one notification
|
|
355
|
-
#
|
|
356
|
-
# @param config [Stoplight::Domain::Config] The light configuration
|
|
357
|
-
# @return [Boolean] true if this is the first instance to detect this transition
|
|
358
|
-
private def transition_to_yellow(config)
|
|
359
|
-
current_ts = clock.current_time.to_f
|
|
360
|
-
meta_key = metadata_key(config)
|
|
361
|
-
|
|
362
|
-
became_yellow = scripting.call(
|
|
363
|
-
:transition_to_yellow,
|
|
364
|
-
args: [current_ts],
|
|
365
|
-
keys: [meta_key]
|
|
366
|
-
)
|
|
367
|
-
became_yellow == 1
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# Transitions to RED state and ensures only one notification
|
|
371
|
-
#
|
|
372
|
-
# @param config [Stoplight::Domain::Config] The light configuration
|
|
373
|
-
# @return [Boolean] true if this is the first instance to detect this transition
|
|
374
|
-
private def transition_to_red(config)
|
|
375
|
-
current_ts = clock.current_time.to_f
|
|
376
|
-
meta_key = metadata_key(config)
|
|
377
|
-
recovery_scheduled_after_ts = current_ts + config.cool_off_time
|
|
378
|
-
|
|
379
|
-
became_red = scripting.call(
|
|
380
|
-
:transition_to_red,
|
|
381
|
-
args: [current_ts, recovery_scheduled_after_ts],
|
|
382
|
-
keys: [meta_key]
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
became_red == 1
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
# Removes all traces of a light from Redis metadata (metrics will expire by TTL).
|
|
389
|
-
#
|
|
390
|
-
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
391
|
-
# @return [void]
|
|
392
|
-
def delete_light(config)
|
|
393
|
-
@redis.then do |client|
|
|
394
|
-
client.del(metadata_key(config), recovery_metrics_key(config))
|
|
395
|
-
end
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
# @param failure_json [String, nil]
|
|
399
|
-
# @return [Domain::Failure, nil]
|
|
400
|
-
private def deserialize_failure(failure_json)
|
|
401
|
-
return if failure_json.nil?
|
|
402
|
-
|
|
403
|
-
object = JSON.parse(failure_json)
|
|
404
|
-
error_object = object["error"]
|
|
405
|
-
|
|
406
|
-
error_class = error_object["class"]
|
|
407
|
-
error_message = error_object["message"]
|
|
408
|
-
time = clock.at(object["time"])
|
|
409
|
-
|
|
410
|
-
Domain::Failure.new(error_class, error_message, time)
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
# @param failure [Domain::Failure]
|
|
414
|
-
# @return [String]
|
|
415
|
-
private def serialize_failure(failure)
|
|
416
|
-
JSON.generate(
|
|
417
|
-
{
|
|
418
|
-
error: {
|
|
419
|
-
class: failure.error_class,
|
|
420
|
-
message: failure.error_message
|
|
421
|
-
},
|
|
422
|
-
time: failure.time.to_f
|
|
423
|
-
}
|
|
424
|
-
)
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
def_delegator "self.class", :key
|
|
428
|
-
|
|
429
|
-
private def failure_bucket_keys(config, window_end:)
|
|
430
|
-
self.class.buckets_for_window(
|
|
431
|
-
config.name,
|
|
432
|
-
metric: "failure",
|
|
433
|
-
window_end: window_end,
|
|
434
|
-
window_size: config.window_size
|
|
435
|
-
)
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
private def success_bucket_keys(config, window_end:)
|
|
439
|
-
self.class.buckets_for_window(
|
|
440
|
-
config.name,
|
|
441
|
-
metric: "success",
|
|
442
|
-
window_end: window_end,
|
|
443
|
-
window_size: config.window_size
|
|
444
|
-
)
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
private def recovery_probe_failure_bucket_keys(config, window_end:)
|
|
448
|
-
self.class.buckets_for_window(
|
|
449
|
-
config.name,
|
|
450
|
-
metric: "recovery_probe_failure",
|
|
451
|
-
window_end: window_end,
|
|
452
|
-
window_size: config.cool_off_time
|
|
453
|
-
)
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
private def recovery_probe_success_bucket_keys(config, window_end:)
|
|
457
|
-
self.class.buckets_for_window(
|
|
458
|
-
config.name,
|
|
459
|
-
metric: "recovery_probe_success",
|
|
460
|
-
window_end: window_end,
|
|
461
|
-
window_size: config.cool_off_time
|
|
462
|
-
)
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
private def successes_key(config, time:)
|
|
466
|
-
self.class.bucket_key(config.name, metric: "success", time:)
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
private def errors_key(config, time:)
|
|
470
|
-
self.class.bucket_key(config.name, metric: "failure", time:)
|
|
471
|
-
end
|
|
472
|
-
|
|
473
|
-
private def recovery_metrics_key(config)
|
|
474
|
-
key("recovery_metrics", config.name)
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
private def metadata_key(config)
|
|
478
|
-
key("metadata", config.name)
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
METRICS_TTL = 86400 # 1 day
|
|
482
|
-
private_constant :METRICS_TTL
|
|
483
|
-
|
|
484
|
-
private def metrics_ttl(config)
|
|
485
|
-
config.window_size || METRICS_TTL
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
METADATA_TTL = 86400 * 7 # 7 days
|
|
489
|
-
private_constant :METADATA_TTL
|
|
490
|
-
|
|
491
|
-
private def metadata_ttl
|
|
492
|
-
METADATA_TTL
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
SKEW_TOLERANCE = 5 # seconds
|
|
496
|
-
private_constant :SKEW_TOLERANCE
|
|
497
|
-
|
|
498
|
-
private def detect_clock_skew
|
|
499
|
-
return unless @warn_on_clock_skew
|
|
500
|
-
return unless should_sample?(0.01) # 1% chance
|
|
501
|
-
|
|
502
|
-
redis_seconds, _redis_millis = @redis.then(&:time)
|
|
503
|
-
app_seconds = clock.current_time.to_i
|
|
504
|
-
if (redis_seconds - app_seconds).abs > SKEW_TOLERANCE
|
|
505
|
-
warn("Detected clock skew between Redis and the application server. Redis time: #{redis_seconds}, Application time: #{app_seconds}. See https://github.com/bolshakov/stoplight/wiki/Clock-Skew-and-Stoplight-Reliability")
|
|
506
|
-
end
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
private def should_sample?(probability)
|
|
510
|
-
rand <= probability
|
|
511
|
-
end
|
|
512
|
-
end
|
|
513
|
-
# steep:ignore:end
|
|
514
|
-
end
|
|
515
|
-
end
|
|
516
|
-
end
|