# 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/)
