Merge with fuel-infra/puppet-pacemaker

* Import all providers, specs and tests to this module
* Use corosync module to actually install paceamker

Fuel-CI: disable
Change-Id: I9a16ad1453b694aa0a3e78d079f9c57365a5fcf1
This commit is contained in:
Dmitry Ilyin 2016-04-01 05:04:39 +03:00 committed by Sofer Athlan-Guyot
parent 3cfaf36e2f
commit 4d2e554f68
254 changed files with 18608 additions and 1174 deletions

View File

@ -1,7 +1,7 @@
---
fixtures:
repositories:
firewall: "git://github.com/puppetlabs/puppetlabs-firewall"
stdlib: "git://github.com/puppetlabs/puppetlabs-stdlib"
firewall: "git://github.com/puppetlabs/puppetlabs-firewall"
symlinks:
my_module: "#{source_dir}"
pacemaker: "#{source_dir}"

10
.gitignore vendored
View File

@ -5,7 +5,13 @@ spec/fixtures/
.vagrant/
.bundle/
coverage/
log/
.idea/
*.swp
*.iml
openstack/
.*.sw
# documentation
doc
.yardoc
.yardwarns
strings.json

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--format documentation
--color

87
.rubocop.yml Normal file
View File

@ -0,0 +1,87 @@
AllCops:
Include:
- ./**/*.rb
Exclude:
- vendor/**/*
- pkg/**/*
- spec/fixtures/**/*
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
Max: 328
# 'Complexity' is very relative
Metrics/PerceivedComplexity:
Enabled: false
# 'Complexity' is very relative
Metrics/CyclomaticComplexity:
Enabled: false
# 'Complexity' is very relative
Metrics/AbcSize:
Enabled: false
# Method length is not necessarily an indicator of code quality
Metrics/MethodLength:
Enabled: false
# Module length is not necessarily an indicator of code quality
Metrics/ModuleLength:
Enabled: false
# Class length is not necessarily an indicator of code quality
Metrics/ClassLength:
Enabled: false
# dealbreaker:
Style/TrailingCommaInArguments:
Enabled: false
Style/TrailingCommaInLiteral:
Enabled: false
Style/ClosingParenthesisIndentation:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: true
Style/RegexpLiteral:
Enabled: true
Style/WordArray:
Enabled: true
# this catches the cases of using `module` for parser functions, types, or
# providers
Style/ClassAndModuleChildren:
Enabled: false
Style/Documentation:
Description: 'Document classes and non-namespace modules.'
Enabled: false
# More comfortable block layouts
Style/BlockDelimiters:
Enabled: false
Style/MultilineBlockLayout:
Enabled: false
Style/GuardClause:
Enabled: false
Style/NestedParenthesizedCalls:
Enabled: false
Style/ClassAndModuleCamelCase:
Enabled: false
Style/PredicateName:
Enabled: false
Style/VariableName:
Enabled: false
Style/MethodName:
Enabled: false
Style/FormatString:
Enabled: false

44
.travis.yml Normal file
View File

@ -0,0 +1,44 @@
---
sudo: false
language: ruby
cache: bundler
bundler_args: --without system_tests
before_install: rm Gemfile.lock || true
script:
- 'bundle exec rake $CHECK'
matrix:
fast_finish: true
include:
- rvm: 1.9.3
env: PUPPET_VERSION="~> 3.0" STRICT_VARIABLES="yes" CHECK=test
- rvm: 2.1.8
env: PUPPET_VERSION="~> 3.0" STRICT_VARIABLES="yes" CHECK=test
- rvm: 1.9.3
env: PUPPET_VERSION="~> 3.0" STRICT_VARIABLES="yes" CHECK=test FUTURE_PARSER=yes
- rvm: 2.1.8
env: PUPPET_VERSION="~> 3.0" STRICT_VARIABLES="yes" CHECK=test FUTURE_PARSER=yes
- rvm: 2.1.8
env: PUPPET_VERSION="~> 4.0" STRICT_VARIABLES="yes" CHECK=test
- rvm: 2.2.4
env: PUPPET_VERSION="~> 4.0" STRICT_VARIABLES="yes" CHECK=test
- rvm: 2.2.4
env: PUPPET_VERSION="~> 4.0" STRICT_VARIABLES="yes" CHECK=rubocop
- rvm: 2.3.0
env: PUPPET_VERSION="~> 4.0" STRICT_VARIABLES="yes" CHECK=test
allow_failures:
- rvm: 2.3.0
env: PUPPET_VERSION="~> 4.0" STRICT_VARIABLES="yes" CHECK=test
notifications:
email: false
deploy:
provider: puppetforge
user: puppet
password:
secure: ""
on:
tags: true
# all_branches is required to use tags
all_branches: true
# Only publish if our main Ruby target builds
rvm: 1.9.3
condition: "$FUTURE_PARSER = yes"

