diff --git a/manifests/init.pp b/manifests/init.pp index 42b9642e..41f07263 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -596,12 +596,18 @@ # addresses $node_string = join($quorum_members, ' ') + # Define the pcs host command, this changed with 0.10.0 as per #513 + $pcs_auth_command = versioncmp($version_pcs, '0.10.0') ? { + '-1' => 'pcs cluster auth', + default => 'pcs host auth', + } + # Attempt to authorize all members. The command will return successfully # if they were already authenticated so it's safe to run every time this # is applied. # TODO - make it run only once - exec { 'pcs_cluster_auth': - command => "pcs cluster auth ${node_string} ${auth_credential_string}", + exec { 'Authorize members': + command => "${pcs_auth_command} ${node_string} ${auth_credential_string}", path => $exec_path, require => [ Service['pcsd'], @@ -624,14 +630,18 @@ } if $manage_quorum_device and $manage_pcsd_auth and $is_auth_node and $set_votequorum { + $pcs_cluster_setup_namearg = versioncmp($version_pcs, '0.10.0') ? { + '-1' => '--name', + default => '', + } # If the cluster hasn't been configured yet, temporarily configure it so - # the pcs_cluster_auth_qdevice command doesn't fail. This should generate + # the Authorize qdevice command doesn't fail. This should generate # a temporary corosync.conf which will then be overwritten exec { 'pcs_cluster_temporary': - command => "pcs cluster setup --force --name ${cluster_name} ${node_string}", + command => "pcs cluster setup --force ${pcs_cluster_setup_namearg} ${cluster_name} ${node_string}", path => $exec_path, onlyif => 'test ! -f /etc/corosync/corosync.conf', - require => Exec['pcs_cluster_auth'], + require => Exec['Authorize members'], } # We need to do this so the temporary cluster doesn't delete our authkey if $enable_secauth { @@ -644,13 +654,13 @@ $qdevice_token_check = "${token_prefix} ${quorum_device_host} ${token_suffix}" $quorum_device_password = $sensitive_quorum_device_password.unwrap - exec { 'pcs_cluster_auth_qdevice': - command => "pcs cluster auth ${quorum_device_host} -u hacluster -p ${quorum_device_password}", + exec { 'Authorize qdevice': + command => "${pcs_auth_command} ${quorum_device_host} -u hacluster -p ${quorum_device_password}", path => $exec_path, onlyif => $qdevice_token_check, require => [ Package[$package_quorum_device], - Exec['pcs_cluster_auth'], + Exec['Authorize members'], Exec['pcs_cluster_temporary'], ], } @@ -666,7 +676,7 @@ onlyif => [ 'test 0 -ne $(pcs quorum config | grep "host:" >/dev/null 2>&1; echo $?)', ], - require => Exec['pcs_cluster_auth_qdevice'], + require => Exec['Authorize qdevice'], before => File['/etc/corosync/corosync.conf'], notify => Service['corosync-qdevice'], } diff --git a/spec/classes/corosync_spec.rb b/spec/classes/corosync_spec.rb index e8ff37fa..8b2c426c 100644 --- a/spec/classes/corosync_spec.rb +++ b/spec/classes/corosync_spec.rb @@ -648,9 +648,30 @@ end end - on_supported_os.each do |os, os_facts| + on_supported_os.each do |os, facts| context "on #{os}" do - let(:facts) { os_facts } + let(:facts) do + facts.merge(os_specific_facts(facts)) + end + + auth_command = if facts[:default_provider] == 'pcs' + if Gem::Version.new(fact('pcs_version')) < Gem::Version.new('0.10.0') + 'pcs cluster auth' + else + 'pcs host auth' + end + else + 'pcs cluster auth' + end + cluster_name_arg = if facts[:default_provider] == 'pcs' + if Gem::Version.new(fact('pcs_version')) < Gem::Version.new('0.10.0') + '--name' + else + '' + end + else + '--name' + end context 'without secauth' do let(:params) do @@ -692,116 +713,113 @@ it_configures 'corosync' - context 'on RH osfamily', if: os_facts[:os]['family'] == 'RedHat' do + # Check default package installations per platform + case facts[:os]['family'] + when 'RedHat' it 'installs fence-agents-all' do is_expected.to contain_package('fence-agents-all') end + end + if facts[:default_provider] == 'pcs' it 'installs the pcs package' do is_expected.to contain_package('pcs').with( ensure: 'present', install_options: nil ) end - - it 'does manage the pacemaker service' do - is_expected.to contain_service('pacemaker').with( - ensure: 'running' + else + it 'install the crmsh package' do + is_expected.to contain_package('crmsh').with( + ensure: 'present', + install_options: nil ) end + end - # Tests for pcsd_auth management - context 'when mananging pcsd authorization' do - let(:params) do - super().merge( - manage_pcsd_service: true, - manage_pcsd_auth: true, - sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')"), - sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-password')"), - quorum_members: [ - 'node1.test.org', - 'node2.test.org', - 'node3.test.org' - ] - ) - end - let(:node) { 'node1.test.org' } + it 'does manage the pacemaker service' do + is_expected.to contain_service('pacemaker').with( + ensure: 'running' + ) + end - [ - [:sensitive_hacluster_password, %r{The hacluster password and hash must be provided to authorize nodes via pcsd}], - [:sensitive_hacluster_hash, %r{The hacluster password and hash must be provided to authorize nodes via pcsd}] - ].each do |param, expected| - context "without #{param}" do - let(:params) do - result = super() - result.delete(param) - result - end + # Tests for pcsd_auth management + context 'when mananging pcsd authorization' do + let(:params) do + super().merge( + manage_pcsd_service: true, + manage_pcsd_auth: true, + sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')"), + sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-password')"), + quorum_members: [ + 'node1.test.org', + 'node2.test.org', + 'node3.test.org' + ] + ) + end + let(:node) { 'node1.test.org' } - it { is_expected.to compile.and_raise_error(expected) } + [ + [:sensitive_hacluster_password, %r{The hacluster password and hash must be provided to authorize nodes via pcsd}], + [:sensitive_hacluster_hash, %r{The hacluster password and hash must be provided to authorize nodes via pcsd}] + ].each do |param, expected| + context "without #{param}" do + let(:params) do + result = super() + result.delete(param) + result end + + it { is_expected.to compile.and_raise_error(expected) } end + end - context 'and not the first node' do - let(:node) { 'node2.test.org' } + context 'and not the first node' do + let(:node) { 'node2.test.org' } - it 'does not perform the auth' do - is_expected.not_to contain_exec('pcs_cluster_auth') - end + it 'does not perform the auth' do + is_expected.not_to contain_exec('Authorize members') end + end + + it 'configures the hacluster user and haclient group' do + is_expected.to contain_group('haclient').that_requires('Package[pcs]') + is_expected.to contain_user('hacluster').with( + ensure: 'present', + gid: 'haclient', + password: 'some-secret-hash' + ) + end - it 'configures the hacluster user and haclient group' do - is_expected.to contain_group('haclient').that_requires('Package[pcs]') - is_expected.to contain_user('hacluster').with( - ensure: 'present', - gid: 'haclient', - password: 'some-secret-hash' + context 'with a password' do + let(:params) do + super().merge( + sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-sauce')"), + sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')") ) end - context 'with a password' do - let(:params) do - super().merge( - sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-sauce')"), - sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')") - ) - end - - it 'authorizes all nodes' do - is_expected.to contain_exec('pcs_cluster_auth').with( - command: 'pcs cluster auth node1.test.org node2.test.org node3.test.org -u hacluster -p some-secret-sauce', - path: '/sbin:/bin:/usr/sbin:/usr/bin', - require: [ - 'Service[pcsd]', - 'User[hacluster]' - ] - ) - end + it 'authorizes all nodes' do + is_expected.to contain_exec('Authorize members').with( + command: "#{auth_command} node1.test.org node2.test.org node3.test.org -u hacluster -p some-secret-sauce", + path: '/sbin:/bin:/usr/sbin:/usr/bin', + require: [ + 'Service[pcsd]', + 'User[hacluster]' + ] + ) end - - context 'using an ip baseid node list' do + context 'with pcs 0.10.0' do let(:params) do super().merge( - sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-sauce')"), - sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')"), - quorum_members: [ - '192.168.0.10', - '192.168.0.12', - '192.168.0.13' - ], - quorum_members_names: [ - 'node1.test.org', - 'node2.test.org', - 'node3.test.org' - ] + 'version_pcs' => '0.10.0' ) end - let(:facts) { override_facts(super(), networking: { ip: '192.168.0.10' }) } - - it 'match ip and auth nodes by member names' do - is_expected.to contain_exec('pcs_cluster_auth').with( - command: 'pcs cluster auth 192.168.0.10 192.168.0.12 192.168.0.13 -u hacluster -p some-secret-sauce', + it 'authorizes all nodes' do + is_expected.to contain_exec('Authorize members').with( + command: 'pcs host auth node1.test.org node2.test.org node3.test.org -u hacluster -p some-secret-sauce', path: '/sbin:/bin:/usr/sbin:/usr/bin', require: [ 'Service[pcsd]', @@ -809,231 +827,263 @@ ] ) end - - context 'where the auth-node IP is not the default IP' do - let(:facts) do - override_facts(super(), - networking: { - ip: '10.0.0.48', - interfaces: { - eth0: { - ip: '10.0.0.48' - }, - eth1: { - ip: '192.168.0.10' - } - } - }) - end - - it 'still detects that this is the auth-node' do - is_expected.to contain_exec('pcs_cluster_auth') - end - end end end - # Corosync qnet device is enabled - context 'when quorum device is configured' do + context 'using an ip baseid node list' do let(:params) do super().merge( - set_votequorum: true, - manage_pcsd_service: true, - manage_pcsd_auth: true, sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-sauce')"), sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')"), quorum_members: [ + '192.168.0.10', + '192.168.0.12', + '192.168.0.13' + ], + quorum_members_names: [ 'node1.test.org', 'node2.test.org', 'node3.test.org' - ], - cluster_name: 'cluster_test', - manage_quorum_device: true, - quorum_device_host: 'quorum1.test.org', - quorum_device_algorithm: 'ffsplit', - sensitive_quorum_device_password: RSpec::Puppet::RawString.new("Sensitive('quorum-secret-password')") + ] ) end - let(:node) { 'node1.test.org' } - - context 'without the proper arguments' do - [ - [:quorum_device_host, %r{The quorum device host must be specified!}], - [:sensitive_quorum_device_password, %r{The password for the hacluster user on the quorum device node is mandatory!}], - [:cluster_name, %r{A cluster name must be specified when a quorm device is configured!}] - ].each do |param, expected| - context "without #{param}" do - let(:params) do - result = super() - result.delete(param) - result - end - - it { is_expected.to compile.and_raise_error(expected) } - end - end + + let(:facts) { override_facts(super(), networking: { ip: '192.168.0.10' }) } + + it 'match ip and auth nodes by member names' do + is_expected.to contain_exec('Authorize members').with( + command: 'pcs cluster auth 192.168.0.10 192.168.0.12 192.168.0.13 -u hacluster -p some-secret-sauce', + path: '/sbin:/bin:/usr/sbin:/usr/bin', + require: [ + 'Service[pcsd]', + 'User[hacluster]' + ] + ) end - context 'and a cluster member is specified as the quorum device' do - let(:params) do - super().merge( - quorum_device_host: 'node3.test.org' - ) + context 'where the auth-node IP is not the default IP' do + let(:facts) do + override_facts(super(), + networking: { + ip: '10.0.0.48', + interfaces: { + eth0: { + ip: '10.0.0.48' + }, + eth1: { + ip: '192.168.0.10' + } + } + }) end - it 'fails to delpoy' do - is_expected.to raise_error( - Puppet::Error, - %r{Quorum device host cannot also be a member of the cluster!} - ) + it 'still detects that this is the auth-node' do + is_expected.to contain_exec('Authorize members') end end + end + end - context 'without managing pcsd auth' do - let(:params) do - result = super() - result.delete(:manage_pcsd_auth) - result + # Corosync qnet device is enabled + context 'when quorum device is configured' do + let(:params) do + super().merge( + set_votequorum: true, + manage_pcsd_service: true, + manage_pcsd_auth: true, + sensitive_hacluster_password: RSpec::Puppet::RawString.new("Sensitive('some-secret-sauce')"), + sensitive_hacluster_hash: RSpec::Puppet::RawString.new("Sensitive('some-secret-hash')"), + quorum_members: [ + 'node1.test.org', + 'node2.test.org', + 'node3.test.org' + ], + cluster_name: 'cluster_test', + manage_quorum_device: true, + quorum_device_host: 'quorum1.test.org', + quorum_device_algorithm: 'ffsplit', + sensitive_quorum_device_password: RSpec::Puppet::RawString.new("Sensitive('quorum-secret-password')") + ) + end + let(:node) { 'node1.test.org' } + + context 'without the proper arguments' do + [ + [:quorum_device_host, %r{The quorum device host must be specified!}], + [:sensitive_quorum_device_password, %r{The password for the hacluster user on the quorum device node is mandatory!}], + [:cluster_name, %r{A cluster name must be specified when a quorm device is configured!}] + ].each do |param, expected| + context "without #{param}" do + let(:params) do + result = super() + result.delete(param) + result + end + + it { is_expected.to compile.and_raise_error(expected) } end + end + end + + context 'and a cluster member is specified as the quorum device' do + let(:params) do + super().merge( + quorum_device_host: 'node3.test.org' + ) + end + + it 'fails to delpoy' do + is_expected.to raise_error( + Puppet::Error, + %r{Quorum device host cannot also be a member of the cluster!} + ) + end + end + + context 'without managing pcsd auth' do + let(:params) do + result = super() + result.delete(:manage_pcsd_auth) + result + end - it 'does not contain the quorum device config in corosync.conf' do - is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( - %r!quorum { - provider: corosync_votequorum + it 'does not contain the quorum device config in corosync.conf' do + is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( + %r!quorum { +provider: corosync_votequorum }$!m - ) - end + ) + end - it 'does not install the pcs quorum device package' do - is_expected.not_to contain_package('corosync-qdevice') - end + it 'does not install the pcs quorum device package' do + is_expected.not_to contain_package('corosync-qdevice') + end - it 'does not attempt to authorize or configure the quorum node' do - is_expected.not_to contain_exec('pcs_cluster_auth_qdevice') - is_expected.not_to contain_exec('pcs_cluster_add_qdevice') - end + it 'does not attempt to authorize or configure the quorum node' do + is_expected.not_to contain_exec('Authorize qdevice') + is_expected.not_to contain_exec('pcs_cluster_add_qdevice') end + end + + context 'and not the first node' do + let(:node) { 'node2.test.org' } - context 'and not the first node' do - let(:node) { 'node2.test.org' } - - it 'contains the quorum configuration' do - is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( - %r!quorum { - provider: corosync_votequorum - device { - model: net - votes: 1 - - net { - algorithm: ffsplit - host: quorum1[.]test[.]org - } + it 'contains the quorum configuration' do + is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( + %r!quorum { +provider: corosync_votequorum +device { + model: net + votes: 1 + + net { + algorithm: ffsplit + host: quorum1[.]test[.]org } +} }!m - ) - end + ) + end - it 'installs the quorum device package' do - is_expected.to contain_package('corosync-qdevice').with( - ensure: 'present' - ) - end + it 'installs the quorum device package' do + is_expected.to contain_package('corosync-qdevice').with( + ensure: 'present' + ) + end - it 'configures the qdevice service' do - is_expected.to contain_service('corosync-qdevice').with( - ensure: 'running', - enable: 'true', - require: 'Package[corosync-qdevice]', - subscribe: 'Service[corosync]' - ) - end + it 'configures the qdevice service' do + is_expected.to contain_service('corosync-qdevice').with( + ensure: 'running', + enable: 'true', + require: 'Package[corosync-qdevice]', + subscribe: 'Service[corosync]' + ) + end - it 'does not authorize or add the quorum device' do - is_expected.not_to contain_exec('pcs_cluster_auth_qdevice') - is_expected.not_to contain_exec('pcs_cluster_add_qdevice') - end + it 'does not authorize or add the quorum device' do + is_expected.not_to contain_exec('Authorize qdevice') + is_expected.not_to contain_exec('pcs_cluster_add_qdevice') end + end - context 'with all parameters' do - it 'installs the quorum device package' do - is_expected.to contain_package('corosync-qdevice').with( - ensure: 'present' - ) - end + context 'with all parameters' do + it 'installs the quorum device package' do + is_expected.to contain_package('corosync-qdevice').with( + ensure: 'present' + ) + end - it 'configures the qdevice service' do - is_expected.to contain_service('corosync-qdevice').with( - ensure: 'running', - enable: 'true', - require: 'Package[corosync-qdevice]', - subscribe: 'Service[corosync]' - ) - end + it 'configures the qdevice service' do + is_expected.to contain_service('corosync-qdevice').with( + ensure: 'running', + enable: 'true', + require: 'Package[corosync-qdevice]', + subscribe: 'Service[corosync]' + ) + end - it 'configures a temporary cluster if corosync.conf is missing' do - is_expected.to contain_exec('pcs_cluster_temporary').with( - command: 'pcs cluster setup --force --name cluster_test node1.test.org node2.test.org node3.test.org', - path: '/sbin:/bin:/usr/sbin:/usr/bin', - onlyif: 'test ! -f /etc/corosync/corosync.conf', - require: 'Exec[pcs_cluster_auth]' - ) - end + it 'configures a temporary cluster if corosync.conf is missing' do + is_expected.to contain_exec('pcs_cluster_temporary').with( + command: "pcs cluster setup --force #{cluster_name_arg} cluster_test node1.test.org node2.test.org node3.test.org", + path: '/sbin:/bin:/usr/sbin:/usr/bin', + onlyif: 'test ! -f /etc/corosync/corosync.conf', + require: "Exec['Authorize members']" + ) + end - it 'authorizes and adds the quorum device' do - is_expected.to contain_exec('pcs_cluster_auth_qdevice').with( - command: 'pcs cluster auth quorum1.test.org -u hacluster -p quorum-secret-password', - path: '/sbin:/bin:/usr/sbin:/usr/bin', - onlyif: 'test 0 -ne $(grep quorum1.test.org /var/lib/pcsd/tokens >/dev/null 2>&1; echo $?)', - require: [ - 'Package[corosync-qdevice]', - 'Exec[pcs_cluster_auth]', - 'Exec[pcs_cluster_temporary]' - ] - ) - is_expected.to contain_exec('pcs_cluster_add_qdevice').with( - command: 'pcs quorum device add model net host=quorum1.test.org algorithm=ffsplit', - path: '/sbin:/bin:/usr/sbin:/usr/bin', - onlyif: [ - 'test 0 -ne $(pcs quorum config | grep "host:" >/dev/null 2>&1; echo $?)' - ], - require: 'Exec[pcs_cluster_auth_qdevice]' - ) - end + it 'authorizes and adds the quorum device' do + is_expected.to contain_exec('Authorize qdevice').with( + command: "#{auth_command} quorum1.test.org -u hacluster -p quorum-secret-password", + path: '/sbin:/bin:/usr/sbin:/usr/bin', + onlyif: 'test 0 -ne $(grep quorum1.test.org /var/lib/pcsd/tokens >/dev/null 2>&1; echo $?)', + require: [ + 'Package[corosync-qdevice]', + "Exec['Authorize members']", + 'Exec[pcs_cluster_temporary]' + ] + ) - it 'contains the quorum configuration' do - is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( - %r!quorum { - provider: corosync_votequorum - device { - model: net - votes: 1 - - net { - algorithm: ffsplit - host: quorum1[.]test[.]org - } + is_expected.to contain_exec('pcs_cluster_add_qdevice').with( + command: 'pcs quorum device add model net host=quorum1.test.org algorithm=ffsplit', + path: '/sbin:/bin:/usr/sbin:/usr/bin', + onlyif: [ + 'test 0 -ne $(pcs quorum config | grep "host:" >/dev/null 2>&1; echo $?)' + ], + require: "Exec['Authorize qdevice']" + ) + end + it 'contains the quorum configuration' do + is_expected.to contain_file('/etc/corosync/corosync.conf').with_content( + %r!quorum { +provider: corosync_votequorum +device { + model: net + votes: 1 + + net { + algorithm: ffsplit + host: quorum1[.]test[.]org } +} }!m - ) - end + ) end + end - context 'with two nodes' do - let(:params) do - super().merge( - quorum_members: [ - 'node1.test.org', - 'node2.test.org' - ] - ) - end + context 'with two nodes' do + let(:params) do + super().merge( + quorum_members: [ + 'node1.test.org', + 'node2.test.org' + ] + ) + end - it 'does not configure two node' do - is_expected.not_to contain_file('/etc/corosync/corosync.conf').with_content( - %r{two_node: 1\n} - ) - end + it 'does not configure two node' do + is_expected.not_to contain_file('/etc/corosync/corosync.conf').with_content( + %r{two_node: 1\n} + ) end end end diff --git a/spec/spec_helper_corosync.rb b/spec/spec_helper_corosync.rb index 91355bc5..d7496fd3 100644 --- a/spec/spec_helper_corosync.rb +++ b/spec/spec_helper_corosync.rb @@ -1,5 +1,46 @@ # This file contains helpers that are specific to this module +def os_specific_facts(facts) + case facts[:os]['family'] + when 'RedHat' + default_provider = 'pcs' + pcs_version = if fact_on(host, 'os.release.major').to_i > 7 + '0.10.0' + else + '0.9.0' + end + when 'Debian' + case facts[:os]['name'] + when 'Debian' + if facts[:os]['release']['major'].to_i > 9 + default_provider = 'pcs' + pcs_version = '0.10.0' + else + default_provider = 'crm' + pcs_version = 'undef' + end + when 'Ubuntu' + if facts[:os]['release']['major'].to_i > 18 + default_provider = 'pcs' + pcs_version = '0.10.0' + elsif facts[:os]['release']['major'].to_i > 16 + default_provider = 'pcs' + pcs_version = '0.9.0' + else + default_provider = 'crm' + pcs_version = 'undef' + end + end + when 'Suse' + default_provider = 'crm' + pcs_version = 'undef' + else + default_provider = 'crm' + pcs_version = 'undef' + end + { 'default_provider' => default_provider, 'pcs_version' => pcs_version } +end + def expect_commands(patterns) command_suite = sequence('pcs commands') Array(patterns).each do |pattern|