big_brother 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +21 -0
  2. data/.rake_commit +1 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +6 -0
  8. data/big_brother.gemspec +33 -0
  9. data/bin/bigbro +6 -0
  10. data/bin/ocf_big_brother +174 -0
  11. data/config.ru +5 -0
  12. data/lib/big_brother.rb +49 -0
  13. data/lib/big_brother/app.rb +30 -0
  14. data/lib/big_brother/cli.rb +82 -0
  15. data/lib/big_brother/cluster.rb +70 -0
  16. data/lib/big_brother/configuration.rb +48 -0
  17. data/lib/big_brother/ipvs.rb +51 -0
  18. data/lib/big_brother/logger.rb +11 -0
  19. data/lib/big_brother/node.rb +30 -0
  20. data/lib/big_brother/shell_executor.rb +18 -0
  21. data/lib/big_brother/status_file.rb +26 -0
  22. data/lib/big_brother/ticker.rb +28 -0
  23. data/lib/big_brother/version.rb +3 -0
  24. data/lib/sinatra/synchrony.rb +40 -0
  25. data/lib/thin/backends/tcp_server_with_callbacks.rb +20 -0
  26. data/lib/thin/callback_rack_handler.rb +14 -0
  27. data/lib/thin/callbacks.rb +19 -0
  28. data/spec/big_brother/app_spec.rb +127 -0
  29. data/spec/big_brother/cluster_spec.rb +102 -0
  30. data/spec/big_brother/configuration_spec.rb +81 -0
  31. data/spec/big_brother/ipvs_spec.rb +26 -0
  32. data/spec/big_brother/node_spec.rb +44 -0
  33. data/spec/big_brother/shell_executor_spec.rb +21 -0
  34. data/spec/big_brother/status_file_spec.rb +39 -0
  35. data/spec/big_brother/ticker_spec.rb +60 -0
  36. data/spec/big_brother/version_spec.rb +7 -0
  37. data/spec/big_brother_spec.rb +119 -0
  38. data/spec/spec_helper.rb +57 -0
  39. data/spec/support/example_config.yml +34 -0
  40. data/spec/support/factories/cluster_factory.rb +13 -0
  41. data/spec/support/factories/node_factory.rb +9 -0
  42. data/spec/support/ipvsadm +3 -0
  43. data/spec/support/mock_session.rb +14 -0
  44. data/spec/support/null_logger.rb +7 -0
  45. data/spec/support/playback_executor.rb +13 -0
  46. data/spec/support/recording_executor.rb +11 -0
  47. data/spec/support/stub_server.rb +22 -0
  48. metadata +271 -0
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::Cluster do
4
+ describe "#start_monitoring!" do
5
+ it "marks the cluster as monitored" do
6
+ cluster = Factory.cluster
7
+ cluster.should_not be_monitored
8
+ cluster.start_monitoring!
9
+ cluster.should be_monitored
10
+ end
11
+
12
+ it "starts the service in IPVS" do
13
+ cluster = Factory.cluster(:fwmark => 100, :scheduler => 'wrr')
14
+
15
+ cluster.start_monitoring!
16
+ @recording_executor.commands.should include('ipvsadm --add-service --fwmark-service 100 --scheduler wrr')
17
+ end
18
+ end
19
+
20
+ describe "#stop_monitoring!" do
21
+ it "marks the cluster as unmonitored" do
22
+ cluster = Factory.cluster(:fwmark => 100)
23
+
24
+ cluster.start_monitoring!
25
+ cluster.should be_monitored
26
+
27
+ cluster.stop_monitoring!
28
+ cluster.should_not be_monitored
29
+ @recording_executor.commands.should include("ipvsadm --delete-service --fwmark-service 100")
30
+ end
31
+ end
32
+
33
+ describe "#needs_check?" do
34
+ it "requires the cluster to be monitored" do
35
+ cluster = Factory.cluster
36
+ cluster.needs_check?.should be_false
37
+ cluster.start_monitoring!
38
+ cluster.needs_check?.should be_true
39
+ end
40
+ end
41
+
42
+ describe "#monitor_nodes" do
43
+ it "marks the cluster as no longer requiring monitoring" do
44
+ cluster = Factory.cluster
45
+ cluster.start_monitoring!
46
+ cluster.needs_check?.should be_true
47
+ cluster.monitor_nodes
48
+ cluster.needs_check?.should be_false
49
+ end
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])
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')
79
+
80
+ 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
+ end
84
+ end
85
+
86
+ describe "#resume_monitoring!" do
87
+ it "marks the cluster as monitored" do
88
+ cluster = Factory.cluster
89
+
90
+ cluster.monitored?.should be_false
91
+ cluster.resume_monitoring!
92
+ cluster.monitored?.should be_true
93
+ end
94
+ end
95
+
96
+ describe "#to_s" do
97
+ it "is the clusters name and fwmark" do
98
+ cluster = Factory.cluster(:name => 'name', :fwmark => 100)
99
+ cluster.to_s.should == "name (100)"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::Configuration do
4
+ describe '.evaluate' do
5
+ it 'returns a hash of clusters' do
6
+ clusters = BigBrother::Configuration.evaluate(TEST_CONFIG)
7
+
8
+ clusters['test1'].check_interval.should == 1
9
+ clusters['test1'].scheduler.should == 'wrr'
10
+ clusters['test1'].fwmark.should == 1
11
+
12
+ clusters['test2'].check_interval.should == 1
13
+ clusters['test2'].scheduler.should == 'wrr'
14
+ clusters['test2'].fwmark.should == 2
15
+
16
+ clusters['test3'].check_interval.should == 1
17
+ clusters['test3'].scheduler.should == 'wrr'
18
+ clusters['test3'].fwmark.should == 3
19
+ end
20
+
21
+ it 'populates a clusters nodes' do
22
+ clusters = BigBrother::Configuration.evaluate(TEST_CONFIG)
23
+
24
+ clusters['test1'].nodes.length.should == 2
25
+
26
+ clusters['test1'].nodes[0].address == '127.0.0.1'
27
+ clusters['test1'].nodes[0].port == '9001'
28
+ clusters['test1'].nodes[0].path == '/test/valid'
29
+
30
+ clusters['test1'].nodes[1].address == '127.0.0.1'
31
+ clusters['test1'].nodes[1].port == '9002'
32
+ clusters['test1'].nodes[1].path == '/test/valid'
33
+ end
34
+ end
35
+
36
+ describe '.synchronize_with_ipvs' do
37
+ it "monitors clusters that were already monitored" do
38
+ clusters = {}
39
+ clusters['one'] = Factory.cluster(:fwmark => 1)
40
+ clusters['two'] = Factory.cluster(:fwmark => 2)
41
+ clusters['three'] = Factory.cluster(:fwmark => 3)
42
+
43
+ ipvs_state = {}
44
+ ipvs_state['1'] = ['127.0.0.1']
45
+ ipvs_state['3'] = ['127.0.0.1']
46
+
47
+ BigBrother::Configuration.synchronize_with_ipvs(clusters, ipvs_state)
48
+
49
+ clusters['one'].should be_monitored
50
+ clusters['two'].should_not be_monitored
51
+ clusters['three'].should be_monitored
52
+ end
53
+
54
+ it "does not attempt to re-add the services it was monitoring" do
55
+ clusters = { 'one' => Factory.cluster(:fwmark => 1, :nodes => [Factory.node(:address => '127.0.0.1')]) }
56
+ ipvs_state = { '1' => ['127.0.0.1'] }
57
+
58
+ BigBrother::Configuration.synchronize_with_ipvs(clusters, ipvs_state)
59
+
60
+ @recording_executor.commands.should be_empty
61
+ end
62
+
63
+ it "removes nodes that are no longer part of the cluster" do
64
+ clusters = { 'one' => Factory.cluster(:fwmark => 1, :nodes => [Factory.node(:address => '127.0.0.1')]) }
65
+ ipvs_state = { '1' => ['127.0.1.1', '127.0.0.1'] }
66
+
67
+ BigBrother::Configuration.synchronize_with_ipvs(clusters, ipvs_state)
68
+
69
+ @recording_executor.commands.last.should == "ipvsadm --delete-server --fwmark-service 1 --real-server 127.0.1.1"
70
+ end
71
+
72
+ it "adds new nodes to the cluster" do
73
+ clusters = { 'one' => Factory.cluster(:fwmark => 1, :nodes => [Factory.node(:address => '127.0.0.1'), Factory.node(:address => "127.0.1.1")]) }
74
+ ipvs_state = { '1' => ['127.0.0.1'] }
75
+
76
+ BigBrother::Configuration.synchronize_with_ipvs(clusters, ipvs_state)
77
+
78
+ @recording_executor.commands.should include("ipvsadm --add-server --fwmark-service 1 --real-server 127.0.1.1 --ipip --weight 100")
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::IPVS do
4
+ describe "#running_configuration" do
5
+ it "returns a parsed version of the running config" do
6
+ playback = PlaybackExecutor.new
7
+ playback.add_response(<<-OUTPUT, 0)
8
+ -A -f 3 -s wrr
9
+ -a -f 3 -r 10.0.1.220:80 -i -w 1
10
+ -a -f 3 -r 10.0.1.221:80 -i -w 1
11
+ -a -f 3 -r 10.0.1.222:80 -i -w 1
12
+ -A -f 1 -s wrr
13
+ -a -f 1 -r 10.0.1.223:80 -i -w 1
14
+ -a -f 1 -r 10.0.1.224:80 -i -w 1
15
+ -A -f 2 -s wrr
16
+ -a -f 2 -r 10.0.1.225:80 -i -w 1
17
+ OUTPUT
18
+ config = BigBrother::IPVS.new(playback).running_configuration
19
+
20
+ config.size.should == 3
21
+ config['3'].should == ['10.0.1.220', '10.0.1.221', '10.0.1.222']
22
+ config['1'].should == ['10.0.1.223', '10.0.1.224']
23
+ config['2'].should == ['10.0.1.225']
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
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
16
+ end
17
+
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
25
+ end
26
+
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
30
+ end
31
+
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
37
+
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
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::ShellExecutor do
4
+ describe "#invoke" do
5
+ run_in_reactor
6
+
7
+ it "runs a command" do
8
+ executor = BigBrother::ShellExecutor.new
9
+ output, exit_status = executor.invoke('echo hi')
10
+ output.should == "hi\n"
11
+ exit_status.should == 0
12
+ end
13
+
14
+ it "failed command" do
15
+ executor = BigBrother::ShellExecutor.new
16
+ output, exit_status = executor.invoke('test -e /this/isnt/here')
17
+ output.should == ""
18
+ exit_status.should == 1
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::StatusFile do
4
+ describe "create" do
5
+ it "creates a nested file" do
6
+ status_file = BigBrother::StatusFile.new("foo", "bar")
7
+ status_file.create("for testing")
8
+
9
+ status_file.exists?.should == true
10
+ end
11
+
12
+ it "creates a file" do
13
+ status_file = BigBrother::StatusFile.new("foo")
14
+ status_file.create("for testing")
15
+
16
+ status_file.exists?.should == true
17
+ end
18
+
19
+ it "writes the content" do
20
+ status_file = BigBrother::StatusFile.new("foo")
21
+ status_file.create("for testing")
22
+
23
+ status_file.content.should == "for testing"
24
+ end
25
+ end
26
+
27
+ describe "delete" do
28
+ it "removes the file" do
29
+ status_file = BigBrother::StatusFile.new("foo")
30
+ status_file.create("for testing")
31
+
32
+ status_file.exists?.should be_true
33
+
34
+ status_file.delete
35
+
36
+ status_file.exists?.should be_false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::Ticker do
4
+ describe ".schedule!" do
5
+ it "schedules the ticker to run ten times every second" do
6
+ EventMachine.should_receive(:add_periodic_timer).with(0.1)
7
+
8
+ BigBrother::Ticker.schedule!
9
+ end
10
+ end
11
+
12
+ describe ".tick" do
13
+ describe "end-to-end" do
14
+ run_in_reactor
15
+ with_litmus_server '127.0.0.1', 8081, 74
16
+ with_litmus_server public_ip_address, 8082, 76
17
+
18
+ it "monitors clusters requiring monitoring" do
19
+ BigBrother.clusters['test'] = Factory.cluster(
20
+ :fwmark => 100,
21
+ :nodes => [
22
+ Factory.node(:address => '127.0.0.1', :port => 8081),
23
+ Factory.node(:address => public_ip_address, :port => 8082)
24
+ ]
25
+ )
26
+ BigBrother.clusters['test'].start_monitoring!
27
+ @recording_executor.commands.clear
28
+
29
+ BigBrother::Ticker.tick
30
+
31
+ @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 74")
32
+ @recording_executor.commands.should include("ipvsadm --edit-server --fwmark-service 100 --real-server #{public_ip_address} --ipip --weight 76")
33
+ end
34
+
35
+ it "only monitors a cluster once in the given interval" do
36
+ BigBrother.clusters['test'] = Factory.cluster(
37
+ :fwmark => 100,
38
+ :nodes => [Factory.node(:address => '127.0.0.1', :port => 8081)]
39
+ )
40
+ BigBrother.clusters['test'].start_monitoring!
41
+ @recording_executor.commands.clear
42
+
43
+ BigBrother::Ticker.tick
44
+ BigBrother::Ticker.tick
45
+
46
+ @recording_executor.commands.should == ["ipvsadm --edit-server --fwmark-service 100 --real-server 127.0.0.1 --ipip --weight 74"]
47
+ end
48
+ end
49
+
50
+ it "monitors clusters requiring monitoring" do
51
+ BigBrother.clusters['one'] = Factory.cluster
52
+ BigBrother.clusters['two'] = Factory.cluster
53
+ BigBrother.clusters['two'].start_monitoring!
54
+
55
+ BigBrother.clusters['two'].should_receive(:monitor_nodes)
56
+
57
+ BigBrother::Ticker.tick
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother::VERSION do
4
+ it "is not nil" do
5
+ BigBrother::VERSION.should_not be_nil
6
+ end
7
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe BigBrother do
4
+ describe '.configure' do
5
+ it "reads the configuration file" do
6
+ BigBrother.configure(TEST_CONFIG)
7
+ BigBrother.clusters.size.should == 3
8
+ end
9
+
10
+ it "synchronizes the configuration with the current state of IPVS" do
11
+ playback = PlaybackExecutor.new
12
+ playback.add_response(<<-OUTPUT, 0)
13
+ -A -f 1 -s wrr
14
+ -a -f 1 -r 10.0.1.223:80 -i -w 1
15
+ -a -f 1 -r 10.0.1.224:80 -i -w 1
16
+ -A -f 2 -s wrr
17
+ -a -f 2 -r 10.0.1.225:80 -i -w 1
18
+ OUTPUT
19
+ BigBrother.ipvs = BigBrother::IPVS.new(playback)
20
+ BigBrother.configure(TEST_CONFIG)
21
+
22
+ BigBrother.clusters['test1'].should be_monitored
23
+ BigBrother.clusters['test2'].should be_monitored
24
+ end
25
+ end
26
+
27
+ describe '.reconfigure' do
28
+ run_in_reactor
29
+ around(:each) do |spec|
30
+ response_time = 1 #seconds
31
+ server = StubServer.new(<<HTTP, response_time, 9001, '127.0.0.1')
32
+ HTTP/1.0 200 OK
33
+ Connection: close
34
+
35
+ Health: 50
36
+ HTTP
37
+ spec.run
38
+ server.stop
39
+ end
40
+ it "reconfigures the clusters" do
41
+ config_file = Tempfile.new('config.yml')
42
+ File.open(config_file, 'w') do |f|
43
+ f.puts(<<-EOF)
44
+ ---
45
+ test1:
46
+ checkInterval: 1
47
+ scheduler: wrr
48
+ fwmark: 1
49
+ nodes:
50
+ - address: 127.0.0.1
51
+ port: 9001
52
+ path: /test/valid
53
+ EOF
54
+ end
55
+ BigBrother.configure(config_file)
56
+ BigBrother.start_ticker!
57
+ BigBrother.clusters['test1'].nodes.first.path.should == "/test/valid"
58
+
59
+ File.open(config_file, 'w') do |f|
60
+ f.puts(<<-EOF)
61
+ ---
62
+ test1:
63
+ checkInterval: 1
64
+ scheduler: wrr
65
+ fwmark: 1
66
+ nodes:
67
+ - address: 127.0.0.1
68
+ port: 9001
69
+ path: /test/another/path
70
+ EOF
71
+ end
72
+ BigBrother.reconfigure
73
+ BigBrother.clusters['test1'].nodes.first.path.should == "/test/another/path"
74
+ end
75
+
76
+ it "stops the ticker and reconfigures after it has finished all its ticks" do
77
+ config_file = Tempfile.new('config.yml')
78
+ File.open(config_file, 'w') do |f|
79
+ f.puts(<<-EOF)
80
+ ---
81
+ test1:
82
+ checkInterval: 1
83
+ scheduler: wrr
84
+ fwmark: 1
85
+ nodes:
86
+ - address: 127.0.0.1
87
+ port: 9001
88
+ path: /test/valid
89
+ EOF
90
+ end
91
+ BigBrother.configure(config_file)
92
+ BigBrother.clusters['test1'].start_monitoring!
93
+ @recording_executor.commands.clear
94
+
95
+ BigBrother.start_ticker!
96
+
97
+ EM::Synchrony.sleep(0.2)
98
+
99
+ File.open(config_file, 'w') do |f|
100
+ f.puts(<<-EOF)
101
+ ---
102
+ test1:
103
+ checkInterval: 1
104
+ scheduler: wrr
105
+ fwmark: 1
106
+ nodes:
107
+ - address: 127.0.0.1
108
+ port: 9001
109
+ path: /test/another/path
110
+ EOF
111
+ end
112
+ BigBrother.reconfigure
113
+ BigBrother.clusters['test1'].nodes.first.path.should == "/test/another/path"
114
+
115
+ @recording_executor.commands.first.should include("--weight 50")
116
+ @recording_executor.commands.last.should == "ipvsadm --save --numeric"
117
+ end
118
+ end
119
+ end