96
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,96 @@
This module has grown over time based on a range of contributions from
people using it. If you follow these contributing guidelines your patch
will likely make it into a release a little quicker.
## Contributing
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. [Contributor Code of Conduct](https://voxpupuli.org/coc/).
1. Fork the repo.
1. Create a separate branch for your change.
1. Run the tests. We only take pull requests with passing tests, and
documentation.
1. Add a test for your change. Only refactoring and documentation
changes require no new tests. If you are adding functionality
or fixing a bug, please add a test.
1. Squash your commits down into logical components. Make sure to rebase
against the current master.
1. Push the branch to your fork and submit a pull request.
Please be prepared to repeat some of these steps as our contributors review
your code.
## Dependencies
The testing and development tools have a bunch of dependencies,
all managed by [bundler](http://bundler.io/) according to the
[Puppet support matrix](http://docs.puppetlabs.com/guides/platforms.html#ruby-versions).
By default the tests use a baseline version of Puppet.
If you have Ruby 2.x or want a specific version of Puppet,
you must set an environment variable such as:
export PUPPET_VERSION="~> 4.2.0"
Install the dependencies like so...
bundle install
## Syntax and style
The test suite will run [Puppet Lint](http://puppet-lint.com/) and
[Puppet Syntax](https://github.com/gds-operations/puppet-syntax) to
check various syntax and style things. You can run these locally with:
bundle exec rake lint
bundle exec rake validate
## Running the unit tests
The unit test suite covers most of the code, as mentioned above please
add tests if you're adding new functionality. If you've not used
[rspec-puppet](http://rspec-puppet.com/) before then feel free to ask
about how best to test your new feature.
To run your all the unit tests
bundle exec rake spec SPEC_OPTS='--format documentation'
To run a specific spec test set the `SPEC` variable:
bundle exec rake spec SPEC=spec/foo_spec.rb
To run the linter, the syntax checker and the unit tests:
bundle exec rake test
## Integration tests
The unit tests just check the code runs, not that it does exactly what
we want on a real machine. For that we're using
[beaker](https://github.com/puppetlabs/beaker).
This fires up a new virtual machine (using vagrant) and runs a series of
simple tests against it after applying the module. You can run this
with:
bundle exec rake acceptance
This will run the tests on an Ubuntu 12.04 virtual machine. You can also
run the integration tests against Centos 6.5 with.
BEAKER_set=centos-64-x64 bundle exec rake acceptances
If you don't want to have to recreate the virtual machine every time you
can use `BEAKER_DESTROY=no` and `BEAKER_PROVISION=no`. On the first run you will
at least need `BEAKER_PROVISION` set to yes (the default). The Vagrantfile
for the created virtual machines will be in `.vagrant/beaker_vagrant_fies`.

73
Gemfile
View File

@ -1,40 +1,65 @@
source ENV['GEM_SOURCE'] || "https://rubygems.org"
group :development, :test do
gem 'puppetlabs_spec_helper', :require => 'false'
gem 'rspec-puppet', '~> 2.2.0', :require => 'false'
gem 'rspec-puppet-facts', :require => 'false'
gem 'metadata-json-lint', :require => 'false'
gem 'puppet-lint-param-docs', :require => 'false'
gem 'puppet-lint-absolute_classname-check', :require => 'false'
gem 'puppet-lint-absolute_template_path', :require => 'false'
gem 'puppet-lint-trailing_newline-check', :require => 'false'
gem 'puppet-lint-unquoted_string-check', :require => 'false'
gem 'puppet-lint-leading_zero-check', :require => 'false'
gem 'puppet-lint-variable_contains_upcase', :require => 'false'
gem 'puppet-lint-numericvariable', :require => 'false'
gem 'json', :require => 'false'
gem 'puppet-openstack_spec_helper',
:git => 'https://git.openstack.org/openstack/puppet-openstack_spec_helper',
:require => false
def location_for(place, fake_version = nil)
if place =~ /^(git[:@][^#]*)#(.*)/
[fake_version, { :git => $1, :branch => $2, :require => false }].compact
elsif place =~ /^file:\/\/(.*)/
['>= 0', { :path => File.expand_path($1), :require => false }]
else
[place, { :require => false }]
end
end
group :test do
gem 'rake', :require => false
gem 'rspec-puppet', :require => false
gem 'puppet-lint', :require => false
gem 'metadata-json-lint', :require => false
gem 'rspec-puppet-facts', :require => false
gem 'rspec', :require => false
gem 'rspec-puppet-utils', :require => false
gem 'puppet-lint-absolute_classname-check', :require => false
gem 'puppet-lint-leading_zero-check', :require => false
gem 'puppet-lint-trailing_comma-check', :require => false
gem 'puppet-lint-version_comparison-check', :require => false
gem 'puppet-lint-classes_and_types_beginning_with_digits-check', :require => false
gem 'puppet-lint-unquoted_string-check', :require => false
gem 'puppet-lint-variable_contains_upcase', :require => false
gem 'rubocop', :require => false
gem 'unicode-display_width', :require => false
gem 'puppetlabs_spec_helper', :require => false
end
group :development do
gem 'pry'
end
group :system_tests do
gem 'beaker-rspec', :require => 'false'
gem 'beaker-puppet_install_helper', :require => 'false'
gem 'r10k', :require => 'false'
if beaker_version = ENV['BEAKER_VERSION']
gem 'beaker', *location_for(beaker_version)
else
gem 'beaker', :require => false
end
if beaker_rspec_version = ENV['BEAKER_RSPEC_VERSION']
gem 'beaker-rspec', *location_for(beaker_rspec_version)
else
gem 'beaker-rspec', :require => false
end
gem 'beaker-puppet_install_helper', :require => false
end
if facterversion = ENV['FACTER_GEM_VERSION']
gem 'facter', facterversion, :require => false
gem 'facter', facterversion.to_s, :require => false, :groups => [:test]
else
gem 'facter', :require => false
gem 'facter', :require => false, :groups => [:test]
end
if puppetversion = ENV['PUPPET_GEM_VERSION']
gem 'puppet', puppetversion, :require => false
gem 'puppet', puppetversion, :require => false, :groups => [:test]
else
gem 'puppet', :require => false
gem 'puppet', :require => false, :groups => [:test]
end
# vim:ft=ruby

View File

@ -1,13 +0,0 @@
name 'radez-pacemaker'
version '0.2.0'
source 'https://github.com/radez/puppet-pacemaker'
author 'radez'
license 'Apache License, Version 2.0'
summary 'Puppet module to provide configuration managent of pacemaker and associated resources'
description 'Puppet module to provide configuration managent of pacemaker and associated resources'
project_page 'https://github.com/radez/puppet-pacemaker'
## Add dependencies, if any:
# dependency 'username/name', '>= 1.2.0'
dependency 'puppetlabs/stdlib'
dependency 'puppetlabs/firewall'

1102
README.md

File diff suppressed because it is too large Load Diff

View File

@ -3,23 +3,45 @@ require 'puppet-lint/tasks/puppet-lint'
require 'puppet-syntax/tasks/puppet-syntax'
require 'metadata-json-lint/rake_task'
# require 'puppet-strings/rake_tasks'
# require 'rubocop/rake_task'
# RuboCop::RakeTask.new
PuppetLint.configuration.log_format = '%{path}:%{linenumber}:%{check}:%{KIND}:%{message}'
PuppetLint.configuration.fail_on_warnings = true
PuppetLint.configuration.send('disable_80chars')
PuppetLint.configuration.send('relative')
PuppetLint.configuration.send('disable_140chars')
PuppetLint.configuration.send('disable_class_inherits_from_params_class')
PuppetLint.configuration.send('disable_documentation')
PuppetLint.configuration.send('disable_single_quote_string_with_variables')
PuppetSyntax.exclude_paths ||= []
PuppetSyntax.exclude_paths << "spec/fixtures/**/*"
PuppetSyntax.exclude_paths << "pkg/**/*"
PuppetSyntax.exclude_paths << "vendor/**/*"
exclude_paths = %w(
pkg/**/*
vendor/**/*
spec/**/*
)
PuppetLint.configuration.ignore_paths = exclude_paths
PuppetSyntax.exclude_paths = exclude_paths
Rake::Task[:lint].clear
PuppetLint::RakeTask.new :lint do |config|
config.ignore_paths = ["spec/**/*.pp", "vendor/**/*.pp"]
config.fail_on_warnings = true
config.log_format = '%{path}:%{linenumber}:%{KIND}: %{message}'
config.disable_checks = ["80chars", "class_inherits_from_params_class", "only_variable_string"]
desc 'Run acceptance tests'
RSpec::Core::RakeTask.new(:acceptance) do |t|
t.pattern = 'spec/acceptance'
end
desc 'Run metadata_lint, lint, syntax, and spec tests.'
task test: [
:metadata_lint,
:lint,
:syntax,
:spec,
]
desc 'Generate the Stonith modules'
task :generate_stonith do
sh './agent_generator/generate_manifests.sh'
end
ENV['BEAKER_debug'] = 'yes'
ENV['BEAKER_set'] = 'vagrant-ubuntu-14.04-64' unless ENV['BEAKER_set']
ENV['BEAKER_destroy'] = 'onpass' unless ENV['BEAKER_destroy']
ENV['BEAKER_provision'] = 'yes' unless ENV['BEAKER_provision']

12
TODO.md Normal file
View File

@ -0,0 +1,12 @@
* add pacemaker_group type
* pacemaker_location add date_expressions support
* pacemaker_location rules format/validation
* pacemaker_resource convert complex to simple and back
* pacemaker_resource add utilization support
* cleanup unused methods from pacemaker_nodes provider
* unit tests for location, colocation, order autorequire functions
* change tests behaviour according to the options and test several possible options
* noop provider is not working for non-ensurable types
* colocation/location/order will prevent its primitives from being removed. remove constraints first?
* primitive should use similar functions to constraint_location_add/remove to reduce code duplication
* primitive_is_started? and primitive_is_managed don't support resource defaults and management-mode

View File

@ -7,7 +7,6 @@
# * fence-generator.rb ilo.xml fence_ilo2 fence-agents-ilo2
# [ XML metadata, name of the class, name of the package for dependency check ]
require 'rexml/document'
class FencingMetadataParser
@ -17,33 +16,31 @@ class FencingMetadataParser
file = File.new(filename)
@doc = REXML::Document.new file
@params = []
@params_max_len = 14 # pcmk_host_list
@params_max_len = 14 # pcmk_host_list
end
def getPackageName()
return @packageName
def getPackageName
@packageName
end
def getAgentName()
return @agentName
def getAgentName
@agentName
end
def getParameters()
def getParameters
## result have to be array as order should be preserved
return @params unless @params.empty?
@doc.elements.each("resource-agent/parameters/parameter") { |p|
param = Hash.new
param["name"] = REXML::XPath.match(p, "string(./@name)")[0]
@doc.elements.each('resource-agent/parameters/parameter') { |p|
param = {}
param['name'] = REXML::XPath.match(p, 'string(./@name)')[0]
@params_max_len = param['name'].length if param['name'].length > @params_max_len
param["type"] = REXML::XPath.match(p, "string(./content/@type)")[0]
param['type'] = REXML::XPath.match(p, 'string(./content/@type)')[0]
## if 'default' is list then we can not enter it as parameter !!
## this is problem only for 'cmd_prompt'
param["default"] = REXML::XPath.match(p, "string(./content/@default)")[0]
param['default'] = REXML::XPath.match(p, 'string(./content/@default)')[0]
param['description'] = REXML::XPath.match(p, 'string(./shortdesc)')[0]
## remove parameters that are not usable during automatic execution
if not ["help", "version", "action"].include?(param["name"])
@params.push(param)
end
@params.push(param) unless %w(help version action).include?(param['name'])
}
@params
end
@ -151,13 +148,13 @@ eos
text = ''
@parser.getParameters.each { |p|
text += "# [*#{p['name']}*]\n"
text += "# #{p['description']}\n#\n"
text += "# #{p['description']}\n#\n"
}
text
end
def getManifestParameters
text = ""
text = ''
@parser.getParameters.each { |p|
text += format_param(p['name'])
}
@ -170,11 +167,11 @@ eos
text += format_param('tries')
text += format_param('try_sleep')
return text
text
end
def getVariableValues
text = ""
text = ''
@parser.getParameters.each { |p|
text += " $#{p['name']}_chunk = $#{p['name']} ? {\n"
text += " undef => '',\n"
@ -182,26 +179,26 @@ eos
text += " }\n"
}
return text
text
end
def getChunks
text = ""
text = ''
@parser.getParameters.each { |p|
text += "${#{p['name']}_chunk} "
}
return text
text
end
private
def format_param(param, value='undef')
def format_param(param, value = 'undef')
" $%-#{@parser.getMaxLen}s = %s,\n" % [param, value]
end
end
if ARGV.length != 3 then
puts "You have to enter three arguments: path to metadata, name of fence agent and fence agent package"
if ARGV.length != 3
puts 'You have to enter three arguments: path to metadata, name of fence agent and fence agent package'
exit 1
end

View File

@ -0,0 +1,35 @@
class hosts (
$hostname = 'node',
) {
resources { 'host':
purge => true,
}
host { 'localhost' :
ip => '127.0.0.1',
host_aliases => [$hostname],
}
}
class hostname (
$hostname = 'node',
) {
if $::osfamily == 'Debian' {
file { 'hostname' :
ensure => 'present',
path => '/etc/hostname',
content => "${hostname}\n",
}
}
exec { 'set-hostname' :
command => "hostname ${hostname}",
unless => "test `uname -n` = '${hostname}'",
provider => 'shell',
}
}
include ::hosts
include ::hostname

View File

@ -0,0 +1,29 @@
class properties {
pacemaker_property { 'stonith-enabled' :
ensure => 'present',
value => false,
}
pacemaker_property { 'no-quorum-policy' :
ensure => 'present',
value => 'ignore',
}
}
include ::properties
class { '::pacemaker::new' :
cluster_nodes => ['node'],
cluster_password => 'hacluster',
# firewall is not needed on a signle node
firewall_corosync_manage => false,
firewall_pcsd_manage => false,
}
Class['pacemaker::new'] ->
Class['properties']

View File

@ -0,0 +1,43 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_colocation {
ensure => 'present',
}
pacemaker_resource { 'colocation-test1' :
parameters => {
'fake' => '1',
},
}
pacemaker_resource { 'colocation-test2' :
parameters => {
'fake' => '2',
},
}
pacemaker_colocation { 'colocation-test2_with_and_after_colocation-test1' :
first => 'colocation-test1',
second => 'colocation-test2',
score => '200',
}
pacemaker_resource { 'colocation-test3' :
parameters => {
'fake' => '3',
},
}
pacemaker_colocation { 'colocation-test3_with_and_after_colocation-test1' :
first => 'colocation-test1',
second => 'colocation-test3',
score => '400',
}
Pacemaker_resource<||> ->
Pacemaker_colocation<||>

View File

@ -0,0 +1,23 @@
Pacemaker_resource {
ensure => 'absent',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_colocation {
ensure => 'absent',
}
pacemaker_resource { 'colocation-test1' :}
pacemaker_resource { 'colocation-test2' :}
pacemaker_colocation { 'colocation-test2_with_and_after_colocation-test1' :}
pacemaker_resource { 'colocation-test3' :}
pacemaker_colocation { 'colocation-test3_with_and_after_colocation-test1' :}
Pacemaker_colocation<||> ->
Pacemaker_resource<||>

View File

@ -0,0 +1,10 @@
#!/bin/sh
show() {
puppet resource pacemaker_colocation "${1}"
cibadmin --query --xpath "/cib/configuration/constraints/rsc_colocation[@id='${1}']"
echo '--------------------'
}
show 'colocation-test2_with_and_after_colocation-test1'
show 'colocation-test3_with_and_after_colocation-test1'

View File

@ -0,0 +1,43 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_colocation {
ensure => 'present',
}
pacemaker_resource { 'colocation-test1' :
parameters => {
'fake' => '1',
},
}
pacemaker_resource { 'colocation-test2' :
parameters => {
'fake' => '2',
},
}
pacemaker_colocation { 'colocation-test2_with_and_after_colocation-test1' :
first => 'colocation-test1',
second => 'colocation-test2',
score => '201',
}
pacemaker_resource { 'colocation-test3' :
parameters => {
'fake' => '3',
},
}
pacemaker_colocation { 'colocation-test3_with_and_after_colocation-test1' :
first => 'colocation-test1',
second => 'colocation-test3',
score => '401',
}
Pacemaker_resource<||> ->
Pacemaker_colocation<||>

View File

@ -0,0 +1,51 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_location {
ensure => 'present',
}
pacemaker_resource { 'location-test1' :
parameters => {
'fake' => '1',
},
}
$rules = [
{
'score' => '100',
'expressions' => [
{
'attribute' => 'a',
'operation' => 'defined',
},
]
},
{
'score' => '200',
'expressions' => [
{
'attribute' => 'b',
'operation' => 'defined',
},
]
}
]
pacemaker_location { 'location-test1_location_with_rule' :
primitive => 'location-test1',
rules => $rules,
}
pacemaker_location { 'location-test1_location_with_score' :
primitive => 'location-test1',
node => $pacemaker_node_name,
score => '200',
}
Pacemaker_resource<||> ->
Pacemaker_location<||>

View File

@ -0,0 +1,19 @@
Pacemaker_resource {
ensure => 'absent',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_location {
ensure => 'absent',
}
pacemaker_resource { 'location-test1' :}
pacemaker_location { 'location-test1_location_with_rule' :}
pacemaker_location { 'location-test1_location_with_score' :}
Pacemaker_location<||> ->
Pacemaker_resource<||>

View File

@ -0,0 +1,10 @@
#!/bin/sh
show() {
puppet resource pacemaker_location "${1}"
cibadmin --query --xpath "/cib/configuration/constraints/rsc_location[@id='${1}']"
echo '--------------------'
}
show 'location-test1_location_with_rule'
show 'location-test1_location_with_score'

View File

@ -0,0 +1,51 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_location {
ensure => 'present',
}
pacemaker_resource { 'location-test1' :
parameters => {
'fake' => '1',
},
}
$rules = [
{
'score' => '101',
'expressions' => [
{
'attribute' => 'a',
'operation' => 'defined',
},
]
},
{
'score' => '201',
'expressions' => [
{
'attribute' => 'b',
'operation' => 'defined',
},
]
}
]
pacemaker_location { 'location-test1_location_with_rule' :
primitive => 'location-test1',
rules => $rules,
}
pacemaker_location { 'location-test1_location_with_score' :
primitive => 'location-test1',
node => $pacemaker_node_name,
score => '201',
}
Pacemaker_resource<||> ->
Pacemaker_location<||>

View File

@ -0,0 +1,4 @@
pacemaker_operation_default { 'interval' :
ensure => 'present',
value => '300',
}

View File

@ -0,0 +1,3 @@
pacemaker_operation_default { 'interval' :
ensure => 'absent',
}

View File

@ -0,0 +1,9 @@
#!/bin/sh
show() {
puppet resource pacemaker_operation_default "${1}"
cibadmin --query --xpath "/cib/configuration/op_defaults/meta_attributes/nvpair[@name='${1}']"
echo '--------------------'
}
show 'interval'

View File

@ -0,0 +1,4 @@
pacemaker_operation_default { 'interval' :
ensure => 'present',
value => '301',
}

View File

@ -0,0 +1,41 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_order {
ensure => 'present',
}
pacemaker_resource { 'order-test1' :
parameters => {
'fake' => '1',
},
}
pacemaker_resource { 'order-test2' :
parameters => {
'fake' => '2',
},
}
pacemaker_order { 'order-test2_after_order-test1_score' :
first => 'order-test1',
second => 'order-test2',
score => '200',
}
# Pacemaker 1.1+
pacemaker_order { 'order-test2_after_order-test1_kind' :
first => 'order-test1',
first_action => 'promote',
second => 'order-test2',
second_action => 'demote',
kind => 'mandatory',
symmetrical => true,
}
Pacemaker_resource<||> ->
Pacemaker_order<||>

View File

@ -0,0 +1,21 @@
Pacemaker_resource {
ensure => 'absent',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_order {
ensure => 'absent',
}
pacemaker_resource { 'order-test1' :}
pacemaker_resource { 'order-test2' :}
pacemaker_order { 'order-test2_after_order-test1_score' :}
pacemaker_order { 'order-test2_after_order-test1_kind' :}
Pacemaker_order<||> ->
Pacemaker_resource<||>

View File

@ -0,0 +1,10 @@
#!/bin/sh
show() {
puppet resource pacemaker_order "${1}"
cibadmin --query --xpath "/cib/configuration/constraints/rsc_order[@id='${1}']"
echo '--------------------'
}
show 'order-test2_after_order-test1_score'
show 'order-test2_after_order-test1_kind'

View File

@ -0,0 +1,41 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
}
Pacemaker_order {
ensure => 'present',
}
pacemaker_resource { 'order-test1' :
parameters => {
'fake' => '1',
},
}
pacemaker_resource { 'order-test2' :
parameters => {
'fake' => '2',
},
}
pacemaker_order { 'order-test2_after_order-test1_score' :
first => 'order-test1',
second => 'order-test2',
score => '201',
}
# Pacemaker 1.1+
pacemaker_order { 'order-test2_after_order-test1_kind' :
first => 'order-test1',
first_action => 'promote',
second => 'order-test2',
second_action => 'start',
kind => 'serialize',
symmetrical => true,
}
Pacemaker_resource<||> ->
Pacemaker_order<||>

View File

@ -0,0 +1,9 @@
pacemaker_property { 'cluster-delay' :
ensure => 'present',
value => '50',
}
pacemaker_property { 'batch-limit' :
ensure => 'present',
value => '50',
}

View File

@ -0,0 +1,7 @@
pacemaker_property { 'cluster-delay' :
ensure => 'absent',
}
pacemaker_property { 'batch-limit' :
ensure => 'absent',
}

View File

@ -0,0 +1,10 @@
#!/bin/sh
show() {
puppet resource pacemaker_property "${1}"
cibadmin --query --xpath "/cib/configuration/crm_config/cluster_property_set/nvpair[@name='${1}']"
echo '--------------------'
}
show 'cluster-delay'
show 'batch-limit'

View File

@ -0,0 +1,9 @@
pacemaker_property { 'cluster-delay' :
ensure => 'present',
value => '51',
}
pacemaker_property { 'batch-limit' :
ensure => 'present',
value => '51',
}

View File

@ -0,0 +1,108 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_provider => 'pacemaker',
primitive_type => 'Dummy',
}
pacemaker_resource { 'test-simple1' :
parameters => {
'fake' => '1',
},
}
pacemaker_resource { 'test-simple2' :
parameters => {
'fake' => '2',
},
}
pacemaker_resource { 'test-simple-params1' :
parameters => {
'fake' => '3',
},
metadata => {
'migration-threshold' => '3',
'failure-timeout' => '120',
},
operations => {
'monitor' => {
'interval' => '20',
'timeout' => '10',
},
'start' => {
'timeout' => '30',
},
'stop' => {
'timeout' => '30',
},
},
}
pacemaker_resource { 'test-simple-params2' :
parameters => {
'fake' => '4',
},
metadata => {
'migration-threshold' => '3',
'failure-timeout' => '120',
},
operations => [
{
'name' => 'monitor',
'interval' => '10',
'timeout' => '10',
},
{
'name' => 'monitor',
'interval' => '60',
'timeout' => '10',
},
{
'name' => 'start',
'timeout' => '30',
},
{
'name' => 'stop',
'timeout' => '30',
},
],
}
pacemaker_resource { 'test-clone' :
complex_type => 'clone',
complex_metadata => {
'interleave' => true,
},
parameters => {
'fake' => '5',
},
}
pacemaker_resource { 'test-master' :
primitive_type => 'Stateful',
complex_type => 'master',
complex_metadata => {
'interleave' => true,
'master-max' => '1',
},
parameters => {
'fake' => '6',
},
}
pacemaker_resource { 'test-clone-change' :
primitive_type => 'Stateful',
complex_type => 'simple',
parameters => {
'fake' => '7',
},
}
pacemaker_resource { 'test-master-change' :
primitive_type => 'Stateful',
complex_type => 'master',
parameters => {
'fake' => '8',
},
}

View File

@ -0,0 +1,22 @@
Pacemaker_resource {
ensure => 'absent',
primitive_class => 'ocf',
primitive_provider => 'pacemaker',
primitive_type => 'Dummy',
}
pacemaker_resource { 'test-simple1' :}
pacemaker_resource { 'test-simple2' :}
pacemaker_resource { 'test-simple-params1' :}
pacemaker_resource { 'test-simple-params2' :}
pacemaker_resource { 'test-clone' :}
pacemaker_resource { 'test-master' :}
pacemaker_resource { 'test-clone-change' :}
pacemaker_resource { 'test-master-change' :}

View File

@ -0,0 +1,26 @@
#!/bin/sh
show() {
puppet resource pacemaker_resource "${1}"
cibadmin --query --xpath "/cib/configuration/resources/primitive[@id='${1}']"
echo '--------------------'
}
show_clone() {
puppet resource pacemaker_resource "${1}"
cibadmin --query --xpath "/cib/configuration/resources/clone[@id='${1}-clone']"
echo '--------------------'
}
show_master() {
puppet resource pacemaker_resource "${1}"
cibadmin --query --xpath "/cib/configuration/resources/master[@id='${1}-master']"
echo '--------------------'
}
show 'test-simple1'
show 'test-simple2'
show 'test-simple-params1'
show 'test-simple-params2'
show_clone 'test-clone'
show_master 'test-master'

View File

@ -0,0 +1,108 @@
Pacemaker_resource {
ensure => 'present',
primitive_class => 'ocf',
primitive_provider => 'pacemaker',
primitive_type => 'Dummy',
}
pacemaker_resource { 'test-simple1' :
parameters => {
'fake' => '2',
},
}
pacemaker_resource { 'test-simple2' :
parameters => {
'fake' => '3',
},
}
pacemaker_resource { 'test-simple-params1' :
parameters => {
'fake' => '4',
},
metadata => {
'migration-threshold' => '4',
'failure-timeout' => '121',
},
operations => {
'monitor' => {
'interval' => '21',
'timeout' => '11',
},
'start' => {
'timeout' => '31',
},
'stop' => {
'timeout' => '31',
},
},
}
pacemaker_resource { 'test-simple-params2' :
parameters => {
'fake' => '5',
},
metadata => {
'migration-threshold' => '4',
'failure-timeout' => '121',
},
operations => [
{
'name' => 'monitor',
'interval' => '11',
'timeout' => '11',
},
{
'name' => 'monitor',
'interval' => '61',
'timeout' => '11',
},
{
'name' => 'start',
'timeout' => '31',
},
{
'name' => 'stop',
'timeout' => '31',
},
],
}
pacemaker_resource { 'test-clone' :
complex_type => 'clone',
complex_metadata => {
'interleave' => true,
},
parameters => {
'fake' => '6',
},
}
pacemaker_resource { 'test-master' :
primitive_type => 'Stateful',
complex_type => 'master',
complex_metadata => {
'interleave' => true,
'master-max' => '1',
},
parameters => {
'fake' => '7',
},
}
pacemaker_resource { 'test-clone-change' :
primitive_type => 'Stateful',
complex_type => 'clone',
parameters => {
'fake' => '8',
},
}
pacemaker_resource { 'test-master-change' :
primitive_type => 'Stateful',
complex_type => 'simple',
parameters => {
'fake' => '9',
},
}

View File

@ -0,0 +1,4 @@
pacemaker_resource_default { 'resource-stickiness' :
ensure => 'present',
value => '100',
}

View File

@ -0,0 +1,3 @@
pacemaker_resource_default { 'resource-stickiness' :
ensure => 'absent',
}

View File

@ -0,0 +1,9 @@
#!/bin/sh
show() {
puppet resource pacemaker_resource_default "${1}"
cibadmin --query --xpath "/cib/configuration/rsc_defaults/meta_attributes/nvpair[@name='${1}']"
echo '--------------------'
}
show 'resource-stickiness'

View File

@ -0,0 +1,4 @@
pacemaker_resource_default { 'resource-stickiness' :
ensure => 'present',
value => '101',
}

View File

@ -0,0 +1,7 @@
pacemaker_resource { 'service-test1' :
ensure => 'absent',
}
pacemaker_resource { 'service-test2' :
ensure => 'absent',
}

52
examples/service/start.pp Normal file
View File

@ -0,0 +1,52 @@
# Using the wrapper
# a simple service
service { 'service-test1' :
ensure => 'running',
enable => true,
}
# apply a wrapper
::pacemaker::new::wrapper { 'service-test1' :
primitive_class => 'ocf',
primitive_provider => 'pacemaker',
primitive_type => 'Dummy',
parameters => {
'fake' => '1',
},
operations => {
'monitor' => {
'interval' => '10',
'timeout' => '10',
},
},
}
# Without the wrapper
pacemaker_resource { 'service-test2' :
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
parameters => {
'fake' => '2',
},
operations => {
'monitor' => {
'interval' => '10',
'timeout' => '10',
},
},
}
service { 'service-test2' :
ensure => 'running',
enable => true,
provider => 'pacemaker_xml',
}
Pacemaker_resource['service-test2'] ->
Service['service-test2']

52
examples/service/stop.pp Normal file
View File

@ -0,0 +1,52 @@
# Using the wrapper
# a simple service
service { 'service-test1' :
ensure => 'stopped',
enable => true,
}
# apply a wrapper
::pacemaker::new::wrapper { 'service-test1' :
primitive_class => 'ocf',
primitive_provider => 'pacemaker',
primitive_type => 'Dummy',
parameters => {
'fake' => '1',
},
operations => {
'monitor' => {
'interval' => '10',
'timeout' => '10',
},
},
}
# Without the wrapper
pacemaker_resource { 'service-test2' :
ensure => 'present',
primitive_class => 'ocf',
primitive_type => 'Dummy',
primitive_provider => 'pacemaker',
parameters => {
'fake' => '2',
},
operations => {
'monitor' => {
'interval' => '10',
'timeout' => '10',
},
},
}
service { 'service-test2' :
ensure => 'stopped',
enable => true,
provider => 'pacemaker_xml',
}
Pacemaker_resource['service-test2'] ->
Service['service-test2']

View File

@ -0,0 +1,7 @@
require 'facter'
Facter.add('pacemaker_node_name') do
setcode do
Facter::Core::Execution.exec 'crm_node -n'
end
end

31
lib/pacemaker/options.rb Normal file
View File

@ -0,0 +1,31 @@
module Pacemaker
# pacemaker options submodule
# takes the options structure from the YAML file
# other options sources can be implemented here
module Options
# the YAML file with pacemaker options
# @return [String]
def self.pacemaker_options_file
File.join File.dirname(__FILE__), 'options.yaml'
end
# pacemaker options structure (class level)
# @return [Hash]
def self.pacemaker_options
return @pacemaker_options if @pacemaker_options
@pacemaker_options = YAML.load_file pacemaker_options_file
end
# pacemaker options structure (instance level)
# @return [Hash]
def pacemaker_options
Pacemaker::Options.pacemaker_options
end
# maximum possible waiting time of retry functions
# @return [Integer]
def max_wait_time
pacemaker_options[:retry_count] * pacemaker_options[:retry_step]
end
end
end

View File

@ -0,0 +1,99 @@
---
# how many times a command should retry if it's failing
:retry_count: 360
# how long to wait between retries (seconds)
:retry_step: 5
# how long to wait for a single command to finish running (seconds)
:retry_timeout: 60
# count false or nil block return values as failures or only exceptions?
:retry_false_is_failure: true
# raise error if no more retries left and command is still failing?
:retry_fail_on_timeout: true
# what cluster properties should be shown on the debug status output
:debug_show_properties:
- symmetric-cluster
- no-quorum-policy
# don't actually do any changes to the system
# only show what command would have been run
:debug_enabled: false
# how do we determine that the service have been started?
# :global - The service is running on any node
# :master - The service is running in the master mode on any node
# :local - The service is running on the local node
:start_mode_master: :master
:start_mode_clone: :global
:start_mode_simple: :global
# what method should be used to stop the service?
# :global - Stop the running service by disabling it
# :local - Stop the locally running service by banning it on this node
# Note: by default restart does not stop services
# if they are not running locally on the node
:stop_mode_master: :local
:stop_mode_clone: :local
:stop_mode_simple: :global
# what service is considered running?
# :global - The service is running on any node
# :local - The service is running on the local node
:status_mode_master: :local
:status_mode_clone: :local
:status_mode_simple: :global
# cleanup the primitive during these actions?
:cleanup_on_status: false
:cleanup_on_start: true
:cleanup_on_stop: true
# try to stop and disable the basic service on these provider actions
# the basic service is the service managed by the system
# init scripts or the upstart/systemd units
# in order to run the Pacemaker service the basic service
# should be stopped and disabled first so it will not mess
# with the OCF script
:disable_basic_service_on_status: false
:disable_basic_service_on_start: true
:disable_basic_service_on_stop: false
# don't try to stop basic service for these primitive classes
# because they are based on the native service manager
# and the basic service and the Pacemaker service is the same thing
:native_based_primitive_classes:
- lsb
- systemd
- upstart
# add location constraint to allow the service to run on the current node
# useful for asymmetric cluster mode
:add_location_constraint: true
# restart the service only if it's running on this node
# and skip restart if it's running elsewhere
:restart_only_if_local: true
# cleanup primitive only if it has failures
:cleanup_only_if_failures: true
# use prefetch in the providers
:prefetch: false
# Use additional idempotency checks before cibadmin create and delete actions
# It may be needed if many nodes are running at the same time
:cibadmin_idempotency_checks: true
# Should the constraints auto require their primitives?
# It can cause unwanted dependency cycles.
:autorequire_primitives: false
# meta attributes that are related to the primitive's service status
# and should be excluded from the configuration
:status_meta_attributes:
- target-role
- is-managed

View File

@ -0,0 +1,40 @@
module Pacemaker
# this submodule contains "pcs" based function for cluster property provider
module PcsClusterProperty
# @return [String]
def pcs_cluster_properties_list
pcs 'property', 'list'
rescue Puppet::ExecutionFailure
''
end
# @return [Hash]
def pcs_cluster_properties
pcs_list_to_hash pcs_cluster_properties_list
end
# @return [String,true,false,nil]
def pcs_cluster_property_value(name)
pcs_cluster_properties.fetch name.to_s, nil
end
# @param name [String]
# @param value [String,true,false]
def pcs_cluster_property_set(name, value)
cmd = ['property', 'set', "#{name}=#{value}"]
retry_block { pcs_safe cmd }
end
# @param name [String]
def pcs_cluster_property_delete(name)
cmd = ['property', 'unset', name]
retry_block { pcs_safe cmd }
end
# @param name [String]
# @return [true,false]
def pcs_cluster_property_defined?(name)
pcs_cluster_properties.key? name.to_s
end
end
end

View File

@ -0,0 +1,48 @@
module Pacemaker
# this submodule contains "pcs" based common functions
module PcsCommon
# check if debug is enabled either in the pacemaker options
# or the resource has the 'debug' parameter and it's enabled
# @return [TrueClass,FalseClass]
def debug_mode_enabled?
return true if pacemaker_options[:debug_enabled]
return true if @resource && @resource.parameters.keys.include?(:debug) && @resource[:debug]
false
end
# safe pcs command
# @param args [Array] command arguments
# @return [String,NilClass]
def pcs_safe(*args)
command_line = (['pcs'] + args).join ' '
if debug_mode_enabled?
debug "Exec: #{command_line}"
return
end
begin
pcs *args
rescue StandardError => exception
debug "Command execution have failed: #{command_line}"
raise exception
end
end
# parse a list of "key: value" data to a hash
# @param list [String]
# @return [Hash]
def pcs_list_to_hash(list)
hash = {}
list.split("\n").each do |line|
line_arr = line.split ':'
next unless line_arr.length == 2
name = line_arr[0].chomp.strip
value = line_arr[1].chomp.strip
next if name.empty? || value.empty?
value = false if value == 'false'
value = true if value == 'true'
hash.store name, value
end
hash
end
end
end

View File

@ -0,0 +1,40 @@
module Pacemaker
# this submodule contains "pcs" based function for operation default provider
module PcsOperationDefault
# @return [String]
def pcs_operation_default_list
pcs 'resource', 'op', 'defaults'
rescue Puppet::ExecutionFailure
''
end
# @return [Hash]
def pcs_operation_defaults
pcs_list_to_hash pcs_operation_default_list
end
# @return [String,true,false,nil]
def pcs_operation_default_value(name)
pcs_operation_defaults.fetch name.to_s, nil
end
# @param name [String]
# @param value [String,true,false]
def pcs_operation_default_set(name, value)
cmd = ['resource', 'op', 'defaults', "#{name}=#{value}"]
retry_block { pcs_safe cmd }
end
# @param name [String]
def pcs_operation_default_delete(name)
cmd = ['resource', 'op', 'defaults', "#{name}="]
retry_block { pcs_safe cmd }
end
# @param name [String]
# @return [true,false]
def pcs_operation_default_defined?(name)
pcs_operation_defaults.key? name.to_s
end
end
end

View File

@ -0,0 +1,64 @@
module Pacemaker
# this submodule contains "pcs" based function for cluster property provider
module PcsPcsdAuth
# run the 'pcs cluster auth' command and capture
# the debug output, returned by the pcsd.cli ruby tool
# returns nil if could not get the data
# @param nodes [Array<String>] the list of cluster nodes top auth
# @param username [String]
# @param password [String]
# @param force [String] auth even if already have been auth'ed
# @param local [String] auth only the local node
# @return [String,nil]
def pcs_auth_command(nodes, username, password, force=false, local=false)
command = %w(cluster auth --debug)
command << '--force' if force
command << '--local' if local
command += [ '-u', username ]
command += [ '-p', password ]
command += [nodes]
command.flatten!
begin
output = pcs *command
rescue Puppet::ExecutionFailure => e
output = e.to_s
end
return unless output
inside_debug_block = false
result = []
output.split("\n").each do |line|
inside_debug_block = false if line == '--Debug Output End--'
result << line if inside_debug_block
inside_debug_block = true if line == '--Debug Output Start--'
end
return unless result.any?
result.join("\n")
end
# parse the debug output of the pcs auth command
# to a hash of nodes and their statuses
# returns nil on error
# @param result [String]
# @return [Hash<String => String>,nil]
def pcs_auth_parse(result)
result_structure = begin
JSON.load result
rescue StandardError
nil
end
return unless result_structure.is_a? Hash
responses = result_structure.fetch('data', {}).fetch('auth_responses', {})
status_hash = {}
responses.each do |node, response|
next unless response.is_a? Hash
node_status = response['status']
next unless node_status
status_hash.store node, node_status
end
status_hash
end
end
end

View File

@ -0,0 +1,40 @@
module Pacemaker
# this submodule contains "pcs" based function for resource default provider
module PcsResourceDefault
# @return [String]
def pcs_resource_default_list
pcs 'resource', 'defaults'
rescue Puppet::ExecutionFailure
''
end
# @return [Hash]
def pcs_resource_defaults
pcs_list_to_hash pcs_resource_default_list
end
# @return [String,true,false,nil]
def pcs_resource_default_value(name)
pcs_resource_defaults.fetch name.to_s, nil
end
# @param name [String]
# @param value [String,true,false]
def pcs_resource_default_set(name, value)
cmd = ['resource', 'defaults', "#{name}=#{value}"]
retry_block { pcs_safe cmd }
end
# @param name [String]
def pcs_resource_default_delete(name)
cmd = ['resource', 'defaults', "#{name}="]
retry_block { pcs_safe cmd }
end
# @param name [String]
# @return [true,false]
def pcs_resource_default_defined?(name)
pcs_resource_defaults.key? name.to_s
end
end
end

169
lib/pacemaker/type.rb Normal file
View File

@ -0,0 +1,169 @@
require 'set'
module Pacemaker
# contains functions that can be included to the pacemaker types
module Type
# output IS and SHOULD values for debugging
# @param is [Object] the current value of the parameter
# @param should [Object] the catalog value of the parameter
# @param tag [String] log tag comment to trace calls
def insync_debug(is, should, tag = nil)
debug "insync?: #{tag}" if tag
debug "IS: #{is.inspect} #{is.class}"
debug "SH: #{should.inspect} #{should.class}"
end
# return inspected data structure, used in should_to_s and is_to_s functions
# @param data [Object]
# @return [String]
def inspect_to_s(data)
data.inspect
end
# convert data structure's keys and values to strings
# @param data [Object]
# @return [Object]
def stringify_data(data)
if data.is_a? Hash
new_data = {}
data.each do |key, value|
new_data.store stringify_data(key), stringify_data(value)
end
data.clear
data.merge! new_data
elsif data.is_a? Array
data.map! do |element|
stringify_data element
end
else
data.to_s
end
end
# modify provided operations data
# @param [Hash,Array] operations_input parameter value from catalog
def munge_operations(operations_input)
operations_input = [operations_input] unless operations_input.is_a? Array
operations = Set.new
operations_input.each do |operation|
# operations are an array of sets
if operation.is_a? Set
operations.merge operation
next
end
# # operations were provided as an array of hashes
if operation.is_a? Hash and operation['name']
munge_operation operation
operations.add operation
next
end
# operations were provided as a hash of hashes
operation.each do |operation_name, operation_data|
next unless operation_data.is_a? Hash
operation = {}
if operation_name.include? ':'
operation_name_array = operation_name.split(':')
operation_name = operation_name_array[0]
if not operation_data['role'] and operation_name_array[1]
operation_data['role'] = operation_name_array[1]
end
end
operation['name'] = operation_name
operation.merge! operation_data
munge_operation operation
operations.add operation if operation.any?
end
end
operations
end
# munge a single operations hash
# @param [Hash] operation
def munge_operation(operation)
return unless operation.is_a? Hash
operation['name'] = 'monitor' unless operation['name']
operation['interval'] = '0' unless operation['name'] == 'monitor'
operation['interval'] = '0' unless operation['interval']
operation['role'].capitalize! if operation['role']
operation
end
# compare meta_attribute hashes excluding status meta attributes
# @param is [Hash]
# @param should [Hash]
# @return [TrueClass,FalseClass]
def compare_meta_attributes(is, should)
return unless is.is_a?(Hash) && should.is_a?(Hash)
is_without_state = is.reject do |k, _v|
pacemaker_options[:status_meta_attributes].include? k.to_s
end
should_without_state = should.reject do |k, _v|
pacemaker_options[:status_meta_attributes].include? k.to_s
end
result = is_without_state == should_without_state
debug "compare_meta_attributes: #{result}"
result
end
# sort operations array before insync?
# to make different order and same data arrays equal
# @param is [Array]
# @param should [Array]
# @return [TrueClass,FalseClass]
def compare_operations(is, should)
is = is.first if is.is_a? Array
should = should.first if should.is_a? Array
result = (is == should)
debug "compare_operations: #{result}"
result
end
# remove status related meta attributes
# from the meta attributes hash
# @param attributes_from [Hash]
# @return [Hash]
def munge_meta_attributes(attributes_from)
attributes_to = {}
attributes_from.each do |name, parameters|
next if pacemaker_options[:status_meta_attributes].include? name
attributes_to.store name, parameters
end
attributes_to
end
# normalize a single location rule
# @param rule [Hash] rule structure
# @param rule_number [Integer] rule index number
# @param title [String] constraint name
# @return [Hash] normalized rule structure
def munge_rule(rule, rule_number, title)
rule['id'] = "#{title}-rule-#{rule_number}" unless rule['id']
rule['boolean-op'] = 'or' unless rule['boolean-op']
rule['score'].gsub! 'inf', 'INFINITY' if rule['score']
if rule['expressions']
unless rule['expressions'].is_a? Array
expressions_array = []
expressions_array << rule['expressions']
rule['expressions'] = expressions_array
end
expression_number = 0
rule['expressions'].each do |expression|
unless expression['id']
expression['id'] = "#{title}-rule-#{rule_number}-expression-#{expression_number}"
end
expression_number += 1
end
end
rule
end
# remove "-clone" or "-master" suffix
# and "role" suffix (:Master, :Slave) from a primitive's name
# @param primitive [String]
# @return [String]
def primitive_base_name(primitive)
primitive = primitive.split(':').first
primitive.gsub(/-clone$|-master$/, '')
end
end
end

235
lib/pacemaker/wait.rb Normal file
View File

@ -0,0 +1,235 @@
module Pacemaker
# functions that can wait for something repeatedly
# polling the system status until the condition is met
module Wait
# retry the given command until it runs without errors
# or for RETRY_COUNT times with RETRY_STEP sec step
# print cluster status report on fail
# @param options [Hash]
def retry_block(options = {})
options = pacemaker_options.merge options
options[:retry_count].times do
begin
out = Timeout.timeout(options[:retry_timeout]) { yield }
if options[:retry_false_is_failure]
return out if out
else
return out
end
rescue => e
debug "Execution failure: #{e.message}"
end
sleep options[:retry_step]
end
raise "Execution timeout after #{options[:retry_count] * options[:retry_step]} seconds!" if options[:retry_fail_on_timeout]
end
# wait for pacemaker to become online
# @param comment [String] log tag comment to trace calls
def wait_for_online(comment = nil)
message = "Waiting #{max_wait_time} seconds for Pacemaker to become online"
message += " (#{comment})" if comment
debug message
retry_block { online? }
debug 'Pacemaker is online'
end
# wait until a primitive has known status
# @param primitive [String] primitive name
# @param node [String] on this node if given
def wait_for_status(primitive, node = nil)
message = "Waiting #{max_wait_time} seconds for a known status of '#{primitive}'"
message += " on node '#{node}'" if node
debug message
retry_block do
cib_reset 'wait_for_status'
!primitive_status(primitive).nil?
end
message = "Primitive '#{primitive}' has status '#{primitive_status primitive}'"
message += " on node '#{node}'" if node
debug message
end
# wait for primitive to start
# if node is given then start on this node
# @param primitive [String] primitive id
# @param node [String] on this node if given
def wait_for_start(primitive, node = nil)
message = "Waiting #{max_wait_time} seconds for the service '#{primitive}' to start"
message += " on node '#{node}'" if node
debug message
retry_block do
cib_reset 'wait_for_start'
primitive_is_running? primitive, node
end
message = "Service '#{primitive}' have started"
message += " on node '#{node}'" if node
debug message
end
# wait for primitive to start as a master
# if node is given then start as a master on this node
# @param primitive [String] primitive id
# @param node [String] on this node if given
def wait_for_master(primitive, node = nil)
message = "Waiting #{max_wait_time} seconds for the service '#{primitive}' to start master"
message += " on node '#{node}'" if node
debug message
retry_block do
cib_reset 'wait_for_master'
primitive_has_master_running? primitive, node
end
message = "Service '#{primitive}' have started master"
message += " on node '#{node}'" if node
debug message
end
# wait for primitive to stop
# if node is given then start on this node
# @param primitive [String] primitive id
# @param node [String] on this node if given
def wait_for_stop(primitive, node = nil)
message = "Waiting #{max_wait_time} seconds for the service '#{primitive}' to stop"
message += " on node '#{node}'" if node
debug message
retry_block do
cib_reset 'wait_for_stop'
result = primitive_is_running? primitive, node
result.is_a? FalseClass
end
message = "Service '#{primitive}' was stopped"
message += " on node '#{node}'" if node
debug message
end
# add a new primitive to CIB
# and wait for it to be actually created
# @param xml [String, REXML::Element] XML block to add
# @param primitive [String] the id of the new primitive
# @param scope [String] XML root scope
def wait_for_primitive_create(xml, primitive, scope = 'resources')
message = "Waiting #{max_wait_time} seconds for the primitive '#{primitive}' to be created"
debug message
retry_block do
if pacemaker_options[:cibadmin_idempotency_checks]
cib_reset 'wait_for_primitive_create'
break true if primitive_exists? primitive
end
cibadmin_create xml, scope
end
message = "Primitive '#{primitive}' was created"
debug message
end
# remove a primitive from CIB
# and wait for it to be actually removed
# @param xml [String, REXML::Element] XML block to remove
# @param primitive [String] the id of the removed primitive
# @param scope [String] XML root scope
def wait_for_primitive_remove(xml, primitive, scope = 'resources')
message = "Waiting #{max_wait_time} seconds for the primitive '#{primitive}' to be removed"
debug message
retry_block do
if pacemaker_options[:cibadmin_idempotency_checks]
cib_reset 'wait_for_primitive_remove'
break true unless primitive_exists? primitive
end
cibadmin_delete xml, scope
end
message = "Primitive '#{primitive}' was removed"
debug message
end
# update a primitive in CIB
# and wait for it to be actually updated
# @param xml [String, REXML::Element] XML block to update
# @param primitive [String] the id of the updated primitive
# @param scope [String] XML root scope
def wait_for_primitive_update(xml, primitive, scope = 'resources')
message = "Waiting #{max_wait_time} seconds for the primitive '#{primitive}' to be updated"
debug message
retry_block do
# replace action is already idempotent
cibadmin_replace xml, scope
end
message = "Primitive '#{primitive}' was updated"
debug message
end
# add a new constraint to CIB
# and wait for it to be actually created
# @param xml [String, REXML::Element] XML block to add
# @param constraint [String] the id of the new constraint
# @param scope [String] XML root scope
def wait_for_constraint_create(xml, constraint, scope = 'constraints')
message = "Waiting #{max_wait_time} seconds for the constraint '#{constraint}' to be created"
debug message
retry_block do
if pacemaker_options[:cibadmin_idempotency_checks]
cib_reset 'wait_for_constraint_create'
break true if constraint_exists? constraint
end
cibadmin_create xml, scope
end
message = "Constraint '#{constraint}' was created"
debug message
end
# remove a constraint from CIB
# and wait for it to be actually removed
# @param xml [String, REXML::Element] XML block to remove
# @param constraint [String] the id of the removed constraint
# @param scope [String] XML root scope
def wait_for_constraint_remove(xml, constraint, scope = 'constraints')
message = "Waiting #{max_wait_time} seconds for the constraint '#{constraint}' to be removed"
debug message
retry_block do
if pacemaker_options[:cibadmin_idempotency_checks]
cib_reset 'wait_for_constraint_remove'
break true unless constraint_exists? constraint
end
cibadmin_delete xml, scope
end
message = "Constraint '#{constraint}' was removed"
debug message
end
# update a constraint in CIB
# and wait for it to be actually updated
# @param xml [String, REXML::Element] XML block to update
# @param constraint [String] the id of the updated constraint
# @param scope [String] XML root scope
def wait_for_constraint_update(xml, constraint, scope = 'constraints')
message = "Waiting #{max_wait_time} seconds for the constraint '#{constraint}' to be updated"
debug message
retry_block do
# replace action is already idempotent
cibadmin_replace xml, scope
end
message = "Constraint '#{constraint}' was updated"
debug message
end
# check if pacemaker is online and we can work with it
# * pacemaker is online if cib can be downloaded
# * dc_version attribute can be obtained
# * DC have been designated
# Times out a stuck command calls and catches failed command calls
# @return [TrueClass,FalseClass]
def online?
Timeout.timeout(pacemaker_options[:retry_timeout]) do
return false unless dc_version
return false unless dc
return false unless cib_section_node_state
true
end
rescue Puppet::ExecutionFailure => e
debug "Cluster is offline: #{e.message}"
false
rescue Timeout::Error
debug 'Online check timeout!'
false
end
end
end

123
lib/pacemaker/xml/cib.rb Normal file
View File

@ -0,0 +1,123 @@
# the Pacemaker module contains many submodules separated
# by the preformed functions. They are later merged
# together in the 'provided' file
module Pacemaker
# this submodule contains the basic functions
# for low-level actions with CIB data
module Cib
# get the raw CIB from Pacemaker
# @return [String] cib xml data
def raw_cib
raw_cib = cibadmin '-Q'
raise 'Could not dump CIB XML!' if !raw_cib || raw_cib == ''
raw_cib
end
# REXML::Document of the CIB data
# @return [REXML::Document] at '/'
def cib
return @cib if @cib
@cib = REXML::Document.new(raw_cib)
end
# insert a new cib xml data instead of retrieving it
# can be used either for prefetching or for debugging
# @param cib [String,REXML::Document] CIB XML text or element
def cib=(cib)
@cib = if cib.is_a? REXML::Document
cib
else
REXML::Document.new(cib)
end
end
# check id the CIB is retrieved and memorized
# @return [TrueClass,FalseClass]
def cib?
!@cib.nil?
end
# add a new XML element to CIB
# @param xml [String, REXML::Element] XML block to add
# @param scope [String] XML root scope
def cibadmin_create(xml, scope = nil)
xml = xml_pretty_format xml if xml.is_a? REXML::Element
options = %w(--force --sync-call --create)
options += ['--scope', scope.to_s] if scope
cibadmin_safe options, '--xml-text', xml.to_s
end
# delete the XML element to CIB
# @param xml [String, REXML::Element] XML block to delete
# @param scope [String] XML root scope
def cibadmin_delete(xml, scope = nil)
xml = xml_pretty_format xml if xml.is_a? REXML::Element
options = %w(--force --sync-call --delete)
options += ['--scope', scope.to_s] if scope
cibadmin_safe options, '--xml-text', xml.to_s
end
# modify the XML element
# @param xml [String, REXML::Element] XML element to modify
# @param scope [String] XML root scope
def cibadmin_modify(xml, scope = nil)
xml = xml_pretty_format xml if xml.is_a? REXML::Element
options = %w(--force --sync-call --modify)
options += ['--scope', scope.to_s] if scope
cibadmin_safe options, '--xml-text', xml.to_s
end
# replace the XML element
# @param xml [String, REXML::Element] XML element to replace
# @param scope [String] XML root scope
def cibadmin_replace(xml, scope = nil)
xml = xml_pretty_format xml if xml.is_a? REXML::Element
options = %w(--force --sync-call --replace)
options += ['--scope', scope.to_s] if scope
cibadmin_safe options, '--xml-text', xml.to_s
end
# get the name of the DC (Designated Controller) node
# used to determine if the cluster have elected one and is ready
# @return [String, nil]
def dc
cib_element = cib.elements['/cib']
return unless cib_element
dc_node_id = cib_element.attribute('dc-uuid')
return unless dc_node_id
return if dc_node_id == 'NONE'
dc_node_id.to_s
end
# get the dc_version string from the CIB configuration
# used to determine that the cluster have finished forming a correct cib structure
# uses an independent command call because CIB may not be ready yet
# @return [String, nil]
def dc_version
dc_version = crm_attribute '-q', '--type', 'crm_config', '--query', '--name', 'dc-version'
return if dc_version.empty?
dc_version
end
# reset all mnemoization variables
# to force pacemaker to reload all the data structures
# @param comment [String] log file comment tag to trace calls
def cib_reset(comment = nil)
message = 'Call: cib_reset'
message += " (#{comment})" if comment
debug message
@cib = nil
@primitives_structure = nil
@locations_structure = nil
@colocations_structure = nil
@orders_structure = nil
@node_status_structure = nil
@cluster_properties_structure = nil
@nodes_structure = nil
@resource_defaults_structure = nil
@operation_defaults_structure = nil
end
end
end

View File

@ -0,0 +1,43 @@
module Pacemaker
# functions related to colocations constraints
# main structure "constraint_colocations"
module ConstraintColocations
# get colocation constraints and use mnemoization on the list
# @return [Hash<String => Hash>]
def constraint_colocations
return @colocations_structure if @colocations_structure
@colocations_structure = constraints 'rsc_colocation'
end
# check if colocation constraint exists
# @param id [String] the constraint id
# @return [TrueClass,FalseClass]
def constraint_colocation_exists?(id)
constraint_colocations.key? id
end
# add a colocation constraint
# @param colocation_structure [Hash<String => String>] the location data structure
def constraint_colocation_add(colocation_structure)
colocation_patch = xml_document
colocation_element = xml_rsc_colocation colocation_structure
raise "Could not create XML patch from colocation '#{colocation_structure.inspect}'!" unless colocation_element
colocation_patch.add_element colocation_element
wait_for_constraint_create xml_pretty_format(colocation_patch.root), colocation_structure['id']
end
# remove a colocation constraint
# @param id [String] the constraint id
def constraint_colocation_remove(id)
wait_for_constraint_remove "<rsc_colocation id='#{id}'/>\n", id
end
# generate rsc_colocation elements from data structure
# @param data [Hash]
# @return [REXML::Element]
def xml_rsc_colocation(data)
return unless data && data.is_a?(Hash)
xml_element 'rsc_colocation', data, 'type'
end
end
end

View File

@ -0,0 +1,104 @@
module Pacemaker
# functions related to locations constraints
# main structure "constraint_locations"
module ConstraintLocations
# construct the constraint unique name
# from primitive's and node's names
# @param primitive [String]
# @param node [String]
# @return [String]
def service_location_name(primitive, node)
"#{primitive}-on-#{node}"
end
# check if service location exists for this primitive on this node
# @param primitive [String] the primitive's name
# @param node [String] the node's name
# @return [true,false]
def service_location_exists?(primitive, node)
id = service_location_name primitive, node
constraint_location_exists? id
end
# add a location constraint to enable a service on a node
# @param primitive [String] the primitive's name
# @param node [String] the node's name
# @param score [Numeric,String] score value
def service_location_add(primitive, node, score = 100)
location_structure = {
'id' => service_location_name(primitive, node),
'node' => node,
'rsc' => primitive,
'score' => score,
}
constraint_location_add location_structure
end
# remove the service location on this node
# @param primitive [String] the primitive's name
# @param node [String] the node's name
def service_location_remove(primitive, node)
id = service_location_name primitive, node
constraint_location_remove id
end
# get location constraints and use mnemoization on the list
# @return [Hash<String => Hash>]
def constraint_locations
return @locations_structure if @locations_structure
@locations_structure = constraints 'rsc_location'
end
# add a location constraint
# @param location_structure [Hash<String => String>] the location data structure
def constraint_location_add(location_structure)
location_patch = xml_document
location_element = xml_rsc_location location_structure
raise "Could not create XML patch from location '#{location_structure.inspect}'!" unless location_element
location_patch.add_element location_element
wait_for_constraint_create xml_pretty_format(location_patch.root), location_structure['id']
end
# remove a location constraint
# @param id [String] the constraint id
def constraint_location_remove(id)
wait_for_constraint_remove "<rsc_location id='#{id}'/>\n", id
end
# check if locations constraint exists
# @param id [String] the constraint id
# @return [TrueClass,FalseClass]
def constraint_location_exists?(id)
constraint_locations.key? id
end
# generate rsc_location elements from data structure
# @param data [Hash]
# @return [REXML::Element]
def xml_rsc_location(data)
return unless data && data.is_a?(Hash)
# create an element from the top level hash and skip 'rules' attribute
# because if should be processed as children elements and useless 'type' attribute
rsc_location_element = xml_element 'rsc_location', data, %w(rules type)
# there are no rule elements
return rsc_location_element unless data['rules'] && data['rules'].respond_to?(:each)
# create a rule element with attributes and treat expressions as children elements
sort_data(data['rules']).each do |rule|
next unless rule.is_a? Hash
rule_element = xml_element 'rule', rule, 'expressions'
# add expression children elements to the rule element if the are present
if rule['expressions'] && rule['expressions'].respond_to?(:each)
sort_data(rule['expressions']).each do |expression|
next unless expression.is_a? Hash
expression_element = xml_element 'expression', expression
rule_element.add_element expression_element
end
end
rsc_location_element.add_element rule_element
end
rsc_location_element
end
end
end

View File

@ -0,0 +1,43 @@
module Pacemaker
# functions related to constraint_orders constraints
# main structure "constraint_orders"
module ConstraintOrders
# get order constraints and use memoization on the list
# @return [Hash<String => Hash>]
def constraint_orders
return @orders_structure if @orders_structure
@orders_structure = constraints 'rsc_order'
end
# check if order constraint exists
# @param id [String] the constraint id
# @return [TrueClass,FalseClass]
def constraint_order_exists?(id)
constraint_orders.key? id
end
# add a order constraint
# @param order_structure [Hash<String => String>] the location data structure
def constraint_order_add(order_structure)
order_patch = xml_document
order_element = xml_rsc_order order_structure
raise "Could not create XML patch from colocation '#{order_structure.inspect}'!" unless order_element
order_patch.add_element order_element
wait_for_constraint_create xml_pretty_format(order_patch.root), order_structure['id']
end
# remove an order constraint
# @param id [String] the constraint id
def constraint_order_remove(id)
wait_for_constraint_remove "<rsc_order id='#{id}'/>\n", id
end
# generate rsc_order elements from data structure
# @param data [Hash]
# @return [REXML::Element]
def xml_rsc_order(data)
return unless data && data.is_a?(Hash)
xml_element 'rsc_order', data, 'type'
end
end
end

View File

@ -0,0 +1,77 @@
module Pacemaker
# functions related to constraints (order, location, colocation)
# main structure "constraints"
# this structure is used by other specific location colocation and order
# submodules to form their data structures
module Constraints
# get all 'rsc_location', 'rsc_order' and 'rsc_colocation' sections from CIB
# @return [Array<REXML::Element>] at /cib/configuration/constraints/*
def cib_section_constraints
REXML::XPath.match cib, '//constraints/*'
end
# get all rule elements from the constraint element
# @return [Array<REXML::Element>] at /cib/configuration/constraints/*/rule
def cib_section_constraint_rules(constraint)
return unless constraint.is_a? REXML::Element
REXML::XPath.match constraint, 'rule'
end
# parse constraint rule elements to the rule structure
# @param element [REXML::Element]
# @return [Hash<String => Hash>]
def decode_constraint_rules(element)
rules = cib_section_constraint_rules element
return [] unless rules.any?
rules_array = []
rules.each do |rule|
rule_structure = attributes_to_hash rule
next unless rule_structure['id']
rule_expressions = children_elements_to_array rule, 'expression'
rule_structure.store 'expressions', rule_expressions if rule_expressions
rules_array << rule_structure
end
rules_array.sort_by { |rule| rule['id'] }
end
# decode a single constraint element to the data structure
# @param element [REXML::Element]
# @return [Hash<String => String>]
def decode_constraint(element)
return unless element.is_a? REXML::Element
return unless element.attributes['id']
return unless element.name
constraint_structure = attributes_to_hash element
constraint_structure.store 'type', element.name
rules = decode_constraint_rules element
constraint_structure.store 'rules', rules if rules.any?
constraint_structure
end
# constraints found in the CIB
# filter them by the provided tag name
# @param type [String] filter this location type
# @return [Hash<String => Hash>]
def constraints(type = nil)
constraints = {}
cib_section_constraints.each do |constraint|
constraint_structure = decode_constraint constraint
next unless constraint_structure
next unless constraint_structure['id']
next if type && !(constraint_structure['type'] == type)
constraint_structure.delete 'type'
constraints.store constraint_structure['id'], constraint_structure
end
constraints
end
# check if a constraint exists
# @param id [String] the constraint id
# @return [TrueClass,FalseClass]
def constraint_exists?(id)
constraints.key? id
end
end
end

119
lib/pacemaker/xml/debug.rb Normal file
View File

@ -0,0 +1,119 @@
module Pacemaker
# debug related functions
# "cluster_debug_report" the main debug text generation function
# "safe_methods" are used to debug providers without making any actual changes to the system
module Debug
# check if debug is enabled either in the pacemaker options
# or the resource has the 'debug' parameter and it's enabled
# @return [TrueClass,FalseClass]
def debug_mode_enabled?
return true if pacemaker_options[:debug_enabled]
return true if @resource && @resource.parameters.keys.include?(:debug) && @resource[:debug]
false
end
# Call a Puppet shell command method with wrappers
# If debug is enabled, show what would be executed and don't actually
# run the command. Used to debug commands that should modify the system
# and don't return any data. Should never be use with commands that retrieve data.
# If a command have failed, show command and the arguments and the raise the exception.
# The actual commands methods should be created by the provider's "commands" helper.
# @param cmd [Symbol, String] command name
# @param args [Array] command arguments
# @return [String,NilClass]
def safe_method(cmd, *args)
cmd = cmd.to_sym unless cmd.is_a? Symbol
command_line = ([cmd.to_s] + args).join ' '
if debug_mode_enabled?
debug "Exec: #{command_line}"
return
end
begin
send cmd, *args
rescue StandardError => exception
debug "Command execution have failed: #{command_line}"
raise exception
end
end
# safe cibadmin command
# @param args [Array] command arguments
# @return [String,NilClass]
def cibadmin_safe(*args)
safe_method :cibadmin, *args
end
# safe crm_node command
# @param args [Array] command arguments
# @return [String,NilClass]
def crm_node_safe(*args)
safe_method :crm_node, *args
end
# safe cmapctl command
# @param args [Array] command arguments
# @return [String,NilClass]
def cmapctl_safe(*args)
safe_method :cmapctl, *args
end
# safe crm_resource command
# @param args [Array] command arguments
# @return [String,NilClass]
def crm_resource_safe(*args)
safe_method :crm_resource, *args
end
# safe crm_attribute command
# @param args [Array] command arguments
# @return [String,NilClass]
def crm_attribute_safe(*args)
safe_method :crm_attribute, *args
end
################################################################################
# form a cluster status report for debugging
# "(L)" - location constraint for this primitive is present on this node
# "(F)" - the primitive is not running and have failed on this node
# "(M)" - this primitive is not managed
# @param tag [String] log comment tag to to trace calls
# @return [String]
def cluster_debug_report(tag = nil)
return unless cib?
report = "\n"
report += 'Pacemaker debug block start'
report += " at '#{tag}'" if tag
report += "\n"
primitives_status_by_node.each do |primitive, data|
primitive_name = primitive
next unless primitives.key? primitive
primitive_name = primitives[primitive]['name'] if primitives[primitive]['name']
primitive_type = 'Simple'
primitive_type = 'Clone' if primitive_is_clone? primitive
primitive_type = 'Master' if primitive_is_master? primitive
report += "-> #{primitive_type} primitive: '#{primitive_name}'"
report += ' (M)' unless primitive_is_managed? primitive
report += "\n"
nodes = []
data.keys.sort.each do |node_name|
node_status_string = data.fetch node_name
node_status_string = '?' unless node_status_string.is_a? String
node_status_string = node_status_string.upcase
node_block = "#{node_name}: #{node_status_string}"
node_block += ' (F)' if primitive_has_failures?(primitive, node_name) && (!primitive_is_running? primitive, node_name)
node_block += ' (L)' if service_location_exists? primitive_full_name(primitive), node_name
nodes << node_block
end
report += ' ' + nodes.join(' | ') + "\n"
end
pacemaker_options[:debug_show_properties].each do |p|
report += "* #{p}: #{cluster_property_value p}\n" if cluster_property_defined? p
end
report += 'Pacemaker debug block end'
report += " at '#{tag}'" if tag
report + "\n"
end
end
end

View File

@ -0,0 +1,139 @@
module Pacemaker
# misc helper methods used in other submodules
module Helpers
# convert elements's attributes to hash
# @param element [REXML::Element]
# @return [Hash<String => String>]
def attributes_to_hash(element, hash = {})
element.attributes.each do |a, v|
next if a == '__crm_diff_marker__'
hash.store a.to_s, v.to_s
end
hash
end
# convert element's children to hash
# of their attributes using key and hash key
# @param element [REXML::Element]
# @param key <String> use this attribute as hash key
# @param tag <String> get only this type of children
# @return [Hash<String => String>]
def children_elements_to_hash(element, key, tag = nil)
return unless element.is_a? REXML::Element
elements = {}
children = element.get_elements tag
return elements unless children
children.each do |child|
child_structure = attributes_to_hash child
name = child_structure[key]
next unless name
elements.store name, child_structure
end
elements
end
# convert element's children to array of their attributes
# @param element [REXML::Element]
# @param tag [String] get only this type of children
# @return [Array<Hash>]
def children_elements_to_array(element, tag = nil)
return unless element.is_a? REXML::Element
elements = []
children = element.get_elements tag
return elements unless children
children.each do |child|
child_structure = attributes_to_hash child
next unless child_structure['id']
elements << child_structure
end
elements
end
# copy value from one hash_like structure to another
# if the value is present
# @param from[Hash]
# @param from_key [String,Symbol]
# @param to [Hash]
# @param to_key [String,Symbol,NilClass]
def copy_value(from, from_key, to, to_key = nil)
value = from[from_key]
return value unless value
to_key = from_key unless to_key
to[to_key] = value
value
end
# sort hash of hashes into an array of hashes
# by one of the subhash's attributes
# @param data [Hash<String => Hash>]
# @param key [String]
# @return [Array<Hash>]
def sort_data(data, key = 'id')
data = data.values if data.is_a? Hash
data.sort do |x, y|
break 0 unless x[key] && y[key]
x[key] <=> y[key]
end
end
# return service status value expected by Puppet
# puppet wants :running or :stopped symbol
# @param primitive [String] primitive id
# @param node [String] on this node if given
# @return [:running,:stopped]
def get_primitive_puppet_status(primitive, node = nil)
if primitive_is_running? primitive, node
:running
else
:stopped
end
end
# return service enabled status value expected by Puppet
# puppet wants :true or :false symbols
# @param primitive [String]
# @return [:true,:false]
def get_primitive_puppet_enable(primitive)
if primitive_is_managed? primitive
:true
else
:false
end
end
# import the library representation of the attributes structure
# to the Puppet one
def import_attributes_structure(attributes)
return unless attributes.respond_to? :each
hash = {}
attributes.each do |attribute|
if attribute.is_a?(Array) && attribute.length == 2
attribute = attribute[1]
end
next unless attribute['name'] && attribute['value']
hash.store attribute['name'], attribute['value']
end
hash
end
# export the Puppet representation of attributes
# to the library one
# @param hash [Hash] attributes (name => value)
# @param attributes_id_tag [String] attributes name for id naming
# @return [Hash,NilClass]
def export_attributes_structure(hash, attributes_id_tag)
return unless hash.is_a? Hash
attributes = {}
hash.each do |attribute_name, attribute_value|
id_components = [resource[:name], attributes_id_tag, attribute_name]
id_components.reject!(&:nil?)
attribute_structure = {}
attribute_structure['id'] = id_components.join '-'
attribute_structure['name'] = attribute_name
attribute_structure['value'] = attribute_value
attributes.store attribute_name, attribute_structure
end
attributes
end
end
end

View File

@ -0,0 +1,47 @@
module Pacemaker
# functions related to the cluster nodes
# main structure "nodes" with node's names and ids
module Nodes
# get nodes CIB section
# @return [REXML::Element] at /cib/configuration/nodes
def cib_section_nodes
REXML::XPath.match cib, '/cib/configuration/nodes/*'
end
# hostname of the current node
# @return [String]
def node_name
return @node_name if @node_name
@node_name = crm_node('-n').chomp.strip
end
alias hostname node_name
# the nodes structure
# uname => id
# @return [Hash<String => Hash>]
def nodes
return @nodes_structure if @nodes_structure
@nodes_structure = {}
cib_section_nodes.each do |node_block|
node = attributes_to_hash node_block
next unless node['id'] && node['uname']
@nodes_structure.store node['uname'], node
end
@nodes_structure
end
# the name of the current DC node
# @return [String,nil]
def dc_name
dc_node_id = dc
return unless dc_node_id
nodes.each do |node, attrs|
next unless attrs['id'] == dc_node_id
return node
end
nil
end
end
end

View File

@ -0,0 +1,56 @@
module Pacemaker
# functions related to the operation defaults
# main structure "operation_defaults"
module OperationDefault
# get operation defaults CIB section
# @return [REXML::Element]
def cib_section_operation_defaults
REXML::XPath.match(cib, '/cib/configuration/op_defaults/meta_attributes').first
end
# the main 'operation_defaults' structure
# contains defaults operations and their values
# @return [Hash]
def operation_defaults
return @operation_defaults_structure if @operation_defaults_structure
@operation_defaults_structure = children_elements_to_hash cib_section_operation_defaults, 'name'
@operation_defaults_structure = {} unless @operation_defaults_structure
@operation_defaults_structure
end
# extract a single operation default attribute value
# returns nil if it have not been set
# @param attribute_name [String]
# @return [String, nil]
def operation_default_value(attribute_name)
return unless operation_default_defined? attribute_name
operation_defaults[attribute_name]['value']
end
# set a single operation default value
# @param attribute_name [String]
# @param attribute_value [String]
def operation_default_set(attribute_name, attribute_value)
options = ['--quiet', '--type', 'op_defaults', '--attr-name', attribute_name]
options += ['--attr-value', attribute_value]
retry_block { crm_attribute_safe options }
end
# remove a defined operation default attribute
# @param attribute_name [String]
def operation_default_delete(attribute_name)
options = ['--quiet', '--type', 'op_defaults', '--attr-name', attribute_name]
options += ['--delete-attr']
retry_block { crm_attribute_safe options }
end
# check if this operation default attribute have been defined
# @param attribute_name [String]
# @return [true,false]
def operation_default_defined?(attribute_name)
return false unless operation_defaults.key? attribute_name
return false unless operation_defaults[attribute_name].is_a?(Hash) && operation_defaults[attribute_name]['value']
true
end
end
end

View File

@ -0,0 +1,386 @@
require 'set'
module Pacemaker
# function related to the primitives configuration
# main structure "primitives"
module Primitives
# get all 'primitive' sections from CIB
# @return [Array<REXML::Element>] at /cib/configuration/resources/primitive
def cib_section_primitives
REXML::XPath.match cib, '//primitive'
end
# sets the meta attribute of a primitive
# @param primitive [String] primitive's id
# @param attribute [String] atttibute's name
# @param value [String] attribute's value
def set_primitive_meta_attribute(primitive, attribute, value)
options = ['--quiet', '--resource', primitive]
options += ['--set-parameter', attribute, '--meta', '--parameter-value', value]
retry_block { crm_resource_safe options }
end
# disable this primitive
# @param primitive [String] what primitive to disable
def disable_primitive(primitive)
set_primitive_meta_attribute primitive, 'target-role', 'Stopped'
end
alias stop_primitive disable_primitive
# enable this primitive
# @param primitive [String] what primitive to enable
def enable_primitive(primitive)
set_primitive_meta_attribute primitive, 'target-role', 'Started'
end
alias start_primitive enable_primitive
# manage this primitive
# @param primitive [String] what primitive to manage
def manage_primitive(primitive)
set_primitive_meta_attribute primitive, 'is-managed', 'true'
end
# unmanage this primitive
# @param primitive [String] what primitive to unmanage
def unmanage_primitive(primitive)
set_primitive_meta_attribute primitive, 'is-managed', 'false'
end
# ban this primitive
# @param primitive [String] what primitive to ban
# @param node [String] on which node this primitive should be banned
def ban_primitive(primitive, node)
options = ['--quiet', '--resource', primitive, '--node', node]
options += ['--ban']
retry_block { crm_resource_safe options }
end
# unban this primitive
# @param primitive [String] what primitive to unban
# @param node [String] on which node this primitive should be unbanned
def unban_primitive(primitive, node)
options = ['--quiet', '--resource', primitive, '--node', node]
options += ['--clear']
retry_block { crm_resource_safe options }
end
alias clear_primitive unban_primitive
# move this primitive
# @param primitive [String] what primitive to un-move
# @param node [String] to which node the primitive should be moved
def move_primitive(primitive, node)
options = ['--quiet', '--resource', primitive, '--node', node]
options += ['--move']
retry_block { crm_resource_safe options }
end
# un-move this primitive
# @param primitive [String] what primitive to un-move
# @param node [String] from which node the primitive should be un-moved
def unmove_primitive(primitive, node)
options = ['--quiet', '--resource', primitive, '--node', node]
options += ['--un-move']
retry_block { crm_resource_safe options }
end
# cleanup this primitive
# @param primitive [String] what primitive to cleanup
# @param node [String] on which node to cleanup (optional)
# cleanups on every node if node is not given
def cleanup_primitive(primitive, node = nil)
options = ['--quiet', '--resource', primitive]
options += ['--node', node] if node
options += ['--cleanup']
retry_block { crm_resource_safe options }
end
# the list of complex types the library should
# read from the CIB and parse their meta-data
# @return [Array<String>]
def read_complex_types
%w(clone master group)
end
# the list of complex type the library should
# be able to create XML elements for
# @return [Array<String>]
def write_complex_types
%w(clone master)
end
##############################################################################
# get primitives configuration structure with primitives and their attributes
# @return [Hash<String => Hash>]
def primitives
return @primitives_structure if @primitives_structure
@primitives_structure = {}
cib_section_primitives.each do |primitive|
id = primitive.attributes['id']
next unless id
primitive_structure = attributes_to_hash primitive
primitive_structure.store 'name', id
if read_complex_types.include?(primitive.parent.name) && primitive.parent.attributes['id']
complex_structure = {
'id' => primitive.parent.attributes['id'],
'type' => primitive.parent.name
}
complex_meta_attributes = primitive.parent.elements['meta_attributes']
if complex_meta_attributes
complex_meta_attributes_structure = children_elements_to_hash complex_meta_attributes, 'name', 'nvpair'
complex_structure.store 'meta_attributes', complex_meta_attributes_structure
end
primitive_structure.store 'name', complex_structure['id'] unless complex_structure['type'] == 'group'
primitive_structure.store 'complex', complex_structure
end
instance_attributes = primitive.elements['instance_attributes']
if instance_attributes
instance_attributes_structure = children_elements_to_hash instance_attributes, 'name', 'nvpair'
primitive_structure.store 'instance_attributes', instance_attributes_structure
end
meta_attributes = primitive.elements['meta_attributes']
if meta_attributes
meta_attributes_structure = children_elements_to_hash meta_attributes, 'name', 'nvpair'
primitive_structure.store 'meta_attributes', meta_attributes_structure
end
operations = primitive.elements['operations']
if operations
operations_structure = parse_operations operations
primitive_structure.store 'operations', operations_structure
end
@primitives_structure.store id, primitive_structure
end
@primitives_structure
end
# parse the operations structure of a primitive
# @param operations_element [REXML::Element]
# @return [Hash]
def parse_operations(operations_element)
return unless operations_element.is_a? REXML::Element
operations = {}
ops = operations_element.get_elements 'op'
return operations unless ops.any?
ops.each do |op|
op_structure = attributes_to_hash op
id = op_structure['id']
next unless id
instance_attributes = op.elements['instance_attributes']
if instance_attributes
instance_attributes_structure = children_elements_to_hash instance_attributes, 'name', 'nvpair'
if instance_attributes_structure.key? 'OCF_CHECK_LEVEL'
value = instance_attributes_structure.fetch('OCF_CHECK_LEVEL', {}).fetch('value', nil)
op_structure['OCF_CHECK_LEVEL'] = value if value
end
end
operations.store id, op_structure
end
operations
end
# check if primitive exists in the configuration
# @param primitive primitive id or name
def primitive_exists?(primitive)
primitives.key? primitive
end
# return primitive class
# @param primitive [String] primitive id
# @return [String,nil] primitive class
def primitive_class(primitive)
return unless primitive_exists? primitive
primitives[primitive]['class']
end
# return primitive type
# @param primitive [String] primitive id
# @return [String,nil] primitive type
def primitive_type(primitive)
return unless primitive_exists? primitive
primitives[primitive]['type']
end
# return primitive provider
# @param primitive [String] primitive id
# @return [String,nil] primitive type
def primitive_provider(primitive)
return unless primitive_exists? primitive
primitives[primitive]['provider']
end
# return primitive complex type
# or nil is the primitive is simple
# @param primitive [String] primitive id
# @return [Symbol] primitive complex type
def primitive_complex_type(primitive)
return unless primitive_is_complex? primitive
primitives[primitive]['complex']['type'].to_sym
end
# return the full name of the complex primitive
# or just a name for a simple primitive
# @return [String] primitive type
def primitive_full_name(primitive)
return unless primitive_exists? primitive
primitives[primitive]['name']
end
# check if primitive is clone or master
# primitives in groups are not considered complex
# despite having the complex structure
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_complex?(primitive)
return unless primitive_exists? primitive
return false unless primitives[primitive].key? 'complex'
primitives[primitive]['complex']['type'] != 'group'
end
# reverse of the complex? predicate
# but returns nil if resource doesn't exist
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_simple?(primitive)
return unless primitive_exists? primitive
! primitive_is_complex?(primitive)
end
# check if the primitive is assigned to a group
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_in_group?(primitive)
return unless primitive_exists? primitive
return false unless primitives[primitive].key? 'complex'
primitives[primitive]['complex']['type'] == 'group'
end
# get the group name of the primitive
# returns nil if primitive is not in a group
# @return [String,nil] primitive group
def primitive_group(primitive)
return unless primitive_in_group? primitive
primitives[primitive]['complex']['id']
end
# check if primitive is clone
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_clone?(primitive)
is_complex = primitive_is_complex? primitive
return is_complex unless is_complex
primitives[primitive]['complex']['type'] == 'clone'
end
# check if primitive is master
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_master?(primitive)
is_complex = primitive_is_complex? primitive
return is_complex unless is_complex
primitives[primitive]['complex']['type'] == 'master'
end
# determine if primitive is managed
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_managed?(primitive)
return unless primitive_exists? primitive
is_managed = primitives.fetch(primitive).fetch('meta_attributes', {}).fetch('is-managed', {}).fetch('value', 'true')
is_managed == 'true'
end
# determine if primitive has target-state started
# @param primitive [String] primitive id
# @return [TrueClass,FalseClass]
def primitive_is_started?(primitive)
return unless primitive_exists? primitive
target_role = primitives.fetch(primitive).fetch('meta_attributes', {}).fetch('target-role', {}).fetch('value', 'Started')
target_role == 'Started'
end
# generate a new XML element object
# and fill it with the primitive data from
# the provided primitive structure
# @param data [Hash] primitive_structure
# @return [REXML::Element]
def xml_primitive(data)
raise "Primitive data should be a hash! Got: #{data.inspect}" unless data.is_a? Hash
primitive_skip_attributes = %w(name parent instance_attributes operations meta_attributes utilization)
primitive_element = xml_element 'primitive', data, primitive_skip_attributes
# instance attributes
if data['instance_attributes'].respond_to?(:each) && data['instance_attributes'].any?
instance_attributes_document = xml_document 'instance_attributes', primitive_element
instance_attributes_document.add_attribute 'id', data['id'] + '-instance_attributes'
sort_data(data['instance_attributes']).each do |instance_attribute|
instance_attribute_element = xml_element 'nvpair', instance_attribute
instance_attributes_document.add_element instance_attribute_element if instance_attribute_element
end
end
# meta attributes
if data['meta_attributes'].respond_to?(:each) && data['meta_attributes'].any?
complex_meta_attributes_document = xml_document 'meta_attributes', primitive_element
complex_meta_attributes_document.add_attribute 'id', data['id'] + '-meta_attributes'
sort_data(data['meta_attributes']).each do |meta_attribute|
meta_attribute_element = xml_element 'nvpair', meta_attribute
complex_meta_attributes_document.add_element meta_attribute_element if meta_attribute_element
end
end
# operations
if data['operations'].respond_to?(:each) && data['operations'].any?
operations_document = xml_document 'operations', primitive_element
sort_data(data['operations']).each do |operation|
operation_element = xml_element 'op', operation, %w(OCF_CHECK_LEVEL)
if operation.key? 'OCF_CHECK_LEVEL'
instance_attributes_document = xml_document 'instance_attributes', operation_element
instance_attributes_id = operation['id'] + '-instance_attributes'
instance_attributes_document.add_attribute 'id', instance_attributes_id
ocf_check_level_id = instance_attributes_id + '-OCF_CHECK_LEVEL'
ocf_check_level_structure = {
'id' => ocf_check_level_id,
'name' => 'OCF_CHECK_LEVEL',
'value' => operation['OCF_CHECK_LEVEL'],
}
ocf_check_level_element = xml_element 'nvpair', ocf_check_level_structure
instance_attributes_document.add_element ocf_check_level_element if ocf_check_level_element
end
operations_document.add_element operation_element if operation_element
end
end
# complex structure
if data['complex'].is_a?(Hash) && write_complex_types.include?(data['complex']['type'].to_s)
skip_complex_attributes = 'type'
complex_tag_name = data['complex']['type'].to_s
complex_element = xml_element complex_tag_name, data['complex'], skip_complex_attributes
# complex meta attributes
if data['complex']['meta_attributes'].respond_to?(:each) && data['complex']['meta_attributes'].any?
complex_meta_attributes_document = xml_document 'meta_attributes', complex_element
complex_meta_attributes_document.add_attribute 'id', data['complex']['id'] + '-meta_attributes'
sort_data(data['complex']['meta_attributes']).each do |meta_attribute|
complex_meta_attribute_element = xml_element 'nvpair', meta_attribute
complex_meta_attributes_document.add_element complex_meta_attribute_element if complex_meta_attribute_element
end
end
complex_element.add_element primitive_element
return complex_element
end
primitive_element
end
end
end

View File

@ -0,0 +1,52 @@
module Pacemaker
# functions related to the cluster properties
# main structure "cluster_properties"
module Properties
# get cluster property CIB section
# @return [REXML::Element]
def cib_section_cluster_property
REXML::XPath.match(cib, '/cib/configuration/crm_config/cluster_property_set').first
end
# get cluster property structure
# @return [Hash<String => Hash>]
def cluster_properties
return @cluster_properties_structure if @cluster_properties_structure
@cluster_properties_structure = children_elements_to_hash cib_section_cluster_property, 'name'
end
# get the value of a cluster property by it's name
# @param property_name [String] the name of the property
# @return [String]
def cluster_property_value(property_name)
return unless cluster_property_defined? property_name
cluster_properties[property_name]['value']
end
# set the value to this cluster's property
# @param property_name [String] the name of the property
# @param property_value [String] the value of the property
def cluster_property_set(property_name, property_value)
options = ['--quiet', '--type', 'crm_config', '--name', property_name]
options += ['--update', property_value]
retry_block { crm_attribute_safe options }
end
# delete this cluster's property
# @param property_name [String] the name of the property
def cluster_property_delete(property_name)
options = ['--quiet', '--type', 'crm_config', '--name', property_name]
options += ['--delete']
retry_block { crm_attribute_safe options }
end
# check if this property has a value
# @param property_name [String] the name of the property
# @return [TrueClass,FalseClass]
def cluster_property_defined?(property_name)
return false unless cluster_properties.key? property_name
return false unless cluster_properties[property_name].is_a?(Hash) && cluster_properties[property_name]['value']
true
end
end
end

View File

@ -0,0 +1,56 @@
module Pacemaker
# functions related to the resource defaults
# main structure "resource_defaults"
module ResourceDefault
# get resource defaults CIB section
# @return [REXML::Element]
def cib_section_resource_defaults
REXML::XPath.match(cib, '/cib/configuration/rsc_defaults/meta_attributes').first
end
# the main 'resource_defaults' structure
# contains defaults operations and their values
# @return [Hash]
def resource_defaults
return @resource_defaults_structure if @resource_defaults_structure
@resource_defaults_structure = children_elements_to_hash cib_section_resource_defaults, 'name'
@resource_defaults_structure = {} unless @resource_defaults_structure
@resource_defaults_structure
end
# extract a single resource default attribute value
# returns nil if it have not been set
# @param attribute_name [String]
# @return [String, nil]
def resource_default_value(attribute_name)
return unless resource_default_defined? attribute_name
resource_defaults[attribute_name]['value']
end
# set a single resource default value
# @param attribute_name [String]
# @param attribute_value [String]
def resource_default_set(attribute_name, attribute_value)
options = ['--quiet', '--type', 'rsc_defaults', '--attr-name', attribute_name]
options += ['--attr-value', attribute_value]
retry_block { crm_attribute_safe options }
end
# remove a defined resource default attribute
# @param attribute_name [String]
def resource_default_delete(attribute_name)
options = ['--quiet', '--type', 'rsc_defaults', '--attr-name', attribute_name]
options += ['--delete-attr']
retry_block { crm_attribute_safe options }
end
# check if this resource default attribute have been defined
# @param attribute_name [String]
# @return [true,false]
def resource_default_defined?(attribute_name)
return false unless resource_defaults.key? attribute_name
return false unless resource_defaults[attribute_name].is_a?(Hash) && resource_defaults[attribute_name]['value']
true
end
end
end

274
lib/pacemaker/xml/status.rb Normal file
View File

@ -0,0 +1,274 @@
module Pacemaker
# functions related to the primitive and node status
# main structure "node_status"
module Status
# get lrm_rsc_ops section from lrm_resource section CIB section
# @param lrm_resource [REXML::Element]
# at /cib/status/node_state/lrm[@id="node-name"]/lrm_resources/lrm_resource[@id="resource-name"]/lrm_rsc_op
# @return [REXML::Element]
def cib_section_lrm_rsc_ops(lrm_resource)
return unless lrm_resource.is_a? REXML::Element
REXML::XPath.match lrm_resource, 'lrm_rsc_op'
end
# get node_state CIB section
# @return [REXML::Element] at /cib/status/node_state
def cib_section_node_state
REXML::XPath.match cib, '//node_state'
end
# get lrm_rsc_ops section from lrm_resource section CIB section
# @param lrm [REXML::Element]
# at /cib/status/node_state/lrm[@id="node-name"]/lrm_resources/lrm_resource
# @return [REXML::Element]
def cib_section_lrm_resources(lrm)
return unless lrm.is_a? REXML::Element
REXML::XPath.match lrm, 'lrm_resources/lrm_resource'
end
# determine the status of a single operation
# @param op [Hash<String => String>]
# @return ['start','stop','master',nil]
def operation_status(op)
# skip pendings ops
# we should waqit until status becomes known
return if op['op-status'] == '-1'
if op['operation'] == 'monitor'
# for monitor operation status is determined by its rc-code
# 0 - start, 8 - master, 7 - stop, else - error
case op['rc-code']
when '0'
'start'
when '7'
'stop'
when '8'
'master'
else
# not entirely correct but count failed monitor as 'stop'
'stop'
end
elsif %w(start stop promote demote).include? op['operation']
# if the operation was not successful the status is unknown
# it will be determined by the next monitor
# if Pacemaker is unable to bring the resource to a known state
# it can use STONITH on this node if it's configured
return unless op['rc-code'] == '0'
# for a successful start/stop/promote/demote operations
# we use use master instead of promote and start instead of demote
if op['operation'] == 'promote'
'master'
elsif op['operation'] == 'demote'
'start'
else
op['operation']
end
end
end
# determine resource status by parsing its operations
# it goes from the first operation to the last updating
# status if it's defined in the end there will be the
# actual status of this primitive
# @param ops [Array<Hash>]
# @return ['start','stop','master',nil]
# nil means that the status is unknown
def determine_primitive_status(ops)
status = nil
ops.each do |op|
op_status = operation_status op
status = op_status if op_status
end
status
end
# decode lrm_resources section of CIB
# @param lrm_resources [REXML::Element]
# @return [Hash<String => Hash>]
def decode_lrm_resources(lrm_resources)
resources = {}
lrm_resources.each do |lrm_resource|
resource = attributes_to_hash lrm_resource
id = resource['id']
next unless id
lrm_rsc_ops = cib_section_lrm_rsc_ops lrm_resource
next unless lrm_rsc_ops
ops = decode_lrm_rsc_ops lrm_rsc_ops
resource.store 'ops', ops
resource.store 'status', determine_primitive_status(ops)
resource.store 'failed', failed_operations_found?(ops)
resources.store id, resource
end
resources
end
# decode lrm_rsc_ops section of the resource's CIB
# @param lrm_rsc_ops [REXML::Element]
# @return [Array<Hash>]
def decode_lrm_rsc_ops(lrm_rsc_ops)
ops = []
lrm_rsc_ops.each do |lrm_rsc_op|
op = attributes_to_hash lrm_rsc_op
next unless op['call-id']
ops << op
end
ops.sort { |a, b| a['call-id'].to_i <=> b['call-id'].to_i }
end
# get nodes_status structure with resources and their statuses
# @return [Hash<String => Hash>]
def node_status
return @node_status_structure if @node_status_structure
@node_status_structure = {}
cib_section_node_state.each do |node_state|
node = attributes_to_hash node_state
node_name = node['uname']
next unless node_name
lrm = node_state.elements['lrm']
next unless lrm
lrm_resources = cib_section_lrm_resources lrm
next unless lrm_resources
resources = decode_lrm_resources lrm_resources
node.store 'primitives', resources
@node_status_structure.store node_name, node
end
@node_status_structure
end
# check if operations have same failed operations
# that should be cleaned up later
# @param ops [Array<Hash>]
# @return [TrueClass,FalseClass]
def failed_operations_found?(ops)
ops.each do |op|
# skip incompleate ops
next unless op['op-status'] == '0'
# skip useless ops
next unless %w(start stop monitor promote).include? op['operation']
# are there failed start, stop
if %w(start stop promote).include? op['operation']
return true if op['rc-code'] != '0'
end
# are there failed monitors
if op['operation'] == 'monitor'
return true unless %w(0 7 8).include? op['rc-code']
end
end
false
end
# get a status of a primitive on the entire cluster
# of on a node if node name param given
# @param primitive [String]
# @param node [String]
# @return [String]
def primitive_status(primitive, node = nil)
if node
node_status
.fetch(node, {})
.fetch('primitives', {})
.fetch(primitive, {})
.fetch('status', nil)
else
statuses = []
node_status.each do |_k, v|
status = v.fetch('primitives', {})
.fetch(primitive, {})
.fetch('status', nil)
statuses << status
end
status_values = {
'stop' => 0,
'start' => 1,
'master' => 2,
}
statuses.max_by do |status|
return nil unless status
status_values[status]
end
end
end
# does this primitive have failed operations?
# @param primitive [String] primitive name
# @param node [String] on this node if given
# @return [TrueClass,FalseClass]
def primitive_has_failures?(primitive, node = nil)
return unless primitive_exists? primitive
if node
node_status
.fetch(node, {})
.fetch('primitives', {})
.fetch(primitive, {})
.fetch('failed', nil)
else
node_status.each do |_k, v|
failed = v.fetch('primitives', {})
.fetch(primitive, {})
.fetch('failed', nil)
return true if failed
end
false
end
end
# determine if a primitive is running on the entire cluster
# of on a node if node name param given
# @param primitive [String] primitive id
# @param node [String] on this node if given
# @return [TrueClass,FalseClass]
def primitive_is_running?(primitive, node = nil)
return unless primitive_exists? primitive
status = primitive_status primitive, node
return status unless status
%w(start master).include? status
end
# check if primitive is running as a master
# either anywhere or on the give node
# @param primitive [String] primitive id
# @param node [String] on this node if given
# @return [TrueClass,FalseClass]
def primitive_has_master_running?(primitive, node = nil)
is_master = primitive_is_master? primitive
return is_master unless is_master
status = primitive_status primitive, node
return status unless status
status == 'master'
end
# generate the report of primitive statuses by node
# @return [Hash]
def primitives_status_by_node
report = {}
return unless node_status.is_a? Hash
node_status.each do |node_name, node_data|
primitives_of_node = node_data['primitives']
next unless primitives_of_node.is_a? Hash
primitives_of_node.each do |primitive, primitive_data|
primitive_status = primitive_data['status']
report[primitive] = {} unless report[primitive].is_a? Hash
report[primitive][node_name] = primitive_status
end
end
report
end
# Get the list on node names where this primitive
# has the specified status.
# @param [String] primitive
# @param [String,Symbol] expected_status (stop/start/master)
# @return [Array<String>] The array of node names where the primitive has this status
def primitive_has_status_on(primitive, expected_status = 'start')
expected_status = expected_status.to_s.downcase
primitive_status_by_node = primitives_status_by_node[primitive]
primitive_status_by_node.inject([]) do |found_nodes, node_and_status|
next found_nodes unless node_and_status.last == expected_status
found_nodes << node_and_status.first
end
end
end
end

65
lib/pacemaker/xml/xml.rb Normal file
View File

@ -0,0 +1,65 @@
module Pacemaker
# functions that are used to generate XML documents and create XML patches
module Xml
# create a new xml document
# @param path [String,Array<String>] create this sequence of path elements
# @param root [REXML::Document] use existing element as a root instead of creating a new one
# @return [REXML::Element] element point to the last path component
# use .root to get the document root
def xml_document(path = [], root = nil)
root = REXML::Document.new unless root
element = root
path = Array(path) unless path.is_a? Array
path.each do |component|
element = element.add_element component
end
element
end
# convert hash to xml element
# @param tag [String] what xml tag to create
# @param hash [Hash] attributes data structure
# @param skip_attributes [String,Array<String>] skip these hash keys
# @return [REXML::Element]
def xml_element(tag, hash, skip_attributes = nil)
return unless hash.is_a? Hash
element = REXML::Element.new tag.to_s
hash.each do |attribute, value|
attribute = attribute.to_s
# skip attributes that were specified to be skipped
next if skip_attributes == attribute ||
(skip_attributes.respond_to?(:include?) && skip_attributes.include?(attribute))
# skip array and hash values. add only scalar ones
next if value.is_a?(Array) || value.is_a?(Hash)
element.add_attribute attribute, value
end
element
end
# output xml element as the actual xml text with indentation
# @param element [REXML::Element]
# @return [String]
def xml_pretty_format(element)
return unless element.is_a? REXML::Element
formatter = REXML::Formatters::Pretty.new
formatter.compact = true
xml = ''
formatter.write element, xml
xml + "\n"
end
end
end
# external REXML module patching
module REXML
# make REXML's attributes to be sorted by their name
# when iterating through them instead of randomly placing them each time
# it's required to generate stable XML texts for unit testing
class Attributes
def each_value # :yields: attribute
keys.sort.each do |key|
yield fetch key
end
end
end
end

View File

@ -0,0 +1,190 @@
module Puppet::Parser::Functions
newfunction(
:pacemaker_cluster_nodes,
type: :rvalue,
arity: -2,
doc: <<-eof
Convert different forms of node list to the required form.
Input data can be:
* String 'n1 n2a,n2b n3'
* Array ['n1', ['n2a','n2b'], 'n3']
* Hash { 'name' => 'n1', 'ring0' => '192.168.0.1' }
Hash can have optional keys: name, id, ip, votes,
Node id will be autogenerated unless id key is provided.
Internal nodes structure example:
```
[
{
name: 'node1',
id: '1',
votes: '2',
ring0: '192.168.0.1',
ring1: '172.16.0.1',
},
{
name: 'node2',
id: '2',
votes: '1',
ring0: '192.168.0.2',
ring1: '172.16.0.2',
},
{
name: 'node3',
id: '3',
votes: '1',
ring0: '192.168.0.3',
ring1: '172.16.0.3',
}
]
# All fields except at least one ring address are optional
```
If neither 'ring0' nor 'ring1' are found fields 'ip' and 'name' will be used.
Output forms:
* hash - output the hash of the node ids and their data (used for corosync config template)
* list - output the space ad comma separated nodes and interfaces (used for "pcs cluster setup")
* array - output the plain array of nodes (used for pacemaker_auth or "pcs cluster auth")
eof
) do |args|
nodes = args[0]
form = args[1] || 'hash'
form = form.to_s.downcase
fail 'Nodes are not provided!' unless nodes
forms = %w(list array hash)
fail "Unknown form: '#{form}'" unless forms.include? form
array_formatter = lambda do |structure|
list = []
structure.each do |node|
next unless node.is_a? Hash
list << node['ring0'] if node['ring0']
list << node['ring1'] if node['ring1']
end
list.flatten.compact.uniq
end
list_formatter = lambda do |structure|
list = []
structure.each do |node|
node_rings = []
node_rings[0] = node['ring0'] if node['ring0']
node_rings[1] = node['ring1'] if node['ring1']
list << node_rings.join(',')
end
list.join ' '
end
hash_formatter = lambda do |structure|
hash = {}
structure.each do |node|
id = node['id']
next unless id
hash[id] = node
end
hash
end
node_split_to_rings = lambda do |node|
node = node.to_s.chomp.strip
rings = node.split ','
node_hash = {}
node_hash['ring0'] = rings[0].to_s if rings[0] and rings[0] != ''
node_hash['ring1'] = rings[1].to_s if rings[1] and rings[1] != ''
node_hash
end
node_hash_process = lambda do |node|
ring0 = node['ring0']
ring1 = node['ring1']
ring0 = node['ip'] if node['ip'] and not ring0
ring0 = node['name'] if node['name'] and not ring0
node_hash = {}
node_hash['ring0'] = ring0.to_s if ring0
node_hash['ring1'] = ring1.to_s if ring1
node_hash['name'] = node['name'].to_s if node['name']
node_hash['id'] = node['id'].to_s if node['id']
node_hash['vote'] = node['vote'].to_s if node['vote']
node_hash
end
string_parser = lambda do |string|
string = string.to_s.chomp.strip
node_list = []
string.split.each do |node|
node_hash = node_split_to_rings.call node
next unless node_hash['ring0'] or node_hash['ring1']
node_list << node_hash
end
node_list
end
array_parser = lambda do |array|
array = [array] unless array.is_a? Array
node_list = []
array.each do |node|
if node.is_a? Array
node_hash = {}
node_hash['ring0'] = node[0].to_s if node[0]
node_hash['ring1'] = node[1].to_s if node[1]
elsif node.is_a? Hash
node_hash = node_hash_process.call node
else
node_hash = node_split_to_rings.call node.to_s
end
next unless node_hash['ring0'] or node_hash['ring1']
node_list << node_hash
end
node_list
end
hash_parser = lambda do |hash|
fail "Data is not a hash: #{hash.inspect}" unless hash.is_a? Hash
node_list = []
hash.each do |node_name, node|
node['name'] = node_name if node_name and not node['name']
node_hash = node_hash_process.call node
next unless node_hash['ring0'] or node_hash['ring1']
node_list << node_hash
end
node_list
end
set_node_ids = lambda do |structure|
next_id = 1
structure.each do |node|
unless node['id']
node['id'] = next_id.to_s
next_id += 1
end
end
end
structure = []
if nodes.is_a? String
structure = string_parser.call nodes
elsif nodes.is_a? Array
structure = array_parser.call nodes
elsif nodes.is_a? Hash
structure = hash_parser.call nodes
else
fail "Got unsupported nodes input data: #{nodes.inspect}"
end
set_node_ids.call structure
if form == 'hash'
hash_formatter.call structure
elsif form == 'list'
list_formatter.call structure
elsif form == 'array'
array_formatter.call structure
end
end
end

View File

@ -0,0 +1,28 @@
module Puppet::Parser::Functions
newfunction(
:pacemaker_cluster_options,
type: :rvalue,
arity: 1,
doc: <<-eof
Convert the cluster options to the "pcs cluster create" CLI options string
eof
) do |args|
options = args[0]
break '' unless options
break options if options.is_a? String
if options.is_a? Hash
options_array = []
options.each do |option, value|
option = "--#{option}" unless option.start_with? '--'
if value.is_a? TrueClass or value.is_a? FalseClass
options_array << option if value
else
options_array << option
options_array << value
end
end
options = options_array
end
[options].flatten.join ' '
end
end

View File

@ -0,0 +1,24 @@
module Puppet::Parser::Functions
newfunction(
:pacemaker_resource_parameters,
type: :rvalue,
arity: -1,
doc: <<-eof
Gather resource parameters and their values
eof
) do |args|
parameters = {}
args.flatten.each_slice(2) do |key, value|
if value.nil? and key.is_a? Hash
parameters.merge! key
else
next if key.nil?
next if value.nil?
next if value == ''
key = key.to_s
parameters.store key, value
end
end
parameters
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_colocation).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,170 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_colocation).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Specific provider for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This provider will check the state
of current primitive colocations on the system; add, delete, or adjust various
aspects.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.constraint_colocations.map do |title, data|
parameters = {}
debug "Prefetch constraint_colocation: #{title}"
proxy_instance.retrieve_data data, parameters
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# retrieve data from library to the target_structure
# @param data [Hash] extracted colocation data
# will extract the current colocation data unless a value is provided
# @param target_structure [Hash] copy data to this structure
# defaults to the property_hash of this provider
def retrieve_data(data = nil, target_structure = property_hash)
data = constraint_colocations.fetch resource[:name], {} unless data
target_structure[:name] = data['id'] if data['id']
target_structure[:ensure] = :present
target_structure[:first] = data['with-rsc'] if data['with-rsc']
target_structure[:first] += ":#{data['with-rsc-role']}" if data['with-rsc-role']
target_structure[:second] = data['rsc'] if data['rsc']
target_structure[:second] += ":#{data['rsc-role']}" if data['rsc-role']
target_structure[:score] = data['score'] if data['score']
end
def exists?
debug 'Call: exists?'
out = constraint_colocation_exists? resource[:name]
retrieve_data
debug "Return: #{out}"
out
end
# check if the colocation ensure is set to present
# @return [TrueClass,FalseClass]
def present?
property_hash[:ensure] == :present
end
# Create just adds our resource to the property_hash and flush will take care
# of actually doing the work.
def create
debug 'Call: create'
self.property_hash = {
name: resource[:name],
ensure: :absent,
first: resource[:first],
second: resource[:second],
score: resource[:score],
}
end
# Unlike create we actually immediately delete the item.
def destroy
debug 'Call: destroy'
constraint_colocation_remove resource[:name]
property_hash.clear
cluster_debug_report "#{resource} destroy"
end
# Getter that obtains the our score that should have been populated by
# prefetch or instances (depends on if your using puppet resource or not).
def score
property_hash[:score]
end
# Getters that obtains the first and second primitives and score in our
# ordering definition that have been populated by prefetch or instances
# (depends on if your using puppet resource or not).
def first
property_hash[:first]
end
def second
property_hash[:second]
end
# Our setters for the first and second primitives and score. 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 first=(should)
property_hash[:first] = should
end
def second=(should)
property_hash[:second] = should
end
def score=(should)
property_hash[:score] = should
end
# Flush is triggered on anything that has been detected as being
# modified in the property_hash. It generates a temporary file with
# the updates that need to be made. The temporary file is then used
# as stdin for the crm command.
def flush
debug 'Call: flush'
return unless property_hash && property_hash.any?
unless property_hash[:name] && property_hash[:score] && property_hash[:first] && property_hash[:second]
raise 'Data does not contain all the required fields!'
end
unless primitive_exists? primitive_base_name property_hash[:first]
raise "Primitive '#{property_hash[:first]}' does not exist!"
end
unless primitive_exists? primitive_base_name property_hash[:second]
raise "Primitive '#{property_hash[:second]}' does not exist!"
end
colocation_structure = {}
colocation_structure['id'] = property_hash[:name]
colocation_structure['score'] = property_hash[:score]
first_element_array = property_hash[:first].split ':'
second_element_array = property_hash[:second].split ':'
colocation_structure['rsc'] = second_element_array[0]
colocation_structure['rsc-role'] = second_element_array[1] if second_element_array[1]
colocation_structure['with-rsc'] = first_element_array[0]
colocation_structure['with-rsc-role'] = first_element_array[1] if first_element_array[1]
colocation_patch = xml_document
colocation_element = xml_rsc_colocation colocation_structure
raise "Could not create XML patch for '#{resource}'" unless colocation_element
colocation_patch.add_element colocation_element
if present?
wait_for_constraint_update xml_pretty_format(colocation_patch.root), colocation_structure['id']
else
wait_for_constraint_create xml_pretty_format(colocation_patch.root), colocation_structure['id']
end
cluster_debug_report "#{resource} flush"
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_location).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,168 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_location).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Specific provider for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This provider will check the state
of current primitive colocations on the system; add, delete, or adjust various aspects.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.constraint_locations.map do |title, data|
parameters = {}
debug "Prefetch constraint_location: #{title}"
proxy_instance.retrieve_data data, parameters
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# retrieve data from library to the target_structure
# @param data [Hash] extracted location data
# will extract the current location data unless a value is provided
# @param target_structure [Hash] copy data to this structure
# defaults to the property_hash of this provider
def retrieve_data(data = nil, target_structure = property_hash)
debug 'Call: retrieve_data'
data = constraint_locations.fetch resource[:name], {} unless data
target_structure[:ensure] = :present
target_structure[:name] = data['id'] if data['id']
target_structure[:primitive] = data['rsc'] if data['rsc']
target_structure[:node] = data['node'] if data['node']
target_structure[:score] = data['score'] if data['score']
target_structure[:rules] = data['rules'] if data['rules']
end
def exists?
debug 'Call: exists?'
out = constraint_location_exists? resource[:name]
retrieve_data
debug "Return: #{out}"
out
end
# check if the location ensure is set to present
# @return [TrueClass,FalseClass]
def present?
property_hash[:ensure] == :present
end
# Create just adds our resource to the property_hash and flush will take care
# of actually doing the work.
def create
debug 'Call: create'
self.property_hash = {
name: resource[:name],
ensure: :absent,
primitive: resource[:primitive],
node: resource[:node],
score: resource[:score],
rules: resource[:rules],
}
end
# Unlike create we actually immediately delete the item.
def destroy
debug 'Call: destroy'
constraint_location_remove resource[:name]
property_hash.clear
cluster_debug_report "#{resource} destroy"
end
# Getter that obtains the primitives array for us that should have
# been populated by prefetch or instances (depends on if your using
# puppet resource or not).
def primitive
property_hash[:primitive]
end
def score
property_hash[:score]
end
def rules
property_hash[:rules]
end
def node
property_hash[:node]
end
# Our setters for the primitives array and score. 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 rules=(should)
property_hash[:rules] = should
end
def primitives=(should)
property_hash[:primitive] = should
end
def score=(should)
property_hash[:score] = should
end
def node=(should)
property_hash[:node] = should
end
# Flush is triggered on anything that has been detected as being
# modified in the property_hash. It generates a temporary file with
# the updates that need to be made. The temporary file is then used
# as stdin for the crm command.
def flush
debug 'Call: flush'
return unless property_hash && property_hash.any?
unless primitive_exists? primitive_base_name property_hash[:primitive]
raise "Primitive '#{property_hash[:primitive]}' does not exist!"
end
unless property_hash[:name] && property_hash[:primitive] &&
(property_hash[:rules] || (property_hash[:score] && property_hash[:node]))
raise 'Data does not contain all the required fields!'
end
location_structure = {}
location_structure['id'] = property_hash[:name]
location_structure['rsc'] = property_hash[:primitive]
location_structure['score'] = property_hash[:score] if property_hash[:score]
location_structure['node'] = property_hash[:node] if property_hash[:node]
location_structure['rules'] = property_hash[:rules] if property_hash[:rules]
location_patch = xml_document
location_element = xml_rsc_location location_structure
raise "Could not create XML patch for '#{resource}'" unless location_element
location_patch.add_element location_element
if present?
wait_for_constraint_update xml_pretty_format(location_patch.root), location_structure['id']
else
wait_for_constraint_create xml_pretty_format(location_patch.root), location_structure['id']
end
cluster_debug_report "#{resource} flush"
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_nodes).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,130 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_nodes).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
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 cmapctl_nodelist
cmapctl '-b', 'nodelist.node'
end
def nodes_data
return {} unless @resource[:nodes].is_a? Hash
@resource[:nodes]
end
# retrieve the current Corosync nodes
# the node number to match the "id" and the ring address lines
# @return <Hash>
def corosync_nodes_structure
return @corosync_nodes_structure if @corosync_nodes_structure
nodes = {}
cmapctl_nodelist.split("\n").each do |line|
if line =~ /^nodelist\.node\.(\d+)\.nodeid\s+\(u32\)\s+=\s+(\d+)/
# this is the 'id' line
node_number = Regexp.last_match(1)
node_id = Regexp.last_match(2)
nodes[node_number] = {} unless nodes[node_number]
nodes[node_number]['id'] = node_id
nodes[node_number]['number'] = node_number
end
if line =~ /^nodelist\.node\.(\d+)\.ring(\d+)_addr\s+\(str\)\s+=\s+(\S+)/
node_number = Regexp.last_match(1)
ring_number = Regexp.last_match(2)
node_ip_addr = Regexp.last_match(3)
nodes[node_number] = {} unless nodes[node_number]
key = "ring#{ring_number}"
nodes[node_number][key] = node_ip_addr
end
end
@corosync_nodes_structure = {}
nodes.values.each do |node|
id = node['id']
next unless id
@corosync_nodes_structure[id] = node
end
@corosync_nodes_structure
end
# ids and name of current Pacemaker nodes
# @return <Hash>
def pacemaker_nodes_structure
@pacemaker_nodes_structure = {}
nodes.each do |name, node|
id = node['id']
next unless name && id
@pacemaker_nodes_structure.store id, name
end
@pacemaker_nodes_structure
end
def pacemaker_nodes_reset
@corosync_nodes_structure = nil
@pacemaker_nodes_structure = nil
@resource_nodes_structure = nil
@node_name = nil
end
def next_corosync_node_number
number = corosync_nodes_structure.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_record(node_name)
cibadmin_safe '--delete', '--scope', 'nodes', '--xml-text', "<node uname='#{node_name}'/>"
end
def remove_pacemaker_node_state(node_name)
cibadmin_safe '--delete', '--scope', 'status', '--xml-text', "<node_state uname='#{node_name}'/>"
end
def remove_location_constraints(node_name)
cibadmin_safe '--delete', '--scope', 'constraints', '--xml-text', "<rsc_location node='#{node_name}'/>"
end
def remove_corosync_node_record(node_number)
cmapctl_safe '-D', "nodelist.node.#{node_number}"
rescue => e
debug "Failed: #{e.message}"
end
def add_corosync_node_record(node_number, node_id, ring0 = nil, ring1 = nil)
cmapctl_safe '-s', "nodelist.node.#{node_number}.nodeid", 'u32', node_id
cmapctl_safe '-s', "nodelist.node.#{node_number}.ring0_addr", 'str', ring0 if ring0
cmapctl_safe '-s', "nodelist.node.#{node_number}.ring1_addr", 'str', ring1 if ring1
end
def remove_pacemaker_node(node_name)
debug "Remove the pacemaker node: '#{node_name}'"
remove_pacemaker_node_record node_name
remove_pacemaker_node_state node_name
remove_location_constraints node_name
pacemaker_nodes_reset
end
def remove_corosync_node(node_id)
debug "Remove the corosync node: '#{node_id}'"
node_number = corosync_nodes_structure.fetch(node_id, {}).fetch('number')
raise "Could not get the node_number of the node_id: '#{node_id}'!" unless node_number
remove_corosync_node_record node_number
pacemaker_nodes_reset
end
def add_corosync_node(node_id)
debug "Add corosync node: '#{node_id}'"
node_number = next_corosync_node_number
raise "Could not find node_id: '#{node_id}' in the resource data!" unless nodes_data[node_id].is_a? Hash
ring0 = nodes_data[node_id]['ring0']
ring1 = nodes_data[node_id]['ring1']
add_corosync_node_record node_number, node_id, ring0, ring1
pacemaker_nodes_reset
end
end

View File

@ -0,0 +1,55 @@
# this is the abstract provider to create "noop" providers for pacemaker types
# if a "noop" provider is used for a resource it will do nothing when applied
# neither the retrieving nor the modification phase
class Puppet::Provider::PacemakerNoop < Puppet::Provider
attr_accessor :property_hash
attr_accessor :resource
# stub "exists?" method that returns "true" and logs its calls
# @return [true]
def exists?
debug 'Call: exists?'
make_property_methods
out = true
debug "Return: #{out}"
out
end
# stub "creat" method method cleans the property hash
# should never be actually called because exists? always returns true
def create
debug 'Call: create'
self.property_hash = {}
end
# stub "destroy" method cleans the property hash
def destroy
debug 'Call: destroy'
self.property_hash = {}
end
# stub "flush" method does nothing
def flush
debug 'Call: flush'
end
# this method creates getters and setters for each
# of the resource properties
# works directly with resource parameter values instead of property_hash
def make_property_methods
properties = resource.properties.map(&:name)
properties.each do |property|
next if property == :ensure
self.class.send :define_method, property do
debug "Call: #{property}"
out = resource[property]
debug "Return: #{out.inspect}"
out
end
self.class.send :define_method, "#{property}=" do |value|
debug "Call: #{property}=#{value.inspect}"
resource[property] = value
end
end
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_online).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,25 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_online).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Use pacemaker library to wait for the cluster to become online before trying to do something with it.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
# get the cluster status
# @return [Symbol]
def status
if online?
:online
else
:offline
end
end
# wait for the cluster to become online
# is status is set to :online
# @param value [Symbol]
def status=(value)
wait_for_online 'pacemaker_online' if value == :online
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_operation_default).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,51 @@
require_relative '../pacemaker_pcs'
Puppet::Type.type(:pacemaker_operation_default).provide(:pcs, parent: Puppet::Provider::PacemakerPCS) do
desc 'Manages default values for pacemaker operation options via pcs'
commands pcs: 'pcs'
# disable this provider
confine(true: false)
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.pcs_operation_defaults.map do |title, value|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = value
parameters[:name] = title
instance = new(parameters)
instances << instance
end
instances
end
def create
debug 'Call: create'
self.value = @resource[:value]
end
def destroy
debug 'Call: destroy'
pcs_operation_default_delete @resource[:name]
end
def exists?
debug 'Call: exists?'
pcs_operation_default_defined? @resource[:name]
end
def value
debug 'Call: value'
pcs_operation_default_value @resource[:name]
end
def value=(value)
debug "Call: value=#{value}"
pcs_operation_default_set @resource[:name], value
end
end

View File

@ -0,0 +1,77 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_operation_default).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Specific operation_default for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This op_defaults will check the state
of Corosync cluster configuration properties.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
wait_for_online 'pacemaker_operation_default'
proxy_instance = new
instances = []
proxy_instance.operation_defaults.map do |title, data|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = data['value']
parameters[:name] = title
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# @return [true,false]
def exists?
debug 'Call: exists?'
wait_for_online 'pacemaker_operation_default'
return property_hash[:ensure] == :present if property_hash[:ensure]
out = operation_default_defined? resource[:name]
debug "Return: #{out}"
out
end
def create
debug 'Call: create'
self.value = resource[:value]
end
def destroy
debug 'Call: destroy'
operation_default_delete resource[:name]
end
def value
debug 'Call: value'
return property_hash[:value] if property_hash[:value]
out = operation_default_value resource[:name]
debug "Return: #{out}"
out
end
def value=(should)
debug "Call: value=#{should}"
operation_default_set resource[:name], should
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_order).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,229 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_order).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc <<-eof
Specific provider for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This provider will check the state
of current primitive start orders on the system; add, delete, or adjust various
aspects.
eof
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.constraint_orders.map do |title, data|
parameters = {}
debug "Prefetch constraint_order: #{title}"
proxy_instance.retrieve_data data, parameters
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# retrieve data from library to the target_structure
# @param data [Hash] extracted order data
# will extract the current order data unless a value is provided
# @param target_structure [Hash] copy data to this structure
# defaults to the property_hash of this provider
def retrieve_data(data = nil, target_structure = property_hash)
data = constraint_orders.fetch resource[:name], {} unless data
target_structure[:name] = data['id'] if data['id']
target_structure[:ensure] = :present
target_structure[:first] = data['first'] if data['first']
target_structure[:second] = data['then'] if data['then']
target_structure[:first_action] = data['first-action'].downcase if data['first-action']
target_structure[:second_action] = data['then-action'].downcase if data['then-action']
target_structure[:score] = data['score'] if data['score']
target_structure[:kind] = data['kind'].downcase if data['kind']
target_structure[:symmetrical] = data['symmetrical'].downcase if data['symmetrical']
target_structure[:require_all] = data['require-all'].downcase if data['require-all']
end
def exists?
debug 'Call: exists?'
out = constraint_order_exists? resource[:name]
retrieve_data
debug "Return: #{out}"
out
end
# check if the order ensure is set to present
# @return [TrueClass,FalseClass]
def present?
property_hash[:ensure] == :present
end
# Create just adds our resource to the property_hash and flush will take care
# of actually doing the work.
def create
debug 'Call: create'
self.property_hash = {
name: resource[:name],
ensure: :absent,
first: resource[:first],
second: resource[:second],
first_action: resource[:first_action],
second_action: resource[:second_action],
score: resource[:score],
kind: resource[:kind],
symmetrical: resource[:symmetrical],
require_all: resource[:require_all],
}
end
# Unlike create we actually immediately delete the item.
def destroy
debug 'Call: destroy'
constraint_order_remove resource[:name]
property_hash.clear
cluster_debug_report "#{resource} destroy"
end
# Getters that obtains the first and second primitives and score in our
# ordering definition that have been populated by prefetch or instances
# (depends on if your using puppet resource or not).
def first
property_hash[:first]
end
def second
property_hash[:second]
end
def first_action
if property_hash[:first_action].respond_to? :to_sym
property_hash[:first_action].to_sym
else
property_hash[:first_action]
end
end
def second_action
if property_hash[:second_action].respond_to? :to_sym
property_hash[:second_action].to_sym
else
property_hash[:second_action]
end
end
def score
property_hash[:score]
end
def kind
if property_hash[:kind].respond_to? :to_sym
property_hash[:kind].to_sym
else
property_hash[:kind]
end
end
def symmetrical
property_hash[:symmetrical]
end
def require_all
property_hash[:require_all]
end
# Our setters for the first and second primitives and score. 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 first=(should)
property_hash[:first] = should
end
def second=(should)
property_hash[:second] = should
end
def first_action=(should)
property_hash[:first_action] = should
end
def second_action=(should)
property_hash[:second_action] = should
end
def score=(should)
property_hash[:score] = should
end
def kind=(should)
property_hash[:kind] = should
end
def symmetrical=(should)
property_hash[:symmetrical] = should
end
def require_all=(should)
property_hash[:require_all] = should
end
# Flush is triggered on anything that has been detected as being
# modified in the property_hash. It generates a temporary file with
# the updates that need to be made. The temporary file is then used
# as stdin for the crm command.
def flush
debug 'Call: flush'
return unless property_hash && property_hash.any?
unless primitive_exists? primitive_base_name property_hash[:first]
raise "Primitive '#{property_hash[:first]}' does not exist!"
end
unless primitive_exists? primitive_base_name property_hash[:second]
raise "Primitive '#{property_hash[:second]}' does not exist!"
end
unless property_hash[:name] && property_hash[:first] && property_hash[:second]
raise 'Data does not contain all the required fields!'
end
order_structure = {}
order_structure['id'] = name
order_structure['first'] = first
order_structure['then'] = second
order_structure['first-action'] = first_action.to_s if first_action
order_structure['then-action'] = second_action.to_s if second_action
order_structure['score'] = score.to_s if score
order_structure['kind'] = kind.to_s.capitalize if kind
order_structure['symmetrical'] = symmetrical.to_s unless symmetrical.nil?
order_structure['require-all'] = require_all.to_s unless require_all.nil?
order_patch = xml_document
order_element = xml_rsc_order order_structure
raise "Could not create XML patch for '#{resource}'" unless order_element
order_patch.add_element order_element
if present?
wait_for_constraint_update xml_pretty_format(order_patch.root), order_structure['id']
else
wait_for_constraint_create xml_pretty_format(order_patch.root), order_structure['id']
end
cluster_debug_report "#{resource} flush"
end
end

