Make pacemaker_resource serializable
Change the internal storage of resource operations from a Set to an Array of Hashes. This prevents an error when a Puppet Agent tries to serialize the data to its transaction store and fails to re-read it with: Error: Transaction store file /opt/puppetlabs/puppet/cache/state/transactionstore.yaml is corrupt ((/opt/puppetlabs/puppet/cache/state/transactionstore.yaml): Tried to load unspecified class: Set); replacing To support the change, the handling of operations is generally stricter, preferring to raise an error on unexpected input instead of silently dropping invalid entries or attempting to convert them. Change-Id: I4bee307c81d606981574cd0004150f6f3f31f81e
This commit is contained in:
parent
2b6d519180
commit
dcc14c8537
|
@ -1,5 +1,3 @@
|
||||||
require 'set'
|
|
||||||
|
|
||||||
module Pacemaker
|
module Pacemaker
|
||||||
# contains functions that can be included to the pacemaker types
|
# contains functions that can be included to the pacemaker types
|
||||||
module Type
|
module Type
|
||||||
|
@ -36,36 +34,44 @@ module Pacemaker
|
||||||
stringify_data element
|
stringify_data element
|
||||||
end
|
end
|
||||||
elsif data.is_a? Set
|
elsif data.is_a? Set
|
||||||
new_data = Set.new
|
raise "unexpected Set data: #{data}"
|
||||||
data.each do |element|
|
|
||||||
new_data.add(stringify_data element)
|
|
||||||
end
|
|
||||||
new_data
|
|
||||||
else
|
else
|
||||||
data.to_s
|
data.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# modify provided operations data
|
# Maintains an array of operation hashes as if it was a sorted set. These
|
||||||
# @param [Hash,Array] operations_input parameter value from catalog
|
# are in Array-of-Hash format ({ 'name' => 'monitor', 'interval' => ...}),
|
||||||
def munge_operations(operations_input)
|
# not { 'monitor' => {...} } ones. The unicity is done on the name and
|
||||||
operations_input = [operations_input] unless operations_input.is_a? Array
|
# interval operation keys. The input is expected to have been stringified
|
||||||
operations = Set.new
|
# and munged.
|
||||||
operations_input.each do |operation|
|
#
|
||||||
# operations are an array of sets
|
# Modifies the operations argument and returns it.
|
||||||
if operation.is_a? Set
|
#
|
||||||
operations.merge operation
|
# We can't use a real Set as it doesn't serialize correctly in Puppet's
|
||||||
next
|
# transaction store. This datastructure is always small, so performance
|
||||||
|
# is irrelevant.
|
||||||
|
def add_to_operations_array(operations, new_op)
|
||||||
|
operations.delete_if { |op| op['name'] == new_op['name'] && op['interval'] == new_op['interval'] }
|
||||||
|
operations << new_op
|
||||||
|
operations.sort_by! { |op| "#{op['name']} #{op['interval']}" }
|
||||||
end
|
end
|
||||||
# # operations were provided as an array of hashes
|
|
||||||
|
# Munges the input into an Array of munged operations.
|
||||||
|
# @param [Hash,Array] operations_input parameter value from catalog
|
||||||
|
def munge_operations_array(operations_input)
|
||||||
|
operations_input = stringify_data(operations_input)
|
||||||
|
operations_input = [operations_input] unless operations_input.is_a? Array
|
||||||
|
operations = []
|
||||||
|
operations_input.each do |operation|
|
||||||
|
# operations were provided as an array of hashes
|
||||||
if operation.is_a? Hash and operation['name']
|
if operation.is_a? Hash and operation['name']
|
||||||
munge_operation operation
|
munge_operation operation
|
||||||
operations.add operation
|
add_to_operations_array(operations, operation)
|
||||||
next
|
elsif operation.is_a? Hash
|
||||||
end
|
|
||||||
# operations were provided as a hash of hashes
|
# operations were provided as a hash of hashes
|
||||||
operation.each do |operation_name, operation_data|
|
operation.each do |operation_name, operation_data|
|
||||||
next unless operation_data.is_a? Hash
|
raise "invalid operation in a hash of hashes: #{operation_data}" unless operation_data.is_a? Hash
|
||||||
operation = {}
|
operation = {}
|
||||||
if operation_name.include? ':'
|
if operation_name.include? ':'
|
||||||
operation_name_array = operation_name.split(':')
|
operation_name_array = operation_name.split(':')
|
||||||
|
@ -77,7 +83,10 @@ module Pacemaker
|
||||||
operation['name'] = operation_name
|
operation['name'] = operation_name
|
||||||
operation.merge! operation_data
|
operation.merge! operation_data
|
||||||
munge_operation operation
|
munge_operation operation
|
||||||
operations.add operation if operation.any?
|
add_to_operations_array(operations, operation) if operation.any?
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise "invalid pacemaker_resource.operations input: #{operations_input}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
operations
|
operations
|
||||||
|
@ -86,7 +95,7 @@ module Pacemaker
|
||||||
# munge a single operations hash
|
# munge a single operations hash
|
||||||
# @param [Hash] operation
|
# @param [Hash] operation
|
||||||
def munge_operation(operation)
|
def munge_operation(operation)
|
||||||
return unless operation.is_a? Hash
|
raise "invalid pacemaker_resource.operations element: #{operation}" unless operation.is_a? Hash
|
||||||
operation['name'] = 'monitor' unless operation['name']
|
operation['name'] = 'monitor' unless operation['name']
|
||||||
operation['interval'] = '0' unless operation['name'] == 'monitor'
|
operation['interval'] = '0' unless operation['name'] == 'monitor'
|
||||||
operation['interval'] = '0' unless operation['interval']
|
operation['interval'] = '0' unless operation['interval']
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
require 'set'
|
|
||||||
|
|
||||||
module Pacemaker
|
module Pacemaker
|
||||||
# function related to the primitives configuration
|
# function related to the primitives configuration
|
||||||
# main structure "primitives"
|
# main structure "primitives"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
require_relative '../pacemaker_xml'
|
require_relative '../pacemaker_xml'
|
||||||
require 'set'
|
|
||||||
|
|
||||||
Puppet::Type.type(:pacemaker_resource).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
|
Puppet::Type.type(:pacemaker_resource).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
|
||||||
desc <<-eof
|
desc <<-eof
|
||||||
|
@ -83,12 +82,13 @@ better model since these values can be almost anything.'
|
||||||
end
|
end
|
||||||
|
|
||||||
if data['operations']
|
if data['operations']
|
||||||
operations_set = Set.new
|
operations_data = []
|
||||||
data['operations'].each do |_id, operation|
|
data['operations'].each do |_id, operation|
|
||||||
operation.delete 'id'
|
operation.delete 'id'
|
||||||
operations_set.add operation
|
operation = munge_operation(operation)
|
||||||
|
add_to_operations_array(operations_data, operation)
|
||||||
end
|
end
|
||||||
target_structure[:operations] = operations_set
|
target_structure[:operations] = operations_data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -232,7 +232,6 @@ better model since these values can be almost anything.'
|
||||||
end
|
end
|
||||||
|
|
||||||
def operations=(should)
|
def operations=(should)
|
||||||
should = should.first if should.is_a? Array
|
|
||||||
property_hash[:operations] = should
|
property_hash[:operations] = should
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -305,14 +304,10 @@ better model since these values can be almost anything.'
|
||||||
|
|
||||||
# operations structure
|
# operations structure
|
||||||
if operations && operations.any?
|
if operations && operations.any?
|
||||||
|
raise "expected operations to be an array" unless operations.is_a? Array
|
||||||
primitive_structure['operations'] = {}
|
primitive_structure['operations'] = {}
|
||||||
operations.each do |operation|
|
operations.each do |operation|
|
||||||
if operation.is_a?(Array) && operation.length == 2
|
raise "expected operations members to be hashes" unless operation.is_a? Hash
|
||||||
# operations were provided and Hash { name => { parameters } }, convert it
|
|
||||||
operation_name = operation[0]
|
|
||||||
operation = operation[1]
|
|
||||||
operation['name'] = operation_name unless operation['name']
|
|
||||||
end
|
|
||||||
unless operation['id']
|
unless operation['id']
|
||||||
# there is no id provided, generate it
|
# there is no id provided, generate it
|
||||||
id_components = [name, operation['name'], operation['interval']]
|
id_components = [name, operation['name'], operation['interval']]
|
||||||
|
|
|
@ -127,17 +127,22 @@ is valid.
|
||||||
eof
|
eof
|
||||||
|
|
||||||
validate do |value|
|
validate do |value|
|
||||||
raise "Operations property must be an Hash or a Set. Got: #{value.inspect}" unless value.is_a? Hash or value.is_a? Set
|
raise "Operations property must be an Hash. Got: #{value.inspect}" unless value.is_a? Hash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Puppet calls this for individual operations inside the Array
|
||||||
munge do |value|
|
munge do |value|
|
||||||
|
raise "expected to munge a single operation" if value.is_a? Array
|
||||||
value = resource.stringify_data value
|
value = resource.stringify_data value
|
||||||
resource.munge_operations(value)
|
resource.munge_operation(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
def should=(value)
|
def should=(value)
|
||||||
super
|
munged = resource.munge_operations_array(value)
|
||||||
@should = [resource.munge_operations(@should)]
|
super(munged)
|
||||||
|
# @shouldorig is supposed to hold the original value, but super will
|
||||||
|
# stored munged, not the original it didn't receive.
|
||||||
|
@shouldorig = value
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_to_s(is)
|
def is_to_s(is)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'set'
|
|
||||||
|
|
||||||
describe Puppet::Type.type(:pacemaker_resource).provider(:xml) do
|
describe Puppet::Type.type(:pacemaker_resource).provider(:xml) do
|
||||||
let(:resource) do
|
let(:resource) do
|
||||||
|
@ -196,13 +195,11 @@ describe Puppet::Type.type(:pacemaker_resource).provider(:xml) do
|
||||||
metadata: {
|
metadata: {
|
||||||
'resource-stickiness' => '1'
|
'resource-stickiness' => '1'
|
||||||
},
|
},
|
||||||
operations: Set.new(
|
operations: [
|
||||||
[
|
|
||||||
{'interval' => '20', 'name' => 'monitor', 'timeout' => '10'},
|
{'interval' => '20', 'name' => 'monitor', 'timeout' => '10'},
|
||||||
{'interval' => '0', 'name' => 'start', 'timeout' => '60'},
|
{'interval' => '0', 'name' => 'start', 'timeout' => '60'},
|
||||||
{'interval' => '0', 'name' => 'stop', 'timeout' => '60'},
|
{'interval' => '0', 'name' => 'stop', 'timeout' => '60'},
|
||||||
]
|
],
|
||||||
)
|
|
||||||
}
|
}
|
||||||
expect(provider.property_hash).to eq data
|
expect(provider.property_hash).to eq data
|
||||||
end
|
end
|
||||||
|
@ -232,8 +229,7 @@ describe Puppet::Type.type(:pacemaker_resource).provider(:xml) do
|
||||||
'migration-threshold' => 'INFINITY',
|
'migration-threshold' => 'INFINITY',
|
||||||
'failure-timeout' => '60s'
|
'failure-timeout' => '60s'
|
||||||
},
|
},
|
||||||
operations: Set.new(
|
operations: [
|
||||||
[
|
|
||||||
{'name' => 'demote', 'timeout' => '60', 'interval' => '0'},
|
{'name' => 'demote', 'timeout' => '60', 'interval' => '0'},
|
||||||
{'name' => 'monitor', 'timeout' => '60', 'interval' => '27', 'role' => 'Master'},
|
{'name' => 'monitor', 'timeout' => '60', 'interval' => '27', 'role' => 'Master'},
|
||||||
{'name' => 'monitor', 'timeout' => '60', 'interval' => '30'},
|
{'name' => 'monitor', 'timeout' => '60', 'interval' => '30'},
|
||||||
|
@ -241,8 +237,7 @@ describe Puppet::Type.type(:pacemaker_resource).provider(:xml) do
|
||||||
{'name' => 'promote', 'timeout' => '120', 'interval' => '0'},
|
{'name' => 'promote', 'timeout' => '120', 'interval' => '0'},
|
||||||
{'name' => 'start', 'timeout' => '120', 'interval' => '0'},
|
{'name' => 'start', 'timeout' => '120', 'interval' => '0'},
|
||||||
{'name' => 'stop', 'timeout' => '60', 'interval' => '0'},
|
{'name' => 'stop', 'timeout' => '60', 'interval' => '0'},
|
||||||
]
|
],
|
||||||
),
|
|
||||||
}
|
}
|
||||||
expect(provider.property_hash).to eq data
|
expect(provider.property_hash).to eq data
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'set'
|
|
||||||
|
|
||||||
describe Puppet::Type.type(:pacemaker_resource) do
|
describe Puppet::Type.type(:pacemaker_resource) do
|
||||||
subject do
|
subject do
|
||||||
|
@ -82,46 +81,62 @@ describe Puppet::Type.type(:pacemaker_resource) do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'on operations' do
|
context 'on operations' do
|
||||||
|
it 'should maintain a set-like operations array' do
|
||||||
|
ops = []
|
||||||
|
instance.add_to_operations_array(ops, { 'name' => 'foo', 'interval' => 10, })
|
||||||
|
instance.add_to_operations_array(ops, { 'name' => 'foo', 'interval' => 20, 'timeout' => 4, })
|
||||||
|
instance.add_to_operations_array(ops, { 'name' => 'foo', 'interval' => 20, })
|
||||||
|
instance.add_to_operations_array(ops, { 'name' => 'bar', 'interval' => 20, })
|
||||||
|
|
||||||
|
ops2 = [
|
||||||
|
{ 'name' => 'bar', 'interval' => 20, },
|
||||||
|
{ 'name' => 'foo', 'interval' => 10, },
|
||||||
|
{ 'name' => 'foo', 'interval' => 20, },
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(ops).to eq ops2
|
||||||
|
end
|
||||||
|
|
||||||
it 'should change operations format if provided as hash' do
|
it 'should change operations format if provided as hash' do
|
||||||
data_from = {'start' => {'timeout' => '20', 'interval' => '0'}, 'monitor' => {'interval' => '10'}}
|
data_from = {'start' => {'timeout' => '20', 'interval' => '0'}, 'monitor' => {'interval' => '10'}}
|
||||||
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start', 'interval' => '0'}]
|
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start', 'interval' => '0'}]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should support several monitor operations' do
|
it 'should support several monitor operations' do
|
||||||
data_from = [{'interval' => '10', 'name' => 'monitor'}, {'interval' => '20', 'name' => 'monitor'}]
|
data_from = [{'interval' => '10', 'name' => 'monitor'}, {'interval' => '20', 'name' => 'monitor'}]
|
||||||
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'interval' => '20', 'name' => 'monitor'}]
|
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'interval' => '20', 'name' => 'monitor'}]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should reset non-monitor operation interval to 0' do
|
it 'should reset non-monitor operation interval to 0' do
|
||||||
data_from = {'start' => {'timeout' => '20', 'interval' => '10'}, 'stop' => {'interval' => '20', 'timeout' => '20'}}
|
data_from = {'start' => {'timeout' => '20', 'interval' => '10'}, 'stop' => {'interval' => '20', 'timeout' => '20'}}
|
||||||
data_to = [{'timeout' => '20', 'name' => 'start', 'interval' => '0'}, {'interval' => '0', 'name' => 'stop', 'timeout' => '20', }]
|
data_to = [{'timeout' => '20', 'name' => 'start', 'interval' => '0'}, {'interval' => '0', 'name' => 'stop', 'timeout' => '20', }]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should add missing interval values' do
|
it 'should add missing interval values' do
|
||||||
data_from = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start'}]
|
data_from = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start'}]
|
||||||
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start', 'interval' => '0'}]
|
data_to = [{'interval' => '10', 'name' => 'monitor'}, {'timeout' => '20', 'name' => 'start', 'interval' => '0'}]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should capitalize role value' do
|
it 'should capitalize role value' do
|
||||||
data_from = [{'interval' => '10', 'name' => 'monitor', 'role' => 'master'}]
|
data_from = [{'interval' => '10', 'name' => 'monitor', 'role' => 'master'}]
|
||||||
data_to = [{'interval' => '10', 'name' => 'monitor', 'role' => 'Master'}]
|
data_to = [{'interval' => '10', 'name' => 'monitor', 'role' => 'Master'}]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should stringify operations structure' do
|
it 'should stringify operations structure' do
|
||||||
data_from = {'interval' => 10, :name => 'monitor'}
|
data_from = {'interval' => 10, :name => 'monitor'}
|
||||||
data_to = [{'interval' => '10', 'name' => 'monitor'}]
|
data_to = [{'interval' => '10', 'name' => 'monitor'}]
|
||||||
instance[:operations] = data_from
|
instance[:operations] = data_from
|
||||||
expect(instance[:operations]).to eq [Set.new(data_to)]
|
expect(instance[:operations]).to eq data_to
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue