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 +7 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +52 -0
- data/jynx.jpg +0 -0
- data/lib/logger_jynx.rb +21 -0
- data/lib/service_jynx/version.rb +3 -0
- data/lib/service_jynx.rb +85 -0
- data/run_test +1 -0
- data/service_jynx.gemspec +21 -0
- data/spec/service_jynx_spec.rb +104 -0
- data/spec/spec_helper.rb +3 -0
- metadata +62 -0
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
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
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
|
+

|
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
|
data/lib/logger_jynx.rb
ADDED
@@ -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
|
data/lib/service_jynx.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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
|