View File

@ -0,0 +1,27 @@
require 'rexml/document'
require 'rexml/formatters/pretty'
require 'timeout'
require 'yaml'
require_relative '../../pacemaker/pcs/resource_default'
require_relative '../../pacemaker/pcs/operation_default'
require_relative '../../pacemaker/pcs/cluster_property'
require_relative '../../pacemaker/pcs/pcsd_auth'
require_relative '../../pacemaker/pcs/common'
require_relative '../../pacemaker/options'
require_relative '../../pacemaker/wait'
# the parent provider for all other pcs providers
class Puppet::Provider::PacemakerPCS < Puppet::Provider
# include instance methods from the pcs library files
include Pacemaker::PcsCommon
include Pacemaker::PcsResourceDefault
include Pacemaker::PcsOperationDefault
include Pacemaker::PcsClusterProperty
include Pacemaker::PcsPcsdAuth
include Pacemaker::Wait
include Pacemaker::Options
# include class methods from the pacemaker options
extend Pacemaker::Options
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_pcsd_auth).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,120 @@
require_relative '../pacemaker_pcs'
require 'json'
Puppet::Type.type(:pacemaker_pcsd_auth).provide(:pcs, parent: Puppet::Provider::PacemakerPCS) do
desc 'Authenticate the nodes using the "pcs" command'
commands pcs: 'pcs'
commands crm_node: 'crm_node'
attr_reader :resource
# these statuses are considered to be successful
# @return [Array<String>]
def success_node_statuses
%w(already_authorized ok)
end
# checks if all nodes in the cluster are already authenticated,
# or only the local one if :whole is not enabled
# @return [true,false]
def success
validate_input
nodes_status = cluster_auth
success = whole_auth_success? nodes_status
unless success or resource[:whole]
success = local_auth_success? nodes_status
end
show_cluster_auth_status nodes_status
success
end
# if the initial check was not successful
# retry the auth command until it succeeds
# or time runs out
# @param value [true,false]
def success=(value)
# if the resource success value is not true don't do anything
return unless value
if resource[:whole]
debug "Waiting #{max_wait_time} seconds for the whole cluster to authenticate"
else
debug "Waiting #{max_wait_time} seconds for the local node to authenticate"
end
# auth may succeed if the missing nodes come online
# if password is not correct, wait for someone or something to change the password
retry_block { success }
if resource[:whole]
debug 'The whole cluster authentication was successful!'
else
debug 'The local node have been successfully authenticated!'
end
end
# show the debug block with the cluster auth status
# @param nodes_status [Hash]
def show_cluster_auth_status(nodes_status)
message = "\nCluster auth status debug start\n"
nodes_status.each do |node, status|
success = success_node_statuses.include? status
prefix = success ? 'OK ' : 'FAIL'
message += "#{prefix} #{node} (#{status})"
message += ' <- this node' if node_name == node
message += "\n"
end
message += 'Cluster auth status debug end'
debug message
end
def validate_input
fail 'Both username and password should be provided!' unless resource[:username] and resource[:password]
resource[:nodes] = [resource[:nodes]] if resource[:nodes].is_a? String
fail 'At least one node should be provided!' unless resource[:nodes].is_a? Array and resource[:nodes].any?
end
def cluster_auth
debug 'Call: cluster_auth'
result = pcs_auth_command(
resource[:nodes],
resource[:username],
resource[:password],
resource[:force],
resource[:local],
)
nodes_status = pcs_auth_parse(result)
fail "Could not parse the result of the cluster auth command: '#{result}'" unless nodes_status.is_a? Hash and nodes_status.any?
nodes_status
end
# get the Pacemaker name of the current node
# @return [String]
def node_name
return @node_name if @node_name
@node_name = crm_node('-n').chomp.strip
end
# check if the local node auth have been successful
# or was already done before
# @param nodes_status [Hash]
# @return [true,false]
def local_auth_success?(nodes_status)
success_node_statuses.include? nodes_status[node_name]
end
# check if all cluster nodes have been successfully authenticated
# or have already been authenticated before
# @param nodes_status [Hash]
# @return [true,false]
def whole_auth_success?(nodes_status)
resource[:nodes].all? do |node|
success_node_statuses.include? nodes_status[node]
end
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_property).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,51 @@
require_relative '../pacemaker_pcs'
Puppet::Type.type(:pacemaker_property).provide(:pcs, parent: Puppet::Provider::PacemakerPCS) do
desc 'Manages default values for pacemaker operation options via pcs'
commands pcs: 'pcs'
# disable this provider
confine(true: false)
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.pcs_cluster_properties.map do |title, value|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = value
parameters[:name] = title
instance = new(parameters)
instances << instance
end
instances
end
def create
debug 'Call: create'
self.value = @resource[:value]
end
def destroy
debug 'Call: destroy'
pcs_cluster_property_delete @resource[:name]
end
def exists?
debug 'Call: exists?'
pcs_cluster_property_defined? @resource[:name]
end
def value
debug 'Call: value'
pcs_cluster_property_value @resource[:name]
end
def value=(value)
debug "Call: value=#{value}"
pcs_cluster_property_set @resource[:name], value
end
end

