Skip to content

Commit 108f1e3

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 - added related doc - based from PR#736, credits to @vchepkov
1 parent 4297485 commit 108f1e3

File tree

11 files changed

+338
-1
lines changed

11 files changed

+338
-1
lines changed

REFERENCE.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ _Private Classes_
2222
**Resource types**
2323

2424
* [`rabbitmq_binding`](#rabbitmq_binding): Native type for managing rabbitmq bindings rabbitmq_binding { 'binding 1': ensure => present, source => 'myexchange'
25+
* [`rabbitmq_cluster`](#rabbitmq_cluster): Type to manage a rabbitmq cluster
2526
* [`rabbitmq_erlang_cookie`](#rabbitmq_erlang_cookie): Type to manage the rabbitmq erlang cookie securely This is essentially a private type used by the rabbitmq::config class to manage the erlan
2627
* [`rabbitmq_exchange`](#rabbitmq_exchange): Native type for managing rabbitmq exchanges
2728
* [`rabbitmq_parameter`](#rabbitmq_parameter): Type for managing rabbitmq parameters
@@ -148,6 +149,21 @@ class { 'rabbitmq':
148149
}
149150
```
150151

152+
To create and join the cluster:
153+
```puppet
154+
class { 'rabbitmq':
155+
config_cluster => true,
156+
cluster_nodes => ['rabbit1', 'rabbit2'],
157+
cluster => {
158+
'name' => 'test_cluster',
159+
'init_node' => 'hostname'
160+
},
161+
cluster_node_type => 'ram',
162+
erlang_cookie => 'A_SECRET_COOKIE_STRING',
163+
wipe_db_on_cookie_change => true,
164+
}
165+
```
166+
151167
#### Parameters
152168

153169
The following parameters are available in the `rabbitmq` class.
@@ -213,6 +229,17 @@ Value to set for `cluster_partition_handling` RabbitMQ configuration variable.
213229

214230
Default value: 'ignore'
215231

232+
##### `cluster`
233+
234+
Data type: `Hash`
235+
236+
If both `name` and `init_node` keys are set then the rabbitmq node is added to
237+
a cluster named after the corresponding key by joining `init_node`.
238+
Note: `init_node` must be included in the [`cluster_nodes`](#cluster_nodes)
239+
parameter.
240+
241+
Default value: '{}'
242+
216243
##### `collect_statistics_interval`
217244

218245
Data type: `Optional[Integer]`
@@ -1132,6 +1159,30 @@ The password to use to connect to rabbitmq
11321159

11331160
Default value: guest
11341161

1162+
### rabbitmq_cluster
1163+
1164+
Type to manage a rabbitmq cluster
1165+
1166+
#### Properties
1167+
1168+
The following properties are available in the `rabbitmq_cluster` type.
1169+
1170+
#### `init_node`
1171+
1172+
Data type: `String`
1173+
1174+
The node to join to initiate the cluster. It is mandatory.
1175+
1176+
Default value: unset
1177+
1178+
#### `node_disc_type`
1179+
1180+
Data type: `Enum['ram', 'disc']`
1181+
1182+
Choose between disc and ram cluster nodes.
1183+
1184+
Default value: disc
1185+
11351186
### rabbitmq_erlang_cookie
11361187

11371188
Type to manage the rabbitmq erlang cookie securely

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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This sets the cluster name to `test_cluster`
2+
# If run on another host than host1, this will join the host1's cluster
3+
rabbitmq_cluster { 'test_cluster':
4+
init_node => 'host1',
5+
}

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: 16 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,15 @@
525531
Class['rabbitmq::install::rabbitmqadmin'] -> Rabbitmq_exchange<| |>
526532
}
527533

534+
if $config_cluster and $cluster['name'] and $cluster['init_node'] {
535+
create_resources('rabbitmq_cluster', {
536+
$cluster['name'] => {
537+
'init_node' => $cluster['init_node'],
538+
'node_disc_type' => $cluster_node_type,
539+
}
540+
})
541+
}
542+
528543
if ($service_restart) {
529544
Class['rabbitmq::config'] ~> Class['rabbitmq::service']
530545
}
@@ -535,5 +550,5 @@
535550
-> Class['rabbitmq::management']
536551

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

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 => { 'name' => 'rabbit_cluster', 'init_node' => $facts['fqdn'] },
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 => { 'name' => 'rabbit_cluster', 'init_node' => $facts['fqdn'] },
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 -q cluster_status') do |r|
67+
expect(r.stdout).to match(%r!({cluster_name,<<"rabbit_cluster">>}|^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

0 commit comments

Comments
 (0)