nerve 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,39 +1,72 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nerve (0.5.2)
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.7.2)
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 (2.14.1)
20
- rspec-core (~> 2.14.0)
21
- rspec-expectations (~> 2.14.0)
22
- rspec-mocks (~> 2.14.0)
23
- rspec-core (2.14.7)
24
- rspec-expectations (2.14.4)
25
- diff-lcs (>= 1.1.3, < 2.0)
26
- rspec-mocks (2.14.4)
27
- zk (1.9.3)
28
- logging (~> 1.7.2)
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.8)
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 certralized automation center: action like scaling distributed systems up or down or alerting ops or engineering about downtime.
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
+ }
@@ -24,6 +24,12 @@ class Nerve::Reporter
24
24
 
25
25
  def ping?
26
26
  end
27
+
28
+ protected
29
+ def parse_data(data)
30
+ return data if data.class == String
31
+ return data.to_json
32
+ end
27
33
  end
28
34
  end
29
35
 
@@ -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
- @zk.close
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
 
@@ -1,3 +1,6 @@
1
+ require 'nerve/utils'
2
+ require 'nerve/log'
3
+ require 'nerve/reporter/base'
1
4
 
2
5
  module Nerve
3
6
  class Reporter
@@ -1,3 +1,5 @@
1
+ require 'nerve/ring_buffer'
2
+
1
3
  module Nerve
2
4
  module ServiceCheck
3
5
  class BaseServiceCheck
@@ -55,6 +57,9 @@ module Nerve
55
57
  end
56
58
  end
57
59
  end
60
+
61
+ CHECKS ||= {}
62
+ CHECKS['base'] = BaseServiceCheck
58
63
  end
59
64
  end
60
65
 
@@ -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 \"#{response.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 \"#{response.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
- begin
29
- service_check_class = ServiceCheck::CHECKS[check['type']]
30
- rescue
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['host'] ||= service['host']
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
- @reporter.ping?
55
-
56
- # what is the status of the service?
57
- is_up = check?
58
- log.debug "nerve: current service status for #{@name} is #{is_up.inspect}"
59
-
60
- if is_up != was_up
61
- if is_up
62
- @reporter.report_up
63
- log.info "nerve: service #{@name} is now up"
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
@@ -1,3 +1,3 @@
1
1
  module Nerve
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.4"
3
3
  end
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.2
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: 2014-03-06 00:00:00.000000000 Z
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/service1.json
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