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 thendispatch()
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.