workarea-circuit_breaker 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/ci.yml +66 -0
  6. data/.gitignore +20 -0
  7. data/.rubocop.yml +2 -0
  8. data/.stylelintrc.json +8 -0
  9. data/CHANGELOG.md +54 -0
  10. data/Gemfile +12 -0
  11. data/LICENSE +52 -0
  12. data/README.md +103 -0
  13. data/Rakefile +59 -0
  14. data/app/assets/stylesheets/circuit_breaker/.keep +0 -0
  15. data/app/controllers/workarea/admin/circuits_controller.rb +26 -0
  16. data/app/view_models/workarea/admin/circuit_view_model.rb +33 -0
  17. data/app/view_models/workarea/admin/circuits_view_model.rb +41 -0
  18. data/app/views/workarea/admin/circuits/index.html.haml +69 -0
  19. data/app/views/workarea/admin/shared/_circuit_breaker_link.html.haml +1 -0
  20. data/bin/rails +25 -0
  21. data/config/initializers/appends.rb +4 -0
  22. data/config/initializers/workarea.rb +11 -0
  23. data/config/locales/en.yml +29 -0
  24. data/config/routes.rb +10 -0
  25. data/lib/workarea/circuit_breaker.rb +96 -0
  26. data/lib/workarea/circuit_breaker/circuit.rb +137 -0
  27. data/lib/workarea/circuit_breaker/engine.rb +12 -0
  28. data/lib/workarea/circuit_breaker/failure_message.rb +51 -0
  29. data/lib/workarea/circuit_breaker/version.rb +5 -0
  30. data/script/admin_ci +5 -0
  31. data/script/ci +8 -0
  32. data/script/core_ci +5 -0
  33. data/script/plugins_ci +5 -0
  34. data/script/storefront_ci +5 -0
  35. data/test/dummy/.ruby-version +1 -0
  36. data/test/dummy/Rakefile +6 -0
  37. data/test/dummy/bin/bundle +3 -0
  38. data/test/dummy/bin/rails +4 -0
  39. data/test/dummy/bin/rake +4 -0
  40. data/test/dummy/bin/setup +28 -0
  41. data/test/dummy/bin/update +28 -0
  42. data/test/dummy/bin/yarn +11 -0
  43. data/test/dummy/config.ru +5 -0
  44. data/test/dummy/config/application.rb +33 -0
  45. data/test/dummy/config/boot.rb +5 -0
  46. data/test/dummy/config/environment.rb +5 -0
  47. data/test/dummy/config/environments/development.rb +52 -0
  48. data/test/dummy/config/environments/production.rb +83 -0
  49. data/test/dummy/config/environments/test.rb +45 -0
  50. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  51. data/test/dummy/config/initializers/assets.rb +14 -0
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  54. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  55. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  56. data/test/dummy/config/initializers/inflections.rb +16 -0
  57. data/test/dummy/config/initializers/mime_types.rb +4 -0
  58. data/test/dummy/config/initializers/workarea.rb +5 -0
  59. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  60. data/test/dummy/config/locales/en.yml +33 -0
  61. data/test/dummy/config/puma.rb +34 -0
  62. data/test/dummy/config/routes.rb +5 -0
  63. data/test/dummy/config/secrets.yml +33 -0
  64. data/test/dummy/config/spring.rb +6 -0
  65. data/test/dummy/db/seeds.rb +2 -0
  66. data/test/dummy/lib/assets/.keep +0 -0
  67. data/test/dummy/log/.keep +0 -0
  68. data/test/integration/workarea/admin/circuits_integration_test.rb +27 -0
  69. data/test/lib/workarea/circuit_breaker/circuit_test.rb +135 -0
  70. data/test/lib/workarea/circuit_breaker/failure_message_test.rb +41 -0
  71. data/test/lib/workarea/circuit_breaker_test.rb +42 -0
  72. data/test/support/circuit_breaker_support.rb +16 -0
  73. data/test/teaspoon_env.rb +6 -0
  74. data/test/test_helper.rb +15 -0
  75. data/workarea-circuit_breaker.gemspec +17 -0
  76. metadata +151 -0
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # ActiveSupport::Reloader.to_prepare do
4
+ # ApplicationController.renderer.defaults.merge!(
5
+ # http_host: 'example.org',
6
+ # https: false
7
+ # )
8
+ # end
@@ -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,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Specify a serializer for the signed and encrypted cookie jars.
4
+ # Valid options are :json, :marshal, and :hybrid.
5
+ Rails.application.config.action_dispatch.cookies_serializer = :json
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Configure sensitive parameters which will be filtered from the log file.
4
+ Rails.application.config.filter_parameters += [:password]
@@ -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,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
@@ -0,0 +1,5 @@
1
+ Workarea.configure do |config|
2
+ # Basic site info
3
+ config.site_name = 'Workarea Circuit Breaker'
4
+ config.host = 'www.example.com'
5
+ 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,5 @@
1
+ Rails.application.routes.draw do
2
+ mount Workarea::Core::Engine => '/'
3
+ mount Workarea::Admin::Engine => '/admin', as: 'admin'
4
+ mount Workarea::Storefront::Engine => '/', as: 'storefront'
5
+ end
@@ -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"] %>
@@ -0,0 +1,6 @@
1
+ %w[
2
+ .ruby-version
3
+ .rbenv-vars
4
+ tmp/restart.txt
5
+ tmp/caching-dev.txt
6
+ ].each { |path| Spring.watch(path) }
@@ -0,0 +1,2 @@
1
+ require 'workarea/seeds'
2
+ Workarea::Seeds.run
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