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:
parent
3cfaf36e2f
commit
4d2e554f68
|
@ -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}"
|
||||
|
|
|
@ -5,7 +5,13 @@ spec/fixtures/
|
|||
.vagrant/
|
||||
.bundle/
|
||||
coverage/
|
||||
log/
|
||||
.idea/
|
||||
*.swp
|
||||
*.iml
|
||||
openstack/
|
||||
.*.sw
|
||||
|
||||
# documentation
|
||||
doc
|
||||
.yardoc
|
||||
.yardwarns
|
||||
strings.json
|
||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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
73
Gemfile
|
@ -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
|
||||
|
|
13
Modulefile
13
Modulefile
|
@ -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'
|
44
Rakefile
44
Rakefile
|
@ -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']
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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']
|
||||
|
||||
|
|
@ -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<||>
|
|
@ -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<||>
|
|
@ -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'
|
|
@ -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<||>
|
|
@ -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<||>
|
|
@ -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<||>
|
|
@ -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'
|
|
@ -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<||>
|
|
@ -0,0 +1,4 @@
|
|||
pacemaker_operation_default { 'interval' :
|
||||
ensure => 'present',
|
||||
value => '300',
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pacemaker_operation_default { 'interval' :
|
||||
ensure => 'absent',
|
||||
}
|
|
@ -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'
|
|
@ -0,0 +1,4 @@
|
|||
pacemaker_operation_default { 'interval' :
|
||||
ensure => 'present',
|
||||
value => '301',
|
||||
}
|
|
@ -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<||>
|
|
@ -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<||>
|
|
@ -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'
|
|
@ -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<||>
|
|
@ -0,0 +1,9 @@
|
|||
pacemaker_property { 'cluster-delay' :
|
||||
ensure => 'present',
|
||||
value => '50',
|
||||
}
|
||||
|
||||
pacemaker_property { 'batch-limit' :
|
||||
ensure => 'present',
|
||||
value => '50',
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
pacemaker_property { 'cluster-delay' :
|
||||
ensure => 'absent',
|
||||
}
|
||||
|
||||
pacemaker_property { 'batch-limit' :
|
||||
ensure => 'absent',
|
||||
}
|
|
@ -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'
|
|
@ -0,0 +1,9 @@
|
|||
pacemaker_property { 'cluster-delay' :
|
||||
ensure => 'present',
|
||||
value => '51',
|
||||
}
|
||||
|
||||
pacemaker_property { 'batch-limit' :
|
||||
ensure => 'present',
|
||||
value => '51',
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
}
|
|
@ -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' :}
|
|
@ -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'
|
|
@ -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',
|
||||
},
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
pacemaker_resource_default { 'resource-stickiness' :
|
||||
ensure => 'present',
|
||||
value => '100',
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pacemaker_resource_default { 'resource-stickiness' :
|
||||
ensure => 'absent',
|
||||
}
|
|
@ -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'
|
|
@ -0,0 +1,4 @@
|
|||
pacemaker_resource_default { 'resource-stickiness' :
|
||||
ensure => 'present',
|
||||
value => '101',
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
pacemaker_resource { 'service-test1' :
|
||||
ensure => 'absent',
|
||||
}
|
||||
|
||||
pacemaker_resource { 'service-test2' :
|
||||
ensure => 'absent',
|
||||
}
|
|
@ -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']
|
|
@ -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']
|
|
@ -0,0 +1,7 @@
|
|||
require 'facter'
|
||||
|
||||
Facter.add('pacemaker_node_name') do
|
||||
setcode do
|
||||
Facter::Core::Execution.exec 'crm_node -n'
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue