Skip to content

Commit fa40bee

Browse files
dn1sfatpat
authored andcommitted
Add auto cluster configuration support
- added clustername fact and spec tests for it - added rabbitmq_cluster type and provider with tests
1 parent 4297485 commit fa40bee

File tree

10 files changed

+285
-1
lines changed

10 files changed

+285
-1
lines changed

data/common.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
rabbitmq::admin_enable: true
33
rabbitmq::management_enable: false
44
rabbitmq::use_config_file_for_plugins: false
5+
rabbitmq::cluster: {}
56
rabbitmq::cluster_node_type: 'disc'
67
rabbitmq::cluster_nodes: []
78
rabbitmq::config: 'rabbitmq/rabbitmq.config.erb'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
rabbitmq_cluster { 'test_cluster':
2+
init_node => 'host1',
3+
}
4+
# This will join host2 to host1s cluster and set the cluster name to test_cluster

lib/facter/rabbitmq_clustername.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Facter.add(:rabbitmq_clustername) do
2+
setcode do
3+
if Facter::Util::Resolution.which('rabbitmqctl')
4+
ret = nil
5+
cluster_status = Facter::Core::Execution.execute('rabbitmqctl -q cluster_status 2>&1')
6+
[%r!^ {cluster_name,<<"(\S+)">>},$!, %r{^Cluster name: (\S+)$}].each do |r|
7+
if (data = r.match(cluster_status))
8+
ret = data[1]
9+
break
10+
end
11+
end
12+
end
13+
ret
14+
end
15+
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmq_cli'))
2+
Puppet::Type.type(:rabbitmq_cluster).provide(
3+
:rabbitmqctl,
4+
parent: Puppet::Provider::RabbitmqCli
5+
) do
6+
confine feature: :posix
7+
8+
def exists?
9+
cluster_name == @resource[:name].to_s
10+
end
11+
12+
def create
13+
storage_type = @resource[:node_disc_type].to_s
14+
15+
init_node = @resource[:init_node].to_s.gsub(%r{^.*@}, '')
16+
17+
if [Facter.value(:hostname), Facter.value(:fqdn)].include? init_node
18+
return rabbitmqctl('set_cluster_name', @resource[:name]) unless cluster_name == resource[:name].to_s
19+
else
20+
rabbitmqctl('stop_app')
21+
rabbitmqctl('join_cluster', "rabbit@#{init_node}", "--#{storage_type}")
22+
rabbitmqctl('start_app')
23+
end
24+
end
25+
26+
def destroy
27+
rabbitmqctl('stop_app')
28+
rabbitmqctl('reset')
29+
rabbitmqctl('start_app')
30+
end
31+
32+
def cluster_name
33+
cluster_status = rabbitmqctl('-q', 'cluster_status')
34+
[%r!^ {cluster_name,<<"(\S+)">>},$!, %r{^Cluster name: (\S+)$}].each do |r|
35+
if (data = r.match(cluster_status))
36+
return data[1]
37+
end
38+
end
39+
end
40+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Puppet::Type.newtype(:rabbitmq_cluster) do
2+
desc <<-DESC
3+
Native type for managing rabbitmq cluster
4+
5+
@example Configure a cluster, rabbit_cluster
6+
rabbitmq_cluster { 'rabbit_cluster':
7+
init_node => 'host1'
8+
}
9+
10+
@example Optional parameter tags will set further rabbitmq tags like monitoring, policymaker, etc.
11+
To set the cluster name use cluster_name.
12+
rabbitmq_cluster { 'rabbit_cluster':
13+
init_node => 'host1',
14+
node_disc_type => 'ram',
15+
}
16+
DESC
17+
18+
ensurable do
19+
defaultto(:present)
20+
newvalue(:present) do
21+
provider.create
22+
end
23+
newvalue(:absent) do
24+
provider.destroy
25+
end
26+
end
27+
28+
autorequire(:service) { 'rabbitmq-server' }
29+
30+
newparam(:name, namevar: true) do
31+
desc 'The cluster name'
32+
end
33+
34+
newparam(:init_node) do
35+
desc 'Name of which cluster node to join.'
36+
validate do |value|
37+
resource.validate_init_node(value)
38+
end
39+
end
40+
41+
newparam(:node_disc_type) do
42+
desc 'Storage type of node, default disc.'
43+
newvalues(%r{disc|ram})
44+
defaultto('disc')
45+
end
46+
47+
def validate_init_node(value)
48+
raise ArgumentError, 'init_node must be defined' if value.empty?
49+
end
50+
end