View File

@ -0,0 +1,77 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_property).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Specific provider for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This provider will check the state
of Corosync cluster configuration properties.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
wait_for_online 'pacemaker_property'
proxy_instance = new
instances = []
proxy_instance.cluster_properties.map do |title, data|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = data['value']
parameters[:name] = title
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# @return [true,false]
def exists?
debug 'Call: exists?'
wait_for_online 'pacemaker_property'
return property_hash[:ensure] == :present if property_hash[:ensure]
out = cluster_property_defined? resource[:name]
debug "Return: #{out}"
out
end
def create
debug 'Call: create'
self.value = resource[:value]
end
def destroy
debug 'Call: destroy'
cluster_property_delete resource[:name]
end
def value
debug 'Call: value'
return property_hash[:value] if property_hash[:value]
out = cluster_property_value resource[:name]
debug "Return: #{out}"
out
end
def value=(should)
debug "Call: value=#{should}"
cluster_property_set resource[:name], should
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_resource).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,350 @@
require_relative '../pacemaker_xml'
require 'set'
Puppet::Type.type(:pacemaker_resource).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc <<-eof
Specific provider for a rather specific type since I currently have no
plan to abstract corosync/pacemaker vs. keepalived. Primitives in
Corosync are the thing we desire to monitor; websites, ipaddresses,
databases, etc, etc. Here we manage the creation and deletion of
these primitives. We will accept a hash for what Corosync calls
operations and parameters. A hash is used instead of constucting a
better model since these values can be almost anything.'
eof
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.primitives.map do |title, data|
parameters = {}
debug "Prefetch resource: #{title}"
proxy_instance.retrieve_data data, parameters
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# retrieve data from library to the target_structure
# @param data [Hash] extracted primitive data
# will extract the current primitive data unless a value is provided
# @param target_structure [Hash] copy data to this structure
# defaults to the property_hash of this provider
def retrieve_data(data = nil, target_structure = property_hash)
debug 'Call: retrieve_data'
data = primitives.fetch resource[:name], {} unless data
target_structure[:ensure] = :present
target_structure[:complex_type] = :simple
copy_value data, 'id', target_structure, :name
copy_value data, 'class', target_structure, :primitive_class
copy_value data, 'provider', target_structure, :primitive_provider
copy_value data, 'type', target_structure, :primitive_type
if data['complex']
data_complex_type = data['complex']['type'].to_sym
target_structure[:complex_type] = data_complex_type if complex_types.include? data_complex_type
complex_metadata = import_attributes_structure data['complex']['meta_attributes']
target_structure[:complex_metadata] = complex_metadata if complex_metadata
end
if data['instance_attributes']
parameters_data = import_attributes_structure data['instance_attributes']
if parameters_data && parameters_data.is_a?(Hash)
target_structure[:parameters] = parameters_data if parameters_data
end
end
if data['meta_attributes']
metadata_data = import_attributes_structure data['meta_attributes']
if metadata_data && metadata_data.is_a?(Hash)
target_structure[:metadata] = metadata_data
end
end
if data['operations']
operations_set = Set.new
data['operations'].each do |_id, operation|
operation.delete 'id'
operations_set.add operation
end
target_structure[:operations] = operations_set
end
end
def exists?
debug 'Call: exists?'
out = primitive_exists? resource[:name]
retrieve_data
debug "Return: #{out}"
out
end
# check if the location ensure is set to present
# @return [TrueClass,FalseClass]
def present?
property_hash[:ensure] == :present
end
# check if the complex type of the resource is changing
# and we have to recreate it
# @return [true,false]
def complex_change?
current_complex_type = primitive_complex_type(name) || :simple
current_complex_type != complex_type
end
# is this primitive complex?
# @return [true,false]
def is_complex?
complex_types.include? complex_type
end
# list of the actually supported complex types
# @return [Array<Symbol>]
def complex_types
[:clone, :master]
end
# Create just adds our resource to the property_hash and flush will take care
# of actually doing the work.
def create
debug 'Call: create'
self.property_hash = {
ensure: :absent,
name: resource[:name],
}
parameters = [
:primitive_class,
:primitive_provider,
:primitive_type,
:parameters,
:operations,
:metadata,
:complex_type,
:complex_metadata,
]
parameters.each do |parameter|
send "#{parameter}=".to_sym, resource[parameter]
end
end
# use cibadmin to remove the XML section describing this primitive
def remove_primitive
return unless primitive_exists? resource[:name]
stop_service
primitive_tag = 'primitive'
primitive_tag = primitive_complex_type resource[:name] if primitive_is_complex? resource[:name]
wait_for_primitive_remove "<#{primitive_tag} id='#{primitive_full_name resource[:name]}'/>\n", resource[:name]
property_hash[:ensure] = :absent
end
# stop the primitive before its removal
def stop_service
stop_primitive primitive_full_name resource[:name]
cleanup_primitive primitive_full_name resource[:name]
wait_for_stop resource[:name]
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 'Call: destroy'
remove_primitive
property_hash.clear
cluster_debug_report "#{resource} destroy"
end
# Getters that obtains the parameters and operations defined in our primitive
# that have been populated by prefetch or instances (depends on if your using
# puppet resource or not).
def parameters
property_hash[:parameters]
end
def operations
property_hash[:operations]
end
def metadata
property_hash[:metadata]
end
def complex_metadata
property_hash[:complex_metadata]
end
def complex_type
if property_hash[:complex_type].respond_to? :to_sym
property_hash[:complex_type].to_sym
else
property_hash[:complex_type]
end
end
def primitive_class
property_hash[:primitive_class]
end
def primitive_provider
property_hash[:primitive_provider]
end
def primitive_type
property_hash[:primitive_type]
end
def full_name
if is_complex?
"#{name}-#{complex_type}"
else
name
end
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)
property_hash[:parameters] = should
end
def operations=(should)
should = should.first if should.is_a? Array
property_hash[:operations] = should
end
def metadata=(should)
property_hash[:metadata] = should
end
def complex_metadata=(should)
property_hash[:complex_metadata] = should
end
def complex_type=(should)
property_hash[:complex_type] = should
end
def primitive_class=(should)
property_hash[:primitive_class] = should
end
def primitive_provider=(should)
property_hash[:primitive_provider] = should
end
def primitive_type=(should)
property_hash[:primitive_type] = should
end
# Flush is triggered on anything that has been detected as being
# modified in the property_hash. It generates a temporary file with
# the updates that need to be made. The temporary file is then used
# as stdin for the crm command. We have to do a bit of munging of our
# operations and parameters hash to eventually flatten them into a string
# that can be used by the crm command.
def flush
debug 'Call: flush'
return unless property_hash && property_hash.any?
unless primitive_class && primitive_type
raise 'Primitive class and type should be present!'
end
# if the complex type is changing we have to remove the resource
# and create a new one with the correct complex type
if complex_change?
debug 'Changing the complex type of the primitive. First remove and then create it!'
remove_primitive
end
# basic primitive structure
primitive_structure = {}
primitive_structure['id'] = name
primitive_structure['name'] = full_name
primitive_structure['class'] = primitive_class
primitive_structure['provider'] = primitive_provider if primitive_provider
primitive_structure['type'] = primitive_type
# complex structure
if is_complex?
complex_structure = {}
complex_structure['type'] = complex_type
complex_structure['id'] = full_name
# complex meta_attributes structure
if complex_metadata && complex_metadata.any?
meta_attributes_structure = export_attributes_structure complex_metadata, 'meta_attributes'
complex_structure['meta_attributes'] = meta_attributes_structure if meta_attributes_structure
end
primitive_structure['complex'] = complex_structure
end
# operations structure
if operations && operations.any?
primitive_structure['operations'] = {}
operations.each do |operation|
if operation.is_a?(Array) && operation.length == 2
# 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']
# there is no id provided, generate it
id_components = [name, operation['name'], operation['interval']]
id_components.reject!(&:nil?)
operation['id'] = id_components.join '-'
end
primitive_structure['operations'].store operation['id'], operation
end
end
# instance_attributes structure
if parameters && parameters.any?
instance_attributes_structure = export_attributes_structure parameters, 'instance_attributes'
primitive_structure['instance_attributes'] = instance_attributes_structure if instance_attributes_structure
end
# meta_attributes structure
if metadata && metadata.any?
meta_attributes_structure = export_attributes_structure metadata, 'meta_attributes'
primitive_structure['meta_attributes'] = meta_attributes_structure
end
# create and apply XML patch
primitive_patch = xml_document
primitive_element = xml_primitive primitive_structure
raise "Could not create XML patch for '#{resource}'" unless primitive_element
primitive_patch.add_element primitive_element
if present?
wait_for_primitive_update xml_pretty_format(primitive_patch.root), primitive_structure['id']
else
wait_for_primitive_create xml_pretty_format(primitive_patch.root), primitive_structure['id']
end
cluster_debug_report "#{resource} flush"
end
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:pacemaker_resource_default).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,51 @@
require_relative '../pacemaker_pcs'
Puppet::Type.type(:pacemaker_resource_default).provide(:pcs, parent: Puppet::Provider::PacemakerPCS) do
desc 'Manages default values for pacemaker resource options via pcs'
commands pcs: 'pcs'
# disable this provider
confine(true: false)
def self.instances
debug 'Call: self.instances'
proxy_instance = new
instances = []
proxy_instance.pcs_resource_defaults.map do |title, value|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = value
parameters[:name] = title
instance = new(parameters)
instances << instance
end
instances
end
def create
debug 'Call: create'
self.value = @resource[:value]
end
def destroy
debug 'Call: destroy'
pcs_resource_default_delete @resource[:name]
end
def exists?
debug 'Call: exists?'
pcs_resource_default_defined? @resource[:name]
end
def value
debug 'Call: value'
pcs_resource_default_value @resource[:name]
end
def value=(value)
debug "Call: value=#{value}"
pcs_resource_default_set @resource[:name], value
end
end

