Implement stonith levels resource

This resource implements a stonith levels definition.
This is used so that we can define a specific sequence of stonith
operations on a node.

More documentation about stonith levels can be found here:
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/high_availability_add-on_reference/#s1-fencelevels-HAAR

Change-Id: I9cc5064e776a3771e30d853d9453026d2731c98d
This commit is contained in:
Michele Baldessari 2017-09-29 22:36:25 +02:00
parent 48c8599793
commit ebef43cadf
4 changed files with 326 additions and 0 deletions

View File

@ -0,0 +1,51 @@
require_relative '../pcmk_common'
Puppet::Type.type(:pcmk_stonith_level).provide(:default) do
desc 'A base resource definition for a pacemaker stonith level definition'
### overloaded methods
def create
level = @resource[:level]
target = @resource[:target]
stonith_resources = @resource[:stonith_resources]
res = stonith_resources.join(',')
cmd = 'stonith level add ' + level.to_s + ' ' + target + ' ' + res
pcs('create', "#{name}-#{target}-#{res}", cmd, @resource[:tries],
@resource[:try_sleep], @resource[:verify_on_create], @resource[:post_success_sleep])
end
def destroy
# Any corresponding location rules will be deleted by
# pcs automatically, if present
target = @resource[:target]
level = @resource[:level]
cmd = 'stonith level remove ' + level.to_s + ' ' + target
pcs('delete', @resource[:name], cmd, @resource[:tries],
@resource[:try_sleep], @resource[:verify_on_create], @resource[:post_success_sleep])
end
def exists?
# stonith level output is a bit cumbersome to parse:
# Target: overcloud-galera-0
# Level 1 - stonith-fence_ipmilan-006809859383,stonith-fence_compute-fence-nova
# Target: overcloud-novacompute-0
# Level 1 - stonith-fence_ipmilan-006809859383,stonith-fence_compute-fence-nova
# Level 2 - stonith-fence_ipmilan-006809859383,stonith-fence_compute-fence-nova
# Target: overcloud-rabbit-0
# Level 2 - stonith-fence_ipmilan-006809859383,stonith-fence_compute-fence-nova
target = @resource[:target]
level = @resource[:level]
stonith_resources = @resource[:stonith_resources]
res = stonith_resources.join(',')
Puppet.debug("Exists: stonith level exists #{level} #{target} #{res}")
# The below cmd return the "Level X - ...." strings after the Target: string until the next
# Target: string (or until the bottom of the file if it is the last Target in the output
cmd = 'stonith level | sed -n "/^Target: ' + target + '$/,/^Target:/{/^Target: ' + target + '$/b;/^Target:/b;p}"'
cmd += ' | grep -e "Level[[:space:]]*' + level.to_s + '[[:space:]]*-[[:space:]]*' + res + '"'
ret = pcs('show', @resource[:name], cmd, @resource[:tries],
@resource[:try_sleep], @resource[:verify_on_create], @resource[:post_success_sleep])
return ret == false ? false : true
end
end

View File

@ -0,0 +1,99 @@
require 'puppet/parameter/boolean'
Puppet::Type.newtype(:pcmk_stonith_level) do
@doc = "Base resource definition for a pacemaker stonith level resource"
ensurable
newparam(:name) do
desc "A unique name for the stonith level"
end
newparam(:level) do
desc "The stonith level"
munge do |value|
if value.is_a?(String)
unless value =~ /^[\d]+$/
raise ArgumentError, "The stonith level must be an integer"
end
value = Integer(value)
end
raise ArgumentError, "Level must be an integer >= 1" if value < 1
value
end
end
newparam(:target) do
desc "The pacemaker stonith target to apply the level to"
end
newparam(:stonith_resources) do
desc "The array containing the list of stonith devices"
# FIXME: check for an array of strings
end
## borrowed from exec.rb
newparam(:tries) do
desc "The number of times to attempt to create a pcs resource.
Defaults to '1'."
munge do |value|
if value.is_a?(String)
unless value =~ /^[\d]+$/
raise ArgumentError, "Tries must be an integer"
end
value = Integer(value)
end
raise ArgumentError, "Tries must be an integer >= 1" if value < 1
value
end
defaultto 1
end
newparam(:try_sleep) do
desc "The time to sleep in seconds between 'tries'."
munge do |value|
if value.is_a?(String)
unless value =~ /^[-\d.]+$/
raise ArgumentError, "try_sleep must be a number"
end
value = Float(value)
end
raise ArgumentError, "try_sleep cannot be a negative number" if value < 0
value
end
defaultto 0
end
newparam(:verify_on_create, :boolean => true, :parent => Puppet::Parameter::Boolean) do
desc "Whether to verify pcs resource creation with an additional
call to 'pcs resource show' rather than just relying on the exit
status of 'pcs resource create'. When true, $try_sleep
determines how long to wait to verify and $post_success_sleep is
ignored. Defaults to `false`."
defaultto false
end
newparam(:post_success_sleep) do
desc "The time to sleep after successful pcs action. The reason to set
this is to avoid immediate back-to-back 'pcs resource create' calls
when creating multiple resources. Defaults to '0'."
munge do |value|
if value.is_a?(String)
unless value =~ /^[-\d.]+$/
raise ArgumentError, "post_success_sleep must be a number"
end
value = Float(value)
end
raise ArgumentError, "post_success_sleep cannot be a negative number" if value < 0
value
end
defaultto 0
end
end

View File

@ -0,0 +1,90 @@
# == Define: pacemaker::stonith::level
#
# A resource type to create pacemaker stonith levels
#
# === Parameters
#
# [*level*]
# (required) The stonith level
#
# [*target*]
# (required) The stonith level target
#
# [*stonith_resources*]
# (required) Array containing the list of stonith devices
#
# [*ensure*]
# (optional) Whether to make sure resource is created or removed
# Defaults to present
#
# [*post_success_sleep*]
# (optional) How long to wait acfter successful action
# Defaults to 0
#
# [*tries*]
# (optional) How many times to attempt to perform the action
# Defaults to 1
#
# [*try_sleep*]
# (optional) How long to wait between tries
# Defaults to 0
#
# [*verify_on_create*]
# (optional) Whether to verify creation of resource
# Defaults to false
#
# === Dependencies
#
# None
#
# === Authors
#
# Michele Baldessari <michele@acksyn.org>
#
# === Copyright
#
# Copyright (C) 2017 Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
define pacemaker::stonith::level(
$level,
$target,
$stonith_resources,
$ensure = 'present',
$post_success_sleep = 0,
$tries = 1,
$try_sleep = 0,
$verify_on_create = false,
) {
if $level == undef {
fail('Must provide level')
}
if $target == undef {
fail('Must provide target')
}
if $stonith_resources == undef {
fail('Must provide stonith_resources')
}
$res = join($stonith_resources, '_')
pcmk_stonith_level{ "stonith-level-${level}-${target}-${res}":
ensure => $ensure,
level => $level,
target => $target,
stonith_resources => $stonith_resources,
post_success_sleep => $post_success_sleep,
tries => $tries,
try_sleep => $try_sleep,
verify_on_create => $verify_on_create,
}
}

View File

@ -0,0 +1,86 @@
#
# Copyright (C) 2017 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
require 'spec_helper'
describe 'pacemaker::stonith::level', type: :define do
let(:title) { 'test' }
let(:pre_condition) { [
'class pacemaker::corosync {}',
'class { "pacemaker::corosync" : }'
] }
shared_examples_for 'pacemaker::stonith::level' do
context 'with ensure absent' do
let(:params) { {
:ensure => 'absent',
:level => '1',
:target => 'node-foo',
:stonith_resources => ['ipmi-node-foo','fence-bar'],
:tries => 1,
:try_sleep => 5,
:verify_on_create => false,
} }
it {
is_expected.to contain_pcmk_stonith_level("stonith-level-1-node-foo-ipmi-node-foo_fence-bar").with(
:ensure => 'absent',
:level => '1',
:target => 'node-foo',
:stonith_resources => ['ipmi-node-foo','fence-bar'],
:tries => 1,
:try_sleep => 5,
:verify_on_create => false,
)
}
end
context 'with params' do
let(:params) { {
:level => '1',
:target => 'node-foo',
:stonith_resources => ['ipmi-node-foo','fence-bar'],
:tries => 1,
:try_sleep => 5,
:verify_on_create => false,
} }
it {
is_expected.to contain_pcmk_stonith_level("stonith-level-1-node-foo-ipmi-node-foo_fence-bar").with(
:ensure => 'present',
:level => '1',
:target => 'node-foo',
:stonith_resources => ['ipmi-node-foo','fence-bar'],
:tries => 1,
:try_sleep => 5,
:verify_on_create => false,
)
}
end
end
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts.merge({ :hostname => 'node.example.com' })
end
it_behaves_like 'pacemaker::stonith::level'
end
end
end