big_brother 0.1.0 → 0.2.0

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/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # BigBrother
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/braintree/big_brother.png)](http://travis-ci.org/braintree/big_brother)
4
+
3
5
  TODO: Write a gem description
4
6
 
5
7
  ## Installation
@@ -46,25 +46,20 @@ module BigBrother
46
46
  end
47
47
 
48
48
  def monitor_nodes
49
- @nodes.each do |node|
50
- BigBrother.ipvs.edit_node(@fwmark, node.address, _determine_weight(node))
51
- end
52
-
49
+ @nodes.each { |node| node.monitor(self) }
53
50
  @last_check = Time.now
54
51
  end
55
52
 
56
- def _determine_weight(node)
57
- if @up_file.exists?
58
- 100
59
- elsif @down_file.exists?
60
- 0
61
- else
62
- node.current_health
63
- end
64
- end
65
-
66
53
  def to_s
67
54
  "#{@name} (#{@fwmark})"
68
55
  end
56
+
57
+ def up_file_exists?
58
+ @up_file.exists?
59
+ end
60
+
61
+ def down_file_exists?
62
+ @down_file.exists?
63
+ end
69
64
  end
70
65
  end
@@ -11,15 +11,19 @@ module BigBrother
11
11
  end
12
12
 
13
13
  def self.synchronize_with_ipvs(clusters, ipvs_state)
14
- clusters.values.each do |cluster|
15
- if ipvs_state.has_key?(cluster.fwmark.to_s)
16
- cluster.resume_monitoring!
14
+ ipvs_state.each do |fwmark, running_nodes|
15
+ running_cluster = clusters.values.detect { |cluster| cluster.fwmark == fwmark.to_i }
17
16
 
18
- running_nodes = ipvs_state[cluster.fwmark.to_s]
19
- cluster_nodes = cluster.nodes.map(&:address)
17
+ if running_cluster.nil?
18
+ BigBrother.ipvs.stop_cluster(fwmark)
19
+ else
20
+ running_cluster.resume_monitoring!
20
21
 
21
- _remove_nodes(cluster, running_nodes - cluster_nodes)
22
- _add_nodes(cluster, cluster_nodes - running_nodes)
22
+ running_nodes = ipvs_state[running_cluster.fwmark.to_s]
23
+ cluster_nodes = running_cluster.nodes.map(&:address)
24
+
25
+ _remove_nodes(running_cluster, running_nodes - cluster_nodes)
26
+ _add_nodes(running_cluster, cluster_nodes - running_nodes)
23
27
  end
24
28
  end
25
29
  end
@@ -0,0 +1,13 @@
1
+ module BigBrother
2
+ class HealthFetcher
3
+ def self.current_health(address, port, path)
4
+ http_response = EventMachine::HttpRequest.new("http://#{address}:#{port}#{path}").get
5
+
6
+ if http_response.response_header.has_key?('X_HEALTH')
7
+ http_response.response_header['X_HEALTH'].to_i
8
+ else
9
+ http_response.response.slice(/Health: (\d+)/, 1).to_i
10
+ end
11
+ end
12
+ end
13
+ end
@@ -8,22 +8,24 @@ module BigBrother
8
8
  @address = address
9
9
  @port = port
10
10
  @path = path
11
+ @weight = nil
11
12
  end
12
13
 
13
- def current_health
14
- response = _get("http://#{@address}:#{@port}#{@path}")
15
- _parse_health(response)
16
- end
17
-
18
- def _get(url)
19
- EventMachine::HttpRequest.new(url).get
14
+ def monitor(cluster)
15
+ new_weight = _determine_weight(cluster)
16
+ if new_weight != @weight
17
+ BigBrother.ipvs.edit_node(cluster.fwmark, address, _determine_weight(cluster))
18
+ @weight = new_weight
19
+ end
20
20
  end
21
21
 
22
- def _parse_health(http_response)
23
- if http_response.response_header.has_key?('X_HEALTH')
24
- http_response.response_header['X_HEALTH'].to_i
22
+ def _determine_weight(cluster)
23
+ if cluster.up_file_exists?
24
+ 100
25
+ elsif cluster.down_file_exists?
26
+ 0
25
27
  else
26
- http_response.response.slice(/Health: (\d+)/, 1).to_i
28
+ BigBrother::HealthFetcher.current_health(@address, @port, @path)
27
29
  end
28
30
  end
29
31
  end
@@ -1,3 +1,3 @@
1
1
  module BigBrother
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/big_brother.rb CHANGED
@@ -10,6 +10,7 @@ require 'sinatra/synchrony'
10
10
  require 'big_brother/app'
11
11
  require 'big_brother/cluster'
12
12
  require 'big_brother/configuration'
13
+ require 'big_brother/health_fetcher'
13
14
  require 'big_brother/ipvs'
14
15
  require 'big_brother/logger'
15
16
  require 'big_brother/node'
@@ -48,38 +48,15 @@ describe BigBrother::Cluster do
48
48
  cluster.needs_check?.should be_false
49
49
  end
50
50
 
51
- it "updates the weight for each node" do
52
- node = Factory.node(:address => '127.0.0.1')
53
- node.should_receive(:current_health).and_return(56)
54
- cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
51
+ it "calls monitor on each of the nodes" do
52
+ node1 = Factory.node
53
+ node2 = Factory.node
54
+ cluster = Factory.cluster(:nodes => [node1, node2])
55
55
 
56
- cluster.monitor_nodes
57
-
58
- @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 56")
59
- end
60
-
61
- it "sets the weight to 100 for each node if an upfile exists" do
62
- node = Factory.node(:address => '127.0.0.1')
63
- node.stub(:current_health).and_return(56)
64
- cluster = Factory.cluster(:name => 'test', :fwmark => 100, :nodes => [node])
65
-
66
- BigBrother::StatusFile.new('up', 'test').create('Up for testing')
67
-
68
- cluster.monitor_nodes
69
-
70
- @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 100")
71
- end
72
-
73
- it "sets the weight to 0 for each node if a downfile exists" do
74
- node = Factory.node(:address => '127.0.0.1')
75
- node.stub(:current_health).and_return(56)
76
- cluster = Factory.cluster(:name => 'test', :fwmark => 100, :nodes => [node])
77
-
78
- BigBrother::StatusFile.new('down', 'test').create('Down for testing')
56
+ node1.should_receive(:monitor).with(cluster)
57
+ node2.should_receive(:monitor).with(cluster)
79
58
 
80
59
  cluster.monitor_nodes
81
-
82
- @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 0")
83
60
  end
84
61
  end
85
62
 
@@ -99,4 +76,26 @@ describe BigBrother::Cluster do
99
76
  cluster.to_s.should == "name (100)"
100
77
  end
101
78
  end
79
+
80
+ describe "#up_file_exists?" do
81
+ it "returns true when an up file exists" do
82
+ cluster = Factory.cluster(:name => 'name')
83
+ cluster.up_file_exists?.should be_false
84
+
85
+ BigBrother::StatusFile.new('up', 'name').create('Up for testing')
86
+
87
+ cluster.up_file_exists?.should be_true
88
+ end
89
+ end
90
+
91
+ describe "#down_file_exists?" do
92
+ it "returns true when an down file exists" do
93
+ cluster = Factory.cluster(:name => 'name')
94
+ cluster.down_file_exists?.should be_false
95
+
96
+ BigBrother::StatusFile.new('down', 'name').create('down for testing')
97
+
98
+ cluster.down_file_exists?.should be_true
99
+ end
100
+ end
102
101
  end
@@ -77,5 +77,14 @@ describe BigBrother::Configuration do
77
77
 
78
78
  @recording_executor.commands.should include("ipvsadm --add-server --fwmark-service 1 --real-server 127.0.1.1 --ipip --weight 100")
79
79
  end
80
+
81
+ it "will remove clusters that are no longer configured" do
82
+ clusters = { 'two' => Factory.cluster(:fwmark => 2) }
83
+ ipvs_state = { '1' => ['127.0.0.1'] }
84
+
85
+ BigBrother::Configuration.synchronize_with_ipvs(clusters, ipvs_state)
86
+
87
+ @recording_executor.commands.should include("ipvsadm --delete-service --fwmark-service 1")
88
+ end
80
89
  end
81
90
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::HealthFetcher do
4
+ describe "#current_health" do
5
+ run_in_reactor
6
+
7
+ it "returns its health" do
8
+ StubServer.new(<<-HTTP, 0.25)
9
+ HTTP/1.0 200 OK
10
+ Connection: close
11
+
12
+ Health: 59
13
+ HTTP
14
+ BigBrother::HealthFetcher.current_health("127.0.0.1", 8081, "/test/status").should == 59
15
+ end
16
+
17
+ it "returns 0 for an unknown service" do
18
+ StubServer.new(<<-HTTP, 0.25)
19
+ HTTP/1.0 503 Service Unavailable
20
+ Connection: close
21
+ HTTP
22
+ BigBrother::HealthFetcher.current_health("127.0.0.1", 8081, "/test/status").should == 0
23
+ end
24
+
25
+ it "returns 0 for an unknown DNS entry" do
26
+ BigBrother::HealthFetcher.current_health("junk.local", 8081, "/test/status").should == 0
27
+ end
28
+
29
+ it "returns the health if it is passed in a header" do
30
+ StubServer.new(<<-HTTP, 0.25)
31
+ HTTP/1.0 200 OK
32
+ Connection: close
33
+ X-Health: 61
34
+
35
+ This part is for people.
36
+ HTTP
37
+ BigBrother::HealthFetcher.current_health("127.0.0.1", 8081, "/test/status").should == 61
38
+ end
39
+ end
40
+
41
+ end
@@ -1,44 +1,67 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe BigBrother::Node do
4
- describe "#current_health" do
5
- run_in_reactor
6
-
7
- it "returns its health" do
8
- StubServer.new(<<-HTTP, 0.25)
9
- HTTP/1.0 200 OK
10
- Connection: close
11
-
12
- Health: 59
13
- HTTP
14
- node = BigBrother::Node.new("127.0.0.1", 8081, "/test/status")
15
- node.current_health.should == 59
4
+
5
+ describe "#monitor" do
6
+ it "updates the weight for the node" do
7
+ BigBrother::HealthFetcher.stub(:current_health).and_return(56)
8
+ node = Factory.node(:address => '127.0.0.1')
9
+ cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
10
+
11
+ node.monitor(cluster)
12
+
13
+ @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 56")
14
+ end
15
+
16
+ it "sets the weight to 100 for each node if an up file exists" do
17
+ BigBrother::HealthFetcher.stub(:current_health).and_return(56)
18
+ node = Factory.node(:address => '127.0.0.1')
19
+ cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
20
+
21
+ BigBrother::StatusFile.new('up', 'test').create('Up for testing')
22
+
23
+ node.monitor(cluster)
24
+
25
+ @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 100")
16
26
  end
17
27
 
18
- it "returns 0 for an unknown service" do
19
- StubServer.new(<<-HTTP, 0.25)
20
- HTTP/1.0 503 Service Unavailable
21
- Connection: close
22
- HTTP
23
- node = BigBrother::Node.new("127.0.0.1", 8081, "/test/status")
24
- node.current_health.should == 0
28
+ it "sets the weight to 0 for each node if a down file exists" do
29
+ BigBrother::HealthFetcher.stub(:current_health).and_return(56)
30
+ node = Factory.node(:address => '127.0.0.1')
31
+ cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
32
+
33
+ BigBrother::StatusFile.new('down', 'test').create('Down for testing')
34
+
35
+ node.monitor(cluster)
36
+
37
+ @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 0")
25
38
  end
26
39
 
27
- it "returns 0 for an unknown DNS entry" do
28
- node = BigBrother::Node.new("junk.local", 8081, "/test/status")
29
- node.current_health.should == 0
40
+ it "does not run multiple ipvsadm commands if the health does not change" do
41
+ BigBrother::HealthFetcher.stub(:current_health).and_return(56)
42
+ node = Factory.node(:address => '127.0.0.1')
43
+ cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
44
+
45
+ node.monitor(cluster)
46
+ node.monitor(cluster)
47
+
48
+ @recording_executor.commands.should == ["ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 56"]
30
49
  end
31
50
 
32
- it "returns the health if it is passed in a header" do
33
- StubServer.new(<<-HTTP, 0.25)
34
- HTTP/1.0 200 OK
35
- Connection: close
36
- X-Health: 61
51
+ it "will run multiple ipvsadm commands if the health does change" do
52
+ BigBrother::HealthFetcher.stub(:current_health).and_return(56)
53
+ node = Factory.node(:address => '127.0.0.1')
54
+ cluster = Factory.cluster(:fwmark => 100, :nodes => [node])
55
+
56
+ node.monitor(cluster)
57
+ node.monitor(cluster)
58
+ BigBrother::HealthFetcher.stub(:current_health).and_return(41)
59
+ node.monitor(cluster)
37
60
 
38
- This part is for people.
39
- HTTP
40
- node = BigBrother::Node.new("127.0.0.1", 8081, "/test/status")
41
- node.current_health.should == 61
61
+ @recording_executor.commands.should == [
62
+ "ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 56",
63
+ "ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 41"
64
+ ]
42
65
  end
43
66
  end
44
67
  end
@@ -7,5 +7,6 @@ class RecordingExecutor
7
7
 
8
8
  def invoke(command)
9
9
  @commands << command
10
+ ["", 0]
10
11
  end
11
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: big_brother
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-15 00:00:00.000000000 Z
12
+ date: 2012-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thin
16
- requirement: &70321466649500 !ruby/object:Gem::Requirement
16
+ requirement: &70346477113640 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.3.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70321466649500
24
+ version_requirements: *70346477113640
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: async-rack
27
- requirement: &70321466648640 !ruby/object:Gem::Requirement
27
+ requirement: &70346477113100 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.5.1
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70321466648640
35
+ version_requirements: *70346477113100
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sinatra
38
- requirement: &70321466648140 !ruby/object:Gem::Requirement
38
+ requirement: &70346477112480 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '1.0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70321466648140
46
+ version_requirements: *70346477112480
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rack-fiber_pool
49
- requirement: &70321466647520 !ruby/object:Gem::Requirement
49
+ requirement: &70346477111740 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0.9'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70321466647520
57
+ version_requirements: *70346477111740
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: eventmachine
60
- requirement: &70321466646780 !ruby/object:Gem::Requirement
60
+ requirement: &70346477111000 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>'
@@ -68,10 +68,10 @@ dependencies:
68
68
  version: 1.0.0.beta.100
69
69
  type: :runtime
70
70
  prerelease: false
71
- version_requirements: *70321466646780
71
+ version_requirements: *70346477111000
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: em-http-request
74
- requirement: &70321466645620 !ruby/object:Gem::Requirement
74
+ requirement: &70346477109640 !ruby/object:Gem::Requirement
75
75
  none: false
76
76
  requirements:
77
77
  - - ~>
@@ -79,10 +79,10 @@ dependencies:
79
79
  version: '1.0'
80
80
  type: :runtime
81
81
  prerelease: false
82
- version_requirements: *70321466645620
82
+ version_requirements: *70346477109640
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: em-synchrony
85
- requirement: &70321466644700 !ruby/object:Gem::Requirement
85
+ requirement: &70346477108780 !ruby/object:Gem::Requirement
86
86
  none: false
87
87
  requirements:
88
88
  - - ~>
@@ -90,10 +90,10 @@ dependencies:
90
90
  version: '1.0'
91
91
  type: :runtime
92
92
  prerelease: false
93
- version_requirements: *70321466644700
93
+ version_requirements: *70346477108780
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: em-resolv-replace
96
- requirement: &70321466643840 !ruby/object:Gem::Requirement
96
+ requirement: &70346477107660 !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements:
99
99
  - - ~>
@@ -101,10 +101,10 @@ dependencies:
101
101
  version: '1.1'
102
102
  type: :runtime
103
103
  prerelease: false
104
- version_requirements: *70321466643840
104
+ version_requirements: *70346477107660
105
105
  - !ruby/object:Gem::Dependency
106
106
  name: em-syslog
107
- requirement: &70321466642720 !ruby/object:Gem::Requirement
107
+ requirement: &70346477106820 !ruby/object:Gem::Requirement
108
108
  none: false
109
109
  requirements:
110
110
  - - ~>
@@ -112,10 +112,10 @@ dependencies:
112
112
  version: 0.0.2
113
113
  type: :runtime
114
114
  prerelease: false
115
- version_requirements: *70321466642720
115
+ version_requirements: *70346477106820
116
116
  - !ruby/object:Gem::Dependency
117
117
  name: rspec
118
- requirement: &70321466641880 !ruby/object:Gem::Requirement
118
+ requirement: &70346477106340 !ruby/object:Gem::Requirement
119
119
  none: false
120
120
  requirements:
121
121
  - - ~>
@@ -123,10 +123,10 @@ dependencies:
123
123
  version: 2.9.0
124
124
  type: :development
125
125
  prerelease: false
126
- version_requirements: *70321466641880
126
+ version_requirements: *70346477106340
127
127
  - !ruby/object:Gem::Dependency
128
128
  name: rack-test
129
- requirement: &70321466641380 !ruby/object:Gem::Requirement
129
+ requirement: &70346477105760 !ruby/object:Gem::Requirement
130
130
  none: false
131
131
  requirements:
132
132
  - - ~>
@@ -134,10 +134,10 @@ dependencies:
134
134
  version: 0.6.1
135
135
  type: :development
136
136
  prerelease: false
137
- version_requirements: *70321466641380
137
+ version_requirements: *70346477105760
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: rake
140
- requirement: &70321466640920 !ruby/object:Gem::Requirement
140
+ requirement: &70346477105080 !ruby/object:Gem::Requirement
141
141
  none: false
142
142
  requirements:
143
143
  - - ! '>='
@@ -145,10 +145,10 @@ dependencies:
145
145
  version: '0'
146
146
  type: :development
147
147
  prerelease: false
148
- version_requirements: *70321466640920
148
+ version_requirements: *70346477105080
149
149
  - !ruby/object:Gem::Dependency
150
150
  name: rake_commit
151
- requirement: &70321466640060 !ruby/object:Gem::Requirement
151
+ requirement: &70346477104300 !ruby/object:Gem::Requirement
152
152
  none: false
153
153
  requirements:
154
154
  - - ~>
@@ -156,10 +156,10 @@ dependencies:
156
156
  version: '0.13'
157
157
  type: :development
158
158
  prerelease: false
159
- version_requirements: *70321466640060
159
+ version_requirements: *70346477104300
160
160
  - !ruby/object:Gem::Dependency
161
161
  name: vagrant
162
- requirement: &70321466639400 !ruby/object:Gem::Requirement
162
+ requirement: &70346477103860 !ruby/object:Gem::Requirement
163
163
  none: false
164
164
  requirements:
165
165
  - - ! '>='
@@ -167,7 +167,7 @@ dependencies:
167
167
  version: '0'
168
168
  type: :development
169
169
  prerelease: false
170
- version_requirements: *70321466639400
170
+ version_requirements: *70346477103860
171
171
  description: IPVS backend supervisor
172
172
  email:
173
173
  - code@getbraintree.com
@@ -180,6 +180,7 @@ files:
180
180
  - .gitignore
181
181
  - .rake_commit
182
182
  - .rvmrc
183
+ - .travis.yml
183
184
  - Gemfile
184
185
  - LICENSE
185
186
  - README.md
@@ -193,6 +194,7 @@ files:
193
194
  - lib/big_brother/cli.rb
194
195
  - lib/big_brother/cluster.rb
195
196
  - lib/big_brother/configuration.rb
197
+ - lib/big_brother/health_fetcher.rb
196
198
  - lib/big_brother/ipvs.rb
197
199
  - lib/big_brother/logger.rb
198
200
  - lib/big_brother/node.rb
@@ -207,6 +209,7 @@ files:
207
209
  - spec/big_brother/app_spec.rb
208
210
  - spec/big_brother/cluster_spec.rb
209
211
  - spec/big_brother/configuration_spec.rb
212
+ - spec/big_brother/health_fetcher_spec.rb
210
213
  - spec/big_brother/ipvs_spec.rb
211
214
  - spec/big_brother/node_spec.rb
212
215
  - spec/big_brother/shell_executor_spec.rb
@@ -252,6 +255,7 @@ test_files:
252
255
  - spec/big_brother/app_spec.rb
253
256
  - spec/big_brother/cluster_spec.rb
254
257
  - spec/big_brother/configuration_spec.rb
258
+ - spec/big_brother/health_fetcher_spec.rb
255
259
  - spec/big_brother/ipvs_spec.rb
256
260
  - spec/big_brother/node_spec.rb
257
261
  - spec/big_brother/shell_executor_spec.rb