View File

@ -0,0 +1,78 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:pacemaker_resource_default).provide(:xml, parent: Puppet::Provider::PacemakerXML) do
desc 'Specific resource_default for a rather specific type since I currently have no plan to
abstract corosync/pacemaker vs. keepalived. This rsc_defaults will check the state
of Corosync cluster configuration properties.'
commands cibadmin: 'cibadmin'
commands crm_attribute: 'crm_attribute'
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
attr_accessor :property_hash
attr_accessor :resource
def self.instances
debug 'Call: self.instances'
wait_for_online 'pacemaker_resource_default'
proxy_instance = new
instances = []
proxy_instance.resource_defaults.map do |title, data|
parameters = {}
debug "Prefetch: #{title}"
parameters[:ensure] = :present
parameters[:value] = data['value']
parameters[:name] = title
instance = new(parameters)
instance.cib = proxy_instance.cib
instances << instance
end
instances
end
def self.prefetch(catalog_instances)
debug 'Call: self.prefetch'
return unless pacemaker_options[:prefetch]
discovered_instances = instances
discovered_instances.each do |instance|
next unless catalog_instances.key? instance.name
catalog_instances[instance.name].provider = instance
end
end
# @return [true,false]
def exists?
debug 'Call: exists?'
wait_for_online 'pacemaker_resource_default'
return property_hash[:ensure] == :present if property_hash[:ensure]
out = resource_default_defined? resource[:name]
debug "Return: #{out}"
out
end
def create
debug 'Call: create'
self.value = resource[:value]
end
def destroy
debug 'Call: destroy'
resource_default_delete resource[:name]
end
def value
debug 'Call: value'
return property_hash[:value] if property_hash[:value]
out = resource_default_value resource[:name]
debug "Return: #{out}"
out
end
def value=(should)
debug "Call: value=#{should}"
raise 'There is no value!' unless should
resource_default_set resource[:name], should
end
end

