Skip to content

Commit

Permalink
Add auto cluster configuration support
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
dn1s authored and fatpat committed Apr 20, 2021
1 parent 4297485 commit 108f1e3
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 1 deletion.
51 changes: 51 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ _Private Classes_
**Resource types**

* [`rabbitmq_binding`](#rabbitmq_binding): Native type for managing rabbitmq bindings rabbitmq_binding { 'binding 1': ensure => present, source => 'myexchange'
* [`rabbitmq_cluster`](#rabbitmq_cluster): Type to manage a rabbitmq cluster
* [`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
* [`rabbitmq_exchange`](#rabbitmq_exchange): Native type for managing rabbitmq exchanges
* [`rabbitmq_parameter`](#rabbitmq_parameter): Type for managing rabbitmq parameters
Expand Down Expand Up @@ -148,6 +149,21 @@ class { 'rabbitmq':
}
```

To create and join the cluster:
```puppet
class { 'rabbitmq':
config_cluster => true,
cluster_nodes => ['rabbit1', 'rabbit2'],
cluster => {
'name' => 'test_cluster',
'init_node' => 'hostname'
},
cluster_node_type => 'ram',
erlang_cookie => 'A_SECRET_COOKIE_STRING',
wipe_db_on_cookie_change => true,
}
```

#### Parameters

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

Default value: 'ignore'

##### `cluster`

Data type: `Hash`

If both `name` and `init_node` keys are set then the rabbitmq node is added to
a cluster named after the corresponding key by joining `init_node`.
Note: `init_node` must be included in the [`cluster_nodes`](#cluster_nodes)
parameter.

Default value: '{}'

##### `collect_statistics_interval`

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

Default value: guest

### rabbitmq_cluster

Type to manage a rabbitmq cluster

#### Properties

The following properties are available in the `rabbitmq_cluster` type.

#### `init_node`

Data type: `String`

The node to join to initiate the cluster. It is mandatory.

Default value: unset

#### `node_disc_type`

Data type: `Enum['ram', 'disc']`

Choose between disc and ram cluster nodes.

Default value: disc

### rabbitmq_erlang_cookie

Type to manage the rabbitmq erlang cookie securely
Expand Down
1 change: 1 addition & 0 deletions data/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
rabbitmq::admin_enable: true
rabbitmq::management_enable: false
rabbitmq::use_config_file_for_plugins: false
rabbitmq::cluster: {}
rabbitmq::cluster_node_type: 'disc'
rabbitmq::cluster_nodes: []
rabbitmq::config: 'rabbitmq/rabbitmq.config.erb'
Expand Down
5 changes: 5 additions & 0 deletions examples/cluster/join_cluster_and_change_name.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This sets the cluster name to `test_cluster`
# If run on another host than host1, this will join the host1's cluster
rabbitmq_cluster { 'test_cluster':
init_node => 'host1',
}
15 changes: 15 additions & 0 deletions lib/facter/rabbitmq_clustername.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Facter.add(:rabbitmq_clustername) do
setcode do
if Facter::Util::Resolution.which('rabbitmqctl')
ret = nil
cluster_status = Facter::Core::Execution.execute('rabbitmqctl -q cluster_status 2>&1')
[%r!{cluster_name,<<"(\S+)">>}!, %r!^Cluster name: (\S+)$!].each do |r|
if (data = r.match(cluster_status))
ret = data[1]
break
end
end
end
ret
end
end
40 changes: 40 additions & 0 deletions lib/puppet/provider/rabbitmq_cluster/rabbitmqctl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmq_cli'))
Puppet::Type.type(:rabbitmq_cluster).provide(
:rabbitmqctl,
parent: Puppet::Provider::RabbitmqCli
) do
confine feature: :posix

def exists?
cluster_name == @resource[:name].to_s
end

def create
storage_type = @resource[:node_disc_type].to_s

init_node = @resource[:init_node].to_s.gsub(%r{^.*@}, '')

if [Facter.value(:hostname), Facter.value(:fqdn)].include? init_node
return rabbitmqctl('set_cluster_name', @resource[:name]) unless cluster_name == resource[:name].to_s
else
rabbitmqctl('stop_app')
rabbitmqctl('join_cluster', "rabbit@#{init_node}", "--#{storage_type}")
rabbitmqctl('start_app')
end
end

def destroy
rabbitmqctl('stop_app')
rabbitmqctl('reset')
rabbitmqctl('start_app')
end

def cluster_name
cluster_status = rabbitmqctl('-q', 'cluster_status')
[%r!{cluster_name,<<"(\S+)">>}!, %r!^Cluster name: (\S+)$!].each do |r|
if (data = r.match(cluster_status))
return data[1]
end
end
end
end
50 changes: 50 additions & 0 deletions lib/puppet/type/rabbitmq_cluster.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Puppet::Type.newtype(:rabbitmq_cluster) do
desc <<-DESC
Native type for managing rabbitmq cluster
@example Configure a cluster, rabbit_cluster
rabbitmq_cluster { 'rabbit_cluster':
init_node => 'host1'
}
@example Optional parameter tags will set further rabbitmq tags like monitoring, policymaker, etc.
To set the cluster name use cluster_name.
rabbitmq_cluster { 'rabbit_cluster':
init_node => 'host1',
node_disc_type => 'ram',
}
DESC

ensurable do
defaultto(:present)
newvalue(:present) do
provider.create
end
newvalue(:absent) do
provider.destroy
end
end

autorequire(:service) { 'rabbitmq-server' }

newparam(:name, namevar: true) do
desc 'The cluster name'
end

newparam(:init_node) do
desc 'Name of which cluster node to join.'
validate do |value|
resource.validate_init_node(value)
end
end

newparam(:node_disc_type) do
desc 'Storage type of node, default disc.'
newvalues(%r{disc|ram})
defaultto('disc')
end

def validate_init_node(value)
raise ArgumentError, 'init_node must be defined' if value.empty?
end
end
17 changes: 16 additions & 1 deletion manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
#
# @example Use RabbitMQ clustering facilities
# class { 'rabbitmq':
# cluster => {
# 'name' => 'test_cluster',
# 'init_node' => 'hostname'
# },
# config_cluster => true,
# cluster_nodes => ['rabbit1', 'rabbit2'],
# cluster_node_type => 'ram',
Expand All @@ -92,6 +96,7 @@
# An array specifying authorization/authentication backend to use. Single quotes should be placed around array entries,
# ex. `['{foo, baz}', 'baz']` Defaults to [rabbit_auth_backend_internal], and if using LDAP defaults to [rabbit_auth_backend_internal,
# rabbit_auth_backend_ldap].
# @param cluster Join cluster and change name of cluster.
# @param cluster_node_type
# Choose between disc and ram nodes.
# @param cluster_nodes
Expand Down Expand Up @@ -313,6 +318,7 @@
Boolean $admin_enable = true,
Boolean $management_enable = false,
Boolean $use_config_file_for_plugins = false,
Hash $cluster = $rabbitmq::cluster,
Enum['ram', 'disc'] $cluster_node_type = 'disc',
Array $cluster_nodes = [],
String $config = 'rabbitmq/rabbitmq.config.erb',
Expand Down Expand Up @@ -525,6 +531,15 @@
Class['rabbitmq::install::rabbitmqadmin'] -> Rabbitmq_exchange<| |>
}

if $config_cluster and $cluster['name'] and $cluster['init_node'] {
create_resources('rabbitmq_cluster', {
$cluster['name'] => {
'init_node' => $cluster['init_node'],
'node_disc_type' => $cluster_node_type,
}
})
}

if ($service_restart) {
Class['rabbitmq::config'] ~> Class['rabbitmq::service']
}
Expand All @@ -535,5 +550,5 @@
-> Class['rabbitmq::management']

# Make sure the various providers have their requirements in place.
Class['rabbitmq::install'] -> Rabbitmq_plugin<| |>
Class['rabbitmq::install'] -> Rabbitmq_plugin<| |> -> Rabbitmq_cluster<| |>
}
16 changes: 16 additions & 0 deletions spec/acceptance/clustering_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
it 'runs successfully' do
pp = <<-EOS
class { 'rabbitmq':
cluster => { 'name' => 'rabbit_cluster', 'init_node' => $facts['fqdn'] },
config_cluster => true,
cluster_nodes => ['rabbit1', 'rabbit2'],
cluster_node_type => 'ram',
environment_variables => { 'RABBITMQ_USE_LONGNAME' => true },
erlang_cookie => 'TESTCOOKIE',
wipe_db_on_cookie_change => false,
}
Expand All @@ -28,9 +30,11 @@ class { 'erlang': epel_enable => true}
it 'runs successfully' do
pp = <<-EOS
class { 'rabbitmq':
cluster => { 'name' => 'rabbit_cluster', 'init_node' => $facts['fqdn'] },
config_cluster => true,
cluster_nodes => ['rabbit1', 'rabbit2'],
cluster_node_type => 'ram',
environment_variables => { 'RABBITMQ_USE_LONGNAME' => true },
erlang_cookie => 'TESTCOOKIE',
wipe_db_on_cookie_change => true,
}
Expand All @@ -55,5 +59,17 @@ class { 'erlang': epel_enable => true}
it { is_expected.to be_file }
it { is_expected.to contain 'TESTCOOKIE' }
end

describe 'rabbitmq_cluster' do
context 'cluster_name => rabbit_cluster' do
it 'cluster has name' do
shell('rabbitmqctl -q cluster_status') do |r|
expect(r.stdout).to match(%r!({cluster_name,<<"rabbit_cluster">>}|^Cluster name: rabbit_cluster$)!)
expect(r.exit_code).to be_zero
end
# rubocop:enable RSpec/MultipleExpectations
end
end
end
end
end
77 changes: 77 additions & 0 deletions spec/unit/facter/util/fact_rabbitmq_clustername_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require 'spec_helper'

describe Facter::Util::Fact do
before do
Facter.clear
end

describe 'rabbitmq_clusternam' do
context 'with value' do
it do
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(' {cluster_name,<<"monty">>},')
expect(Facter.fact(:rabbitmq_clustername).value).to eq('monty')
end
end

context 'with dashes in hostname' do
it do
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns('Cluster name: rabbit-1')
expect(Facter.fact(:rabbitmq_clustername).value).to eq('rabbit-1')
end
end

context 'with dashes in clustername/hostname' do
it do
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(' {cluster_name,<<"monty-python@rabbit-1">>},')
expect(Facter.fact(:rabbitmq_clustername).value).to eq('monty-python@rabbit-1')
end
end

context 'with quotes around node name' do
it do
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns("monty\npython\nCluster name: 'monty@rabbit-1'\nend\nof\nfile")
expect(Facter.fact(:rabbitmq_clustername).value).to eq("'monty@rabbit-1'")
end
end

context 'rabbitmq is not running' do
it do
error_string = <<-EOS
Status of node 'monty@rabbit-1' ...
Error: unable to connect to node 'monty@rabbit-1': nodedown
DIAGNOSTICS
===========
attempted to contact: ['monty@rabbit-1']
monty@rabbit-1:
* connected to epmd (port 4369) on centos-7-x64
* epmd reports: node 'rabbit' not running at all
no other nodes on centos-7-x64
* suggestion: start the node
current node details:
- node name: 'rabbitmq-cli-73@centos-7-x64'
- home dir: /var/lib/rabbitmq
- cookie hash: 6WdP0nl6d3HYqA5vTKMkIg==
EOS
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(true)
Facter::Core::Execution.expects(:execute).with('rabbitmqctl -q cluster_status 2>&1').returns(error_string)
expect(Facter.fact(:rabbitmq_clustername).value).to be_nil
end
end

context 'rabbitmqctl is not in path' do
it do
Facter::Util::Resolution.expects(:which).with('rabbitmqctl').returns(false)
expect(Facter.fact(:rabbitmq_clustername).value).to be_nil
end
end
end
end
Loading

0 comments on commit 108f1e3

Please sign in to comment.