manifests/init.pp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
#
7373
# @example Use RabbitMQ clustering facilities
7474
# class { 'rabbitmq':
75+
# cluster => {
76+
# 'name' => 'test_cluster',
77+
# 'init_node' => 'hostname'
78+
# },
7579
# config_cluster => true,
7680
# cluster_nodes => ['rabbit1', 'rabbit2'],
7781
# cluster_node_type => 'ram',
@@ -92,6 +96,7 @@
9296
# An array specifying authorization/authentication backend to use. Single quotes should be placed around array entries,
9397
# ex. `['{foo, baz}', 'baz']` Defaults to [rabbit_auth_backend_internal], and if using LDAP defaults to [rabbit_auth_backend_internal,
9498
# rabbit_auth_backend_ldap].
99+
# @param cluster Join cluster and change name of cluster.
95100
# @param cluster_node_type
96101
# Choose between disc and ram nodes.
97102
# @param cluster_nodes
@@ -313,6 +318,7 @@
313318
Boolean $admin_enable = true,
314319
Boolean $management_enable = false,
315320
Boolean $use_config_file_for_plugins = false,
321+
Hash $cluster = $rabbitmq::cluster,
316322
Enum['ram', 'disc'] $cluster_node_type = 'disc',
317323
Array $cluster_nodes = [],
318324
String $config = 'rabbitmq/rabbitmq.config.erb',
@@ -525,6 +531,14 @@
525531
Class['rabbitmq::install::rabbitmqadmin'] -> Rabbitmq_exchange<| |>
526532
}
527533

534+
if $config_cluster and !empty($cluster) {
535+
create_resources('rabbitmq_cluster', {
536+
$cluster['name'] => {
537+
'init_node' => $cluster['init_node'],
538+
}
539+
})
540+
}
541+
528542
if ($service_restart) {
529543
Class['rabbitmq::config'] ~> Class['rabbitmq::service']
530544
}
@@ -535,5 +549,5 @@
535549
-> Class['rabbitmq::management']
536550

537551
# Make sure the various providers have their requirements in place.
538-
Class['rabbitmq::install'] -> Rabbitmq_plugin<| |>
552+
Class['rabbitmq::install'] -> Rabbitmq_plugin<| |> -> Rabbitmq_cluster<| |>
539553
}