View File

@ -0,0 +1,48 @@
require 'rexml/document'
require 'rexml/formatters/pretty'
require 'timeout'
require 'yaml'
require_relative '../../pacemaker/xml/cib'
require_relative '../../pacemaker/xml/constraints'
require_relative '../../pacemaker/xml/constraint_colocations'
require_relative '../../pacemaker/xml/constraint_locations'
require_relative '../../pacemaker/xml/constraint_orders'
require_relative '../../pacemaker/xml/helpers'
require_relative '../../pacemaker/xml/nodes'
require_relative '../../pacemaker/xml/primitives'
require_relative '../../pacemaker/xml/properties'
require_relative '../../pacemaker/xml/resource_default'
require_relative '../../pacemaker/xml/operation_default'
require_relative '../../pacemaker/xml/status'
require_relative '../../pacemaker/xml/debug'
require_relative '../../pacemaker/options'
require_relative '../../pacemaker/wait'
require_relative '../../pacemaker/xml/xml'
require_relative '../../pacemaker/type'
# the parent provider for all other pacemaker providers
# includes all functions from all submodules
class Puppet::Provider::PacemakerXML < Puppet::Provider
# include instance methods from the pacemaker library files
include Pacemaker::Cib
include Pacemaker::Constraints
include Pacemaker::ConstraintOrders
include Pacemaker::ConstraintLocations
include Pacemaker::ConstraintColocations
include Pacemaker::Helpers
include Pacemaker::Nodes
include Pacemaker::Options
include Pacemaker::Primitives
include Pacemaker::Properties
include Pacemaker::Debug
include Pacemaker::ResourceDefault
include Pacemaker::OperationDefault
include Pacemaker::Status
include Pacemaker::Wait
include Pacemaker::Xml
include Pacemaker::Type
# include class methods from the pacemaker options
extend Pacemaker::Options
end

