service_downtime_simulator 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b251f2b6694651096a5ae0ca961b6011625f7de5fd454760f6806457c745d813
4
+ data.tar.gz: 1e62307f82e95f9eb3017f06f8a5c89974992625aa9fabde4bd25a4aed2455d3
5
+ SHA512:
6
+ metadata.gz: df90f8ca05da0c71e34d2eb8427301d128a13f3049f6628039d2a3df08421843073cef6382b2b553e7090bada1f112782e997453a03d9af0b713c302bd9b41e2
7
+ data.tar.gz: 0eed6fcb9ab37f11ea39270b28ada5ceb772d944b2a5bed7448ddc2748a8ad6b8833c6ff1400979f16590237dfdc4d3cef5efe4de3c6b663cbde5aca30ccce12
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ docker:
5
+ - image: circleci/ruby:2.5.1
6
+ steps:
7
+ - checkout
8
+ - run:
9
+ name: Bundle Is Massive
10
+ command: make install
11
+ - run:
12
+ name: Lint
13
+ command: make lint
14
+ - run:
15
+ name: Test
16
+ command: make test
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ # Created by https://www.gitignore.io/api/ruby
2
+
3
+ ### Ruby ###
4
+ *.gem
5
+ *.rbc
6
+ /.config
7
+ /coverage/
8
+ /InstalledFiles
9
+ /pkg/
10
+ /spec/reports/
11
+ /spec/examples.txt
12
+ /test/tmp/
13
+ /test/version_tmp/
14
+ /tmp/
15
+
16
+ # Used by dotenv library to load environment variables.
17
+ # .env
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+
56
+ # End of https://www.gitignore.io/api/ruby
57
+
58
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ # 80 char line length is not enough chars. 120 is better. We're not working on 1024x768 CRTs.
2
+ Metrics/LineLength:
3
+ Max: 120
4
+
5
+ # Documentation is for nerds.
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ # I like my code verbose and spaced out.
10
+ Metrics/MethodLength:
11
+ Max: 20
12
+
13
+ # Because RSpec tests are block-centric, this is an insane linter to run on them.
14
+ Metrics/BlockLength:
15
+ Exclude:
16
+ - spec/**/*
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ service_downtime_simulator (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ diff-lcs (1.3)
11
+ jaro_winkler (1.5.1)
12
+ parallel (1.12.1)
13
+ parser (2.5.1.2)
14
+ ast (~> 2.4.0)
15
+ powerpack (0.1.2)
16
+ rainbow (3.0.0)
17
+ rspec (3.8.0)
18
+ rspec-core (~> 3.8.0)
19
+ rspec-expectations (~> 3.8.0)
20
+ rspec-mocks (~> 3.8.0)
21
+ rspec-core (3.8.0)
22
+ rspec-support (~> 3.8.0)
23
+ rspec-expectations (3.8.1)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.8.0)
26
+ rspec-mocks (3.8.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.8.0)
29
+ rspec-support (3.8.0)
30
+ rubocop (0.59.1)
31
+ jaro_winkler (~> 1.5.1)
32
+ parallel (~> 1.10)
33
+ parser (>= 2.5, != 2.5.1.1)
34
+ powerpack (~> 0.1)
35
+ rainbow (>= 2.2.2, < 4.0)
36
+ ruby-progressbar (~> 1.7)
37
+ unicode-display_width (~> 1.0, >= 1.0.1)
38
+ ruby-progressbar (1.10.0)
39
+ unicode-display_width (1.4.0)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ bundler (~> 1.16)
46
+ rspec (~> 3.0)
47
+ rubocop
48
+ service_downtime_simulator!
49
+
50
+ BUNDLED WITH
51
+ 1.16.1
data/Makefile ADDED
@@ -0,0 +1,15 @@
1
+ install:
2
+ bundle install
3
+
4
+ test:
5
+ bundle exec rspec
6
+
7
+ publish:
8
+ bundle exec gem build *.gemspec
9
+ -bundle exec gem push *.gem
10
+ rm *.gem
11
+
12
+ lint:
13
+ bundle exec rubocop
14
+
15
+ .PHONY: install test publish lint
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Service Downtime Simulator
2
+
3
+ [![CircleCI](https://circleci.com/gh/deliveroo/service_downtime_simulator/tree/master.svg?style=svg&circle-token=d66bc2c0246da5cdf4eeaeb6c9f7b10bcd74bb7b)](https://circleci.com/gh/deliveroo/service_downtime_simulator/tree/master)
4
+
5
+ This is a piece of Rack middleware that simulates failures you would want to tolerate in upstream services.
6
+
7
+ ## Installation
8
+
9
+ ### Rails
10
+
11
+ Add the following in `application.rb`:
12
+
13
+ ```ruby
14
+ config.middleware.use(
15
+ ServiceDowntimeSimulator::Middleware,
16
+ config # See below for info about how to configure this
17
+ )
18
+ ```
19
+
20
+ ### Other Rack-based applications
21
+
22
+ Instructions TBC.
23
+
24
+ ## Configuration
25
+
26
+ The middleware takes a `config` argument in the form of a hash. Said hash should have the following shape:
27
+
28
+ ```ruby
29
+ {
30
+ enabled: Boolean,
31
+ mode: Symbol,
32
+ excluded_paths: Array<String>,
33
+ logger: Logger?
34
+ }
35
+ ```
36
+
37
+ Here's what you can supply for each of those options:
38
+
39
+ - **enabled** (`Boolean`)
40
+ - `true` will enable simulation of failures (assuming you supply a valid `mode`, see below)
41
+ - `false` will disable simulation and your application will function as normal
42
+ - **mode** (`Symbol`)
43
+ - `:hard_down` will cause all requests to return a 500 error
44
+ - `:intermittently_down` will cause 50% of requests to return a 500 error
45
+ - `:successful_but_gibberish` will return a 200, but with a response body that is not machine readable
46
+ - `:timing_out` will wait for 15 seconds on each request, and then return a 503
47
+ - **excluded_paths** (`Array<String>`)
48
+ - You can supply a list of paths that you don't want to be affected by the simulation here (e.g. `['/foobar']`)
49
+ - The most common thing you're going to want to include here is your service's health check endpoint, as if it is returning a 5xx thanks to this middleware your application will not deploy
50
+ - **logger** (`Logger?`)
51
+ - If supplied, useful debug information will be sent here
52
+
53
+ In order for the middleware to kick in, `enabled` must be explicitly set to `true` and `mode` must be a valid option. Unless both are explicitly supplied, the underlying application will continue to function as normal.
54
+
55
+ ### Examples
56
+
57
+ Here's a couple of example configurations:
58
+
59
+ #### Hard-coded Hard Down
60
+
61
+ This example will always return a 500 for all requests.
62
+
63
+ ```ruby
64
+ config.middleware.use(
65
+ ServiceDowntimeSimulator::Middleware,
66
+ {
67
+ enabled: true,
68
+ mode: :hard_down,
69
+ excluded_paths: ['/health'],
70
+ logger: Rails.logger
71
+ }
72
+ )
73
+ ```
74
+
75
+ #### Environment-variable Controlled Simulation
76
+
77
+ This is a more practical example, allowing failure simulation to happen based on environment variables. It requires an environment variable with a specific value to enable the failure simulation, and also requires a mode to be provided. If either are missing, the app continues as normal. You can also use this pattern for feature flagging. Probably.
78
+
79
+ ```ruby
80
+ config.middleware.use(
81
+ ServiceDowntimeSimulator::Middleware,
82
+ {
83
+ enabled: ENV['FAILURE_SIMULATION_ENABLED'] == 'I_UNDERSTAND_THE_CONSEQUENCES_OF_THIS',
84
+ mode: ENV['FAILURE_SIMULATION_MODE'],
85
+ excluded_paths: ['/health'],
86
+ logger: Rails.logger
87
+ }
88
+ )
89
+ ```
@@ -0,0 +1,91 @@
1
+ module ServiceDowntimeSimulator
2
+ class Config
3
+ WonkyInputError = Class.new(StandardError)
4
+
5
+ OPTIONS = %i[
6
+ enabled
7
+ mode
8
+ logger
9
+ excluded_paths
10
+ ].freeze
11
+
12
+ attr_reader :logger
13
+
14
+ def self.for(config)
15
+ return config if config.is_a?(self)
16
+
17
+ new(config)
18
+ end
19
+
20
+ def initialize(config_hash)
21
+ raise WonkyInputError unless config_hash.is_a?(Hash)
22
+
23
+ config_hash.each do |key, value|
24
+ instance_variable_set("@#{key}", value)
25
+ end
26
+ end
27
+
28
+ def activated?
29
+ enabled == true && !mode.nil?
30
+ end
31
+
32
+ def enabled
33
+ @enabled == true
34
+ end
35
+
36
+ def mode
37
+ if @mode.nil?
38
+ moan(:mode, 'No mode provided')
39
+ return nil
40
+ end
41
+
42
+ unless @mode.is_a?(Symbol)
43
+ moan(:mode, 'Mode must be a symbol')
44
+ return nil
45
+ end
46
+
47
+ unless ServiceDowntimeSimulator::Modes.exists?(@mode)
48
+ moan(:mode, "Unknown mode #{@mode}")
49
+ return nil
50
+ end
51
+
52
+ @mode
53
+ end
54
+
55
+ def mode_klass
56
+ ServiceDowntimeSimulator::Modes.for(mode)
57
+ end
58
+
59
+ def excluded_paths
60
+ if @excluded_paths.nil?
61
+ moan(:excluded_paths, 'No excluded paths set. What about your health check endpoint?')
62
+ return []
63
+ end
64
+
65
+ unless @excluded_paths.is_a?(Array)
66
+ moan(:excluded_paths, 'Excluded paths must ba an array of paths')
67
+ return []
68
+ end
69
+
70
+ # Detect if any elements are not a string
71
+ if @excluded_paths.any? { |el| !el.is_a?(String) }
72
+ moan(:excluded_paths, 'Excluded paths includes non-string value')
73
+ return []
74
+ end
75
+
76
+ @excluded_paths
77
+ end
78
+
79
+ def path_excluded?(path)
80
+ excluded_paths.include?(path)
81
+ end
82
+
83
+ private
84
+
85
+ def moan(option, message)
86
+ return unless logger.respond_to?(:error)
87
+
88
+ logger.error("[SDS] Issue with #{option}: #{message}. Will not activate unless fixed.")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,22 @@
1
+ module ServiceDowntimeSimulator
2
+ class Middleware
3
+ def initialize(app, config)
4
+ @app = app
5
+ @config = ServiceDowntimeSimulator::Config.for(config)
6
+ end
7
+
8
+ def call(env)
9
+ return app.call(env) if bypass?(env)
10
+
11
+ config.mode_klass.new(app).call(env)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :app, :config
17
+
18
+ def bypass?(env)
19
+ !config.activated? || config.path_excluded?(env['PATH_INFO'])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ class Base
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @env = env
10
+
11
+ [status, headers, body]
12
+ end
13
+
14
+ private
15
+
16
+ def headers
17
+ {
18
+ 'X-SDS-Mode' => identifier
19
+ }
20
+ end
21
+
22
+ def body
23
+ "Simulated Response (#{identifier})"
24
+ end
25
+
26
+ def status
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def identifier
31
+ self.class.name
32
+ end
33
+
34
+ attr_reader :app, :env
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ class HardDown < Base
4
+ private
5
+
6
+ def status
7
+ 500
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ class IntermittentlyDown < Base
4
+ def call(env)
5
+ return app.call(env) unless knackered?
6
+
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def status
13
+ 500
14
+ end
15
+
16
+ def knackered?
17
+ [true, false].sample
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ class SuccessfulButGibberish < Base
4
+ private
5
+
6
+ def status
7
+ 200
8
+ end
9
+
10
+ def body
11
+ cheeseboard.shuffle.join(' ')
12
+ end
13
+
14
+ def cheeseboard
15
+ %w[stilton caerphilly cheddar gloucester wensleydale brie] * 10
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ class TimingOut < Base
4
+ def call(*)
5
+ delay(15)
6
+ super
7
+ end
8
+
9
+ def delay(duration)
10
+ sleep(duration)
11
+ end
12
+
13
+ private
14
+
15
+ def status
16
+ 503
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module ServiceDowntimeSimulator
2
+ module Modes
3
+ NotFound = Class.new(StandardError)
4
+
5
+ AVAILABLE = {
6
+ hard_down: ServiceDowntimeSimulator::Modes::HardDown,
7
+ intermittently_down: ServiceDowntimeSimulator::Modes::IntermittentlyDown,
8
+ successful_but_gibberish: ServiceDowntimeSimulator::Modes::SuccessfulButGibberish,
9
+ timing_out: ServiceDowntimeSimulator::Modes::TimingOut
10
+ }.freeze
11
+
12
+ def self.for(id)
13
+ raise NotFound unless exists?(id)
14
+
15
+ AVAILABLE[id]
16
+ end
17
+
18
+ def self.exists?(id)
19
+ AVAILABLE.key?(id)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module ServiceDowntimeSimulator
2
+ VERSION = '0.1.0'.freeze
3
+ end
4
+
5
+ require_relative 'service_downtime_simulator/config'
6
+ require_relative 'service_downtime_simulator/middleware'
7
+ require_relative 'service_downtime_simulator/modes/base'
8
+ require_relative 'service_downtime_simulator/modes/hard_down'
9
+ require_relative 'service_downtime_simulator/modes/intermittently_down'
10
+ require_relative 'service_downtime_simulator/modes/successful_but_gibberish'
11
+ require_relative 'service_downtime_simulator/modes/timing_out'
12
+ require_relative 'service_downtime_simulator/modes'
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'service_downtime_simulator'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'service_downtime_simulator'
7
+ spec.version = ServiceDowntimeSimulator::VERSION
8
+ spec.authors = ['Josh McMillan']
9
+ spec.email = ['joshua.mcmillan@deliveroo.co.uk']
10
+
11
+ spec.summary = "Want to know what it's like to have me in your dev team? This gem is for you!"
12
+ spec.homepage = 'https://github.com/deliveroo/service_downtime_simulator'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.16'
21
+ spec.add_development_dependency 'rspec', '~> 3.0'
22
+ spec.add_development_dependency 'rubocop'
23
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_downtime_simulator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh McMillan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-09-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - joshua.mcmillan@deliveroo.co.uk
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".circleci/config.yml"
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - Gemfile
67
+ - Gemfile.lock
68
+ - Makefile
69
+ - README.md
70
+ - lib/service_downtime_simulator.rb
71
+ - lib/service_downtime_simulator/config.rb
72
+ - lib/service_downtime_simulator/middleware.rb
73
+ - lib/service_downtime_simulator/modes.rb
74
+ - lib/service_downtime_simulator/modes/base.rb
75
+ - lib/service_downtime_simulator/modes/hard_down.rb
76
+ - lib/service_downtime_simulator/modes/intermittently_down.rb
77
+ - lib/service_downtime_simulator/modes/successful_but_gibberish.rb
78
+ - lib/service_downtime_simulator/modes/timing_out.rb
79
+ - service_downtime_simulator.gemspec
80
+ homepage: https://github.com/deliveroo/service_downtime_simulator
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.7.6
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Want to know what it's like to have me in your dev team? This gem is for
104
+ you!
105
+ test_files: []