workarea-circuit_breaker 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/ci.yml +66 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +2 -0
- data/.stylelintrc.json +8 -0
- data/CHANGELOG.md +54 -0
- data/Gemfile +12 -0
- data/LICENSE +52 -0
- data/README.md +103 -0
- data/Rakefile +59 -0
- data/app/assets/stylesheets/circuit_breaker/.keep +0 -0
- data/app/controllers/workarea/admin/circuits_controller.rb +26 -0
- data/app/view_models/workarea/admin/circuit_view_model.rb +33 -0
- data/app/view_models/workarea/admin/circuits_view_model.rb +41 -0
- data/app/views/workarea/admin/circuits/index.html.haml +69 -0
- data/app/views/workarea/admin/shared/_circuit_breaker_link.html.haml +1 -0
- data/bin/rails +25 -0
- data/config/initializers/appends.rb +4 -0
- data/config/initializers/workarea.rb +11 -0
- data/config/locales/en.yml +29 -0
- data/config/routes.rb +10 -0
- data/lib/workarea/circuit_breaker.rb +96 -0
- data/lib/workarea/circuit_breaker/circuit.rb +137 -0
- data/lib/workarea/circuit_breaker/engine.rb +12 -0
- data/lib/workarea/circuit_breaker/failure_message.rb +51 -0
- data/lib/workarea/circuit_breaker/version.rb +5 -0
- data/script/admin_ci +5 -0
- data/script/ci +8 -0
- data/script/core_ci +5 -0
- data/script/plugins_ci +5 -0
- data/script/storefront_ci +5 -0
- data/test/dummy/.ruby-version +1 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +28 -0
- data/test/dummy/bin/update +28 -0
- data/test/dummy/bin/yarn +11 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +33 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +52 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +45 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +14 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/content_security_policy.rb +25 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/workarea.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +34 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +33 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/seeds.rb +2 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/integration/workarea/admin/circuits_integration_test.rb +27 -0
- data/test/lib/workarea/circuit_breaker/circuit_test.rb +135 -0
- data/test/lib/workarea/circuit_breaker/failure_message_test.rb +41 -0
- data/test/lib/workarea/circuit_breaker_test.rb +42 -0
- data/test/support/circuit_breaker_support.rb +16 -0
- data/test/teaspoon_env.rb +6 -0
- data/test/test_helper.rb +15 -0
- data/workarea-circuit_breaker.gemspec +17 -0
- metadata +151 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Version of your assets, change this if you want to expire all your assets.
|
4
|
+
Rails.application.config.assets.version = '1.0'
|
5
|
+
|
6
|
+
# Add additional assets to the asset load path.
|
7
|
+
# Rails.application.config.assets.paths << Emoji.images_path
|
8
|
+
# Add Yarn node_modules folder to the asset load path.
|
9
|
+
Rails.application.config.assets.paths << Rails.root.join('node_modules')
|
10
|
+
|
11
|
+
# Precompile additional assets.
|
12
|
+
# application.js, application.css, and all non-JS/CSS in the app/assets
|
13
|
+
# folder are already added.
|
14
|
+
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
|
4
|
+
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
|
5
|
+
|
6
|
+
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
|
7
|
+
# Rails.backtrace_cleaner.remove_silencers!
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Define an application-wide content security policy
|
4
|
+
# For further information see the following documentation
|
5
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
6
|
+
|
7
|
+
# Rails.application.config.content_security_policy do |policy|
|
8
|
+
# policy.default_src :self, :https
|
9
|
+
# policy.font_src :self, :https, :data
|
10
|
+
# policy.img_src :self, :https, :data
|
11
|
+
# policy.object_src :none
|
12
|
+
# policy.script_src :self, :https
|
13
|
+
# policy.style_src :self, :https
|
14
|
+
|
15
|
+
# # Specify URI for violation reports
|
16
|
+
# # policy.report_uri "/csp-violation-report-endpoint"
|
17
|
+
# end
|
18
|
+
|
19
|
+
# If you are using UJS then enable automatic nonce generation
|
20
|
+
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
|
21
|
+
|
22
|
+
# Report CSP violations to a specified URI
|
23
|
+
# For further information see the following documentation:
|
24
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
25
|
+
# Rails.application.config.content_security_policy_report_only = true
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Add new inflection rules using the following format. Inflections
|
4
|
+
# are locale specific, and you may define rules for as many different
|
5
|
+
# locales as you wish. All of these examples are active by default:
|
6
|
+
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
7
|
+
# inflect.plural /^(ox)$/i, '\1en'
|
8
|
+
# inflect.singular /^(ox)en/i, '\1'
|
9
|
+
# inflect.irregular 'person', 'people'
|
10
|
+
# inflect.uncountable %w( fish sheep )
|
11
|
+
# end
|
12
|
+
|
13
|
+
# These inflection rules are supported but not enabled by default:
|
14
|
+
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
15
|
+
# inflect.acronym 'RESTful'
|
16
|
+
# end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# This file contains settings for ActionController::ParamsWrapper which
|
4
|
+
# is enabled by default.
|
5
|
+
|
6
|
+
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
wrap_parameters format: [:json]
|
9
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Files in the config/locales directory are used for internationalization
|
2
|
+
# and are automatically loaded by Rails. If you want to use locales other
|
3
|
+
# than English, add the necessary files in this directory.
|
4
|
+
#
|
5
|
+
# To use the locales, use `I18n.t`:
|
6
|
+
#
|
7
|
+
# I18n.t 'hello'
|
8
|
+
#
|
9
|
+
# In views, this is aliased to just `t`:
|
10
|
+
#
|
11
|
+
# <%= t('hello') %>
|
12
|
+
#
|
13
|
+
# To use a different locale, set it with `I18n.locale`:
|
14
|
+
#
|
15
|
+
# I18n.locale = :es
|
16
|
+
#
|
17
|
+
# This would use the information in config/locales/es.yml.
|
18
|
+
#
|
19
|
+
# The following keys must be escaped otherwise they will not be retrieved by
|
20
|
+
# the default I18n backend:
|
21
|
+
#
|
22
|
+
# true, false, on, off, yes, no
|
23
|
+
#
|
24
|
+
# Instead, surround them with single quotes.
|
25
|
+
#
|
26
|
+
# en:
|
27
|
+
# 'true': 'foo'
|
28
|
+
#
|
29
|
+
# To learn more, please read the Rails Internationalization guide
|
30
|
+
# available at http://guides.rubyonrails.org/i18n.html.
|
31
|
+
|
32
|
+
en:
|
33
|
+
hello: "Hello world"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Puma can serve each request in a thread from an internal thread pool.
|
2
|
+
# The `threads` method setting takes two numbers: a minimum and maximum.
|
3
|
+
# Any libraries that use thread pools should be configured to match
|
4
|
+
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
5
|
+
# and maximum; this matches the default thread size of Active Record.
|
6
|
+
#
|
7
|
+
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
|
8
|
+
threads threads_count, threads_count
|
9
|
+
|
10
|
+
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
11
|
+
#
|
12
|
+
port ENV.fetch("PORT") { 3000 }
|
13
|
+
|
14
|
+
# Specifies the `environment` that Puma will run in.
|
15
|
+
#
|
16
|
+
environment ENV.fetch("RAILS_ENV") { "development" }
|
17
|
+
|
18
|
+
# Specifies the number of `workers` to boot in clustered mode.
|
19
|
+
# Workers are forked webserver processes. If using threads and workers together
|
20
|
+
# the concurrency of the application would be max `threads` * `workers`.
|
21
|
+
# Workers do not work on JRuby or Windows (both of which do not support
|
22
|
+
# processes).
|
23
|
+
#
|
24
|
+
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
25
|
+
|
26
|
+
# Use the `preload_app!` method when specifying a `workers` number.
|
27
|
+
# This directive tells Puma to first boot the application and load code
|
28
|
+
# before forking the application. This takes advantage of Copy On Write
|
29
|
+
# process behavior so workers use less memory.
|
30
|
+
#
|
31
|
+
# preload_app!
|
32
|
+
|
33
|
+
# Allow puma to be restarted by `rails restart` command.
|
34
|
+
plugin :tmp_restart
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Your secret key is used for verifying the integrity of signed cookies.
|
4
|
+
# If you change this key, all old signed cookies will become invalid!
|
5
|
+
|
6
|
+
# Make sure the secret is at least 30 characters and all random,
|
7
|
+
# no regular words or you'll be exposed to dictionary attacks.
|
8
|
+
# You can use `rails secret` to generate a secure secret key.
|
9
|
+
|
10
|
+
# Make sure the secrets in this file are kept private
|
11
|
+
# if you're sharing your code publicly.
|
12
|
+
|
13
|
+
# Shared secrets are available across all environments.
|
14
|
+
|
15
|
+
# shared:
|
16
|
+
# api_key: a1B2c3D4e5F6
|
17
|
+
|
18
|
+
# Environmental secrets are only available for that specific environment.
|
19
|
+
|
20
|
+
development:
|
21
|
+
secret_key_base: 9915aad1e1769200f2f0c6679b8bd20578dd01d4e6540473f1fd3715230eec45fb09444717a1933fd1203501e43a2b632ba249a0d95e1a867f9f2ff74e2272ad
|
22
|
+
|
23
|
+
test:
|
24
|
+
secret_key_base: 5d899ec36127d53f353c2540adb659e077191718f0fbc8e6cd8353e6cc05574c193fd36dd6cf8314e6b6a9c21bac4206b03fdcac3bc48323d0ace7f0278fa6ea
|
25
|
+
sentry_dsn: <%= ENV["SENTRY_DSN"] %>
|
26
|
+
|
27
|
+
# Do not keep production secrets in the unencrypted secrets file.
|
28
|
+
# Instead, either read values from the environment.
|
29
|
+
# Or, use `bin/rails secrets:setup` to configure encrypted secrets
|
30
|
+
# and move the `production:` environment over there.
|
31
|
+
|
32
|
+
production:
|
33
|
+
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
File without changes
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Workarea
|
4
|
+
module Admin
|
5
|
+
class CircuitsIntegrationTest < Workarea::IntegrationTest
|
6
|
+
include Admin::IntegrationTest
|
7
|
+
|
8
|
+
def test_turning_on_circuit
|
9
|
+
post admin.enable_circuit_path(:fake_service)
|
10
|
+
assert(CircuitBreaker[:fake_service].healthy?)
|
11
|
+
assert_equal(
|
12
|
+
I18n.t('workarea.admin.circuit_breaker.flash_messages.turned_on', circuit: "Fake service"),
|
13
|
+
flash[:success]
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_turning_off_circuit
|
18
|
+
post admin.disable_circuit_path(:fake_service)
|
19
|
+
refute(CircuitBreaker[:fake_service].healthy?)
|
20
|
+
assert_equal(
|
21
|
+
I18n.t('workarea.admin.circuit_breaker.flash_messages.turned_off', circuit: "Fake service", break_for: "5 minutes"),
|
22
|
+
flash[:success]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Workarea
|
4
|
+
module CircuitBreaker
|
5
|
+
class CircuitTest < Workarea::TestCase
|
6
|
+
include CircuitBreakerSupport
|
7
|
+
|
8
|
+
def test_healthy?
|
9
|
+
assert(circuit.healthy?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_healthy_with_redis_pool_timeout
|
13
|
+
with_connection_pool_timeouts do
|
14
|
+
assert(circuit.healthy?)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_set_healthy
|
19
|
+
3.times { circuit.add_failure message: "failed!" }
|
20
|
+
refute(circuit.healthy?)
|
21
|
+
|
22
|
+
circuit.set_healthy!
|
23
|
+
assert(circuit.healthy?)
|
24
|
+
assert_equal([], CircuitBreaker.redis.with do |redis|
|
25
|
+
redis.zrange circuit.redis_failure_set_key, 0, -1
|
26
|
+
end)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_set_healthy_with_redis_pool_timeout
|
30
|
+
assert_nothing_raised do
|
31
|
+
with_connection_pool_timeouts do
|
32
|
+
circuit.set_healthy!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_break!
|
38
|
+
circuit.break!
|
39
|
+
refute(circuit.healthy?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_add_failure
|
43
|
+
assert_equal(1, circuit.add_failure(message: "Failure"))
|
44
|
+
assert(circuit.healthy?)
|
45
|
+
assert_equal(2, circuit.add_failure(message: "Failure"))
|
46
|
+
assert(circuit.healthy?)
|
47
|
+
assert_equal(3, circuit.add_failure(message: "Failure"))
|
48
|
+
refute(circuit.healthy?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_break_with_redis_pool_timeout
|
52
|
+
assert_nothing_raised do
|
53
|
+
with_connection_pool_timeouts do
|
54
|
+
circuit.break!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_wrap_healthy_without_fallback
|
60
|
+
response = circuit.wrap do
|
61
|
+
"healthy response"
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_equal("healthy response", response)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_wrap_failing_without_fallback
|
68
|
+
error = assert_raises do
|
69
|
+
circuit.wrap do
|
70
|
+
raise 'http timeout'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
assert_equal('http timeout', error.message)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_wrap_failing_with_fallback_with_symbol
|
78
|
+
response = circuit.wrap(fallback: :local_data) do
|
79
|
+
raise "http timeout"
|
80
|
+
end
|
81
|
+
|
82
|
+
assert_equal("fallback response", response)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_wrap_failing_with_callable_fallback
|
86
|
+
fallback = -> { "callable fallback" }
|
87
|
+
|
88
|
+
response = circuit.wrap(fallback: fallback) do
|
89
|
+
raise "http timeout"
|
90
|
+
end
|
91
|
+
|
92
|
+
assert_equal("callable fallback", response)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_wrap_when_broken_without_fallback
|
96
|
+
circuit.break!
|
97
|
+
|
98
|
+
assert_raises CircuitBreakerError do
|
99
|
+
circuit.wrap do
|
100
|
+
raise "never used"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_wrap_when_broken_with_fallback
|
106
|
+
circuit.break!
|
107
|
+
|
108
|
+
response = circuit.wrap(fallback: :local_data) do
|
109
|
+
raise "http timeout"
|
110
|
+
end
|
111
|
+
|
112
|
+
assert_equal("fallback response", response)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_wrap_breaks_circuit
|
116
|
+
circuit.wrap(fallback: :local_data) { raise "http timeout" }
|
117
|
+
assert(circuit.healthy?)
|
118
|
+
circuit.wrap(fallback: :local_data) { raise "http timeout" }
|
119
|
+
assert(circuit.healthy?)
|
120
|
+
circuit.wrap(fallback: :local_data) { raise "http timeout" }
|
121
|
+
refute(circuit.healthy?)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def circuit
|
127
|
+
@circuit ||= Circuit.new(:fake_service, { max_fails: 3 })
|
128
|
+
end
|
129
|
+
|
130
|
+
def local_data
|
131
|
+
"fallback response"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Workarea
|
4
|
+
module CircuitBreaker
|
5
|
+
class FailureMessageTest < Workarea::TestCase
|
6
|
+
def test_to_s_without_event_id
|
7
|
+
failure_message = FailureMessage.new(message: "Test")
|
8
|
+
assert_equal(13, failure_message.to_s.length)
|
9
|
+
assert_equal("0", failure_message.to_s[8, 1])
|
10
|
+
assert_equal("Test", failure_message.to_s[9, 4])
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_to_s_with_event_id
|
14
|
+
event_id = SecureRandom.uuid.delete("-")
|
15
|
+
failure_message = FailureMessage.new(event_id: event_id, message: "Test")
|
16
|
+
assert_equal(47, failure_message.to_s.length)
|
17
|
+
assert_equal("1", failure_message.to_s[8, 1])
|
18
|
+
assert_equal("20", failure_message.to_s[9, 2])
|
19
|
+
assert_equal(event_id, failure_message.to_s[11, 32])
|
20
|
+
assert_equal("Test", failure_message.to_s[43, 4])
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_from_string_without_event_id
|
24
|
+
string = "2d519d2e0Test"
|
25
|
+
failure_message = FailureMessage.from_string(string, Time.current.to_i)
|
26
|
+
|
27
|
+
assert_equal("2d519d2e", failure_message.id)
|
28
|
+
assert_equal("Test", failure_message.message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_from_string_with_event_id
|
32
|
+
string = "6519a8c5120c5d015ec01924a50920efd19a23e555eTest"
|
33
|
+
failure_message = FailureMessage.from_string(string, Time.current.to_i)
|
34
|
+
|
35
|
+
assert_equal("6519a8c5", failure_message.id)
|
36
|
+
assert_equal("c5d015ec01924a50920efd19a23e555e", failure_message.event_id)
|
37
|
+
assert_equal("Test", failure_message.message)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|