View File

@ -0,0 +1,6 @@
require_relative '../pacemaker_noop'
Puppet::Type.type(:service).provide(:noop, parent: Puppet::Provider::PacemakerNoop) do
# disable this provider
confine(true: false)
end

View File

@ -0,0 +1,367 @@
require_relative '../pacemaker_xml'
Puppet::Type.type(:service).provide(:pacemaker_xml, parent: Puppet::Provider::PacemakerXML) do
has_feature :enableable
has_feature :refreshable
commands crm_node: 'crm_node'
commands crm_resource: 'crm_resource'
commands crm_attribute: 'crm_attribute'
commands cibadmin: 'cibadmin'
# original title of the service
# @return [String]
def service_title
@resource.title
end
# original name of the service
# in most cases will be equal to the title
# but can be different
# @return [String]
def service_name
resource[:name]
end
# check if the service name is the same as service title
# @return [true,false]
def name_equals_title?
service_title == service_name
end
# find a primitive name that is present in the CIB
# or nil if none is present
# @return [String,nil]
def pick_existing_name(*names)
names.flatten.find do |name|
primitive_exists? name
end
end
# generate a list of strings the service name could be written as
# perhaps, one of them could be found in the CIB
# @param name [String]
# @return [Array<String>]
def service_name_variations(name)
name = name.to_s
variations = []
variations << name
variations << if name.start_with? 'p_'
name.gsub(/^p_/, '')
else
"p_#{name}"
end
base_name = primitive_base_name name
unless base_name == name
variations << base_name
variations << if base_name.start_with? 'p_'
base_name.gsub(/^p_/, '')
else
"p_#{base_name}"
end
end
variations
end
# get the correct name of the service primitive
# @return [String]
def name
return @name if @name
@name = pick_existing_name service_name_variations(service_title), service_name_variations(service_name)
if @name
message = "Using CIB name '#{@name}' for primitive '#{service_title}'"
message += " with name '#{service_name}'" unless name_equals_title?
debug message
else
message = "Primitive '#{service_title}'"
message += " with name '#{service_name}'" unless name_equals_title?
message += ' was not found in CIB!'
raise message
end
@name
end
# full name of the primitive
# if resource is complex use group name
# @return [String]
def full_name
return @full_name if @full_name
if primitive_is_complex? name
full_name = primitive_full_name name
debug "Using full name '#{full_name}' for complex primitive '#{name}'"
@full_name = full_name
else
@full_name = name
end
end
# name of the basic service without 'p_' prefix
# used to disable the basic service.
# Uses "name" property if it's not the same as title
# because most likely it will be the real system service name
# @return [String]
def basic_service_name
return @basic_service_name if @basic_service_name
basic_service_name = name
basic_service_name = service_name unless name_equals_title?
if basic_service_name.start_with? 'p_'
basic_service_name = basic_service_name.gsub(/^p_/, '')
end
debug "Using '#{basic_service_name}' as the basic service name for the primitive '#{name}'"
@basic_service_name = basic_service_name
end
# cleanup a primitive and
# wait until cleanup finishes
def cleanup
cleanup_primitive full_name, hostname
wait_for_status name
end
# run the disable basic service action only
# if it's enabled fot this provider action
# and is globally enabled too
# @param [Symbol] action (:start/:stop/:status)
def disable_basic_service_on_action(action)
if action == :start
return unless pacemaker_options[:disable_basic_service_on_start]
elsif action == :stop
return unless pacemaker_options[:disable_basic_service_on_stop]
elsif action == :status
return unless pacemaker_options[:disable_basic_service_on_status]
else
fail "Action '#{action}' is incorrect!"
end
disable_basic_service
end
# called by Puppet to determine if the service
# is running on the local node
# @return [:running,:stopped]
def status
debug "Call: 'status' for Pacemaker service '#{name}' on node '#{hostname}'"
disable_basic_service_on_action :status
cib_reset 'service_status'
wait_for_online 'service_status'
if pacemaker_options[:cleanup_on_status]
if !pacemaker_options[:cleanup_only_if_failures] || primitive_has_failures?(name, hostname)
cleanup
end
end
out = if primitive_is_master? name
service_status_mode pacemaker_options[:status_mode_master]
elsif primitive_is_clone? name
service_status_mode pacemaker_options[:status_mode_clone]
else
service_status_mode pacemaker_options[:status_mode_simple]
end
if pacemaker_options[:add_location_constraint]
if out == :running && (!service_location_exists? full_name, hostname)
debug 'Location constraint is missing. Service status set to "stopped".'
out = :stopped
end
end
debug "Return: '#{out}' (#{out.class})"
debug cluster_debug_report "#{@resource} status"
out
end
# called by Puppet to start the service
def start
debug "Call 'start' for Pacemaker service '#{name}' on node '#{hostname}'"
disable_basic_service_on_action :start
enable unless primitive_is_managed? name
if pacemaker_options[:cleanup_on_start]
if !pacemaker_options[:cleanup_only_if_failures] || primitive_has_failures?(name, hostname)
cleanup
end
end
if pacemaker_options[:add_location_constraint]
service_location_add full_name, hostname unless service_location_exists? full_name, hostname
end
unban_primitive name, hostname
start_primitive name
start_primitive full_name
if primitive_is_master? name
debug "Choose master start for Pacemaker service '#{name}'"
wait_for_master name
else
service_start_mode pacemaker_options[:start_mode_simple]
end
debug cluster_debug_report "#{@resource} start"
end
# called by Puppet to stop the service
def stop
debug "Call 'stop' for Pacemaker service '#{name}' on node '#{hostname}'"
disable_basic_service_on_action :stop
enable unless primitive_is_managed? name
if pacemaker_options[:cleanup_on_stop]
if !pacemaker_options[:cleanup_only_if_failures] || primitive_has_failures?(name, hostname)
cleanup
end
end
if pacemaker_options[:add_location_constraint]
service_location_remove full_name, hostname if service_location_exists? full_name, hostname
end
if primitive_is_master? name
service_stop_mode pacemaker_options[:stop_mode_master]
elsif primitive_is_clone? name
service_stop_mode pacemaker_options[:stop_mode_clone]
else
service_stop_mode pacemaker_options[:stop_mode_simple]
end
debug cluster_debug_report "#{@resource} stop"
end
# called by Puppet to restart the service
def restart
debug "Call 'restart' for Pacemaker service '#{name}' on node '#{hostname}'"
if pacemaker_options[:restart_only_if_local] && (!primitive_is_running? name, hostname)
Puppet.info "Pacemaker service '#{name}' is not running on node '#{hostname}'. Skipping restart!"
return
end
begin
stop
rescue
nil
ensure
start
end
end
# wait for the service to start using
# the selected method.
# @param mode [:global, :master, :local]
def service_start_mode(mode = :global)
if mode == :master
debug "Choose master start for Pacemaker service '#{name}'"
wait_for_master name
elsif mode == :local
debug "Choose local start for Pacemaker service '#{name}' on node '#{hostname}'"
wait_for_start name, hostname
elsif mode == :global
debug "Choose global start for Pacemaker service '#{name}'"
wait_for_start name
else
raise "Unknown service start mode '#{mode}'"
end
end
# wait for the service to stop using
# the selected method.
# @param mode [:global, :master, :local]
def service_stop_mode(mode = :global)
if mode == :local
debug "Choose local stop for Pacemaker service '#{name}' on node '#{hostname}'"
ban_primitive name, hostname
wait_for_stop name, hostname
elsif mode == :global
debug "Choose global stop for Pacemaker service '#{name}'"
stop_primitive name
wait_for_stop name
else
raise "Unknown service stop mode '#{mode}'"
end
end
# determine the status of the service using
# the selected method.
# @param mode [:global, :master, :local]
# @return [:running,:stopped]
def service_status_mode(mode = :local)
if mode == :local
debug "Choose local status for Pacemaker service '#{name}' on node '#{hostname}'"
get_primitive_puppet_status name, hostname
elsif mode == :global
debug "Choose global status for Pacemaker service '#{name}'"
get_primitive_puppet_status name
else
raise "Unknown service status mode '#{mode}'"
end
end
# called by Puppet to enable the service
def enable
debug "Call 'enable' for Pacemaker service '#{name}' on node '#{hostname}'"
manage_primitive name
end
# called by Puppet to disable the service
def disable
debug "Call 'disable' for Pacemaker service '#{name}' on node '#{hostname}'"
unmanage_primitive name
end
alias_method :manual_start, :disable
# called by Puppet to determine if the service is enabled
# @return [:true,:false]
def enabled?
debug "Call 'enabled?' for Pacemaker service '#{name}' on node '#{hostname}'"
out = get_primitive_puppet_enable name
debug "Return: '#{out}' (#{out.class})"
out
end
# create an extra provider instance to deal with the basic service
# the provider will be chosen to match the current system
# @return [Puppet::Type::Service::Provider]
def extra_provider(provider_name = nil)
return @extra_provider if @extra_provider
begin
param_hash = {}
param_hash.store :name, basic_service_name
param_hash.store :provider, provider_name if provider_name
type = Puppet::Type::Service.new param_hash
@extra_provider = type.provider
rescue => e
Puppet.info "Could not get extra provider for Pacemaker primitive '#{name}': #{e.message}"
@extra_provider = nil
end
end
# disable and stop the basic service
def disable_basic_service
# skip native-based primitive classes
if pacemaker_options[:native_based_primitive_classes].include?(primitive_class name)
Puppet.info "Not stopping basic service '#{basic_service_name}', since its Pacemaker primitive is using primitive_class '#{primitive_class name}'"
return
end
return unless extra_provider
begin
if extra_provider.enableable? && extra_provider.enabled? == :true
Puppet.info "Disable basic service '#{extra_provider.name}' using provider '#{extra_provider.class.name}'"
extra_provider.disable
else
Puppet.info "Basic service '#{extra_provider.name}' is disabled as reported by '#{extra_provider.class.name}' provider"
end
if extra_provider.status == :running
Puppet.info "Stop basic service '#{extra_provider.name}' using provider '#{extra_provider.class.name}'"
extra_provider.stop
else
Puppet.info "Basic service '#{extra_provider.name}' is stopped as reported by '#{extra_provider.class.name}' provider"
end
rescue => e
Puppet.info "Could not disable basic service for Pacemaker primitive '#{name}' using '#{extra_provider.class.name}' provider: #{e.message}"
end
end
end

Some files were not shown because too many files have changed in this diff Show More