nerve 0.5.2 → 0.5.4
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.
- data/Gemfile.lock +48 -15
- data/README.md +19 -1
- data/example/nerve_services/etcd_service1.json +18 -0
- data/example/nerve_services/{service1.json → zookeeper_service1.json} +0 -0
- data/lib/nerve/reporter/base.rb +6 -0
- data/lib/nerve/reporter/etcd.rb +82 -0
- data/lib/nerve/reporter/zookeeper.rb +2 -6
- data/lib/nerve/reporter.rb +3 -0
- data/lib/nerve/service_watcher/base.rb +5 -0
- data/lib/nerve/service_watcher/http.rb +9 -4
- data/lib/nerve/service_watcher.rb +51 -25
- data/lib/nerve/version.rb +1 -1
- data/nerve.gemspec +5 -1
- data/spec/example_services_spec.rb +29 -0
- data/spec/factories/check.rb +16 -0
- data/spec/factories/service.rb +26 -0
- data/spec/lib/nerve/reporter_etcd_spec.rb +18 -0
- data/spec/lib/nerve/reporter_spec.rb +15 -0
- data/spec/lib/nerve/reporter_zookeeper_spec.rb +14 -0
- data/spec/lib/nerve/service_watcher_spec.rb +89 -0
- data/spec/spec_helper.rb +12 -1
- metadata +80 -4
data/Gemfile.lock
CHANGED
@@ -1,39 +1,72 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nerve (0.5.
|
4
|
+
nerve (0.5.4)
|
5
5
|
bunny (= 1.1.0)
|
6
|
+
etcd (~> 0.2.3)
|
7
|
+
json
|
6
8
|
zk (~> 1.9.2)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
10
12
|
specs:
|
13
|
+
activesupport (4.2.0)
|
14
|
+
i18n (~> 0.7)
|
15
|
+
json (~> 1.7, >= 1.7.7)
|
16
|
+
minitest (~> 5.1)
|
17
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
18
|
+
tzinfo (~> 1.1)
|
11
19
|
amq-protocol (1.9.2)
|
12
20
|
bunny (1.1.0)
|
13
21
|
amq-protocol (>= 1.9.2)
|
22
|
+
coderay (1.1.0)
|
14
23
|
diff-lcs (1.2.5)
|
24
|
+
etcd (0.2.4)
|
25
|
+
mixlib-log
|
26
|
+
factory_girl (4.5.0)
|
27
|
+
activesupport (>= 3.0.0)
|
28
|
+
i18n (0.7.0)
|
29
|
+
json (1.8.2)
|
15
30
|
little-plugger (1.1.3)
|
16
|
-
logging (1.
|
31
|
+
logging (1.8.2)
|
17
32
|
little-plugger (>= 1.1.3)
|
33
|
+
multi_json (>= 1.8.4)
|
34
|
+
method_source (0.8.2)
|
35
|
+
minitest (5.5.1)
|
36
|
+
mixlib-log (1.6.0)
|
37
|
+
multi_json (1.10.1)
|
38
|
+
pry (0.10.1)
|
39
|
+
coderay (~> 1.1.0)
|
40
|
+
method_source (~> 0.8.1)
|
41
|
+
slop (~> 3.4)
|
18
42
|
rake (10.1.1)
|
19
|
-
rspec (
|
20
|
-
rspec-core (~>
|
21
|
-
rspec-expectations (~>
|
22
|
-
rspec-mocks (~>
|
23
|
-
rspec-core (
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
43
|
+
rspec (3.1.0)
|
44
|
+
rspec-core (~> 3.1.0)
|
45
|
+
rspec-expectations (~> 3.1.0)
|
46
|
+
rspec-mocks (~> 3.1.0)
|
47
|
+
rspec-core (3.1.7)
|
48
|
+
rspec-support (~> 3.1.0)
|
49
|
+
rspec-expectations (3.1.2)
|
50
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
+
rspec-support (~> 3.1.0)
|
52
|
+
rspec-mocks (3.1.3)
|
53
|
+
rspec-support (~> 3.1.0)
|
54
|
+
rspec-support (3.1.2)
|
55
|
+
slop (3.6.0)
|
56
|
+
thread_safe (0.3.4)
|
57
|
+
tzinfo (1.2.2)
|
58
|
+
thread_safe (~> 0.1)
|
59
|
+
zk (1.9.5)
|
60
|
+
logging (~> 1.8.2)
|
29
61
|
zookeeper (~> 1.4.0)
|
30
|
-
zookeeper (1.4.
|
62
|
+
zookeeper (1.4.10)
|
31
63
|
|
32
64
|
PLATFORMS
|
33
|
-
java
|
34
65
|
ruby
|
35
66
|
|
36
67
|
DEPENDENCIES
|
68
|
+
factory_girl
|
37
69
|
nerve!
|
70
|
+
pry
|
38
71
|
rake
|
39
|
-
rspec
|
72
|
+
rspec (~> 3.1.0)
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Nerve simplifies underlying services, enables code reuse, and allows us to creat
|
|
15
15
|
It does so by factoring out the boilerplate into it's own application, which independenly handles monitoring and reporting.
|
16
16
|
|
17
17
|
Beyond those benefits, nerve also acts as a general watchdog on systems.
|
18
|
-
The information it reports can be used to take action from a
|
18
|
+
The information it reports can be used to take action from a centralized automation center: action like scaling distributed systems up or down or alerting ops or engineering about downtime.
|
19
19
|
|
20
20
|
## Installation ##
|
21
21
|
|
@@ -61,6 +61,16 @@ If you set your `reporter_type` to `"zookeeper"` you should also set these param
|
|
61
61
|
* `zk_hosts`: a list of the zookeeper hosts comprising the [ensemble](https://zookeeper.apache.org/doc/r3.1.2/zookeeperAdmin.html#sc_zkMulitServerSetup) that nerve will submit registration to
|
62
62
|
* `zk_path`: the path (or [znode](https://zookeeper.apache.org/doc/r3.1.2/zookeeperProgrammers.html#sc_zkDataModel_znodes)) where the registration will be created; nerve will create the [ephemeral node](https://zookeeper.apache.org/doc/r3.1.2/zookeeperProgrammers.html#Ephemeral+Nodes) that is the registration as a child of this path
|
63
63
|
|
64
|
+
#### Etcd Reporter ####
|
65
|
+
|
66
|
+
Note: Etcd support is currently experimental!
|
67
|
+
|
68
|
+
If you set your `reporter_type` to `"etcd"` you should also set these parameters:
|
69
|
+
|
70
|
+
* `etcd_host`: etcd host that nerve will submit registration to
|
71
|
+
* `etcd_port`: port to connect to etcd.
|
72
|
+
* `etcd_path`: the path where the registration will be created; nerve will create a node with a 30s ttl that is the registration as a child of this path, and then update it every few seconds
|
73
|
+
|
64
74
|
### Checks ###
|
65
75
|
|
66
76
|
The core of nerve is a set of service checks.
|
@@ -75,6 +85,14 @@ Although the exact parameters passed to each check are different, all take a num
|
|
75
85
|
* `rise`: (optional) how many consecutive checks must pass before the check is considered passing; defaults to 1
|
76
86
|
* `fall`: (optional) how many consecutive checks must fail before the check is considered failing; defaults to 1
|
77
87
|
|
88
|
+
#### Custom External Checks ####
|
89
|
+
|
90
|
+
If you would like to run a custom check but don't feel like trying to get it merged into this project, there is a mechanism for including external checks thanks to @bakins (airbnb/nerve#36).
|
91
|
+
Build your custom check as a separate gem and make sure to `bundle install` it on your system.
|
92
|
+
|
93
|
+
Ideally, you should name your gem `"nerve-watcher-#{type}"`, as that is what nerve will `require` on boot.
|
94
|
+
However, if you have a custom name for your gem, you can specify that in the `module` argument to the check.
|
95
|
+
|
78
96
|
## Contributing
|
79
97
|
|
80
98
|
1. Fork it
|
@@ -0,0 +1,18 @@
|
|
1
|
+
{
|
2
|
+
"host": "1.2.3.4",
|
3
|
+
"port": 3000,
|
4
|
+
"reporter_type": "etcd",
|
5
|
+
"etcd_host": "localhost",
|
6
|
+
"etcd_port": 4001,
|
7
|
+
"etcd_path": "/nerve/services/your_http_service/services",
|
8
|
+
"check_interval": 2,
|
9
|
+
"checks": [
|
10
|
+
{
|
11
|
+
"type": "http",
|
12
|
+
"uri": "/health",
|
13
|
+
"timeout": 0.2,
|
14
|
+
"rise": 3,
|
15
|
+
"fall": 2
|
16
|
+
}
|
17
|
+
]
|
18
|
+
}
|
File without changes
|
data/lib/nerve/reporter/base.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'nerve/reporter/base'
|
2
|
+
require 'etcd'
|
3
|
+
|
4
|
+
class Nerve::Reporter
|
5
|
+
class Etcd < Base
|
6
|
+
def initialize(service)
|
7
|
+
%w{etcd_host instance_id host port}.each do |required|
|
8
|
+
raise ArgumentError, "missing required argument #{required} for new service watcher" unless service[required]
|
9
|
+
end
|
10
|
+
@host = service['etcd_host']
|
11
|
+
@port = service['etcd_port'] || 4003
|
12
|
+
path = service['etcd_path'] || '/'
|
13
|
+
@path = path.split('/').push(service['instance_id']).join('/')
|
14
|
+
@data = parse_data({'host' => service['host'], 'port' => service['port'], 'name' => service['instance_id']})
|
15
|
+
@key = nil
|
16
|
+
@ttl = (service['check_interval'] || 0.5) * 5
|
17
|
+
@ttl = @ttl.ceil
|
18
|
+
end
|
19
|
+
|
20
|
+
def start()
|
21
|
+
log.info "nerve: connecting to etcd at #{@host}:#{@port}"
|
22
|
+
@etcd = ::Etcd.client(:host => @host, :port => @port)
|
23
|
+
log.info "nerve: successfully created etcd connection to #{@host}:#{@port}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop()
|
27
|
+
report_down
|
28
|
+
@etcd = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def report_up()
|
32
|
+
etcd_save
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_down
|
36
|
+
etcd_delete
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_data(new_data='')
|
40
|
+
# nothing in nerve calls this, but implement it like the zookeeper
|
41
|
+
# reporter just for fun.
|
42
|
+
@data = parse_data(new_data)
|
43
|
+
etcd_save
|
44
|
+
end
|
45
|
+
|
46
|
+
def ping?
|
47
|
+
# we get a ping every check_interval.
|
48
|
+
if @key
|
49
|
+
# we have made a key: save it to prevent the TTL from expiring.
|
50
|
+
etcd_save
|
51
|
+
else
|
52
|
+
# we haven't created a key, so just frob the etcd API to assure that
|
53
|
+
# it's alive.
|
54
|
+
@etcd.leader
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def etcd_delete
|
61
|
+
return unless @etcd and @key
|
62
|
+
begin
|
63
|
+
@etcd.delete(@key)
|
64
|
+
rescue ::Etcd::NotFile
|
65
|
+
rescue Errno::ECONNREFUSED
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def etcd_create
|
70
|
+
# we use create_in_order to create a unique key under our path,
|
71
|
+
# permitting multiple registrations from the same instance_id.
|
72
|
+
@key = @etcd.create_in_order(@path, :value => @data, :ttl => @ttl).key
|
73
|
+
log.info "registered etcd key #{@key} with value #{@data}, TTL #{@ttl}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def etcd_save
|
77
|
+
return etcd_create unless @key
|
78
|
+
@etcd.set(@key, :value => @data, :ttl => @ttl)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -23,7 +23,8 @@ class Nerve::Reporter
|
|
23
23
|
|
24
24
|
def stop()
|
25
25
|
log.info "nerve: closing zk connection at #{@path}"
|
26
|
-
|
26
|
+
report_down
|
27
|
+
@zk.close!
|
27
28
|
end
|
28
29
|
|
29
30
|
def report_up()
|
@@ -65,11 +66,6 @@ class Nerve::Reporter
|
|
65
66
|
zk_create
|
66
67
|
end
|
67
68
|
end
|
68
|
-
|
69
|
-
def parse_data(data)
|
70
|
-
return data if data.class == String
|
71
|
-
return data.to_json
|
72
|
-
end
|
73
69
|
end
|
74
70
|
end
|
75
71
|
|
data/lib/nerve/reporter.rb
CHANGED
@@ -21,6 +21,10 @@ module Nerve
|
|
21
21
|
@open_timeout = opts['open_timeout'] || 0.2
|
22
22
|
@ssl_timeout = opts['ssl_timeout'] || 0.2
|
23
23
|
|
24
|
+
@headers = opts['headers'] || {}
|
25
|
+
|
26
|
+
@expect = opts['expect']
|
27
|
+
|
24
28
|
@name = "http-#{@host}:#{@port}#{@uri}"
|
25
29
|
end
|
26
30
|
|
@@ -28,14 +32,15 @@ module Nerve
|
|
28
32
|
log.debug "running health check #{@name}"
|
29
33
|
|
30
34
|
connection = get_connection
|
31
|
-
response = connection.get(@uri)
|
35
|
+
response = connection.get(@uri, @headers)
|
32
36
|
code = response.code.to_i
|
37
|
+
body = response.body
|
33
38
|
|
34
|
-
if code >= 200 and code < 300
|
35
|
-
log.debug "nerve: check #{@name} got response code #{code} with body \"#{
|
39
|
+
if code >= 200 and code < 300 and (@expect == nil || body.include?(@expect))
|
40
|
+
log.debug "nerve: check #{@name} got response code #{code} with body \"#{body}\""
|
36
41
|
return true
|
37
42
|
else
|
38
|
-
log.error "nerve: check #{@name} got response code #{code} with body \"#{
|
43
|
+
log.error "nerve: check #{@name} got response code #{code} with body \"#{body}\""
|
39
44
|
return false
|
40
45
|
end
|
41
46
|
end
|
@@ -24,23 +24,39 @@ module Nerve
|
|
24
24
|
@service_checks = []
|
25
25
|
service['checks'] ||= []
|
26
26
|
service['checks'].each do |check|
|
27
|
+
# checks inherit attributes from the service overall
|
28
|
+
check['host'] ||= service['host']
|
29
|
+
check['port'] ||= service['port']
|
30
|
+
|
31
|
+
# generate a nice readable name for each check
|
32
|
+
check['name'] ||= "#{@name} #{check['type']}-#{check['host']}:#{check['port']}"
|
33
|
+
|
34
|
+
# make sure a type is set
|
27
35
|
check['type'] ||= "undefined"
|
28
|
-
|
29
|
-
|
30
|
-
|
36
|
+
|
37
|
+
# require a 3rd-party module if necessary for external checkers
|
38
|
+
unless ServiceCheck::CHECKS[check['type']]
|
39
|
+
m = check['module'] ? check['module'] : "nerve-watcher-#{check['type']}"
|
40
|
+
require m
|
41
|
+
end
|
42
|
+
|
43
|
+
# instantiate the check object
|
44
|
+
service_check_class = ServiceCheck::CHECKS[check['type']]
|
45
|
+
if service_check_class.nil?
|
31
46
|
raise ArgumentError,
|
32
47
|
"invalid service check type #{check['type']}; valid types: #{ServiceCheck::CHECKS.keys.join(',')}"
|
33
48
|
end
|
34
49
|
|
35
|
-
check
|
36
|
-
check['port'] ||= service['port']
|
37
|
-
check['name'] ||= "#{@name} #{check['type']}-#{check['host']}:#{check['port']}"
|
50
|
+
# save the check object
|
38
51
|
@service_checks << service_check_class.new(check)
|
39
52
|
end
|
40
53
|
|
41
54
|
# how often do we initiate service checks?
|
42
55
|
@check_interval = service['check_interval'] || 0.5
|
43
56
|
|
57
|
+
# force an initial report on startup
|
58
|
+
@was_up = nil
|
59
|
+
|
44
60
|
log.debug "nerve: created service watcher for #{@name} with #{@service_checks.size} checks"
|
45
61
|
end
|
46
62
|
|
@@ -48,28 +64,19 @@ module Nerve
|
|
48
64
|
log.info "nerve: starting service watch #{@name}"
|
49
65
|
|
50
66
|
@reporter.start()
|
51
|
-
was_up = false
|
52
67
|
|
53
68
|
until $EXIT
|
54
|
-
|
55
|
-
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
@reporter.report_down
|
66
|
-
log.warn "nerve: service #{@name} is now down"
|
67
|
-
end
|
68
|
-
was_up = is_up
|
69
|
+
check_and_report
|
70
|
+
|
71
|
+
# wait to run more checks but make sure to exit if $EXIT
|
72
|
+
# we avoid sleeping for the entire check interval at once
|
73
|
+
# so that nerve can exit promptly if required
|
74
|
+
nap_time = @check_interval
|
75
|
+
while nap_time > 0
|
76
|
+
break if $EXIT
|
77
|
+
sleep [nap_time, 1].min
|
78
|
+
nap_time -= 1
|
69
79
|
end
|
70
|
-
|
71
|
-
# wait to run more checks
|
72
|
-
sleep @check_interval
|
73
80
|
end
|
74
81
|
rescue StandardError => e
|
75
82
|
log.error "nerve: error in service watcher #{@name}: #{e.inspect}"
|
@@ -80,6 +87,25 @@ module Nerve
|
|
80
87
|
@reporter.stop
|
81
88
|
end
|
82
89
|
|
90
|
+
def check_and_report
|
91
|
+
@reporter.ping?
|
92
|
+
|
93
|
+
# what is the status of the service?
|
94
|
+
is_up = check?
|
95
|
+
log.debug "nerve: current service status for #{@name} is #{is_up.inspect}"
|
96
|
+
|
97
|
+
if is_up != @was_up
|
98
|
+
if is_up
|
99
|
+
@reporter.report_up
|
100
|
+
log.info "nerve: service #{@name} is now up"
|
101
|
+
else
|
102
|
+
@reporter.report_down
|
103
|
+
log.warn "nerve: service #{@name} is now down"
|
104
|
+
end
|
105
|
+
@was_up = is_up
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
83
109
|
def check?
|
84
110
|
@service_checks.each do |check|
|
85
111
|
return false unless check.up?
|
data/lib/nerve/version.rb
CHANGED
data/nerve.gemspec
CHANGED
@@ -17,9 +17,13 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
+
gem.add_runtime_dependency "json"
|
20
21
|
gem.add_runtime_dependency "zk", "~> 1.9.2"
|
21
22
|
gem.add_runtime_dependency "bunny", "= 1.1.0"
|
23
|
+
gem.add_runtime_dependency "etcd", "~> 0.2.3"
|
22
24
|
|
23
25
|
gem.add_development_dependency "rake"
|
24
|
-
gem.add_development_dependency "rspec"
|
26
|
+
gem.add_development_dependency "rspec", "~> 3.1.0"
|
27
|
+
gem.add_development_dependency "factory_girl"
|
28
|
+
gem.add_development_dependency "pry"
|
25
29
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'nerve/reporter'
|
3
|
+
require 'nerve/service_watcher'
|
4
|
+
|
5
|
+
describe "example services are valid" do
|
6
|
+
Dir.foreach("#{File.dirname(__FILE__)}/../example/nerve_services") do |item|
|
7
|
+
next if item == '.' or item == '..'
|
8
|
+
service_data = JSON.parse(IO.read("#{File.dirname(__FILE__)}/../example/nerve_services/#{item}"))
|
9
|
+
service_data['name'] = item.gsub(/\.json$/, '')
|
10
|
+
service_data['instance_id'] = '1'
|
11
|
+
|
12
|
+
context "when #{item} can be initialized as a valid reporter" do
|
13
|
+
it 'creates a valid reporter in new_from_service' do
|
14
|
+
reporter = nil
|
15
|
+
expect { reporter = Nerve::Reporter.new_from_service(service_data) }.to_not raise_error()
|
16
|
+
expect(reporter.is_a?(Nerve::Reporter::Base)).to eql(true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when #{item} can be initialized as a valid service watcher" do
|
21
|
+
it "creates a valid service watcher for #{item}" do
|
22
|
+
watcher = nil
|
23
|
+
expect { watcher = Nerve::ServiceWatcher.new(service_data) }.to_not raise_error()
|
24
|
+
expect(watcher.is_a?(Nerve::ServiceWatcher)).to eql(true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :check, :class => Hash do
|
3
|
+
type 'base'
|
4
|
+
timeout 0.2
|
5
|
+
rise 3
|
6
|
+
fall 2
|
7
|
+
|
8
|
+
trait :http do
|
9
|
+
type 'http'
|
10
|
+
uri '/health'
|
11
|
+
end
|
12
|
+
|
13
|
+
initialize_with { Hash[attributes.map{|k,v| [k.to_s,v]}] }
|
14
|
+
to_create {}
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :service, :class => Hash do
|
3
|
+
sequence(:name) {|n| "service_check_#{n}"}
|
4
|
+
instance_id 'public_hostname.example.com'
|
5
|
+
host 'localhost'
|
6
|
+
port 3000
|
7
|
+
reporter_type 'base'
|
8
|
+
checks { create_list(:check, checks_count) }
|
9
|
+
check_interval nil
|
10
|
+
|
11
|
+
trait :zookeeper do
|
12
|
+
reporter_type 'zookeeper'
|
13
|
+
zk_hosts ['localhost:2181']
|
14
|
+
zk_path { "/nerve/services/#{name}/services" }
|
15
|
+
end
|
16
|
+
|
17
|
+
# set up some service checks
|
18
|
+
transient do
|
19
|
+
checks_count 1
|
20
|
+
end
|
21
|
+
|
22
|
+
# thanks to https://stackoverflow.com/questions/10032760
|
23
|
+
initialize_with { Hash[attributes.map{|k,v| [k.to_s,v]}] }
|
24
|
+
to_create {}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nerve/reporter/etcd'
|
3
|
+
|
4
|
+
describe Nerve::Reporter::Etcd do
|
5
|
+
let(:subject) { {
|
6
|
+
'etcd_host' => 'etcdhost1',
|
7
|
+
'etcd_port' => 4001,
|
8
|
+
'etcd_path' => '/path',
|
9
|
+
'instance_id' => 'instance_id',
|
10
|
+
'host' => 'host',
|
11
|
+
'port' => 'port'
|
12
|
+
}
|
13
|
+
}
|
14
|
+
it 'actually constructs an instance' do
|
15
|
+
expect(Nerve::Reporter::Etcd.new(subject).is_a?(Nerve::Reporter::Etcd)).to eql(true)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -25,3 +25,18 @@ describe Nerve::Reporter do
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
class Nerve::Reporter::Test < Nerve::Reporter::Base
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Nerve::Reporter::Test do
|
32
|
+
let(:subject) {Nerve::Reporter::Test.new({}) }
|
33
|
+
it 'has parse data method that passes strings' do
|
34
|
+
expect(subject.send(:parse_data, 'foobar')).to eql('foobar')
|
35
|
+
end
|
36
|
+
it 'jsonifies anything that is not a string' do
|
37
|
+
thing_to_parse = double()
|
38
|
+
expect(thing_to_parse).to receive(:to_json).and_return('{"some":"json"}')
|
39
|
+
expect(subject.send(:parse_data, thing_to_parse)).to eql('{"some":"json"}')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -13,5 +13,19 @@ describe Nerve::Reporter::Zookeeper do
|
|
13
13
|
it 'actually constructs an instance' do
|
14
14
|
expect(Nerve::Reporter::Zookeeper.new(subject).is_a?(Nerve::Reporter::Zookeeper)).to eql(true)
|
15
15
|
end
|
16
|
+
|
17
|
+
it 'deregisters service on exit' do
|
18
|
+
zk = double("zk")
|
19
|
+
allow(zk).to receive(:close!)
|
20
|
+
expect(zk).to receive(:create) { "full_path" }
|
21
|
+
expect(zk).to receive(:delete).with("full_path", anything())
|
22
|
+
|
23
|
+
allow(ZK).to receive(:new).and_return(zk)
|
24
|
+
|
25
|
+
reporter = Nerve::Reporter::Zookeeper.new(subject)
|
26
|
+
reporter.start
|
27
|
+
reporter.report_up
|
28
|
+
reporter.stop
|
29
|
+
end
|
16
30
|
end
|
17
31
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe Nerve::ServiceWatcher do
|
5
|
+
describe 'initialize' do
|
6
|
+
let(:service) { build(:service) }
|
7
|
+
|
8
|
+
it 'can successfully initialize' do
|
9
|
+
Nerve::ServiceWatcher.new(service)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'requires minimum parameters' do
|
13
|
+
%w[name instance_id host port].each do |req|
|
14
|
+
service_without = service.dup
|
15
|
+
service_without.delete(req)
|
16
|
+
|
17
|
+
expect { Nerve::ServiceWatcher.new(service_without) }.to raise_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'check_and_report' do
|
23
|
+
let(:service_watcher) { Nerve::ServiceWatcher.new(build(:service)) }
|
24
|
+
let(:reporter) { service_watcher.instance_variable_get(:@reporter) }
|
25
|
+
|
26
|
+
it 'pings the reporter' do
|
27
|
+
expect(reporter).to receive(:ping?)
|
28
|
+
service_watcher.check_and_report
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'reports the service as down when the checks fail' do
|
32
|
+
expect(service_watcher).to receive(:check?).and_return(false)
|
33
|
+
expect(reporter).to receive(:report_down)
|
34
|
+
service_watcher.check_and_report
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'reports the service as up when the checks succeed' do
|
38
|
+
expect(service_watcher).to receive(:check?).and_return(true)
|
39
|
+
expect(reporter).to receive(:report_up)
|
40
|
+
service_watcher.check_and_report
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'doesn\'t report if the status hasn\'t changed' do
|
44
|
+
expect(service_watcher).to receive(:check?).and_return(true)
|
45
|
+
|
46
|
+
expect(reporter).to receive(:report_up).once
|
47
|
+
expect(reporter).not_to receive(:report_down)
|
48
|
+
service_watcher.check_and_report
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'run' do
|
53
|
+
let(:check_interval) { 0 }
|
54
|
+
let(:service_watcher) { Nerve::ServiceWatcher.new(build(:service, :check_interval => check_interval)) }
|
55
|
+
let(:reporter) { service_watcher.instance_variable_get(:@reporter) }
|
56
|
+
before { $EXIT = false }
|
57
|
+
|
58
|
+
it 'starts the reporter' do
|
59
|
+
$EXIT = true
|
60
|
+
expect(reporter).to receive(:start)
|
61
|
+
service_watcher.run()
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'calls check and report repeatedly' do
|
65
|
+
count = 0
|
66
|
+
|
67
|
+
# expect it to be called twice
|
68
|
+
expect(service_watcher).to receive(:check_and_report).twice do
|
69
|
+
# on the second call, set exit to true
|
70
|
+
$EXIT = true if count == 1
|
71
|
+
count += 1
|
72
|
+
end
|
73
|
+
|
74
|
+
service_watcher.run()
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when the check interval is long' do
|
78
|
+
let(:check_interval) { 10 }
|
79
|
+
|
80
|
+
it 'still exits quickly during nap time' do
|
81
|
+
expect(service_watcher).to receive(:check_and_report) do
|
82
|
+
$EXIT = true
|
83
|
+
end
|
84
|
+
|
85
|
+
expect{ Timeout::timeout(1) { service_watcher.run() } }.not_to raise_error
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,12 +6,23 @@
|
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
require "#{File.dirname(__FILE__)}/../lib/nerve"
|
8
8
|
|
9
|
+
require 'factory_girl'
|
10
|
+
FactoryGirl.find_definitions
|
11
|
+
|
9
12
|
RSpec.configure do |config|
|
10
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
13
|
config.run_all_when_everything_filtered = true
|
12
14
|
config.filter_run :focus
|
13
15
|
config.include Config
|
14
16
|
|
17
|
+
# verify every double we can think of
|
18
|
+
config.mock_with :rspec do |mocks|
|
19
|
+
mocks.verify_doubled_constant_names = true
|
20
|
+
mocks.verify_partial_doubles = true
|
21
|
+
end
|
22
|
+
|
23
|
+
# include factory-girl when running tests
|
24
|
+
config.include FactoryGirl::Syntax::Methods
|
25
|
+
|
15
26
|
# Run specs in random order to surface order dependencies. If you find an
|
16
27
|
# order dependency and want to debug it, you can fix the order by providing
|
17
28
|
# the seed, which is printed after each run.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nerve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,8 +11,24 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2015-02-19 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: json
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
16
32
|
- !ruby/object:Gem::Dependency
|
17
33
|
name: zk
|
18
34
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,6 +61,22 @@ dependencies:
|
|
45
61
|
- - '='
|
46
62
|
- !ruby/object:Gem::Version
|
47
63
|
version: 1.1.0
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: etcd
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 0.2.3
|
72
|
+
type: :runtime
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 0.2.3
|
48
80
|
- !ruby/object:Gem::Dependency
|
49
81
|
name: rake
|
50
82
|
requirement: !ruby/object:Gem::Requirement
|
@@ -63,6 +95,38 @@ dependencies:
|
|
63
95
|
version: '0'
|
64
96
|
- !ruby/object:Gem::Dependency
|
65
97
|
name: rspec
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.1.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ~>
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 3.1.0
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: factory_girl
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: pry
|
66
130
|
requirement: !ruby/object:Gem::Requirement
|
67
131
|
none: false
|
68
132
|
requirements:
|
@@ -99,11 +163,13 @@ files:
|
|
99
163
|
- Vagrantfile
|
100
164
|
- bin/nerve
|
101
165
|
- example/nerve.conf.json
|
102
|
-
- example/nerve_services/
|
166
|
+
- example/nerve_services/etcd_service1.json
|
167
|
+
- example/nerve_services/zookeeper_service1.json
|
103
168
|
- lib/nerve.rb
|
104
169
|
- lib/nerve/log.rb
|
105
170
|
- lib/nerve/reporter.rb
|
106
171
|
- lib/nerve/reporter/base.rb
|
172
|
+
- lib/nerve/reporter/etcd.rb
|
107
173
|
- lib/nerve/reporter/zookeeper.rb
|
108
174
|
- lib/nerve/ring_buffer.rb
|
109
175
|
- lib/nerve/service_watcher.rb
|
@@ -115,8 +181,13 @@ files:
|
|
115
181
|
- lib/nerve/version.rb
|
116
182
|
- nerve.gemspec
|
117
183
|
- spec/.gitkeep
|
184
|
+
- spec/example_services_spec.rb
|
185
|
+
- spec/factories/check.rb
|
186
|
+
- spec/factories/service.rb
|
187
|
+
- spec/lib/nerve/reporter_etcd_spec.rb
|
118
188
|
- spec/lib/nerve/reporter_spec.rb
|
119
189
|
- spec/lib/nerve/reporter_zookeeper_spec.rb
|
190
|
+
- spec/lib/nerve/service_watcher_spec.rb
|
120
191
|
- spec/spec_helper.rb
|
121
192
|
homepage: ''
|
122
193
|
licenses: []
|
@@ -138,12 +209,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
209
|
version: '0'
|
139
210
|
requirements: []
|
140
211
|
rubyforge_project:
|
141
|
-
rubygems_version: 1.8.23
|
212
|
+
rubygems_version: 1.8.23.2
|
142
213
|
signing_key:
|
143
214
|
specification_version: 3
|
144
215
|
summary: summary
|
145
216
|
test_files:
|
146
217
|
- spec/.gitkeep
|
218
|
+
- spec/example_services_spec.rb
|
219
|
+
- spec/factories/check.rb
|
220
|
+
- spec/factories/service.rb
|
221
|
+
- spec/lib/nerve/reporter_etcd_spec.rb
|
147
222
|
- spec/lib/nerve/reporter_spec.rb
|
148
223
|
- spec/lib/nerve/reporter_zookeeper_spec.rb
|
224
|
+
- spec/lib/nerve/service_watcher_spec.rb
|
149
225
|
- spec/spec_helper.rb
|