service_downtime_simulator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []