Fixes for pcmk_nodes and crm_node

* Use crm_node -n to get node's hostname
* Check if node is in cluster in service provider
* implement pacemaker nodes add in pcmk_nodes
* compatibility between fqdn and hostname in pcmk_nodes
* fix new node id and number generation in pcmk_nodes

Change-Id: I2e40e25df975621506141dfe78efa884255f1642
Related-Bug: 1451795
This commit is contained in:
Dmitry Ilyin 2015-05-08 22:17:19 +03:00
parent 9ba57d1728
commit 3b01fecf2e
15 changed files with 499 additions and 207 deletions

View File

@ -4,7 +4,7 @@
#
class cluster (
$internal_address = '127.0.0.1',
$unicast_addresses = undef,
$corosync_nodes = undef,
) {
#todo: move half of openstack::corosync
@ -13,7 +13,7 @@ class cluster (
if defined(Stage['corosync_setup']) {
class { 'openstack::corosync':
bind_address => $internal_address,
unicast_addresses => $unicast_addresses,
corosync_nodes => $corosync_nodes,
stage => 'corosync_setup',
corosync_version => '2',
packages => ['corosync', 'pacemaker', 'crmsh', 'pcs'],
@ -21,7 +21,7 @@ class cluster (
} else {
class { 'openstack::corosync':
bind_address => $internal_address,
unicast_addresses => $unicast_addresses,
corosync_nodes => $corosync_nodes,
corosync_version => '2',
packages => ['corosync', 'pacemaker', 'crmsh', 'pcs'],
}

View File

@ -38,11 +38,8 @@
# Use 'broadcast' to have broadcast instead
# Can be specified as an array to have multiple rings (multicast only).
#
# [*unicast_addresses*]
# An array of IP addresses that make up the cluster's members. These are
# use if you are able to use multicast on your network and instead opt for
# the udpu transport. You need a relatively recent version of Corosync to
# make this possible.
# [*corosync_nodes*]
# { node_name => { 'ip' => '...', 'id' => '...' }}
#
# [*force_online*]
# True/false parameter specifying whether to force nodes that have been put
@ -89,7 +86,7 @@ class corosync(
$port = $::corosync::params::port,
$bind_address = $::corosync::params::bind_address,
$multicast_address = $::corosync::params::multicast_address,
$unicast_addresses = $::corosync::params::unicast_addresses,
$corosync_nodes = $::corosync::params::corosync_nodes,
$force_online = $::corosync::params::force_online,
$check_standby = $::corosync::params::check_standby,
$debug = $::corosync::params::debug,
@ -131,14 +128,14 @@ class corosync(
default => $bind_address,
}
if $unicast_addresses == 'UNSET' {
if $corosync_nodes == 'UNSET' {
$corosync_conf = "${module_name}/corosync.conf.erb"
} else {
$corosync_conf = "${module_name}/corosync.conf.udpu.erb"
}
# $multicast_address is NOT required if $unicast_address is provided
if $multicast_address == 'UNSET' and $unicast_addresses == 'UNSET' {
if $multicast_address == 'UNSET' and $corosync_nodes == 'UNSET' {
fail('You must provide a value for multicast_address')
}

View File

@ -6,7 +6,7 @@ class corosync::params {
$port = '5405'
$bind_address = $::ipaddress
$multicast_address = 'UNSET'
$unicast_addresses = 'UNSET'
$corosync_nodes = 'UNSET'
$force_online = false
$check_standby = false
$debug = false

View File

@ -1,25 +1,25 @@
<% if @corosync_version == '2' %>
<% if @corosync_version == '2' -%>
compatibility: whitetank
quorum {
provider: corosync_votequorum
<% if @unicast_addresses.length == 2 %>
<% if @corosync_nodes.length == 2 -%>
two_node: 1
<% else %>
<% else -%>
two_node: 0
<% end %>
<% end -%>
}
nodelist {
<% id = 0 %>
<% @unicast_addresses.each do |node| %>
<% @corosync_nodes.each do |name, node| -%>
node {
ring0_addr: <%= node %>
nodeid: <%= id+=1 %>
# <%= name %>
ring0_addr: <%= node['ip'] %>
nodeid: <%= node['id'] %>
}
<% end %>
<% end -%>
}
<% end %>
<% end -%>
totem {
version: 2
@ -58,7 +58,7 @@ logging {
to_syslog: yes
syslog_facility: daemon
syslog_priority: info
debug: <%= scope.lookupvar('debug') ? 'on' : 'off' %>
debug: <%= @debug ? 'on' : 'off' %>
function_name: on
timestamp: on
logger_subsys {

View File

@ -1,25 +1,25 @@
<% if @corosync_version == '2' %>
<% if @corosync_version == '2' -%>
compatibility: whitetank
quorum {
provider: corosync_votequorum
<% if @unicast_addresses.length == 2 %>
<% if @corosync_nodes.length == 2 -%>
two_node: 1
<% else %>
<% else -%>
two_node: 0
<% end %>
<% end -%>
}
nodelist {
<% id = 0 %>
<% @unicast_addresses.each do |node| %>
<% @corosync_nodes.each do |name, node| -%>
node {
ring0_addr: <%= node %>
nodeid: <%= id+=1 %>
# <%= name %>
ring0_addr: <%= node['ip'] %>
nodeid: <%= node['id'] %>
}
<% end %>
<% end -%>
}
<% end %>
<% end -%>
totem {
version: 2
@ -35,11 +35,6 @@ totem {
threads: <%= @threads_real %>
transport: udpu
interface {
<% @unicast_addresses.each do |addr| -%>
member {
memberaddr: <%= addr %>
}
<% end -%>
ringnumber: 0
bindnetaddr: <%= @bind_address_real %>
mcastport: <%= @port_real %>
@ -57,7 +52,7 @@ logging {
to_syslog: yes
syslog_facility: daemon
syslog_priority: info
debug: <%= scope.lookupvar('debug') ? 'on' : 'off' %>
debug: <%= @debug ? 'on' : 'off' %>
function_name: on
timestamp: on
logger_subsys {

View File

@ -5,7 +5,7 @@ class openstack::corosync (
$stonith = false,
$quorum_policy = 'ignore',
$expected_quorum_votes = '2',
$unicast_addresses = undef,
$corosync_nodes = undef,
$corosync_version = '1',
$packages = ['corosync', 'pacemaker'],
) {
@ -52,7 +52,7 @@ class openstack::corosync (
enable_secauth => $secauth,
bind_address => $bind_address,
multicast_address => $multicast_address,
unicast_addresses => $unicast_addresses,
corosync_nodes => $corosync_nodes,
corosync_version => $corosync_version,
packages => $packages,
# NOTE(bogdando) debug is *too* verbose

View File

@ -10,10 +10,14 @@ to be used in pcmk_nodes resource.
nodes.each do |node|
fqdn = node['fqdn']
ip = node['internal_address']
uid = node['uid']
role = node['role']
next unless %w(primary-controller controller).include? role
next unless ip and fqdn
corosync_nodes[fqdn] = ip
corosync_nodes[fqdn] = {
'id' => uid,
'ip' => ip,
}
end
corosync_nodes
end

View File

@ -2,14 +2,22 @@ notice('MODULAR: cluster.pp')
$nodes = hiera('nodes')
$corosync_nodes = corosync_nodes($nodes)
$internal_address = hiera('internal_address')
class { '::cluster':
internal_address => hiera('internal_address'),
unicast_addresses => ipsort(values($corosync_nodes)),
class { 'cluster':
internal_address => $internal_address,
corosync_nodes => $corosync_nodes,
}
pcmk_nodes { 'pacemaker' :
nodes => $corosync_nodes,
add_pacemaker_nodes => false,
}
Service <| title == 'corosync' |> {
subscribe => File['/etc/corosync/service.d'],
require => File['/etc/corosync/corosync.conf'],
}
Service['corosync'] -> Pcmk_nodes<||>
Pcmk_nodes<||> -> Service<| provider == 'pacemaker' |>

View File

@ -38,8 +38,14 @@ describe 'the corosync_nodes function' do
let(:corosync_nodes_hash) do
{
"node-1.domain.tld" => "192.168.0.5",
"node-2.domain.tld" => "192.168.0.6",
"node-1.domain.tld" => {
"ip" => "192.168.0.5",
"id" => "1",
},
"node-2.domain.tld" => {
"ip" => "192.168.0.6",
"id" => "2",
}
}
end

View File

@ -276,7 +276,7 @@ class Puppet::Provider::Pacemaker_common < Puppet::Provider
cib_section_node_ids.each do |node_block|
node = attributes_to_hash node_block
next unless node['id'] and node['uname']
@node_ids.store node['uname'], node['id']
@node_ids.store node['uname'], node
end
@node_ids
end

View File

@ -4,6 +4,13 @@ Puppet::Type.type(:pcmk_nodes).provide(:ruby, :parent => Puppet::Provider::Pacem
commands 'cmapctl' => '/usr/sbin/corosync-cmapctl'
commands 'cibadmin' => '/usr/sbin/cibadmin'
commands 'crm_node' => '/usr/sbin/crm_node'
commands 'crm_attribute' => '/usr/sbin/crm_attribute'
def node_name
return @node_name if @node_name
@node_name = crm_node('-n').chomp.strip
end
def cmapctl_nodelist
cmapctl '-b', 'nodelist.node'
@ -14,7 +21,11 @@ Puppet::Type.type(:pcmk_nodes).provide(:ruby, :parent => Puppet::Provider::Pacem
debug (['cmapctl'] + args).join ' '
return
end
cmapctl *args
begin
cmapctl *args
rescue => e
info "Command failed: #{e.message}"
end
end
def cibadmin_safe(*args)
@ -22,106 +33,201 @@ Puppet::Type.type(:pcmk_nodes).provide(:ruby, :parent => Puppet::Provider::Pacem
debug (['cibadmin'] + args).join ' '
return
end
cibadmin *args
begin
cibadmin *args
rescue => e
info "Command failed: #{e.message}"
end
end
###################################
def corosync_nodes_structure
return @corosync_nodes_structure if @corosync_nodes_structure
nodes = {}
cmapctl_nodelist.split("\n").each do |line|
if line =~ %r(^nodelist\.node\.(\d+)\.nodeid\s+\(u32\)\s+=\s+(\d+))
node_number = $1
node_nodeid = $2
nodes[node_number] = {} unless nodes[node_number]
nodes[node_number]['id'] = node_nodeid
nodes[node_number]['number'] = node_number
node_name = pacemaker_node_id_to_name node_nodeid
nodes[node_number]['uname'] = node_name if node_name
end
if line =~ %r(^nodelist\.node\.(\d+)\.ring(\d+)_addr\s+\(str\)\s+=\s+(\S+))
node_number = $1
node_ring_number = $2
node_ip_addr = $3
nodes[node_number] = {} unless nodes[node_number]
ring = "ring#{node_ring_number}_addr"
nodes[node_number][ring] = node_ip_addr
end
end
@corosync_nodes_structure = {}
nodes.each do |number, node|
name = node['uname']
next unless name
@corosync_nodes_structure.store name, node
end
@corosync_nodes_structure
end
def pacemaker_nodes_structure
node_ids
end
def pacemaker_node_id_to_name(id)
pacemaker_nodes_structure.invert[id]
end
def highest_corosync_node_number
corosync_nodes_structure.inject(0) do |max, node|
number = node.last['number'].to_i
max = number if number > max
max
end
end
def highest_pacemaker_node_id
pacemaker_nodes_structure.inject(0) do |max, node|
number = node.last.to_i
max = number if number > max
max
end
end
def nodes_data
@resource[:nodes]
end
def corosync_nodes_data
@resource[:corosync_nodes]
end
def pacemaker_nodes_data
@resource[:pacemaker_nodes]
end
###################################
def corosync_nodes_state
return @corosync_nodes_data if @corosync_nodes_data
@corosync_nodes_data = {}
cmapctl_nodelist.split("\n").each do |line|
if line =~ %r(^nodelist\.node\.(\d+)\.nodeid\s+\(u32\)\s+=\s+(\d+))
node_number = $1
node_id = $2
@corosync_nodes_data[node_number] = {} unless @corosync_nodes_data[node_number]
@corosync_nodes_data[node_number]['id'] = node_id
@corosync_nodes_data[node_number]['number'] = node_number
end
if line =~ %r(^nodelist\.node\.(\d+)\.ring(\d+)_addr\s+\(str\)\s+=\s+(\S+))
node_number = $1
node_ip_addr = $3
@corosync_nodes_data[node_number] = {} unless @corosync_nodes_data[node_number]
@corosync_nodes_data[node_number]['ip'] = node_ip_addr
end
end
@corosync_nodes_data
end
def corosync_nodes_structure
return @corosync_nodes_structure if @corosync_nodes_structure
@corosync_nodes_structure = {}
corosync_nodes_state.each do |number, node|
id = node['id']
ip = node['ip']
next unless id and ip
@corosync_nodes_structure.store id, ip
end
@corosync_nodes_structure
end
def pcmk_nodes_reset
@corosync_nodes_structure = nil
@corosync_nodes_data = nil
@pacemaker_nodes_structure = nil
@node_name = nil
end
###
def change_fqdn_to_name?
begin
return false if nodes_data.keys.include? node_name
return true if nodes_data.keys.map { |fqdn| fqdn.split('.').first }.include? node_name
false
rescue
false
end
end
def change_fqdn_to_name
debug 'Changing Pacemaker node names from FQDNs to Hostnames'
nodes = {}
@resource[:nodes].each do |fqdn, ip|
name = fqdn.split('.').first
nodes.store name, ip
end
@resource[:nodes] = nodes
@resource.set_corosync_nodes
@resource.set_pacemaker_nodes
pcmk_nodes_reset
end
###
def pacemaker_nodes_structure
@pacemaker_nodes_structure = {}
node_ids.each do |name, node|
id = node['id']
next unless name and id
@pacemaker_nodes_structure.store name, id
end
@pacemaker_nodes_structure
end
def next_corosync_node_number
number = corosync_nodes_state.inject(0) do |max, node|
number = node.last['number'].to_i
max = number if number > max
max
end
number += 1
number.to_s
end
def remove_pacemaker_node(node_name)
debug "Remove pacemaker node: '#{node_name}'"
remove_pacemaker_node_record node_name
remove_pacemaker_node_state node_name
purge_node_locations node_name
end
def add_pacemaker_node(node_name)
debug "Add pacemaker node: '#{node_name}'"
node_id = nodes_data.fetch(node_name, {}).fetch 'id', nil
fail "Could not get all the data for the new pacemaker node '#{node_name}'!" unless node_id
add_pacemaker_node_record node_name, node_id
add_pacemaker_node_state node_name, node_id
end
def remove_pacemaker_node_record(node_name)
cibadmin_safe '--delete', '--obj_type', 'nodes', '--crm_xml', "<node uname='#{node_name}'/>"
cibadmin_safe '--delete', '--scope', 'nodes', '--xml-text', "<node uname='#{node_name}'/>"
end
def remove_pacemaker_node_state(node_name)
cibadmin_safe '--delete', '--obj_type', 'status', '--crm_xml', "<node_state uname='#{node_name}'/>"
cibadmin_safe '--delete', '--scope', 'status', '--xml-text', "<node_state uname='#{node_name}'/>"
end
def remove_location_constraint(constraint_id)
cibadmin_safe '--delete', '--obj_type', 'constraints', '--crm_xml', "<rsc_location id='#{constraint_id}'/>"
cibadmin_safe '--delete', '--scope', 'constraints', '--xml-text', "<rsc_location id='#{constraint_id}'/>"
end
def remove_corosync_node(node_name)
node_number = corosync_nodes_structure[node_name]['number']
fail "Could not get node_number of '#{node_name}' node!" unless node_number
def add_pacemaker_node_record(node_name, node_id)
patch = <<-eos
<diff>
<diff-added>
<cib>
<configuration>
<nodes>
<node id="#{node_id}" uname="#{node_name}" __crm_diff_marker__="added:top"/>
</nodes>
</configuration>
</cib>
</diff-added>
</diff>
eos
cibadmin_safe '--patch', '--sync-call', '--xml-text', patch
end
def add_pacemaker_node_state(node_name, node_id)
patch = <<-eos
<diff>
<diff-added>
<cib>
<status>
<node_state id="#{node_id}" uname="#{node_name}" in_ccm="true" crmd="online" crm-debug-origin="do_update_resource" join="member" expected="member" __crm_diff_marker__="added:top"/>
</status>
</cib>
</diff-added>
</diff>
eos
cibadmin_safe '--patch', '--sync-call', '--xml-text', patch
end
def remove_corosync_node(node_id)
debug "Remove corosync node: '#{node_id}'"
node_number = nil
corosync_nodes_state.each do |number, node|
node_number = number if node['id'] == node_id
end
fail "Could not get node_number of node id: '#{node_id}'!" unless node_number
remove_corosync_node_record node_number
pcmk_nodes_reset
end
def remove_corosync_node_record(node_number)
cmapctl_safe '-D', "nodelist.node.#{node_number}"
begin
cmapctl_safe '-D', "nodelist.node.#{node_number}"
rescue => e
debug "Failed: #{e.message}"
end
end
def add_corosync_node(node_name)
node_number = highest_corosync_node_number + 1
node_addr = nodes_data[node_name]
node_id = highest_pacemaker_node_id + 1
unless node_number and node_addr and node_id
fail "Could not get all the data for the new node '#{node_name}' (#{node_number}, #{node_addr}, #{node_id})"
end
add_corosync_node_record node_number, node_addr, node_id
def add_corosync_node(node_id)
debug "Add corosync node: '#{node_id}'"
node_ip = corosync_nodes_data.fetch node_id, nil
node_number = next_corosync_node_number
fail "Could not get all the data for the new corosync node '#{node_name}'!" unless node_id and node_ip and node_number
add_corosync_node_record node_number, node_ip, node_id
pcmk_nodes_reset
end
def add_corosync_node_record(node_number=nil, node_addr=nil, node_id=nil, ring_number=0)
@ -141,38 +247,55 @@ Puppet::Type.type(:pcmk_nodes).provide(:ruby, :parent => Puppet::Provider::Pacem
def corosync_nodes
debug 'Call: corosync_nodes'
corosync_nodes_structure.keys
wait_for_online
change_fqdn_to_name if change_fqdn_to_name?
debug "Return: #{corosync_nodes_structure.inspect}"
corosync_nodes_structure
end
def corosync_nodes=(expected_nodes)
debug "Call: corosync_nodes='#{expected_nodes.inspect}'"
existing_nodes = corosync_nodes_structure.keys
existing_nodes = corosync_nodes_structure
existing_nodes.each do |node|
next if expected_nodes.include? node
remove_corosync_node node
if @resource[:remove_corosync_nodes]
existing_nodes.each do |existing_node_id, existing_node_ip|
next if expected_nodes[existing_node_id] == existing_node_ip
remove_corosync_node existing_node_id
end
end
expected_nodes.each do |node|
next if existing_nodes.include? node
add_corosync_node node
if @resource[:add_corosync_nodes]
expected_nodes.each do |expected_node_id, expected_node_ip|
next if existing_nodes[expected_node_id] == expected_node_ip
add_corosync_node expected_node_id
end
end
end
##
def pacemaker_nodes
debug 'Call: pacemaker_nodes'
pacemaker_nodes_structure.keys
wait_for_online
change_fqdn_to_name if change_fqdn_to_name?
debug "Return: #{pacemaker_nodes_structure.inspect}"
pacemaker_nodes_structure
end
def pacemaker_nodes=(expected_nodes)
debug "Call: pacemaker_nodes='#{expected_nodes.inspect}'"
existing_nodes = pacemaker_nodes_structure.keys
existing_nodes = pacemaker_nodes_structure
existing_nodes.each do |node|
next if expected_nodes.include? node
remove_pacemaker_node node
if @resource[:remove_pacemaker_nodes]
existing_nodes.each do |existing_node_name, existing_node_id|
next if expected_nodes[existing_node_name] == existing_node_id
remove_pacemaker_node existing_node_name
end
end
if @resource[:add_pacemaker_nodes]
expected_nodes.each do |expected_node_name, expected_node_id|
next if existing_nodes[expected_node_name] == expected_node_id
add_pacemaker_node expected_node_name
end
end
end

View File

@ -11,21 +11,34 @@ module Puppet
defaultto false
end
newparam(:nodes, :array_matching => :all) do
desc 'Nodes data structure. Hash { "name" => "ip" }'
newparam(:nodes) do
desc <<-eos
Nodes data structure:
{
'node-1' => { 'id' => '1', 'ip' => '192.168.0.1'},
'node-2' => { 'id' => '2', 'ip' => '192.168.0.2'},
}
eos
validate do |value|
unless value.is_a? Hash and value.any?
fail 'Nodes should be a non-empty hash { "name" => "ip" }!'
fail 'Nodes should be a non-empty hash!'
end
end
end
newproperty(:corosync_nodes, :array_matching => :all) do
defaultto { @resource[:nodes].keys if @resource[:nodes] }
newproperty(:corosync_nodes) do
desc <<-eos
Corosync_nodes data structure:
{
# 'id' => 'ip',
'1' => '192.168.0.1',
'2' => '192.168.0.2',
}
eos
defaultto { @resource.set_corosync_nodes }
def insync?(is)
return false unless is.is_a? Array and should.is_a? Array
is.sort == should.sort
is == should
end
def is_to_s(is)
@ -37,12 +50,19 @@ module Puppet
end
end
newproperty(:pacemaker_nodes, :array_matching => :all) do
defaultto { @resource[:nodes].keys if @resource[:nodes] }
newproperty(:pacemaker_nodes) do
desc <<-eos
Pacemaker_nodes data structure:
{
# 'name' => 'id',
'node-1' => '1',
'node-2' => '2',
}
eos
defaultto { @resource.set_pacemaker_nodes }
def insync?(is)
return false unless is.is_a? Array and should.is_a? Array
is.sort == should.sort
is == should
end
def is_to_s(is)
@ -54,9 +74,48 @@ module Puppet
end
end
newparam(:add_pacemaker_nodes) do
defaultto true
end
newparam(:remove_pacemaker_nodes) do
defaultto true
end
newparam(:add_corosync_nodes) do
defaultto true
end
newparam(:remove_corosync_nodes) do
defaultto true
end
def validate
fail 'No corosync_nodes!' unless self[:corosync_nodes].is_a? Array and self[:corosync_nodes].any?
fail 'No pacemaker_nodes!' unless self[:pacemaker_nodes].is_a? Array and self[:pacemaker_nodes].any?
fail 'No corosync_nodes!' unless self[:corosync_nodes].is_a? Hash and self[:corosync_nodes].any?
fail 'No pacemaker_nodes!' unless self[:pacemaker_nodes].is_a? Hash and self[:pacemaker_nodes].any?
end
def set_corosync_nodes
return unless self[:nodes].respond_to? :each
corosync_nodes = {}
self[:nodes].each do |name, node|
id = node['id']
ip = node['ip']
next unless id and ip
corosync_nodes.store id, ip
end
self[:corosync_nodes] = corosync_nodes
end
def set_pacemaker_nodes
return unless self[:nodes].respond_to? :each
pacemaker_nodes = {}
self[:nodes].each do |name, node|
id = node['id']
next unless id and name
pacemaker_nodes.store name, id
end
self[:pacemaker_nodes] = pacemaker_nodes
end
end

View File

@ -226,8 +226,16 @@ describe Puppet::Provider::Pacemaker_common do
end
context 'node id functions' do
let(:node_ids) do
{
"node-1" => {"id"=>"1", "uname"=>"node-1"},
"node-2" => {"id"=>"2", "uname"=>"node-2"},
"node-3" => {"id"=>"3", "uname"=>"node-3"},
}
end
it 'can get the node ids structure' do
expect(@class.node_ids).to eq({"node-1"=>"1", "node-2"=>"2", "node-3"=>"3"})
expect(@class.node_ids).to eq node_ids
end
end

View File

@ -12,7 +12,7 @@ describe Puppet::Type.type(:pcmk_nodes).provider(:ruby) do
let(:provider) do
provider = resource.provider
if ENV['PUPPET_DEBUG']
if ENV['SPEC_PUPPET_DEBUG']
class << provider
def debug(str)
puts str
@ -22,19 +22,46 @@ describe Puppet::Type.type(:pcmk_nodes).provider(:ruby) do
provider
end
# output of corosync_cmapctl -b nodelist
let(:cmap_nodelist) do
<<-eos
####
nodelist.node.0.nodeid (u32) = 1
nodelist.node.0.ring0_addr (str) = 192.168.0.1
nodelist.node.1.nodeid (u32) = 2
nodelist.node.1.ring0_addr (str) = 192.168.0.2
nodelist.node.2.nodeid (u32) = 3
nodelist.node.2.ring0_addr (str) = 192.168.0.3
#####
eos
end
# comes from 'nodes' library method excluing unrelated data
let(:nodes_input) do
{
"node-1" => {'id' => "1", 'uname' => "node-1"},
"node-2" => {'id' => "2", 'uname' => "node-2"},
"node-3" => {'id' => "3", 'uname' => "node-3"},
}
end
# comes from 'node_ids' library method
let(:node_ids_input) do
{
"node-1" => {'id' => "1", 'uname' => "node-1"},
"node-2" => {'id' => "2", 'uname' => "node-2"},
"node-3" => {'id' => "3", 'uname' => "node-3"},
}
end
# retreived corosync nodes state
let(:corosync_nodes_state) do
{
"0"=>{ "id" => "1", "number" => "0", "ip" => "192.168.0.1" },
"1"=>{ "id" => "2", "number" => "1", "ip" => "192.168.0.2" },
"2"=>{ "id" => "3", "number" => "2", "ip" => "192.168.0.3" },
}
end
# generated existing paceamker nodes structure
let(:pacemaker_nodes_structure) do
{
"node-1" => "1",
@ -43,23 +70,17 @@ nodelist.node.2.ring0_addr (str) = 192.168.0.3
}
end
let(:nodes_states) do
{
"node-1" => {'id' => "1", 'uname' => "node-1"},
"node-2" => {'id' => "2", 'uname' => "node-2"},
"node-3" => {'id' => "3", 'uname' => "node-3"},
}
end
# generated existing corosync nodes structure
let(:corosync_nodes_structure) do
{
"node-1" => {'id' => "1", 'number' => "0", 'uname' => "node-1", 'ring0_addr' => "192.168.0.1"},
"node-2" => {'id' => "2", 'number' => "1", 'uname' => "node-2", 'ring0_addr' => "192.168.0.2"},
"node-3" => {'id' => "3", 'number' => "2", 'uname' => "node-3", 'ring0_addr' => "192.168.0.3"},
"1" => "192.168.0.1",
"2" => "192.168.0.2",
"3" => "192.168.0.3",
}
end
let(:constraint_locations) do
# comes from 'constraint_locations' library method
let(:constraint_locations_input) do
{
"p_neutron-dhcp-agent_on_node-1" => {"id" => "p_neutron-dhcp-agent_on_node-1", "node" => "node-1", "rsc" => "p_neutron-dhcp-agent", "score" => "100"},
"p_neutron-dhcp-agent_on_node-2" => {"id" => "p_neutron-dhcp-agent_on_node-2", "node" => "node-2", "rsc" => "p_neutron-dhcp-agent", "score" => "100"},
@ -70,32 +91,40 @@ nodelist.node.2.ring0_addr (str) = 192.168.0.3
}
end
# 'nodes' parameter when nodes should be added and removed
let(:nodes_data) do
{
'node-1' => "192.168.0.1",
'node-2' => "192.168.0.2",
'node-3' => "192.168.0.3",
'node-4' => "192.168.0.4",
'node-2' => { "ip" => "192.168.0.2", "id" => "2" },
'node-3' => { "ip" => "192.168.0.3", "id" => "3" },
'node-4' => { "ip" => "192.168.0.4", "id" => "4" },
}
end
let(:existing_nodes) do
%w(node-1 node-2 node-3)
end
let(:expected_nodes) do
%w(node-2 node-3 node-4)
# 'nodes' parameter when fqdn should be switched to name
let(:fqdn_nodes_data) do
data = {}
nodes_data.each do |name, node|
name = "#{name}.example.com"
data[name] = node
end
data
end
before(:each) do
provider.stubs(:cmapctl_nodelist).returns cmap_nodelist
provider.stubs(:node_ids).returns pacemaker_nodes_structure
provider.stubs(:nodes).returns nodes_states
provider.stubs(:constraint_locations).returns constraint_locations
provider.stubs(:nodes_data).returns nodes_data
provider.stubs(:node_ids).returns node_ids_input
provider.stubs(:nodes).returns nodes_input
provider.stubs(:constraint_locations).returns constraint_locations_input
provider.stubs(:node_name).returns 'node-2'
provider.stubs(:wait_for_online).returns true
end
context 'data structures' do
it 'corosync_nodes_state' do
expect(provider.corosync_nodes_state).to eq(corosync_nodes_state)
end
it 'corosync_nodes_structure' do
expect(provider.corosync_nodes_structure).to eq(corosync_nodes_structure)
end
@ -107,34 +136,60 @@ nodelist.node.2.ring0_addr (str) = 192.168.0.3
end
context 'main actions' do
before(:each) do
provider.stubs(:add_corosync_node)
provider.stubs(:remove_corosync_node)
provider.stubs(:add_pacemaker_node)
provider.stubs(:remove_pacemaker_node)
end
it 'can get corosync_nodes' do
expect(provider.corosync_nodes).to eq existing_nodes
expect(provider.corosync_nodes).to eq corosync_nodes_structure
end
it 'can get pacemaker_nodes' do
expect(provider.pacemaker_nodes).to eq existing_nodes
expect(provider.pacemaker_nodes).to eq pacemaker_nodes_structure
end
it 'removes unexpected corosync_nodes' do
provider.expects(:remove_corosync_node).with('node-1')
provider.stubs(:add_corosync_node)
provider.corosync_nodes = expected_nodes
provider.expects(:remove_corosync_node).with('1')
provider.corosync_nodes = resource[:corosync_nodes]
end
it 'adds missing corosync_nodes' do
provider.expects(:add_corosync_node).with('node-4')
provider.stubs(:remove_corosync_node)
provider.corosync_nodes = expected_nodes
provider.expects(:add_corosync_node).with('4')
provider.corosync_nodes = resource[:corosync_nodes]
end
it 'removes unexpected pacemaker_nodes' do
provider.expects(:remove_pacemaker_node).with('node-1')
provider.pacemaker_nodes = expected_nodes
provider.pacemaker_nodes = resource[:pacemaker_nodes]
end
it 'adds missing pacemaker_nodes' do
provider.expects(:add_pacemaker_node).with('node-4')
provider.pacemaker_nodes = resource[:pacemaker_nodes]
end
end
context 'when a paceamker node is removed' do
context 'when adding a new pacemaker_node' do
it 'it adds a node record' do
provider.expects(:add_pacemaker_node_record).with('node-4', '4')
provider.stubs(:add_pacemaker_node_state)
provider.add_pacemaker_node 'node-4'
end
it 'adds a node_state record' do
provider.stubs(:add_pacemaker_node_record)
provider.expects(:add_pacemaker_node_state).with('node-4', '4')
provider.add_pacemaker_node 'node-4'
end
end
context 'when removing a paceamker_node' do
before(:each) do
provider.stubs(:remove_pacemaker_node_state)
provider.stubs(:remove_pacemaker_node_record)
@ -159,24 +214,42 @@ nodelist.node.2.ring0_addr (str) = 192.168.0.3
end
context 'when adding a new corosync_node' do
it 'can determine the highest corosync node number' do
expect(provider.highest_corosync_node_number).to eq 2
it 'cat get a new free corosync nodes number' do
expect(provider.next_corosync_node_number).to eq '3'
end
it 'can determine the highest pacemaker node id' do
expect(provider.highest_pacemaker_node_id).to eq 3
end
it 'adds a new corosync node with correct parameters' do
provider.expects(:add_corosync_node_record).with 3, "192.168.0.4", 4
provider.add_corosync_node 'node-4'
it 'adds a new corosync node with the correct parameters' do
provider.expects(:add_corosync_node_record).with '3', '192.168.0.4', '4'
provider.add_corosync_node '4'
end
end
context 'when removing a corosync node' do
context 'when removing a corosync_node' do
it 'removes a node with the correct number' do
provider.expects(:remove_corosync_node_record).with '0'
provider.remove_corosync_node 'node-1'
provider.remove_corosync_node '1'
end
end
context 'FQDN and Hostname compatibility' do
let(:resource) do
Puppet::Type.type(:pcmk_nodes).new(
:name => 'paceamker',
:provider => :ruby,
:nodes => fqdn_nodes_data,
)
end
it 'can determine when the switch is needed' do
expect(provider.change_fqdn_to_name?).to eq true
end
it 'can rewrite fqdns in the node input data to the hostnames' do
provider.change_fqdn_to_name
expect(provider.resource[:nodes]).to eq nodes_data
expect(provider.corosync_nodes_structure).to eq(corosync_nodes_structure)
end
end

View File

@ -7,13 +7,32 @@ describe Puppet::Type.type(:pcmk_nodes) do
let(:nodes_data) do
{
'node-1' => "192.168.0.1",
'node-2' => "192.168.0.2",
'node-3' => "192.168.0.3",
'node-4' => "192.168.0.4",
'node-1' => { "ip" => "192.168.0.1", "id" => "1" },
'node-2' => { "ip" => "192.168.0.2", "id" => "2" },
'node-3' => { "ip" => "192.168.0.3", "id" => "3" },
'node-4' => { "ip" => "192.168.0.4", "id" => "4" },
}
end
let(:corosync_nodes_data) do
{
'1' => "192.168.0.1",
'2' => "192.168.0.2",
'3' => "192.168.0.3",
'4' => "192.168.0.4",
}
end
let(:pacemaker_nodes_data) do
{
'node-1' => "1",
'node-2' => "2",
'node-3' => "3",
'node-4' => "4",
}
end
it "should have a 'name' parameter" do
expect(subject[:name]).to eq 'pacemaker'
end
@ -23,11 +42,11 @@ describe Puppet::Type.type(:pcmk_nodes) do
end
it "should have a 'corosync_nodes' property that defaults to 'nodes' parameter" do
expect(subject[:corosync_nodes]).to eq nodes_data.keys
expect(subject[:corosync_nodes]).to eq corosync_nodes_data
end
it "should have a 'pacemaker_nodes' property that defaults to 'nodes' parameter" do
expect(subject[:pacemaker_nodes]).to eq nodes_data.keys
expect(subject[:pacemaker_nodes]).to eq pacemaker_nodes_data
end
it "should fail if nodes data is not provided or incorrect" do
@ -45,7 +64,7 @@ describe Puppet::Type.type(:pcmk_nodes) do
subject.validate
}.to raise_error
expect {
subject[:corosync_nodes] = []
subject[:corosync_nodes] = {}
subject.validate
}.to raise_error
end
@ -56,7 +75,7 @@ describe Puppet::Type.type(:pcmk_nodes) do
subject.validate
}.to raise_error
expect {
subject[:pacemaker_nodes] = []
subject[:pacemaker_nodes] = {}
subject.validate
}.to raise_error
end