rabbitmq-cluster 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +7 -0
- data/bin/rabbitmq-cluster +15 -0
- data/lib/rabbitmq/cluster.rb +9 -0
- data/lib/rabbitmq/cluster/etcd.rb +38 -0
- data/lib/rabbitmq/cluster/server.rb +117 -0
- data/lib/rabbitmq/cluster/version.rb +5 -0
- data/rabbitmq-cluster.gemspec +28 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/etcd_spec.rb +120 -0
- data/spec/unit/server_spec.rb +268 -0
- metadata +166 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Reevoo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Rabbitmq::Cluster
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'rabbitmq-cluster'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rabbitmq-cluster
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/rabbitmq-cluster/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rabbitmq/cluster'
|
2
|
+
|
3
|
+
server = RabbitMQ::Cluster::Server.new(
|
4
|
+
RabbitMQManager.new 'http://guest:guest@localhost:15672',
|
5
|
+
RabbitMQ::Cluster::Etcd.new(
|
6
|
+
Etcd::Client.connect(uris: [ENV['ETCD_HOST']])
|
7
|
+
)
|
8
|
+
)
|
9
|
+
|
10
|
+
server.start
|
11
|
+
|
12
|
+
while true do
|
13
|
+
server.synchronize
|
14
|
+
sleep 0.5
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'etcd'
|
2
|
+
|
3
|
+
module RabbitMQ::Cluster
|
4
|
+
class Etcd
|
5
|
+
attr_accessor :client
|
6
|
+
private :client
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
self.client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
def aquire_lock
|
13
|
+
sleep 1 until lock = client.update('/rabbitmq/lock', true, false)
|
14
|
+
yield if lock
|
15
|
+
raise unless client.update('/rabbitmq/lock', false, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def nodes
|
19
|
+
(client.get('/rabbitmq/nodes') || {}).values
|
20
|
+
end
|
21
|
+
|
22
|
+
def register(node_name)
|
23
|
+
key = "/rabbitmq/nodes/#{node_name}"
|
24
|
+
client.set(key, node_name) unless client.exists?(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def erlang_cookie
|
28
|
+
client.get('/rabbitmq/erlang_cookie')
|
29
|
+
end
|
30
|
+
|
31
|
+
def erlang_cookie=(erlang_cookie)
|
32
|
+
client.set(
|
33
|
+
'/rabbitmq/erlang_cookie',
|
34
|
+
erlang_cookie
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'rabbitmq_manager'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class RabbitMQManager
|
5
|
+
def aliveness_test(vhost)
|
6
|
+
@conn.get(url :'aliveness-test', vhost).body
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module RabbitMQ::Cluster
|
11
|
+
class Server
|
12
|
+
attr_accessor :client, :etcd
|
13
|
+
private :client, :etcd
|
14
|
+
|
15
|
+
def initialize(client, etcd)
|
16
|
+
self.client = client
|
17
|
+
self.etcd = etcd
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
etcd.aquire_lock do
|
22
|
+
setup_erlang_cookie
|
23
|
+
start_rabbitmq_server
|
24
|
+
save_erlang_cookie
|
25
|
+
join_cluster
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def synchronize
|
30
|
+
return if etcd.nodes.size == running_nodes.size
|
31
|
+
|
32
|
+
etcd.aquire_lock do
|
33
|
+
stopped_nodes.each do |node|
|
34
|
+
`rabbitmqctl forget_cluster_node #{node['name']}`
|
35
|
+
etcd.delete("/rabbitmq/nodes/#{node['name']}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def name
|
41
|
+
@_name ||= client.overview["cluster_name"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def up?
|
45
|
+
test_aliveness?
|
46
|
+
rescue Faraday::ConnectionFailed
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def running_nodes
|
53
|
+
client.nodes.select { |n| n["running"] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def stopped_nodes
|
57
|
+
client.nodes.select { |n| !n["running"] }
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_aliveness?
|
61
|
+
unless client.vhosts.any? {|vh| vh['name'] == 'aliveness-test' }
|
62
|
+
client.vhost_create('aliveness-test')
|
63
|
+
end
|
64
|
+
|
65
|
+
unless client.user_permissions('guest').any? {|up| up['vhost'] == 'aliveness-test' }
|
66
|
+
client.user_set_permissions('guest', 'aliveness-test', '.*', '.*', '.*')
|
67
|
+
end
|
68
|
+
|
69
|
+
JSON.parse(client.aliveness_test('aliveness-test'))['status'] == 'ok'
|
70
|
+
end
|
71
|
+
|
72
|
+
def join_cluster
|
73
|
+
if !clustered? && nodes_to_join.any?
|
74
|
+
`rabbitmqctl stop_app`
|
75
|
+
system("rabbitmqctl join_cluster #{nodes_to_join.first}")
|
76
|
+
`rabbitmqctl start_app`
|
77
|
+
end
|
78
|
+
etcd.register(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clustered?
|
82
|
+
client.nodes.size > 1
|
83
|
+
end
|
84
|
+
|
85
|
+
def nodes_to_join
|
86
|
+
etcd.nodes - [name]
|
87
|
+
end
|
88
|
+
|
89
|
+
def start_rabbitmq_server
|
90
|
+
system("/usr/sbin/rabbitmq-server &")
|
91
|
+
waits = 1
|
92
|
+
until up?
|
93
|
+
sleep waits
|
94
|
+
waits *= 2
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def erlang_cookie
|
99
|
+
IO.read('/var/lib/rabbitmq/.erlang.cookie')
|
100
|
+
end
|
101
|
+
|
102
|
+
def setup_erlang_cookie
|
103
|
+
if etcd.erlang_cookie
|
104
|
+
File.open('/var/lib/rabbitmq/.erlang.cookie', 'w') { |file| file.write etcd.erlang_cookie }
|
105
|
+
`chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie`
|
106
|
+
`chmod 400 /var/lib/rabbitmq/.erlang.cookie`
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def save_erlang_cookie
|
111
|
+
unless etcd.erlang_cookie
|
112
|
+
etcd.erlang_cookie = erlang_cookie
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rabbitmq/cluster/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rabbitmq-cluster"
|
8
|
+
spec.version = RabbitMQ::Cluster::VERSION
|
9
|
+
spec.authors = ["Ed Robinson"]
|
10
|
+
spec.email = ["ed.robinson@reevoo.com"]
|
11
|
+
spec.summary = %q{ Manages rabbitmq-server within a CoreOS cluster }
|
12
|
+
spec.description = %q{ etcd based cluster discovery for rabbitmq }
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'rabbitmq_manager'
|
22
|
+
spec.add_dependency 'etcd-rb'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RabbitMQ::Cluster::Etcd do
|
4
|
+
let(:etcd_client) { double }
|
5
|
+
subject { described_class.new(etcd_client) }
|
6
|
+
|
7
|
+
describe '#nodes' do
|
8
|
+
it 'returns the list of nodes registed in etcd' do
|
9
|
+
allow(etcd_client).to receive(:get).with('/rabbitmq/nodes').and_return(
|
10
|
+
{
|
11
|
+
"/rabbitmq/nodes/rabbit@rabbit1" => "rabbit@rabbit1",
|
12
|
+
"/rabbitmq/nodes/rabbit@rabbit2" => "rabbit@rabbit2"
|
13
|
+
}
|
14
|
+
)
|
15
|
+
expect(subject.nodes).to eq ["rabbit@rabbit1", "rabbit@rabbit2"]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns an empty array if there are no nodes registered' do
|
19
|
+
allow(etcd_client).to receive(:get).with('/rabbitmq/nodes')
|
20
|
+
expect(subject.nodes).to eq []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#register' do
|
25
|
+
let(:nodename) { 'rabbit@mynode' }
|
26
|
+
|
27
|
+
context 'the node is not yet registered' do
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(etcd_client).to receive(:exists?)
|
31
|
+
.with("/rabbitmq/nodes/#{nodename}")
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets the key in etcd' do
|
35
|
+
expect(etcd_client).to receive(:set)
|
36
|
+
.with(
|
37
|
+
"/rabbitmq/nodes/#{nodename}",
|
38
|
+
nodename
|
39
|
+
)
|
40
|
+
subject.register(nodename)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'the node is allready registered' do
|
45
|
+
before do
|
46
|
+
allow(etcd_client).to receive(:exists?)
|
47
|
+
.with("/rabbitmq/nodes/#{nodename}")
|
48
|
+
.and_return true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does nothing' do
|
52
|
+
expect(etcd_client).to_not receive(:set)
|
53
|
+
subject.register(nodename)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#erlang_cookie' do
|
59
|
+
let(:erlang_cookie) { 'afbdgCVB23423bh324h' }
|
60
|
+
before do
|
61
|
+
allow(etcd_client).to receive(:get)
|
62
|
+
.with('/rabbitmq/erlang_cookie')
|
63
|
+
.and_return(erlang_cookie)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'has a getter' do
|
67
|
+
expect(subject.erlang_cookie).to eq erlang_cookie
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'has a setter' do
|
71
|
+
expect(etcd_client).to receive(:set)
|
72
|
+
.with(
|
73
|
+
'/rabbitmq/erlang_cookie',
|
74
|
+
erlang_cookie
|
75
|
+
)
|
76
|
+
subject.erlang_cookie = erlang_cookie
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#aquire_lock' do
|
81
|
+
let(:thingy) { double(run: nil) }
|
82
|
+
before do
|
83
|
+
allow(etcd_client).to receive(:update).with('/rabbitmq/lock', false, true).and_return(true)
|
84
|
+
allow(etcd_client).to receive(:update).with('/rabbitmq/lock', true, false).and_return(true)
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'when we can get the lock' do
|
88
|
+
it 'runs the code' do
|
89
|
+
expect(etcd_client).to receive(:update).with('/rabbitmq/lock', true, false).and_return(true)
|
90
|
+
expect(thingy).to receive(:run)
|
91
|
+
|
92
|
+
subject.aquire_lock { thingy.run }
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'gives the lock back when its done' do
|
96
|
+
expect(etcd_client).to receive(:update).with('/rabbitmq/lock', false, true).and_return(true)
|
97
|
+
|
98
|
+
subject.aquire_lock { thingy.run }
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'gets angry if something odd happens' do
|
102
|
+
allow(etcd_client).to receive(:update).with('/rabbitmq/lock', false, true).and_return(false)
|
103
|
+
|
104
|
+
expect { subject.aquire_lock { thingy.run } }.to raise_error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when we can't get the lock" do
|
109
|
+
it 'retries till the lock can be aquired' do
|
110
|
+
expect(etcd_client).to receive(:update)
|
111
|
+
.with('/rabbitmq/lock', true, false)
|
112
|
+
.at_least(3).times
|
113
|
+
.and_return(false, false, true)
|
114
|
+
expect(thingy).to receive(:run)
|
115
|
+
|
116
|
+
subject.aquire_lock { thingy.run }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RabbitMQ::Cluster::Server do
|
4
|
+
let(:client) { double }
|
5
|
+
let(:etcd) { FakeEtcd.new }
|
6
|
+
|
7
|
+
subject { described_class.new(client, etcd) }
|
8
|
+
|
9
|
+
describe '#name' do
|
10
|
+
let(:client) do
|
11
|
+
double(
|
12
|
+
:rmq,
|
13
|
+
overview: { 'cluster_name' => 'rabbit@awesome-node' }
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'gets the name from RabbitMQ' do
|
18
|
+
expect(subject.name).to eq 'rabbit@awesome-node'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is cached so we can still access the name when the rabbitmq management api is down' do
|
22
|
+
subject.name
|
23
|
+
allow(client).to receive(:overview).and_raise
|
24
|
+
expect(subject.name).to eq 'rabbit@awesome-node'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'up?' do
|
29
|
+
let(:client) { double(:client).as_null_object }
|
30
|
+
|
31
|
+
before do
|
32
|
+
allow(client).to receive(:aliveness_test).and_return('{ "status":"ok" }')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns false if we cannot connect to the management api' do
|
36
|
+
allow(client).to receive(:aliveness_test).and_raise(Faraday::ConnectionFailed, "error")
|
37
|
+
expect(subject).to_not be_up
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'we can can connect to the management api' do
|
41
|
+
context 'and the test vhost is allready setup' do
|
42
|
+
before do
|
43
|
+
allow(client).to receive(:vhosts).and_return([{'name' => 'aliveness-test'}])
|
44
|
+
allow(client).to receive(:user_permissions).and_return([{'vhost' => 'aliveness-test'}])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'wont set up the vhost' do
|
48
|
+
expect(client).to_not receive(:vhost_create)
|
49
|
+
expect(client).to_not receive(:user_set_permissions)
|
50
|
+
subject.up?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'and the test vhost is not setup' do
|
55
|
+
|
56
|
+
before do
|
57
|
+
allow(client).to receive(:vhosts).and_return([])
|
58
|
+
allow(client).to receive(:user_permissions).and_return([])
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'will set up the vhost' do
|
62
|
+
expect(client).to receive(:vhost_create).with('aliveness-test')
|
63
|
+
expect(client).to receive(:user_set_permissions).with('guest', 'aliveness-test', '.*', '.*', '.*')
|
64
|
+
subject.up?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'the aliveness test is working' do
|
69
|
+
specify { expect(subject).to be_up }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'the aliveness test is not working' do
|
73
|
+
before do
|
74
|
+
allow(client).to receive(:aliveness_test).and_return('{ "status":"borked" }')
|
75
|
+
end
|
76
|
+
|
77
|
+
specify { expect(subject).to_not be_up }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'starting the server' do
|
83
|
+
let(:client) { double(:client).as_null_object }
|
84
|
+
|
85
|
+
before do
|
86
|
+
allow(subject).to receive(:system)
|
87
|
+
allow(client).to receive(:aliveness_test).and_return('{ "status":"ok" }')
|
88
|
+
allow(File).to receive(:open)
|
89
|
+
allow(IO).to receive(:read)
|
90
|
+
allow(subject).to receive(:"`")
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'shells out to start rabbitmq-server' do
|
94
|
+
expect(subject).to receive(:system).with("/usr/sbin/rabbitmq-server &")
|
95
|
+
subject.start
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'waiting for the server to start' do
|
99
|
+
it 'waits until the server has started' do
|
100
|
+
expect(subject).to receive(:up?).twice.and_return(false, true)
|
101
|
+
subject.start
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'joining the cluster' do
|
106
|
+
context 'with no nodes in etcd' do
|
107
|
+
it 'does nothing' do
|
108
|
+
expect(subject).to_not receive(:"`")
|
109
|
+
subject.start
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'with some nodes allready in etcd' do
|
114
|
+
before do
|
115
|
+
allow(client).to receive(:nodes)
|
116
|
+
.and_return([{}])
|
117
|
+
etcd.register('rabbit@node1')
|
118
|
+
etcd.register('rabbit@node2')
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'allready in a cluster' do
|
122
|
+
before do
|
123
|
+
allow(client).to receive(:nodes)
|
124
|
+
.and_return([{},{}])
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'does nothing' do
|
128
|
+
expect(subject).to_not receive(:"`")
|
129
|
+
subject.start
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'registers itself' do
|
133
|
+
allow(client).to receive(:overview).and_return('cluster_name' => 'rabbit@this_node')
|
134
|
+
subject.start
|
135
|
+
expect(etcd.nodes).to include('rabbit@this_node')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'tries to join the cluster' do
|
140
|
+
expect(subject).to receive(:system).with("rabbitmqctl join_cluster rabbit@node1")
|
141
|
+
subject.start
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'stops the management app before clustering' do
|
145
|
+
expect(subject).to receive(:"`")
|
146
|
+
.with('rabbitmqctl stop_app')
|
147
|
+
expect(subject).to receive(:"`")
|
148
|
+
.with('rabbitmqctl start_app')
|
149
|
+
subject.start
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'does not try to cluster with itself' do
|
153
|
+
allow(client).to receive(:overview).and_return('cluster_name' => 'rabbit@node1')
|
154
|
+
expect(subject).to receive(:system).with("rabbitmqctl join_cluster rabbit@node2")
|
155
|
+
subject.start
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'registers itself' do
|
159
|
+
allow(client).to receive(:overview).and_return('cluster_name' => 'rabbit@this_node')
|
160
|
+
subject.start
|
161
|
+
expect(etcd.nodes).to include('rabbit@this_node')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'with an erlang cookie in etcd' do
|
167
|
+
|
168
|
+
before do
|
169
|
+
allow(etcd).to receive(:erlang_cookie).and_return('NummmNugnnmNuyum')
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'sets up the erlang cookie' do
|
173
|
+
expect(File).to receive(:open).with('/var/lib/rabbitmq/.erlang.cookie', 'w')
|
174
|
+
subject.start
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'sets up the ownership and permissions of the erlang cooke' do
|
178
|
+
expect(subject).to receive(:"`")
|
179
|
+
.with("chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie")
|
180
|
+
expect(subject).to receive(:"`")
|
181
|
+
.with("chmod 400 /var/lib/rabbitmq/.erlang.cookie")
|
182
|
+
subject.start
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'when there was no erlang cookie in etcd' do
|
187
|
+
before do
|
188
|
+
allow(IO).to receive(:read)
|
189
|
+
.with('/var/lib/rabbitmq/.erlang.cookie')
|
190
|
+
.and_return('ErLAnGCOookie')
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'saves the on disk cookie to etcd' do
|
194
|
+
subject.start
|
195
|
+
expect(etcd.erlang_cookie).to eq 'ErLAnGCOookie'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe '#synchronize' do
|
201
|
+
context 'etcd has more nodes than are running' do
|
202
|
+
before do
|
203
|
+
etcd.register('rabbit@node1')
|
204
|
+
etcd.register('rabbit@node2')
|
205
|
+
allow(client).to receive(:nodes)
|
206
|
+
.and_return([
|
207
|
+
{"name" => "rabbit@node1", "running" => true},
|
208
|
+
{"name" => "rabbit@node2", "running" => false}
|
209
|
+
])
|
210
|
+
allow(subject).to receive(:"`")
|
211
|
+
allow(etcd).to receive(:delete)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'aquires the lock before doing anything' do
|
215
|
+
expect(etcd).to receive(:aquire_lock).and_call_original
|
216
|
+
subject.synchronize
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'removes the stopped node from the cluster' do
|
220
|
+
expect(subject).to receive(:"`")
|
221
|
+
.with('rabbitmqctl forget_cluster_node rabbit@node2')
|
222
|
+
subject.synchronize
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'removes the stopped node from etcd' do
|
226
|
+
expect(etcd).to receive(:delete)
|
227
|
+
.with('/rabbitmq/nodes/rabbit@node2')
|
228
|
+
subject.synchronize
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'etcd has the same nodes as are running' do
|
233
|
+
before do
|
234
|
+
etcd.register('rabbit@node1')
|
235
|
+
etcd.register('rabbit@node2')
|
236
|
+
allow(client).to receive(:nodes)
|
237
|
+
.and_return([
|
238
|
+
{"name" => "rabbit@node1", "running" => true},
|
239
|
+
{"name" => "rabbit@node2", "running" => true}
|
240
|
+
])
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'does nothing' do
|
244
|
+
expect(etcd).to_not receive(:aquire_lock)
|
245
|
+
subject.synchronize
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
class FakeEtcd
|
253
|
+
|
254
|
+
attr_accessor :erlang_cookie, :nodes
|
255
|
+
|
256
|
+
def initialize
|
257
|
+
self.nodes = []
|
258
|
+
end
|
259
|
+
|
260
|
+
def aquire_lock
|
261
|
+
yield
|
262
|
+
end
|
263
|
+
|
264
|
+
def register(node)
|
265
|
+
nodes << node
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabbitmq-cluster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ed Robinson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-07-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rabbitmq_manager
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: etcd-rb
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.5'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.5'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: pry
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: ! ' etcd based cluster discovery for rabbitmq '
|
111
|
+
email:
|
112
|
+
- ed.robinson@reevoo.com
|
113
|
+
executables:
|
114
|
+
- rabbitmq-cluster
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- bin/rabbitmq-cluster
|
124
|
+
- lib/rabbitmq/cluster.rb
|
125
|
+
- lib/rabbitmq/cluster/etcd.rb
|
126
|
+
- lib/rabbitmq/cluster/server.rb
|
127
|
+
- lib/rabbitmq/cluster/version.rb
|
128
|
+
- rabbitmq-cluster.gemspec
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/unit/etcd_spec.rb
|
131
|
+
- spec/unit/server_spec.rb
|
132
|
+
homepage: ''
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
hash: -2992029979682835866
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
hash: -2992029979682835866
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 1.8.25
|
160
|
+
signing_key:
|
161
|
+
specification_version: 3
|
162
|
+
summary: Manages rabbitmq-server within a CoreOS cluster
|
163
|
+
test_files:
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/unit/etcd_spec.rb
|
166
|
+
- spec/unit/server_spec.rb
|