service_jynx 0.0.1

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
+ SHA1:
3
+ metadata.gz: 32660d4e2cf251e609d2bc945d5872a56f921386
4
+ data.tar.gz: 6f1f999edb6108361c6105dced1c2ced1e22c382
5
+ SHA512:
6
+ metadata.gz: 0ac8fd3e814b6b26bfa1f5f176257d5541a3bedfecbf3c3e61e5d02743248f2a870cdac7b99d91c5e971999833a375bfbd063149d677e8ae9e5e5de622294b13
7
+ data.tar.gz: c800ab4f465a5f3bc97fc6711dbdf215a7483b1994f22e37c219e1b83b1ebc65ba7df5db06a995a5f64cdd570e295a3120d541e81cd6f350568ce7a4a0c069e2
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ jynx_gemset
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'pry'
8
+ gem 'pry-debugger'
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Avner Cohen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Service-Jynx
2
+
3
+ Eurasian Wryneck - ***Jynx torquilla***
4
+
5
+ ![Eurasian Wryneck](jynx.jpg)
6
+
7
+ **Jinx** - ***A condition or period of bad luck that appears to have been caused by a specific person or thing.***
8
+
9
+ A simple (yet powerfull) solution, to allow a Ruby application to manage automatic failover and block calls to an external service and return a stubbed data when service is reported as down.
10
+
11
+ The code is MRI depended and is not thread safe, is is also designed specifically to run on a single VM and manage in memory hashes of data, though it can very well be executed with an external shared persistance counters such as, say, Redis.
12
+
13
+
14
+ ````ruby
15
+
16
+ if ServiceJynx.alive?(:amazon_s3_service)
17
+ begin
18
+ HttpParty.get "s3://bucke:username@password/whatever.jpg"
19
+ rescue ResponseError => e
20
+ ServiceJynx.failure!(:amazon_s3_service)
21
+ end
22
+ else
23
+ "S3 is currently unreachable"
24
+ end
25
+
26
+ ````
27
+
28
+ ## Defaults
29
+
30
+ Defined when registering a service:
31
+
32
+ ***time_window_in_seconds***: **10**
33
+
34
+ ***max_errors***: **40**
35
+
36
+ ***grace_period***: **360**
37
+
38
+
39
+ Defaults means that *40 errors* during *10 seconds* would turn the service automatically off, for 5 minutes.
40
+
41
+ ## Methods
42
+
43
+ ````ruby
44
+
45
+ ServiceJynx.register!(:name, {time_window_in_seconds: 360, max_errors: 40})
46
+ ServiceJynx.alive?(:name)
47
+ ServiceJynx.failure!(:name)
48
+ ServiceJynx.down!(:name)
49
+ ServiceJynx.up!(:name)
50
+
51
+ ````
52
+
data/jynx.jpg ADDED
Binary file
@@ -0,0 +1,21 @@
1
+ module LoggerJynx
2
+ def self.logger
3
+ @logger ||= (rails_logger || default_logger)
4
+ end
5
+
6
+ def self.rails_logger
7
+ (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
8
+ (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
9
+ end
10
+
11
+ def self.default_logger
12
+ require 'logger'
13
+ l = Logger.new(STDOUT)
14
+ l.level = Logger::INFO
15
+ l
16
+ end
17
+
18
+ def self.logger=(logger)
19
+ @logger = logger
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module ServiceJynx
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,85 @@
1
+ require "service_jynx/version"
2
+
3
+ module ServiceJynx
4
+
5
+ @counters = {}
6
+ def self.counters
7
+ @counters
8
+ end
9
+
10
+ def self.register!(name, options = {})
11
+ @counters[name] = Jynx.new(name, options)
12
+ end
13
+
14
+ def self.flush!
15
+ @counters = {}
16
+ end
17
+
18
+ def self.alive?(name)
19
+ @counters[name].alive? == true
20
+ end
21
+
22
+ def self.down!(name, reason)
23
+ @counters[name].down!(reason)
24
+ end
25
+
26
+ def self.up!(name)
27
+ @counters[name].up!
28
+ end
29
+
30
+ def self.failure!(name)
31
+ jynx = @counters[name]
32
+ now = Time.now.to_i
33
+ jynx.errors << now
34
+ jynx.clean_aged(now)
35
+ down!(name, "Max error count (#{jynx.max_errors}) reached at #{Time.now}.") if jynx.errors.count > jynx.max_errors
36
+ end
37
+
38
+
39
+ class Jynx
40
+ attr_accessor :errors, :name, :time_window_in_seconds, :max_errors, :alive, :down_at, :grace_period
41
+ def initialize(name, options)
42
+ @name = name
43
+ @down_at = 0
44
+ @alive = true
45
+ @errors = []
46
+ opts = {
47
+ time_window_in_seconds: 10,
48
+ max_errors: 40,
49
+ grace_period: 360
50
+ }.merge!(options)
51
+ @time_window_in_seconds = opts[:time_window_in_seconds]
52
+ @max_errors = opts[:max_errors]
53
+ @grace_period = opts[:grace_period]
54
+ end
55
+
56
+ ## clean up errors that are older than time_window_in_secons
57
+ def clean_aged(time_now)
58
+ near_past = time_now - @time_window_in_seconds
59
+ @errors = @errors.reverse.select{|time_stamp| time_stamp > near_past }.reverse.to_a
60
+ end
61
+
62
+ def down!(reason)
63
+ @alive = false
64
+ @down_at = Time.now.to_i
65
+ LoggerJynx.logger.error "Shutting down [#{@name}] #{reason} at #{@down_at}."
66
+ end
67
+
68
+ def up!
69
+ LoggerJynx.logger.error "Upping [#{@name}]."
70
+ @alive = true
71
+ @down_at = 0
72
+ end
73
+
74
+ def alive?
75
+ return true if @alive
76
+ near_past = Time.now.to_i - @grace_period
77
+ up! if (@down_at < near_past) and return true
78
+ false
79
+ end
80
+
81
+ end
82
+
83
+
84
+
85
+ end
data/run_test ADDED
@@ -0,0 +1 @@
1
+ rspec ./spec/**.*
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'service_jynx/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "service_jynx"
8
+ spec.version = ServiceJynx::VERSION
9
+ spec.authors = ["Avner Cohen"]
10
+ spec.email = ["israbirding@gmail.com"]
11
+ spec.summary = %q{Use errors count over sliding windows to block calls to an external service or method, or whatever.}
12
+ spec.description = %q{Use errors count over sliding windows to block calls to an external service or method, or whatever.}
13
+ spec.homepage = "https://github.com/AvnerCohen/service-jynx"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ # Add when debugging
4
+ # require 'pry'
5
+ # require 'pry-debugger'
6
+
7
+ describe ServiceJynx do
8
+ before(:all) do
9
+ ServiceJynx.flush!
10
+ end
11
+
12
+
13
+ it "should add new services to counter when registerd" do
14
+ jynx = ServiceJynx.counters.fetch(:dummy_service, :ServiceNotFound)
15
+ jynx.should eq(:ServiceNotFound)
16
+ ServiceJynx.register!(:dummy_service)
17
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
18
+ jynx.errors == 0
19
+ end
20
+
21
+ it "should allow registering with options" do
22
+ ServiceJynx.register!(:dummy_service, {max_errors: 5})
23
+ jynx = ServiceJynx.counters.fetch(:dummy_service, :ServiceNotFound)
24
+ jynx.max_errors.should eq(5)
25
+ end
26
+
27
+ it "should allow checking if service is alive" do
28
+ ServiceJynx.register!(:dummy_service)
29
+ ServiceJynx.alive?(:dummy_service).should eq(true)
30
+ end
31
+
32
+ it "should allow shutting down a service" do
33
+ ServiceJynx.register!(:dummy_service)
34
+ ServiceJynx.alive?(:dummy_service).should eq(true)
35
+ ServiceJynx.down!(:dummy_service, "Because of testing")
36
+ ServiceJynx.alive?(:dummy_service).should eq(false)
37
+ end
38
+
39
+ it "should allow upping a service" do
40
+ ServiceJynx.register!(:dummy_service)
41
+ ServiceJynx.alive?(:dummy_service).should eq(true)
42
+ ServiceJynx.down!(:dummy_service, "Because of testing")
43
+ ServiceJynx.alive?(:dummy_service).should eq(false)
44
+ ServiceJynx.up!(:dummy_service)
45
+ ServiceJynx.alive?(:dummy_service).should eq(true)
46
+ end
47
+
48
+ it "should allow marking a failure" do
49
+ ServiceJynx.register!(:dummy_service)
50
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
51
+ jynx.errors.length.should eq(0)
52
+ ServiceJynx.failure!(:dummy_service)
53
+ jynx.errors.length.should eq(1)
54
+ end
55
+
56
+ it "should allow marking multiple failures" do
57
+ ServiceJynx.register!(:dummy_service)
58
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
59
+ jynx.errors.length.should eq(0)
60
+ 10.times {ServiceJynx.failure!(:dummy_service)}
61
+ jynx.errors.length.should eq(10)
62
+ end
63
+
64
+ it "should allow overrding defaults" do
65
+ ServiceJynx.register!(:dummy_service, {time_window_in_seconds: 999})
66
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
67
+ jynx.time_window_in_seconds.should eq(999)
68
+ end
69
+
70
+ it "should clean old errors" do
71
+ ServiceJynx.register!(:dummy_service, {time_window_in_seconds: 2})
72
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
73
+ jynx.errors.length.should eq(0)
74
+ 10.times {ServiceJynx.failure!(:dummy_service)}
75
+ jynx.errors.length.should eq(10)
76
+ sleep 5 ## make sure aged errors are cleaned
77
+ 10.times {ServiceJynx.failure!(:dummy_service)}
78
+ jynx.errors.length.should eq(10)
79
+ end
80
+
81
+ it "should auto disable when errors limit reached old errors" do
82
+ ServiceJynx.register!(:dummy_service, {time_window_in_seconds: 2, max_errors: 20})
83
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
84
+ ServiceJynx.alive?(:dummy_service).should eq(true)
85
+ 10.times {ServiceJynx.failure!(:dummy_service)}
86
+ ServiceJynx.alive?(:dummy_service).should eq(true)
87
+ 11.times {ServiceJynx.failure!(:dummy_service)}
88
+ ServiceJynx.alive?(:dummy_service).should eq(false)
89
+ end
90
+
91
+ it "should auto disable when errors limit reached old errors and restart again when grace period passes" do
92
+ ServiceJynx.register!(:dummy_service, {time_window_in_seconds: 2, max_errors: 20, grace_period: 5})
93
+ jynx = ServiceJynx.counters.fetch(:dummy_service)
94
+ ServiceJynx.alive?(:dummy_service).should eq(true)
95
+ 10.times {ServiceJynx.failure!(:dummy_service)}
96
+ ServiceJynx.alive?(:dummy_service).should eq(true)
97
+ 11.times {ServiceJynx.failure!(:dummy_service)}
98
+ ServiceJynx.alive?(:dummy_service).should eq(false)
99
+ sleep 7
100
+ ServiceJynx.alive?(:dummy_service).should eq(true)
101
+ end
102
+
103
+
104
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+
3
+ Dir["#{File.dirname(__FILE__)}/../lib/**/*.rb"].each { |f| load(f) }
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_jynx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Avner Cohen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Use errors count over sliding windows to block calls to an external service
14
+ or method, or whatever.
15
+ email:
16
+ - israbirding@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .ruby-gemset
23
+ - .ruby-version
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - jynx.jpg
28
+ - lib/logger_jynx.rb
29
+ - lib/service_jynx.rb
30
+ - lib/service_jynx/version.rb
31
+ - run_test
32
+ - service_jynx.gemspec
33
+ - spec/service_jynx_spec.rb
34
+ - spec/spec_helper.rb
35
+ homepage: https://github.com/AvnerCohen/service-jynx
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.0.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Use errors count over sliding windows to block calls to an external service
59
+ or method, or whatever.
60
+ test_files:
61
+ - spec/service_jynx_spec.rb
62
+ - spec/spec_helper.rb