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
  • Loading branch information
dn1s authored and fatpat committed Apr 19, 2021
1 parent 4297485 commit 901f8dd
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 1 deletion.
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
4 changes: 4 additions & 0 deletions examples/cluster/join_cluster_and_change_name.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
rabbitmq_cluster { 'test_cluster':
init_node => 'host1',
}
# This will join host2 to host1s cluster and set the cluster name to test_cluster
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 cluster_status -q') do |r|
expect(r.stdout).to match(%r{^ {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
39 changes: 39 additions & 0 deletions spec/unit/puppet/provider/rabbitmq_cluster/rabbitmqctl_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'spec_helper'

provider_class = Puppet::Type.type(:rabbitmq_cluster).provider(:rabbitmqctl)
describe provider_class do
let(:resource) do
Puppet::Type::Rabbitmq_cluster.new(
name: 'test_cluster',
init_node: 'host1'
)
end
let(:provider) { provider_class.new(resource) }

describe '#exists?' do
it {
provider.expects(:rabbitmqctl).with('-q', 'cluster_status').returns(
'Cluster name: test_cluster'
)
expect(provider.exists?).to be true
}
end

describe '#create on every other node' do
it 'joins a cluster or changes the cluster name' do
provider.expects(:rabbitmqctl).with('stop_app')
provider.expects(:rabbitmqctl).with('join_cluster', 'rabbit@host1', '--disc')
provider.expects(:rabbitmqctl).with('start_app')
provider.create
end
end

describe '#destroy' do
it 'remove cluster setup' do
provider.expects(:rabbitmqctl).with('stop_app')
provider.expects(:rabbitmqctl).with('reset')
provider.expects(:rabbitmqctl).with('start_app')
provider.destroy
end
end
end
28 changes: 28 additions & 0 deletions spec/unit/puppet/type/rabbitmq_cluster_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'spec_helper'
describe Puppet::Type.type(:rabbitmq_cluster) do
let(:rabbitmq_cluster) do
Puppet::Type.type(:rabbitmq_cluster).new(name: 'test_cluster')
end

it 'accepts a cluster name' do
rabbitmq_cluster[:name] = 'test_cluster'
expect(rabbitmq_cluster[:name]).to eq('test_cluster')
end
it 'requires a name' do
expect do
Puppet::Type.type(:rabbitmq_cluster).new({})
end.to raise_error(Puppet::Error, 'Title or name must be provided')
end
it 'check if init_node set to host1' do
rabbitmq_cluster[:init_node] = 'host1'
expect(rabbitmq_cluster[:init_node]).to eq('host1')
end
it 'try to set node_disc_type to ram' do
rabbitmq_cluster[:node_disc_type] = 'ram'
expect(rabbitmq_cluster[:node_disc_type]).to eq('ram')
end
it 'node_disc_type not set should default to disc' do
rabbitmq_cluster[:name] = 'test_cluster'
expect(rabbitmq_cluster[:node_disc_type]).to eq('disc')
end
end

0 comments on commit 901f8dd

Please sign in to comment.