charmhelpers.core.reactive

This module serves as the basis for creating charms and relation implementations using the reactive pattern.

Overview

The pattern is “reactive” because you use @when and similar decorators to indicate that blocks of code react to certain conditions, such as a relation reaching a specific state, a file changing, certain config values being set, etc. More importantly, you can react to not just indvidual conditions, but meaningful combinations of conditions that can span multiple hook invocations, in a natural way.

For example, the following would update a config file when both a database and admin password were available, and, if and only if that file was changed, the appropriate service would be restarted:

@when('db.database.available', 'admin-pass')
def render_config(pgsql):
    render_template('app-config.j2', '/etc/app.conf', {
        'db_conn': pgsql.connection_string(),
        'admin_pass': hookenv.config('admin-pass'),
    })

@when_file_changed('/etc/app.conf')
def restart_service():
    hookenv.service_restart('myapp')

if __name__ == '__main__':
    reactive.main()

Structure of a Reactive Charm

The structure of a reactive charm is similar to existing charms, with the addition of reactive and relations directories under hooks:

.
├── metadata.yaml
└── hooks
    ├── pgsql-relation-changed
    ├── reactive
    │   └── common.py
    └── relations
        └── pgsql
            ├── common
            │   └── __init__.py
            ├── interface.yaml
            ├── peer.py
            ├── provides.py
            └── requires.py

The hooks will need to call reactive.main(), and the decorated handler blocks can be placed in any file under the reactive directory. The relations directory can contain any relation stub implementations that your charm uses.

If using Charm Composition, as is recommended, the relation hooks and relations directories will be automatically managed for you based on your metadata.yaml, so you can focus on writing just the install and config-changed hooks, and the reactive handler files.

Relation Stubs

A big part of the reactive pattern is the use of relation stubs. These are classes, based on RelationBase, that are reponsible for managing the conversation with remote services or units and informing the charm when the conversation has reached key points, called states, upon which the charm can act and do useful work. They allow a single interface author to create code to handle both sides of the conversation, and to expose a well-defined API to charm authors.

Relation stubs allows charm authors to focus on implementing the behavior and resources that the relation provides, while the interface author focuses on the communication necessary to get that behavior and resources between the related services. In general, the author of the charm that provides a particular interface is likely to be the interface author that creates both the provides and requires sides of the relation. After that, charm authors that wish to make use of that interface can just re-use the existing relation stub.

Non-Python Reactive Handlers

Reactive handlers can be written in any language, provided they conform to the ExternalHandler protocol. In short, they must accept a --test and --invoke argument and do the appropriate thing when called with each.

There are helpers for writing handlers in bash. For example:

source `which reactive.sh`

@when 'db.database.available' 'admin-pass'
function render_config() {
    db_conn=$(state_relation_call 'db.database.available' connection_string)
    admin_pass=$(config-get 'admin-pass')
    chlp render_template 'app-config.j2' '/etc/app.conf' --db_conn="$db_conn" --admin_pass="$admin_pass"
}

@when_not 'db.database.available'
function no_db() {
    status-set waiting 'Waiting on database'
}

@when_not 'admin-pass'
function no_db() {
    status-set blocked 'Missing admin password'
}

@when_file_changed '/etc/app.conf'
function restart_service() {
    service myapp restart
}

reactive_handler_main

Reactive API Documentation

charmhelpers.core.reactive.main(relation_name=None)

This is the main entry point for the reactive framework. It calls discover() to find and load all reactive handlers (e.g., @when decorated blocks), and then dispatch() to trigger hook and state handlers until the state settles out. Finally, unitdata.kv().flush is called to persist the state.

Parameters:relation_name (str) – Optional name of the relation which is being handled.