spec/acceptance/clustering_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
it 'runs successfully' do
66
pp = <<-EOS
77
class { 'rabbitmq':
8+
cluster => { 'rabbit_cluster' => { 'init_node' => $facts['fqdn'], 'node_disc_type' => 'ram' } },
89
config_cluster => true,
910
cluster_nodes => ['rabbit1', 'rabbit2'],
1011
cluster_node_type => 'ram',
12+
environment_variables => { 'RABBITMQ_USE_LONGNAME' => true },
1113
erlang_cookie => 'TESTCOOKIE',
1214
wipe_db_on_cookie_change => false,
1315
}
@@ -28,9 +30,11 @@ class { 'erlang': epel_enable => true}
2830
it 'runs successfully' do
2931
pp = <<-EOS
3032
class { 'rabbitmq':
33+
cluster => { 'rabbit_cluster' => { 'init_node' => $facts['fqdn'], 'node_disc_type' => 'ram' } },
3134
config_cluster => true,
3235
cluster_nodes => ['rabbit1', 'rabbit2'],
3336
cluster_node_type => 'ram',
37+
environment_variables => { 'RABBITMQ_USE_LONGNAME' => true },
3438
erlang_cookie => 'TESTCOOKIE',
3539
wipe_db_on_cookie_change => true,
3640
}
@@ -55,5 +59,17 @@ class { 'erlang': epel_enable => true}
5559
it { is_expected.to be_file }
5660
it { is_expected.to contain 'TESTCOOKIE' }
5761
end
62+
63+
describe 'rabbitmq_cluster' do
64+
context 'cluster_name => rabbit_cluster' do
65+
it 'cluster has name' do
66+
shell('rabbitmqctl cluster_status -q') do |r|
67+
expect(r.stdout).to match(%r{^ {cluster_name,<<"rabbit_cluster">>},})
68+
expect(r.exit_code).to be_zero
69+
end
70+
# rubocop:enable RSpec/MultipleExpectations
71+
end
72+
end
73+
end
5874
end
5975
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require 'spec_helper'
2+
3+
describe Facter::Util::Fact do
4+
before do
5+
Facter.clear
6+
end
7+
8+
describe 'rabbitmq_clusternam' do
9+
context 'with value' do
10+
it do
11+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
12+
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(' {cluster_name,<<"monty">>},')
13+
expect(Facter.fact(:rabbitmq_clustername).value).to eq('monty')
14+
end
15+
end
16+
17+
context 'with dashes in hostname' do
18+
it do
19+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
20+
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns('Cluster name: rabbit-1')
21+
expect(Facter.fact(:rabbitmq_clustername).value).to eq('rabbit-1')
22+
end
23+
end
24+
25+
context 'with dashes in clustername/hostname' do
26+
it do
27+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
28+
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(' {cluster_name,<<"monty-python@rabbit-1">>},')
29+
expect(Facter.fact(:rabbitmq_clustername).value).to eq('monty-python@rabbit-1')
30+
end
31+
end
32+
33+
context 'with quotes around node name' do
34+
it do
35+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
36+
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns("monty\npython\nCluster name: 'monty@rabbit-1'\nend\nof\nfile")
37+
expect(Facter.fact(:rabbitmq_clustername).value).to eq("'monty@rabbit-1'")
38+
end
39+
end
40+
41+
context 'rabbitmq is not running' do
42+
it do
43+
error_string = <<-EOS
44+
Status of node 'monty@rabbit-1' ...
45+
Error: unable to connect to node 'monty@rabbit-1': nodedown
46+
47+
DIAGNOSTICS
48+
===========
49+
50+
attempted to contact: ['monty@rabbit-1']
51+
52+
monty@rabbit-1:
53+
* connected to epmd (port 4369) on centos-7-x64
54+
* epmd reports: node 'rabbit' not running at all
55+
no other nodes on centos-7-x64
56+
* suggestion: start the node
57+
58+
current node details:
59+
- node name: 'rabbitmq-cli-73@centos-7-x64'
60+
- home dir: /var/lib/rabbitmq
61+
- cookie hash: 6WdP0nl6d3HYqA5vTKMkIg==
62+
63+
EOS
64+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
65+
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(error_string)
66+
expect(Facter.fact(:rabbitmq_clustername).value).to be_nil
67+
end
68+
end
69+
70+
context 'rabbitmqctl is not in path' do
71+
it do
72+
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(false)
73+
expect(Facter.fact(:rabbitmq_clustername).value).to be_nil
74+
end
75+
end
76+
end
77+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'spec_helper'
2+
3+
provider_class = Puppet::Type.type(:rabbitmq_cluster).provider(:rabbitmqctl)
4+
describe provider_class do
5+
let(:resource) do
6+
Puppet::Type::Rabbitmq_cluster.new(
7+
name: 'test_cluster',
8+
init_node: 'host1'
9+
)
10+
end
11+
let(:provider) { provider_class.new(resource) }
12+
13+
describe '#exists?' do
14+
it {
15+
provider.expects(:rabbitmqctl).with('-q', 'cluster_status').returns(
16+
'Cluster name: test_cluster'
17+
)
18+
expect(provider.exists?).to be true
19+
}
20+
end
21+
22+
describe '#create on every other node' do
23+
it 'joins a cluster or changes the cluster name' do
24+
provider.expects(:rabbitmqctl).with('stop_app')
25+
provider.expects(:rabbitmqctl).with('join_cluster', 'rabbit@host1', '--disc')
26+
provider.expects(:rabbitmqctl).with('start_app')
27+
provider.create
28+
end
29+
end
30+
31+
describe '#destroy' do
32+
it 'remove cluster setup' do
33+
provider.expects(:rabbitmqctl).with('stop_app')
34+
provider.expects(:rabbitmqctl).with('reset')
35+
provider.expects(:rabbitmqctl).with('start_app')
36+
provider.destroy
37+
end
38+
end
39+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'spec_helper'
2+
describe Puppet::Type.type(:rabbitmq_cluster) do
3+
let(:rabbitmq_cluster) do
4+
Puppet::Type.type(:rabbitmq_cluster).new(name: 'test_cluster')
5+
end
6+
7+
it 'accepts a cluster name' do
8+
rabbitmq_cluster[:name] = 'test_cluster'
9+
expect(rabbitmq_cluster[:name]).to eq('test_cluster')
10+
end
11+
it 'requires a name' do
12+
expect do
13+
Puppet::Type.type(:rabbitmq_cluster).new({})
14+
end.to raise_error(Puppet::Error, 'Title or name must be provided')
15+
end
16+
it 'check if init_node set to host1' do
17+
rabbitmq_cluster[:init_node] = 'host1'
18+
expect(rabbitmq_cluster[:init_node]).to eq('host1')
19+
end
20+
it 'try to set node_disc_type to ram' do
21+
rabbitmq_cluster[:node_disc_type] = 'ram'
22+
expect(rabbitmq_cluster[:node_disc_type]).to eq('ram')
23+
end
24+
it 'node_disc_type not set should default to disc' do
25+
rabbitmq_cluster[:name] = 'test_cluster'
26+
expect(rabbitmq_cluster[:node_disc_type]).to eq('disc')
27+
end
28+
end

0 commit comments

Comments
 (0)