Fix idempotency of cs_resource

* insync? to drop status metadata from checks
* code cleanup
* fix rspec for cs_resource type
* switch location add implementation from pcs
  to cibadmin --patch to solve problems with
  cib changes not being synced to other nodes

related-blueprint: pacemaker-improvements
Related-Bug: 1391599
Related-Bug: 1390480
Related-Bug: 1396481

Change-Id: I5410b91ea01fc8c6805de6becdf0800d0d486188
Signed-off-by: Sergii Golovatiuk <sgolovatiuk@mirantis.com>
This commit is contained in:
Dmitry Ilyin 2014-11-27 18:11:16 +03:00
parent 27e4449ad4
commit c045ce3078
19 changed files with 312 additions and 288 deletions

View File

@ -7,7 +7,7 @@ define cluster::corosync::cs_service (
$service_name,
$service_title = undef, # Title of Service, that been mangled for pacemakering
$package_name = undef,
$csr_multistate_hash = undef,
$csr_complex_type = undef,
$csr_ms_metadata = undef,
$csr_parameters = undef,
$csr_metadata = undef,
@ -38,7 +38,7 @@ define cluster::corosync::cs_service (
primitive_class => 'ocf',
provided_by => 'mirantis',
primitive_type => $ocf_script,
multistate_hash => $csr_multistate_hash,
complex_type => $csr_complex_type,
ms_metadata => $csr_ms_metadata,
parameters => $csr_parameters,
metadata => $csr_metadata,

View File

@ -25,9 +25,7 @@ class cluster::haproxy_ocf (
primitive_class => 'ocf',
provided_by => 'mirantis',
primitive_type => 'ns_haproxy',
multistate_hash => {
'type' => 'clone',
},
complex_type => 'clone',
ms_metadata => {
'interleave' => true,
},

View File

@ -21,11 +21,11 @@ class cluster::neutron::dhcp (
value => $agents_per_net
}
$csr_metadata = undef
$csr_multistate_hash = { 'type' => 'clone' }
$csr_complex_type = 'clone'
$csr_ms_metadata = { 'interleave' => 'true' }
} else {
$csr_metadata = { 'resource-stickiness' => '1' }
$csr_multistate_hash = undef
$csr_complex_type = undef
$csr_ms_metadata = undef
}
@ -46,7 +46,7 @@ class cluster::neutron::dhcp (
'amqp_server_port' => $amqp_server_port
},
csr_metadata => $csr_metadata,
csr_multistate_hash => $csr_multistate_hash,
csr_complex_type => $csr_complex_type,
csr_ms_metadata => $csr_ms_metadata,
csr_mon_intr => '20',
csr_mon_timeout => '10',

View File

@ -23,11 +23,11 @@ define cluster::neutron::l3 (
value => true
}
$csr_metadata = undef
$csr_multistate_hash = { 'type' => 'clone' }
$csr_complex_type = 'clone'
$csr_ms_metadata = { 'interleave' => 'true' }
} else {
$csr_metadata = { 'resource-stickiness' => '1' }
$csr_multistate_hash = undef
$csr_complex_type = undef
$csr_ms_metadata = undef
}
@ -50,7 +50,7 @@ define cluster::neutron::l3 (
'multiple_agents' => $multiple_agents
},
csr_metadata => $csr_metadata,
csr_multistate_hash => $csr_multistate_hash,
csr_complex_type => $csr_complex_type,
csr_ms_metadata => $csr_ms_metadata,
csr_mon_intr => '20',
csr_mon_timeout => '10',

View File

@ -14,7 +14,7 @@ class cluster::neutron::metadata (
#TODO (bogdando) move to extras ha wrappers
cluster::corosync::cs_service {'neutron-metadata-agent':
ocf_script => 'neutron-agent-metadata',
csr_multistate_hash => { 'type' => 'clone' },
csr_complex_type => 'clone',
csr_ms_metadata => { 'interleave' => 'true' },
csr_mon_intr => '60',
csr_mon_timeout => '10',

View File

@ -14,7 +14,7 @@ class cluster::neutron::ovs (
cluster::corosync::cs_service {'ovs':
ocf_script => 'neutron-agent-ovs',
csr_multistate_hash => { 'type' => 'clone' },
csr_complex_type => 'clone',
csr_ms_metadata => { 'interleave' => 'true' },
csr_parameters => { 'plugin_config' => $plugin_config },
csr_mon_intr => '20',

View File

@ -109,12 +109,10 @@ define cluster::virtual_ip (
'dampen' => '30s',
'timeout' => '3s',
},
operations => {
operations => {
'monitor' => { 'interval' => '20', 'timeout' => '30' },
},
multistate_hash => {
'type' => 'clone',
},
complex_type => 'clone',
}
service { "ping_${vip_name}":
ensure => 'running',

View File

@ -1,9 +1,8 @@
class Puppet::Provider::Corosync < Puppet::Provider
require "open3"
# Yep, that's right we are parsing XML...FUN! (It really wasn't that bad)
require 'rexml/document'
#require 'system'
require 'pp'
require 'open3'
require 'rexml/document'
class Puppet::Provider::Corosync < Puppet::Provider
def self.dump_cib
self.block_until_ready
@ -68,8 +67,11 @@ class Puppet::Provider::Corosync < Puppet::Provider
def exists?
self.class.block_until_ready
debug(@property_hash.inspect)
!(@property_hash[:ensure] == :absent or @property_hash.empty?)
Puppet.debug "Call exists? on cs_resource '#{@resource[:name]}'"
out = !(@property_hash[:ensure] == :absent or @property_hash.empty?)
Puppet.debug "Return: #{out}"
Puppet.debug "Current state:\n#{@property_hash.pretty_inspect}" if @property_hash.any?
out
end
def get_scope(type)

View File

@ -1,5 +1,5 @@
require 'pathname'
require Pathname.new(__FILE__).dirname.dirname.expand_path + 'corosync'
require File.join File.dirname(__FILE__), '../corosync.rb'
require 'pp'
Puppet::Type.type(:cs_resource).provide(:crm, :parent => Puppet::Provider::Corosync) do
desc 'Specific provider for a rather specific type since I currently have no
@ -10,99 +10,72 @@ Puppet::Type.type(:cs_resource).provide(:crm, :parent => Puppet::Provider::Coros
operations and parameters. A hash is used instead of constucting a
better model since these values can be almost anything.'
# Path to the crm binary for interacting with the cluster configuration.
commands :cibadmin => 'cibadmin'
commands :crm_shadow => 'crm_shadow'
commands :crm => 'crm'
commands :pcs => 'pcs'
commands :crm_diff => 'crm_diff'
commands :crm_attribute => 'crm_attribute'
# parse CIB XML and create the array of found primitives
# @return [Array<Puppet::Provider::Crm>]
def self.instances
block_until_ready
instances = []
# cmd = [ command(:crm), 'configure', 'show', 'xml' ]
raw, status = dump_cib
doc = REXML::Document.new(raw)
# We are obtaining four different sets of data in this block. We obtain
# key/value pairs for basic primitive information (which Corosync stores
# in the configuration as "resources"). After getting that basic data we
# descend into parameters, operations (which the config labels as
# instance_attributes and operations), and metadata then generate embedded
# hash structures of each entry.
REXML::XPath.each(doc, '//primitive') do |e|
primitive = {}
items = e.attributes
primitive.merge!({
items['id'].to_sym => {
:class => items['class'],
:type => items['type'],
:provider => items['provider']
}
})
primitive[items['id'].to_sym][:parameters] = {}
primitive[items['id'].to_sym][:operations] = {}
primitive[items['id'].to_sym][:metadata] = {}
primitive[items['id'].to_sym][:ms_metadata] = {}
primitive[items['id'].to_sym][:multistate_hash] = {}
if ! e.elements['instance_attributes'].nil?
e.elements['instance_attributes'].each_element do |i|
primitive[items['id'].to_sym][:parameters][(i.attributes['name'])] = i.attributes['value']
end
end
if ! e.elements['meta_attributes'].nil?
e.elements['meta_attributes'].each_element do |m|
primitive[items['id'].to_sym][:metadata][(m.attributes['name'])] = m.attributes['value']
end
end
if ! e.elements['operations'].nil?
e.elements['operations'].each_element do |o|
valids = o.attributes.reject do |k,v| k == 'id' end
if valids['role']
op_name = "#{valids['name']}:#{valids['role']}" #.to_sym()
else
op_name = valids['name'] #.to_sym()
end
primitive[items['id'].to_sym][:operations][op_name] = {}
valids.each do |k,v|
primitive[items['id'].to_sym][:operations][op_name][k] = v if k != 'name'
end
end
end
if e.parent.name == 'master' or e.parent.name == 'clone'
primitive[items['id'].to_sym][:multistate_hash][:name] = e.parent.attributes['id']
primitive[items['id'].to_sym][:multistate_hash][:type] = e.parent.name
if ! e.parent.elements['meta_attributes'].nil?
e.parent.elements['meta_attributes'].each_element do |m|
primitive[items['id'].to_sym][:ms_metadata][(m.attributes['name'])] = m.attributes['value']
end
end
end
primitive_instance = {
:name => primitive.first[0],
primitive = {
:ensure => :present,
:primitive_class => primitive.first[1][:class],
:provided_by => primitive.first[1][:provider],
:primitive_type => primitive.first[1][:type],
:parameters => primitive.first[1][:parameters],
:operations => primitive.first[1][:operations],
:metadata => primitive.first[1][:metadata],
:ms_metadata => primitive.first[1][:ms_metadata],
:multistate_hash => primitive.first[1][:multistate_hash],
:provider => self.name
:name => items['id'].to_s,
:primitive_class => items['class'].to_s,
:primitive_type => items['type'].to_s,
:provided_by => items['provider'].to_s,
}
instances << new(primitive_instance)
primitive[:parameters] = {}
primitive[:operations] = {}
primitive[:metadata] = {}
primitive[:ms_metadata] = {}
if e.elements['instance_attributes']
e.elements['instance_attributes'].each_element do |i|
primitive[:parameters].store i.attributes['name'].to_s, i.attributes['value'].to_s
end
end
if e.elements['meta_attributes']
e.elements['meta_attributes'].each_element do |m|
primitive[:metadata].store m.attributes['name'].to_s, m.attributes['value'].to_s
end
end
if e.elements['operations']
e.elements['operations'].each_element do |o|
op_name = o.attributes['name'].to_s
op_name += ":#{o.attributes['role']}" if o.attributes['role']
primitive[:operations][op_name] = {}
o.attributes.each do |k,v|
next if k == 'name'
next if k == 'id'
primitive[:operations][op_name].store k.to_s, v.to_s
end
end
end
if e.parent.name == 'master' or e.parent.name == 'clone'
primitive[:complex_type] = e.parent.name
if e.parent.elements['meta_attributes']
e.parent.elements['meta_attributes'].each_element do |m|
primitive[:ms_metadata].store m.attributes['name'].to_s, m.attributes['value'].to_s
end
end
end
instances << new(primitive)
end
instances
end
@ -110,31 +83,29 @@ Puppet::Type.type(:cs_resource).provide(:crm, :parent => Puppet::Provider::Coros
# Create just adds our resource to the property_hash and flush will take care
# of actually doing the work.
def create
debug "Call: create on cs_resource '#{@resource[:name]}'"
@property_hash = {
:name => @resource[:name],
:ensure => :present,
:primitive_class => @resource[:primitive_class],
:provided_by => @resource[:provided_by],
:primitive_type => @resource[:primitive_type],
:multistate_hash => @resource[:multistate_hash],
:complex_type => @resource[:complex_type],
}
@property_hash[:parameters] = @resource[:parameters] if ! @resource[:parameters].nil?
@property_hash[:operations] = @resource[:operations] if ! @resource[:operations].nil?
@property_hash[:metadata] = @resource[:metadata] if ! @resource[:metadata].nil?
@property_hash[:ms_metadata] = @resource[:ms_metadata] if ! @resource[:ms_metadata].nil?
@property_hash[:cib] = @resource[:cib] if ! @resource[:cib].nil?
@property_hash[:parameters] = @resource[:parameters] if @resource[:parameters]
@property_hash[:operations] = @resource[:operations] if @resource[:operations]
@property_hash[:metadata] = @resource[:metadata] if @resource[:metadata]
@property_hash[:ms_metadata] = @resource[:ms_metadata] if @resource[:ms_metadata]
@property_hash[:cib] = @resource[:cib] if @resource[:cib]
end
# Unlike create we actually immediately delete the item. Corosync forces us
# to "stop" the primitive before we are able to remove it.
def destroy
debug('Stopping primitive before removing it')
crm('resource', 'stop', @resource[:name])
debug('Removing primitive')
## FIXME(aglarendil): may be we need to apply crm_diff related approach
## FIXME(aglarendil): due to 1338594 bug and do this in flush section
try_command("delete",@resource[:name])
@property_hash.clear
debug "Call: destroy on cs_resource '#{@resource[:name]}'"
pcs 'resource', 'disable', @resource[:name]
pcs 'resource', 'cleanup', @resource[:name]
pcs 'resource', 'delete', @resource[:name]
end
# Getters that obtains the parameters and operations defined in our primitive
@ -156,56 +127,42 @@ Puppet::Type.type(:cs_resource).provide(:crm, :parent => Puppet::Provider::Coros
@property_hash[:ms_metadata]
end
def multistate_hash
@property_hash[:multistate_hash]
def complex_type
@property_hash[:complex_type]
end
# Our setters for parameters and operations. Setters are used when the
# resource already exists so we just update the current value in the
# property_hash and doing this marks it to be flushed.
def parameters=(should)
Puppet.debug "Set paramemter:\n#{should.pretty_inspect}"
@property_hash[:parameters] = should
end
def operations=(should)
Puppet.debug "Set operations:\n#{should.pretty_inspect}"
@property_hash[:operations] = should
end
def metadata=(should)
Puppet.debug "Set metadata:\n#{should.pretty_inspect}"
@property_hash[:metadata] = should
end
def ms_metadata=(should)
Puppet.debug "Set ms_metadata:\n#{should.pretty_inspect}"
@property_hash[:ms_metadata] = should
end
def multistate_hash=(should)
#Check if we use default multistate name
#if it is empty
if should[:type] and should[:name].to_s.empty?
newname = "#{should[:type]}_#{@property_hash[:name]}"
else
newname = should[:name]
end
if (should[:type] != @property_hash[:multistate_hash][:type] and @property_hash[:multistate_hash][:type])
#If the type of resource has changed
#simply stop and delete it both in live
#and shadow cib
def complex_type=(should)
Puppet.debug "Set complex_type:\n#{should.pretty_inspect}"
crm('resource', 'stop', "#{@property_hash[:multistate_hash][:name]}")
try_command("delete",@property_hash[:multistate_hash][:name])
try_command("delete",@property_hash[:multistate_hash][:name],nil,@resource[:cib])
elsif
#otherwise, stop it and rename it both
#in shadow and live cib
(should[:type] == @property_hash[:multistate_hash][:type] and @property_hash[:multistate_hash][:type] and
newname != @property_hash[:multistate_hash][:name])
crm('resource', 'stop', "#{@property_hash[:multistate_hash][:name]}")
try_command("rename",@property_hash[:multistate_hash][:name],newname)
try_command("rename",@property_hash[:multistate_hash][:name],newname,@resource[:cib])
# try to change complex_type of the existing primitive by deleting and recreating it later
if should != @property_hash[:complex_type]
self.destroy
end
@property_hash[:multistate_hash][:name] = newname
@property_hash[:multistate_hash][:type] = should[:type]
@property_hash[:complex_type] = should
end
# Flush is triggered on anything that has been detected as being
@ -215,61 +172,66 @@ Puppet::Type.type(:cs_resource).provide(:crm, :parent => Puppet::Provider::Coros
# operations and parameters hash to eventually flatten them into a string
# that can be used by the crm command.
def flush
unless @property_hash.empty?
self.class.block_until_ready
unless @property_hash[:operations].empty?
operations = ''
@property_hash[:operations].each do |o|
op_namerole = o[0].to_s.split(':')
if op_namerole[1]
o[1]['role'] = o[1]['role'] || op_namerole[1] # Hash['role'] has more priority, than Name
end
operations << "op #{op_namerole[0]} "
o[1].each_pair do |k,v|
operations << "#{k}=#{v} "
end
end
end
unless @property_hash[:parameters].empty?
parameters = 'params '
@property_hash[:parameters].each_pair do |k,v|
parameters << "#{k}=#{v} "
end
end
unless @property_hash[:metadata].empty?
metadatas = 'meta '
@property_hash[:metadata].each_pair do |k,v|
metadatas << "#{k}=#{v} "
end
end
updated = "primitive "
updated << "#{@property_hash[:name]} #{@property_hash[:primitive_class]}:"
updated << "#{@property_hash[:provided_by]}:" if @property_hash[:provided_by]
updated << "#{@property_hash[:primitive_type]} "
updated << "#{operations} " unless operations.nil?
updated << "#{parameters} " unless parameters.nil?
updated << "#{metadatas} " unless metadatas.nil?
debug "Call: flush on cs_resource '#{@resource[:name]}'"
return if @property_hash.empty?
self.class.block_until_ready
if ( @property_hash[:multistate_hash][:type] == "master" or @property_hash[:multistate_hash][:type] == "clone" )
debug("creating multistate #{@property_hash[:multistate_hash][:type]} resource for #{@property_hash[:multistate_hash][:name]}")
crm_name = @property_hash[:multistate_hash][:type] == "master" ? :ms : :clone
updated << "\n"
updated << " #{crm_name} #{@property_hash[:multistate_hash][:name]} #{@property_hash[:name]} "
unless @property_hash[:ms_metadata].empty?
updated << 'meta '
@property_hash[:ms_metadata].each_pair do |k,v|
updated << "#{k}=#{v} "
end
unless @property_hash[:operations].empty?
operations = ''
@property_hash[:operations].each do |o|
op_namerole = o[0].to_s.split(':')
if op_namerole[1]
o[1]['role'] = o[1]['role'] || op_namerole[1] # Hash['role'] has more priority, than Name
end
operations << "op #{op_namerole[0]} "
o[1].each_pair do |k,v|
operations << "#{k}=#{v} "
end
end
debug("will update tmp file with #{updated}")
Tempfile.open('puppet_crm_update') do |tmpfile|
tmpfile.write(updated)
tmpfile.flush
#env["CIB_shadow"] = @resource[:cib].to_s if !@resource[:cib].nil?
##LP1338594 part: should be put into separate method, I guess
apply_changes(@resource[:name],tmpfile,'resource')
end
end
unless @property_hash[:parameters].empty?
parameters = 'params '
@property_hash[:parameters].each_pair do |k,v|
parameters << "#{k}=#{v} "
end
end
unless @property_hash[:metadata].empty?
metadatas = 'meta '
@property_hash[:metadata].each_pair do |k,v|
metadatas << "#{k}=#{v} "
end
end
updated = 'primitive '
updated << "#{@property_hash[:name]} #{@property_hash[:primitive_class]}:"
updated << "#{@property_hash[:provided_by]}:" if @property_hash[:provided_by]
updated << "#{@property_hash[:primitive_type]} "
updated << "#{operations} " unless operations.nil?
updated << "#{parameters} " unless parameters.nil?
updated << "#{metadatas} " unless metadatas.nil?
if @property_hash[:complex_type]
complex_name = "#{@property_hash[:complex_type]}_#{@property_hash[:name]}"
crm_cmd_type = @property_hash[:complex_type].to_s == 'master' ? 'ms' : 'clone'
debug "Creating '#{crm_cmd_type}' parent named '#{complex_name}' for #{@property_hash[:name]} resource"
updated << "\n"
updated << " #{crm_cmd_type} #{complex_name} #{@property_hash[:name]} "
unless @property_hash[:ms_metadata].empty?
updated << 'meta '
@property_hash[:ms_metadata].each_pair do |k,v|
updated << "#{k}=#{v} "
end
end
end
debug("Will update tmpfile with '#{updated}'")
Tempfile.open('puppet_crm_update') do |tmpfile|
tmpfile.write(updated)
tmpfile.flush
apply_changes(@resource[:name], tmpfile, 'resource')
end
end
end

View File

@ -387,9 +387,22 @@ class Puppet::Provider::Pacemaker_common < Puppet::Provider
# @param node [String] the node's name
# @param score [Numeric,String] score value
def constraint_location_add(primitive, node, score = 100)
id = "#{primitive}_on_#{node}"
id = "#{primitive}-on-#{node}"
xml = <<-EOF
<diff>
<diff-added>
<cib>
<configuration>
<constraints>
<rsc_location id="#{id}" node="#{node}" rsc="#{primitive}" score="#{score}"/>
</constraints>
</configuration>
</cib>
</diff-added>
</diff>
EOF
retry_command {
pcs 'constraint', 'location', 'add', id, primitive, node, score
cibadmin '--patch', '--sync-call', '--xml-text', xml
}
end

View File

@ -74,10 +74,12 @@ module Puppet
defining a model and just accept a hash."
validate do |value|
raise Puppet::Error, "Puppet::Type::Cs_Primitive: parameters property must be a hash." unless value.is_a? Hash
unless value.is_a? Hash
raise Puppet::Error, "Puppet::Type::Cs_Primitive: parameters property must be a hash."
end
end
munge do |parameters|
convert_to_sym(parameters)
munge do |value|
stringify value
end
defaultto Hash.new
end
@ -91,10 +93,12 @@ module Puppet
is valid."
validate do |value|
raise Puppet::Error, "Puppet::Type::Cs_Primitive: operations property must be a hash." unless value.is_a? Hash
unless value.is_a? Hash
raise Puppet::Error, "Puppet::Type::Cs_Primitive: operations property must be a hash."
end
end
munge do |operations|
convert_to_sym(operations)
munge do |value|
stringify value
end
defaultto Hash.new
end
@ -107,60 +111,60 @@ module Puppet
behavior but have no affect of the data that is synced or manipulated."
validate do |value|
raise Puppet::Error, "Puppet::Type::Cs_Primitive: metadata property must be a hash." unless value.is_a? Hash
unless value.is_a? Hash
raise Puppet::Error, "Puppet::Type::Cs_Primitive: metadata property must be a hash."
end
end
munge do |metadata|
convert_to_sym(metadata)
munge do |value|
stringify value
end
defaultto Hash.new
def insync?(is)
status_metadata = %w(target-role is-managed)
is_without_state = is.reject do |k, v|
status_metadata.include? k.to_s
end
should_without_state = should.reject do |k, v|
status_metadata.include? k.to_s
end
is_without_state == should_without_state
end
end
newproperty(:ms_metadata) do
desc "A hash of metadata for the multistate state."
munge do |value_hash|
value_hash.inject({}) do |memo,(key,value)|
memo[key] = String(value)
memo
validate do |value|
unless value.is_a? Hash
raise Puppet::Error, "Puppet::Type::Cs_Primitive: ms_metadata property must be a hash"
end
end
validate do |value|
raise Puppet::Error, "Puppet::Type::Cs_Primitive: ms_metadata property must be a hash" unless value.is_a? Hash
munge do |value|
stringify value
end
defaultto Hash.new
def insync?(is)
status_metadata = %w(target-role is-managed)
is_without_state = is.reject do |k, v|
status_metadata.include? k.to_s
end
should_without_state = should.reject do |k, v|
status_metadata.include? k.to_s
end
is_without_state == should_without_state
end
end
newproperty(:multistate_hash) do
newproperty(:complex_type) do
desc "Designates if the primitive is capable of being managed in a multistate
state. This will create a new ms or clone resource in your Corosync config and add
this primitive to it. Concequently Corosync will be helpful and update all
your colocation and order resources too but Puppet won't. Hash contains
two key-value pairs: type (master, clone) and its name (${type}_{$primitive_name})
by default"
munge do |value_hash|
munged_hash = value_hash.inject({}) do |memo,(key,value)|
memo[key.to_sym] = String(value)
memo
end
if munged_hash[:name].to_s.empty? and !munged_hash[:type].to_s.empty?
munged_hash[:name] = "#{munged_hash[:type]}_#{@resource[:name]}"
end
munged_hash
end
validate do |value|
raise Puppet::Error, "Puppet::Type::Cs_Resource: multistate_hash property must be a hash" unless
value.is_a? Hash
raise Puppet::Error, "Puppet::Type::Cs_Resource: multistate_hash type property #{value[:type]} must be in master|clone|'' if set" unless
["master", "clone", ""].include?(value[:type]) or value[:type] == nil
end
defaultto Hash.new
newvalues('clone', 'master')
end
autorequire(:cs_shadow) do
@ -169,29 +173,36 @@ module Puppet
Puppet.debug("#{@parameters[:cib].value}")
autos << @parameters[:cib].value
end
autos
end
autorequire(:service) do
[ 'corosync' ]
%w(corosync pacemaker)
end
validate do
unless self[:ms_metadata].empty? or self[:complex_type]
raise Puppet::Error, 'You should not use ms_metadata if your resource is not clone or master!'
end
end
end
end
def convert_to_sym(hash)
if hash.is_a? Hash
hash.inject({}) do |memo,(key,value)|
value = convert_to_sym(value)
if value.is_a?(Array)
value.collect! do |arr_el|
convert_to_sym(arr_el)
end
end
memo[key.to_sym] = value
memo
# convert data structure to strings
def stringify(data)
if data.is_a? Hash
new_data = {}
data.each do |key, value|
new_data.store stringify(key), stringify(value)
end
data.clear
data.merge! new_data
elsif data.is_a? Array
data.map! do |element|
stringify element
end
else
hash
data.to_s
end
end
end

View File

@ -46,7 +46,7 @@ cs_resource { 'bar':
primitive_class => 'ocf',
provided_by => 'pacemaker',
primitive_type => 'Dummy',
multistate_hash => { type => 'master' },
complex_type => 'master',
operations => {
'monitor' => {
'interval' => '20'
@ -58,7 +58,7 @@ cs_resource { 'blort':
primitive_class => 'ocf',
provided_by => 'pacemaker',
primitive_type => 'Dummy',
multistate_hash => { 'type' => 'clone', 'name' => 'blort_clone' },
complex_type => 'clone',
operations => {
'monitor' => {
'interval' => '20'
@ -73,7 +73,7 @@ cs_resource { 'foo':
ensure => present,
primitive_class => 'ocf',
provided_by => 'pacemaker',
multistate_hash => { 'type' => 'clone', 'name' => 'super_good_foo_clone' },
complex_type => 'clone',
primitive_type => 'Dummy',
}
cs_colocation { 'foo-with-bar':

View File

@ -6,7 +6,8 @@ describe Puppet::Type.type(:cs_resource) do
end
it "should have a 'name' parameter" do
subject.new(:name => "mock_resource")[:name].should == "mock_resource"
type = subject.new(:name => "mock_resource")
expect(type[:name]).to eq("mock_resource")
end
describe "basic structure" do
@ -14,26 +15,26 @@ describe Puppet::Type.type(:cs_resource) do
provider_class = Puppet::Type::Cs_resource.provider(Puppet::Type::Cs_resource.providers[0])
Puppet::Type::Cs_resource.expects(:defaultprovider).returns(provider_class)
subject.new(:name => "mock_resource").should_not be_nil
expect(subject.new(:name => "mock_resource")).to_not be_nil
end
[:name, :primitive_class, :primitive_type, :provided_by, :cib].each do |param|
it "should have a #{param} parameter" do
subject.validparameter?(param).should be_true
expect(subject.validparameter?(param)).to be_truthy
end
it "should have documentation for its #{param} parameter" do
subject.paramclass(param).doc.should be_instance_of(String)
expect(subject.paramclass(param).doc).to be_instance_of(String)
end
end
[:parameters, :operations, :ms_metadata, :multistate_hash].each do |property|
[:parameters, :operations, :ms_metadata, :complex_type].each do |property|
it "should have a #{property} property" do
subject.validproperty?(property).should be_true
expect(subject.validproperty?(property)).to be_truthy
end
it "should have documentation for its #{property} property" do
subject.propertybyname(property).doc.should be_instance_of(String)
expect(subject.propertybyname(property).doc).to be_instance_of(String)
end
end
end
@ -41,7 +42,7 @@ describe Puppet::Type.type(:cs_resource) do
describe "when validating attributes" do
[:parameters, :operations, :metadata, :ms_metadata].each do |attribute|
it "should validate that the #{attribute} attribute defaults to a hash" do
subject.new(:name => "mock_resource")[:parameters].should == {}
expect(subject.new(:name => "mock_resource")[:parameters]).to eq({})
end
it "should validate that the #{attribute} attribute must be a hash" do
@ -52,17 +53,16 @@ describe Puppet::Type.type(:cs_resource) do
end
end
it "should validate that the multistate_hash type attribute cannot be other values" do
it "should validate that the complex_type type attribute cannot be other values" do
["fail", 42].each do |value|
expect { subject.new(
:name => "mock_resource",
:multistate_hash => { :type=> value }
:name => "mock_resource",
:complex_type => value,
) }.to raise_error(Puppet::Error, /(master|clone|\'\')/)
end
end
end
describe "when autorequiring resources" do
before :each do
@ -77,7 +77,7 @@ describe "when autorequiring resources" do
@catalog.add_resource @resource
req = @resource.autorequire
req.size.should == 1
expect(req.size).to eq(1)
#rewrite this f*cking should method of property type by the ancestor method
[req[0].target,req[0].source].each do |instance|
class << instance
@ -86,10 +86,57 @@ describe "when autorequiring resources" do
end
end
end
req[0].target.should eql(@resource)
req[0].source.should eql(@shadow)
expect(req[0].target).to eql(@resource)
expect(req[0].source).to eql(@shadow)
end
end
describe 'special insync conditions' do
before :each do
@type = subject.new (
{
:name => 'my_resource',
:ms_metadata => {
'a' => 1,
'is-managed' => 'true',
},
:metadata => {
'a' => 2,
'is-managed' => 'true',
},
:complex_type => 'master',
}
)
end
it 'should ignore status metadata from ms_metadata hash comparison' do
ms_metadata = @type.property(:ms_metadata)
expect(ms_metadata.insync?({"a" => "1", "is-managed" => "false"})).to be_truthy
end
it 'should ignore status metadata from metadata hash comparison' do
metadata = @type.property(:metadata)
expect(metadata.insync?({"a" => "2", "is-managed" => "false"})).to be_truthy
end
it 'should compare non-status ms_metadata' do
ms_metadata = @type.property(:ms_metadata)
expect(ms_metadata.insync?({'a' => 2})).to be_falsey
end
it 'should compare non-status metadata' do
metadata = @type.property(:metadata)
expect(metadata.insync?({'a' => 1})).to be_falsey
end
end
describe 'munging of input data' do
it 'should convert hash keys and values to strings' do
@type = subject.new({:name => 'myresource'})
@type[:ms_metadata] = { :a => 1, 'b' => true, 'c' => { :a => true, 'b' => :s, 4 => 'd' } }
expect(@type[:ms_metadata]).to eq({"a"=>"1", "b"=>"true", "c"=>{"a"=>"true", "b"=>"s", "4"=>"d"}})
end
end
end

View File

@ -30,7 +30,7 @@ cs_resource { 'blort':
primitive_class => 'ocf',
provided_by => 'pacemaker',
primitive_type => 'Dummy',
multistate_hash => { type => 'master' },
complex_type => 'master',
operations => {
'monitor' => {
'interval' => '20'

View File

@ -165,9 +165,7 @@ class galera (
primitive_class => 'ocf',
provided_by => 'mirantis',
primitive_type => 'mysql-wss',
multistate_hash => {
'type' => 'clone',
},
complex_type => 'clone',
parameters => {
'test_user' => "${mysql_user}",
'test_passwd' => "${mysql_password}",

View File

@ -24,10 +24,6 @@ class heat_ha::engine inherits heat::engine {
},
}
$multistate_hash = {
'type' => 'clone',
}
$ms_metadata = {
'interleave' => true,
}
@ -35,10 +31,10 @@ class heat_ha::engine inherits heat::engine {
pacemaker_wrappers::service { $service_name :
primitive_type => $primitive_type,
metadata => $metadata,
multistate_hash => $multistate_hash,
complex_type => 'clone',
ms_metadata => $ms_metadata,
operations => $operations,
ocf_script_template => $ocf_script_template,
}
}
}

View File

@ -171,8 +171,9 @@ class mysql::server (
primitive_class => 'ocf',
provided_by => 'heartbeat',
primitive_type => 'mysql',
multistate_hash => {'type'=>'master'},
ms_metadata => {'notify' => "true"},
cib => 'mysql',
complex_type => 'master',
ms_metadata => {'notify' => "true"},
parameters => {
'binary' => "/usr/bin/mysqld_safe",
'test_table' => 'mysql.user',

View File

@ -140,13 +140,11 @@ class nova::rabbitmq(
'failure-timeout' => '60s'
},
multistate_hash => {
'type' => 'master',
},
complex_type => 'master',
ms_metadata => {
'notify' => 'true',
'ordered' => 'false', # We shouldn't enable ordered start for parallel start of RA.
'interleave' => 'true',
'interleave' => 'true',
'master-max' => '1',
'master-node-max' => '1',
'target-role' => 'Master'

View File

@ -9,7 +9,7 @@ define pacemaker_wrappers::service (
$operations = undef,
$metadata = undef,
$ms_metadata = undef,
$multistate_hash = undef,
$complex_type = undef,
$use_handler = true,
$handler_root_path = '/usr/local/bin',
@ -48,7 +48,7 @@ define pacemaker_wrappers::service (
operations => $operations,
metadata => $metadata,
ms_metadata => $ms_metadata,
multistate_hash => $multistate_hash,
complex_type => $complex_type,
}
}