puppet-pacemaker/README.md

946 lines
29 KiB
Markdown

# Puppet Pacemaker module
This Puppet module is intended to work with the running Pacemaker
cluster to manage its configuration. It can create, update and remove
most of the configuration objects and query their status.
The interface of the Puppet types in the module is loosely based on
**puppetlabs/corosync** types with *cs_* prefix changed to the *pacemaker_*
prefix but it have been significantly reworked and is not compatible.
**puppet-pacemaker** is much more sophisticated then the
**puppetlabs/corosync** module and provides a lot of debugging
features, checks, configuration options and it can work even when
the Puppet is being run on many cluster nodes at the same time and
without neither **crm** nor **pcs** being installed.
## License
Apache 2.0
# Pacemaker types
These types are used to configure Pacemaker object and are the core of
this module. You can find some "interactive examples" of their usage in
the *examples* folder.
## pacemaker_resource
This is the most important resource type. It creates, updates and
removes Pacemaker primitives.
### Parameters
#### primitive_class
The basic class of this primitive. It could be *ocf*, *lsb*, *systemd*
and some others.
Default: ocf
#### primitive_provider
The provider or vendor of the primitive. For OCF class can be
*pacemaker*, *heartbeat* or vendor-specific values.
Default: pacemaker
#### primitive_type
The actual provider script or service to use. Should be equal to the
OCF file name, or to the service name if other classes are used.
Default: Stateful
#### parameters
The Hash of resource instance attribute names and their values.
These attributes are used to configure the running service and,
usually, only OCF class supports them.
Example:
```puppet
{
'a' => '1',
'b' => '2',
},
```
#### operation
This data structure describes this primitive's operations and timeouts.
Example:
```puppet
{
'monitor' => {
'interval' => '20',
'timeout' => '10'
},
'start' => {
'timeout' => '30'
},
'stop' => {
'timeout' => '30'
},
}
```
Using array and multiple monitors:
```puppet
[
{
'name' => 'monitor',
'interval' => '10',
'timeout' => '10',
},
{
'name' => 'monitor',
'interval' => '60',
'timeout' => '10',
},
{
'name' => 'start',
'timeout' => '30',
},
{
'name' => 'stop',
'timeout' => '30',
},
]
```
#### metadata
This hash can contain names and values of primitive's meta attributes.
Example:
```puppet
{
'migration-threshold' => '100',
'failure-timeout' => '120',
}
```
#### complex_type
A primitive can be either a *simple* one, and run only as a single
instance. Or it can be *clone* and have many instances, or it can be
*master* and be able to have master and slave states.
Default: simple
#### complex_metadata
A hash of complex type related metadata names and values.
Example:
```puppet
{
'interleave' => true,
'master-max' => '1',
}
```
#### debug
This option makes supported provides to omit any changes to the system.
Providers will still retrieve the system state, compare it to the desired
state from the catalog and will try to sync state if there are differences.
But it will only show destructive commands that it would be executing in
the normal mode. It's better then Puppet's *noop* mode because it shows
sync actions and is useful for debugging.
Default: false
## pacemaker_location
This type can manage location constraints. Either the node and score
based ones or the rule based ones. This constraints can control the
primitive placement to nodes through priorities or rules.
### Parameters
#### primitive
The name of the Pacemaker primitive of this location.
#### score
The score values for a node/score location.
#### node
The node name of the node/score location.
#### rules
The rules data structure.
Example:
```puppet
[
{
'score' => '100',
'expressions' => [
{
'attribute' => 'test1',
'operation' => 'defined',
}
]
},
{
'score' => '200',
'expressions' => [
{
'attribute' => 'test2',
'operation' => 'defined',
}
]
}
]
```
#### debug
Don't actually do changes
Default: false
## pacemaker_colocation
This type manages colocation constraints. If two resources are in a
colocation they will always be on the same node. Note that colocation
implies the start order because the second resource will always start
after the first.
### Parameters
#### first
The name of the first primitive
#### second
The name of the second primitive
#### score
The priority score of this constraint
#### debug
Don't actually do changes
Default: false
## pacemaker_order
This type can manage the order constraints. These constraints controls
the start and stop ordering of resources. Order doesn't imply colocation
and resources can run on different nodes.
### Parameters
#### first
(Mandatory)
The name of the first primitive.
#### second
(Mandatory)
The name of the second primitive.
#### score
The priority score of this constraint.
If greater than zero, the constraint is mandatory.
Otherwise it is only a suggestion.
Used for Pacemaker version 1.0 and below.
Default: undef
#### first_action
The action that the first resource must complete before second action
can be initiated for the then resource.
Allowed values: start, stop, promote, demote.
Default: undef (means start)
#### second_action
The action that the then resource can execute only after the
first action on the first resource has completed.
Allowed values: start, stop, promote, demote.
Default: undef (means the value of the first action)
#### kind
How to enforce the constraint. Allowed values:
* **optional**: Just a suggestion. Only applies if both resources are
executing the specified actions.
Any change in state by the first resource will have no effect on
the then resource.
* **mandatory**: Always. If first does not perform first-action,
then will not be allowed to performed then-action.
If first is restarted, then (if running) will be stopped beforehand
and started afterward.
* **serialize**: Ensure that no two stop/start actions occur
concurrently for the resources.
First and then can start in either order, but one must complete
starting before the other can be started.
A typical use case is when resource start-up puts a high load on
the host.
Used only with Pacemaker version 1.1 and above.
Default: undef
#### symmetrical
If true, the reverse of the constraint applies for the opposite action
(for example, if B starts after A starts, then B stops before A stops).
Default: undef (means true)
#### require_all
Whether all members of the set must be active before continuing.
Default: undef (means true)
#### debug
Don't actually do changes
Default: false
## pacemaker_operation_default
This little type controls the default operation properties of the
cluster resources. For example, you can set the default *timeout*
for every operation without it's own configured *timeout* value.
### parameters
#### name
The default property name
#### value
The default property value
#### debug
Don't actually do changes
Default: false
Example:
```puppet
pacemaker_operation_default { 'timeout' : value => '30' }
```
## pacemaker_resource_default
This little type controls the default meta-attributes of all resources
without their own defined values.
### parameters
#### name
The default property name
#### value
The default property value
#### debug
Don't actually do changes
Default: false
Example:
```puppet
pacemaker_resource_default { 'resource-stickiness' : value => '100' }
```
## pacemaker_property
This tiny type can the cluster-wide properties.
### parameters
#### name
The property name
#### value
The property value
#### debug
Don't actually do changes
Default: false
Example:
```puppet
pacemaker_property { 'stonith-enabled' :
value => false,
}
pacemaker_property { 'no-quorum-policy' :
value => 'ignore',
}
```
## pacemaker_online
This little resource can wait until the cluster have settled and ready
to be configured. It can be useful in some cases, perhaps as an anchor,
but most other type's *xml* providers can wait for cluster on their own.
Example:
```puppet
pacemaker_online { 'setup-finished' :}
```
## service (pacemaker provider)
This type uses the standard *service* type from the Puppet distribution
but implements the custom *pacemaker* provider. It can be used to start
and stop Pacemaker services the same way the Puppet starts and stops
system services.
It can query the service status, either on the entire cluster or on the
local node, start and stop single, cloned and master services.
There are also two special features:
- Adding location constraints. This provider can add the location
constraint to enable the run of the primitive on the current node.
It's needed in the asymmetric cluster configuration where services
are not allowed to start anywhere unless explicitly allowed to.
- Disabling the basic service. For example, you have the *apache*
primitive service in your cluster and are using an OCF script
to manage it. In this can you will not want another instance of
*apache* to start by the system init scripts or startup service.
The provider will detect the running basic service and will stop
and disable it's auto-run before trying to start the cluster service.
## pacemaker_nodes
This type is very special and designed to add and remove corosync 2 nodes
without restarting the service by providing the data structure like
this:
```puppet
{
'node-1' => { 'id' => '1', 'ip' => '192.168.0.1'},
'node-2' => { 'id' => '2', 'ip' => '192.168.0.2'},
}
```
Most likely you should never use this type.
## Pacemaker providers
Each *pacemaker_* type may have up to three different providers:
- *xml* provider. This provider is based on the *pacemaker* library
XML parsing and generating capabilities and in most canes require
only *cibadmin* to download XML CIB and apply patches, but can
use *crm_attribute* too. These tools are written in C and are the
core parts of the Pacemaker project and most likely will be present
on every system.
- *pcs* provider. These provides are designed around the *pcs* cluster
management tool usually found on Red Hat family systems. They should
not be as complex as *xml* providers, but *pcs* may not be available
on you distribution. Currently it's implemented only for few types
and they disabled because there is no reason to actually use them.
- *noop* provider. These providers do absolutely nothing completely
disabling the resource if the provider is manually set to *noop*.
This resource will not fail even if there is no Pacemaker installed.
It can be useful if you want to turn off several resources.
Puppet's *noop* meta-attribute will not do the same this because it
still does the retrieve phase and will fail if the state cannot be
obtained.
## pacemaker::wrapper
This definition can be applied to any Puppet managed service, even from
a third party module, and make this service a Pacemaker managed service
without modifying the Puppet code.
Wrapper can also create the OCF script from a Puppet file or template,
or the script can be obtained elsewhere. Actually, wrappers are only
practical for OCF managed services, because lsb, systemd or upstart
services can be managed directly by the cluster.
It can also create *ocf_handlers*. The OCF handler is a special shell
script that can call the OCF script with all environment variables
and parameters set. The handler can be used to take manual control
over the pacemaker managed service and start and stop them without
the cluster. It can be useful for debugging or during the
disaster recovery.
### Parameters
#### ensure
(optional) Create or remove the files
Default: present
#### ocf_root_path
(optional) Path to the ocf folder
Default: /usr/lib/ocf
#### primitive_class
(optional) Class of the created primitive
Default: ocf
#### primitive_provider
(optional) Provider of the created primitive
Default: pacemaker
#### primitive_type
(optional) Type of the created primitive. Set this to your OCF script.
Default: Stateful
#### prefix
(optional) Use p_ prefix for the Pacemaker primitive. There is no
need to use it since the service provider can disable the basic
service on its own.
Default: false
#### parameters
(optional) Instance attributes hash of the primitive
Default: undef
#### operations
(optional) Operations hash of the primitive
Default: undef
#### metadata
(optional) Primitive meta-attributes hash
Default: undef
#### complex_metadata
(optional) Meta-attributes of the complex primitive
Default: undef
#### complex_type
(optional) Set this to 'clone' or 'master' to create a
complex primitive
Default: undef
#### use_handler
(optional) Should the handler script be created
Default: true
#### handler_root_path
(optional) Where the handler should be placed
Default: /usr/local/bin
#### ocf_script_template
(optional) Generate the OCF script from this template
Default: undef
#### ocf_script_file
(optional) Download the OCF script from this file
Defaults: undef
#### create_primitive
(optional) Should the Pacemaker primitive be created
Defaults: true
#### service_provider
(optional) The name of Pacemaker service provider
to be set to this service.
Default: pacemaker
For example, if you have a simple service:
```puppet
service { 'apache' :
ensure => 'running',
enable => true,
}
```
You can convert it to the Pacemaker service just by adding this
definition:
```puppet
pacemaker:wrapper { 'apache' :
primitive_type => 'apache',
parameters => {
'port' => '80',
},
operations => {
'monitor' => {
'interval' => '10',
},
},
}
```
Provided there is the ocf:pacemaker:apache script with the port
parameter, the *apache* Pacemaker primitive will be created and
started and the basic *apache* service will be disabled and stopped.
## STONITH
STONITH manifests are auto generated from the XML source
files by the generator script.
```bash
rake generate_stonith
```
The generated defined types can be found in *manifests/stonith*.
Every STONITH implementation has different parameters.
Example:
```puppet
class { "pacemaker::stonith::ipmilan" :
address => "10.10.10.100",
username => "admin",
password => "admin",
}
```
# Development
## Library structure
You can find these folders inside the **lib**:
- *facter* contains the fact **pacemaker_node_name**. It is equal to the
node name from the Pacemaker point of view. May be equal to either
$::hostname of $::fqdn.
- *pacemaker* contains the Pacemaker library files. The Pacemaker
module functions are split to submodules according to their role.
There are also *xml* and *pcs* groups of files related to either
*pcs* or *xml* provider and several common files.
- *puppet* contains Puppet types and provider. They are using the
functions from the Pacemaker library.
- *serverspec* contains the custom ServerSpec types to work with
Pacemaker. They are using the same library Puppet types and providers
do. These types are used in the Acceptance tests to validate that
Pacemaker have really be configured and its configuration contains
the desired elements and parameters.
- *tools* contains two interactive tools: console and status. Console
can be used to manually run the library functions either to debug them
or to configure the cluster by hand. Status uses the library functions
to implement something like pcs or crm status command to see the
current cluster status.
## Data flow
When the catalog is being compiled the instance of each type will
be created and properties and parameters values will be assigned.
At this stage the values can be validated. If the property has
the *validate* function it will be called to check if the value is
correct or the exception can be raised. After tha validation the
*munge* function will be called if the values need to be changed or
converted somehow. If the property accepts the array value every
elements will be validated and then munged separately.
When the catalog is compiled and delivered to the node Puppet will
start to apply it.
### Data retrieval
Puppet type will try to retrieve the current state first. It will
either use *prefetch* mechanics if it's enabled or will simply walk
through every resource in the catalog calling *exists?* functions
and then other getter functions to determine the current system state.
If prefetch is used, it will assign every provider, generated by the
*instances* function to the corresponding resource in the catalog.
During the transaction the resource will be able to use already
acquired data speeding the Puppet run up a little. Without prefetch
each provider will receive the system state when its resource is
processed separately.
#### Complex providers
Providers: pacemaker_resource, pacemaker_location, pacemaker_colocation, pacemaker_order.
This providers use *retrieve_data* function to get the configuration
and status data from the library and convert it to the form used
in the Puppet type by filling the *property_hash*. This happens
either during prefetch or when the *exists?* function is called.
Other getter function will just take their values from the
*property_hash* after it was filled with data.
#### Simple providers
Providers: pacemaker_property, pacemaker_resource_default,
pacemaker_operation_default, pacemaker_online.
These providers are much more simple. There is no *retrieve_data*
function and the values are just passed as the property_hash to
the provider from *instances* if the prefetch is used and then
are taken from this hash by the getter functions. If there is no
prefetch and *property_hash* is empty the values are retrieved
from the library directly by the getters. Actually there is only one
getter for *value* and an implicit getter for *ensure* or no getter
at all for the not ensurable *pacemaker_online*.
#### Library
Both complex and simple providers are using the library functions to
get the current state of the system. There is the *main data structure*
for each entity the library can work with. For example, the resources
use the *primitives* structure.
Every provider can either take the values directly from this structure
or it can use one of the many values helpers and predicate functions
such as *primitive_type* or *primitive_is_complex?*. Most of these
helper functions are taking the resource name as an argument and try to
find the asked values in the data structure and return it.
The main data structures are formed by functions and their values are
memorised and returned from the cache every time they are called again.
Sometimes, when the new values should be acquired from the system this
memoization can be dropped by calling the *cib_reset* function.
Every data structure get its values by parsing the CIB XML. This xml
is got by calling the *cibaqmin -Q* command. Then the *REXML* document
is created with this data and saved too. It can be accessed by the
*cib* function, or, you can even set the new XML text to using
the *cib=* function if you want the library to use the prepared XML
data instead of receiving the new one.
Data structures are formed by using CIB section filter functions like
*cib_section_primitives* which return the requested part of the CIB.
Then these objects are parsed into the data structures.
For a library user is most cases there is no need to work with
anything but main data structures and helper getters and predicates.
These are the main data structures:
- **primitives** The list of primitives and their configurations.
- **node_status** The current primitive status by node.
- **constraints** All types of constraints and their parameters.
- **constraint_colocations** Filtered colocation constraints.
- **constraint_locations** Filtered location constraints.
- **constraint_orders** Filtered order constraints.
- **nodes** Cluster nodes ands their ids.
- **operation_defaults** Defined operation defaults and their values.
- **resource_defaults** Defined resource defaults and their values.
- **cluster_properties** Defined cluster properties and their values.
PCS based versions of the data structures:
- **pcs_operation_defaults** Defined operation defaults and their values.
- **pcs_resource_defaults** Defined resource defaults and their values.
- **pcs_cluster_properties** Defined cluster properties and their values.
### Data matching
After the provider have retrieved the current system state one way or
another and it's getters are able to return the values the types
starts to check is these values are equal to the desired ones.
For every property the value will be retrieved by it's getter function
in the provider and the value will be compared to the value the type
got from the catalog using the *insync?* function. Usually there is
not need to change it's behaviour and this function can be left
unimplemented and taken from the parent implementation, but in some
cases this comparison should use a special function to check
if the data structures are equal if the conversion or filtering
is required and a the custom *insync?* should somehow determine is
*is* is equal to *should* or not. Function *is_to_s* and *should_to_s*
will be used to format the property change message in the puppet log.
### Data syncing
If the retrieved data for the property was different from the desired
one or if the resource doesn't exist at all the type will try to sync
the values.
If the resource was found not to exist the *create* method will be
called. It should create the new resource with all parameters or fill
the property hash with them. If the resource should be removed the
*destroy* function will be called. If should either actually destroy
the resource or clear the property hash and set ensure to absent.
If the resource exists and should not be removed but has incorrect
parameter values the setters will be used to set properties to the
desired values. Each setter can either set the value directly or
modify the property hash.
Finally, the *flush* function will be called if it's defined. This
function should use the values from the property hash to actually
change the system state by creating, removing or updating the resource.
If getters and setters are not using the property hash and are making
changes directly there is not need for the *flush* function.
#### Complex providers
Complex providers are using the property hash to set the values and
the flush function to modify the pacemaker configuration.
When the *property_hash* is formed by using the *create* function or
setter function the *flush* method should convert the values from the
property hash to the library friendly data structure. Then the XML
generator function can be called to convert this structure to the
XML patch that can be applied to the CIB, and the *cibadmin --patch*
command will be called to apply it. If the resource should be removed
the small XML patch can be applied by the remove function directly.
All command calls that are changing the system should be run as their
safe versions. They will not be executed if the debug parameter is
enabled and will be just shown in the log.
#### Simple providers
Simple providers are not using the *flush* function and setters are
modifying the system directly. XML generator are not used too and
the values are set using the *crm_attribute* command calls.
Service provider can also use *crm_attribute* to change the service
status.
PCS versions of these providers are using the *pcs* command calls for
the same purpose. PCS providers should be using their own main
data structures and are designed to be as simple as possible.
### Special providers
The providers of *service* and *pacemaker_nodes* types are working very
differently from other.
Service provider is not ensurable and cannot create services but can
control their status. It will use the library to get the status of the
service's primitive, try to start or stop it, and then will wait
for this action to succeed. It is also capable of adding the service
location constraints using the special library function and stopping
and disabling the basic service using another provider instance.
Pcmk_nodes provider uses the *nodes* structure but work mostly
with corosync nodes using the *corosync-cmapctl* of the Corosync2
installation. It will match the exiting nodes to the desired node list
and will remove all extra corosync nodes and add the missing ones.
It can also remove the extra Pacemaker nodes but adding new nodes is
not required because Pacemaker will handle it on its own and therefore
should be disabled.
## Custom configuration
Some aspects of the providers behaviour can be controlled by the
*options.yaml*. This file can be found at *lib/pacemaker/options.yaml*
and contains all set options and their descriptions.
## Testing and debugging
### Specs
Most of the code base in the library has Ruby specs as well as Puppet
types and providers.
- *unit/puppet* Contains the specs for Puppet types and providers
as well as the spec for the whole Pacemaker library and the fixture
XML file. Most of the library, type and provider function are
tested here.
- *unit/serverspec* Contains the specs for the ServerSpec types. They
are used to check that these types are working correctly as well
as indirectly checking the library too.
- *classes* and *defined* have rspec-puppet tests for
the classes and definitions.
- *acceptance* These tests are using the ServerSpec types to check
that the module is actually configuring the cluster correctly on the
virtual system. First, the corosync and pacemaker is being installed
on the newly created system, then the test manifests in the *examples*
folder are applied to check if the resources can be successfully
created, updated and removed. Every time the specs look into the
pacemaker configuration to ensure that the resources are present
and have the correct properties.
### ServerSpec types
- Pcmk_resource
- Pcmk_location
- Pcmk_colocation
- Pcmk_order
- Pcmk_property
- Pcmk_resource_default
- Pcmk_operation_default
You can find the description of the properties in the actual type files
and examples in the *spec/serverspec* and *spec/acceptance*.
### Manual testing
The library provides debug checkpoints for a lot of function calls and
their output can be seen in the Puppet debug log.
Service provider uses the *cluster_debug_report* function to output
the formatted report of the current cluster state.
Pacemaker debug block start at 'test'
-> Clone primitive: 'p_neutron-plugin-openvswitch-agent-clone'
node-1: START (L) | node-2: STOP | node-3: STOP
-> Simple primitive: 'p_ceilometer-alarm-evaluator'
node-1: STOP | node-2: STOP (F) | node-3: STOP (F)
-> Simple primitive: 'p_heat-engine'
node-1: START (L) | node-2: STOP | node-3: STOP
-> Simple primitive: 'p_ceilometer-agent-central'
node-1: STOP | node-2: STOP (F) | node-3: STOP (F)
-> Simple primitive: 'vip__management'
node-1: START (L) | node-2: STOP (L) | node-3: STOP (L)
-> Clone primitive: 'ping_vip__public-clone'
node-1: START (L) | node-2: START (L) | node-3: START (L)
-> Clone primitive: 'p_neutron-l3-agent-clone'
node-1: START (L) | node-2: STOP | node-3: STOP
-> Clone primitive: 'p_neutron-metadata-agent-clone'
node-1: START (L) | node-2: STOP | node-3: STOP
-> Clone primitive: 'p_mysql-clone'
node-1: START (L) | node-2: START (L) | node-3: STOP
-> Simple primitive: 'p_neutron-dhcp-agent'
node-1: START (L) | node-2: STOP | node-3: STOP
-> Simple primitive: 'vip__public'
node-1: START (L) | node-2: STOP (L) | node-3: STOP (L)
-> Clone primitive: 'p_haproxy-clone'
node-1: START (L) | node-2: START (L) | node-3: STOP
-> Master primitive: 'p_rabbitmq-server-master'
node-1: MASTER (L) | node-2: START (L) | node-3: STOP
* symmetric-cluster: false
* no-quorum-policy: ignore
Pacemaker debug block end at 'test'
- (L) The location constraint for this resource is created in this node
- (F) This resource have failed on this node
- (M) This resource is not managed
Inserting this function into other providers can be helpful if you need
to se the status of all surrounding resources.
Using the **debug** property of most resources can help you to debug
the providers without damaging the system configuration.
## Links
- [Pacemaker Explained](http://clusterlabs.org/doc/en-US/Pacemaker/1.1/html/Pacemaker_Explained/)
- [Pacemaker Cluster from Scratch](http://clusterlabs.org/doc/en-US/Pacemaker/1.1/html/Clusters_from_Scratch/)
- [Puppet Types and Providers](https://puppet.com/docs/puppet/5.5/complete_resource_example.html)
- [RSpec Puppet Test](http://rspec-puppet.com/)
- [ServerSpec Tests](http://serverspec.org/)
- [RSpec-Beaker Acceptance Tests](https://github.com/puppetlabs/beaker-rspec)
- [source code repository](https://git.openstack.org/cgit/openstack/puppet-pacemaker)
- [Development](https://docs.openstack.org/puppet-openstack-guide/latest/)