From ebef43cadf09f622637b4286a1cb8771b58bca21 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Fri, 29 Sep 2017 22:36:25 +0200 Subject: [PATCH] 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 --- .../provider/pcmk_stonith_level/default.rb | 51 ++++++++++ lib/puppet/type/pcmk_stonith_level.rb | 99 +++++++++++++++++++ manifests/stonith/level.pp | 90 +++++++++++++++++ spec/defines/pacemaker_stonith_level_spec.rb | 86 ++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 lib/puppet/provider/pcmk_stonith_level/default.rb create mode 100644 lib/puppet/type/pcmk_stonith_level.rb create mode 100644 manifests/stonith/level.pp create mode 100644 spec/defines/pacemaker_stonith_level_spec.rb diff --git a/lib/puppet/provider/pcmk_stonith_level/default.rb b/lib/puppet/provider/pcmk_stonith_level/default.rb new file mode 100644 index 00000000..e6ae6084 --- /dev/null +++ b/lib/puppet/provider/pcmk_stonith_level/default.rb @@ -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 diff --git a/lib/puppet/type/pcmk_stonith_level.rb b/lib/puppet/type/pcmk_stonith_level.rb new file mode 100644 index 00000000..ac69073f --- /dev/null +++ b/lib/puppet/type/pcmk_stonith_level.rb @@ -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 diff --git a/manifests/stonith/level.pp b/manifests/stonith/level.pp new file mode 100644 index 00000000..387d3a92 --- /dev/null +++ b/manifests/stonith/level.pp @@ -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 +# +# === 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, + } +} diff --git a/spec/defines/pacemaker_stonith_level_spec.rb b/spec/defines/pacemaker_stonith_level_spec.rb new file mode 100644 index 00000000..7619c231 --- /dev/null +++ b/spec/defines/pacemaker_stonith_level_spec.rb @@ -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