Skip to content

Commit 005c251

Browse files
authored
Configurable outage response handling (department-of-veterans-affairs#5)
* configurable outage response handling * bump version
1 parent 983cd3a commit 005c251

6 files changed

+82
-14
lines changed

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ service.end_forced_outage!
154154
Unlike with outages detected by the middleware, forced outages are not periodically tested to see if they have completed and must be
155155
manually ended with a call to `end_forced_outage!`.
156156
157+
### Changing the Outage Response
158+
159+
By default, if you make a request against a service that is experiencing an outage a Breakers::OutageException will be raised. If you would
160+
prefer to receive a response with a certain status code instead, you can change that with:
161+
162+
```ruby
163+
Breakers.outage_response = { type: :status_code, status_code: 503 }
164+
```
165+
157166
### Redis Data Structure
158167
159168
Data is stored in Redis with the following structure:

lib/breakers.rb

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'breakers/client'
22
require 'breakers/outage'
3+
require 'breakers/outage_exception'
34
require 'breakers/service'
45
require 'breakers/uptime_middleware'
56
require 'breakers/version'
@@ -40,4 +41,21 @@ def self.redis_prefix=(prefix)
4041
def self.redis_prefix
4142
@redis_prefix || ''
4243
end
44+
45+
# Configure the middleware's handling of outages. The default is to raise a Breakers::OutageException
46+
# but you can also request that the response comes back with a configurable status code.
47+
#
48+
# @param [Hash] opts A hash of options
49+
# @option opts [Symbol] :type Pass :exception to raise a Breakers::OutageException when an error occurs. Pass :status_code to respond.
50+
# @option opts [Integer] :status_code If the type is :status_code, configure which code to return.
51+
def self.outage_response=(opts)
52+
@outage_response = { type: :exception }.merge(opts)
53+
end
54+
55+
# Query for the outage response configuration
56+
#
57+
# @return [Hash] configuration for the outage response, as defined in outage_response=
58+
def self.outage_response
59+
@outage_response || { type: :exception }
60+
end
4361
end

lib/breakers/outage_exception.rb

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Breakers
2+
# The error that is raised when a request is made against a service that is
3+
# experiencing an outage
4+
class OutageException < StandardError
5+
attr_reader :outage
6+
attr_reader :service
7+
8+
def initialize(outage, service)
9+
@outage = outage
10+
@service = service
11+
end
12+
13+
def message
14+
"Outage detected on #{@service.name} beginning at #{@outage.start_time.to_i}"
15+
end
16+
end
17+
end

lib/breakers/uptime_middleware.rb

+19-13
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ def call(request_env)
3131
protected
3232

3333
def outage_response(outage:, service:)
34-
Faraday::Response.new.tap do |response|
35-
response.finish(
36-
status: 503,
37-
body: "Outage detected on #{service.name} beginning at #{outage.start_time.to_i}",
38-
response_headers: {}
39-
)
34+
if Breakers.outage_response[:type] == :status_code
35+
Faraday::Response.new.tap do |response|
36+
response.finish(
37+
status: Breakers.outage_response[:status_code],
38+
body: "Outage detected on #{service.name} beginning at #{outage.start_time.to_i}",
39+
response_headers: {}
40+
)
41+
end
42+
else
43+
raise Breakers::OutageException.new(outage, service)
4044
end
4145
end
4246

@@ -60,13 +64,15 @@ def handle_request(service:, request_env:, current_outage: nil)
6064
end
6165
end
6266
rescue => e
63-
handle_error(
64-
service: service,
65-
request_env: request_env,
66-
response_env: nil,
67-
error: "#{e.class.name} - #{e.message}",
68-
current_outage: current_outage
69-
)
67+
unless e.is_a?(Breakers::OutageException)
68+
handle_error(
69+
service: service,
70+
request_env: request_env,
71+
response_env: nil,
72+
error: "#{e.class.name} - #{e.message}",
73+
current_outage: current_outage
74+
)
75+
end
7076
raise
7177
end
7278

lib/breakers/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Breakers
2-
VERSION = '0.1.1'.freeze
2+
VERSION = '0.2.0'.freeze
33
end

spec/integration_spec.rb

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
end
3030

3131
before do
32+
Breakers.outage_response = { type: :status_code, status_code: 503 }
3233
Breakers.client = client
3334
end
3435

@@ -399,4 +400,21 @@
399400
end
400401
end
401402
end
403+
404+
context 'configured to raise exceptions' do
405+
let(:start_time) { Time.now.utc - 30 }
406+
let(:now) { Time.now.utc }
407+
before do
408+
Timecop.freeze(now)
409+
redis.zadd('VA-outages', start_time.to_i, MultiJson.dump(start_time: start_time.to_i))
410+
end
411+
412+
before do
413+
Breakers.outage_response = { type: :exception }
414+
end
415+
416+
it 'raises the exception' do
417+
expect { connection.get '/' }.to raise_error(Breakers::OutageException)
418+
end
419+
end
402420
end

0 commit comments

Comments
 (0)