Reacting to Charm config changes with triggers

Here is a quick pattern you can follow to have your charms.reactive handlers correctly triggered on config changes.

charms.reactive automatically sets the config.changed flag when configuration changes are detected. However, it is best not to use it directly in a decorator on your handler, such as in this example:

from charmhelpers.core import hookenv
from charms.reactive import when

@when('config.changed')  # This may run too late
def update_config():
    write_config('/etc/app.cfg', hookenv.config())

While likely fine for simple charms, the problem with the above example is that there is no guarantee that this handler will be run first in the hook. Other handlers whose conditions are met may be run before it, and these handlers may fail if they rely on update_config() having been run and the charm configuration change acted on.

Another example that is problematic:

from charmhelpers.core import hookenv
from charms.reactive import when

@when('config.changed', 'apt.installed.postgresql')  # This may never run
def update_config():
    write_config('/etc/postgresql/9.5/main/postgresql.conf', hookenv.config())

Here the handler is guarded by two flags, and both need to be set before the handler runs. The config.changed flag is only set for the duration of the Juju hook that first sees the configuration change. If the apt.installed.postgresql flag is not also set during that same hook, then the handler will not be run.

And this is the better approach, using charms.reactive triggers:

from charmhelpers.core import hookenv
from charms.reactive import when_not
from charms.reactive.flags import register_trigger

register_trigger(when='config.changed', clear_flag='app.configured')

def update_config():
    write_config('/etc/postgresql/9.5/main/postgresql.conf', hookenv.config())

Triggers are checked at startup and after each handler is run. Their original intention was to allow better communication between layers, so that when for example the apt layer sets the ‘apt.installed.postgresql’ flag, a trigger could immediately set a flag specific to your layer and avoid any period where the layer specific flags were out of sync with the apt layer flags.

In the previous example, the trigger will fire before any handlers have run. After a charm configuration change, a hook will run and the trigger on the config.changed flag will be immediately fired, clearing the app.configured state immediately. No handlers will run that are guarded by @when(‘app.configured’). And the app.configured flag will remain set until the apt.installed.postgresql flag is also set and the update_config() handler actually run, even if that doesn’t happen until several hooks in the future.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s