dependencies
We'd like to make charm authors lives easier by implementing the following idea, from @stub42:
def __init__(self, *args, **kwargs):
# [...]
deps = Dependencies(
PostgreSQLDependency(dbname='mydb', extensions=['citext']),
StorageDependency('backups'),
ValidCharmConfig(self.charm),
)
self.framework.observe(deps.on.ready, self.on_ready)
self.framework.observe(deps.on.unready, self.on_unready)
def on_ready(self, ev):
# Event fired after all dependencies become ready,
# after initial deployment, after upgrade-charm, or
# after unready fired.
def on_unready(self, ev):
# Event fired once after a ready event has fired
# and one of the dependencies is no longer ready.
In the example, I imagine PostgreSQLDependency would be provided by the PostgreSQL interface library, StorageDependency by the OperatorFramework (along with other core Juju features such as perhaps resources or network spaces), and ValidCharmConfig part of the charm itself. All implement some interface to be determined. It might be as simple as bool(dependency) evaluating to True or False to indicate readyness, which would allow things like lambda expressions to be used as a dependency when constructing the Dependencies object. Or perhaps it needs to be more complex; in particular, the charm will want the workload status to be automatically set to waiting or blocked when the PostgreSQLDependency is not ready, so perhaps they instead provide a get_status method that returns a workload status instance and the Dependencies object uses that to set the workload status if it isn't active.
The Dependencies constructor will need parent and name arguments added, since it will need to store state.
(perhaps the unready event needs to fire whenever the unready reason changes, rather than just once as the comment suggests. ev.status could provide the suggested workload status, allowing the on_unready handler to set the workload status itself rather than having the Dependencies object set the status magically).
+1 to have a Dependency protocol that all those need to conform.
Leaving open for now -- should be considered in the context of charm libraries and "components".
This relates to the ongoing discussion about holistic ("one event handler to rule them all") vs delta-based charms, which we're going to discuss further and try to determine some direction on in Riga (Oct 2023).
Basically the recommendation now is to observe all the events that can cause state changes, possibly funnel them through the same function / event handler, and in that function check that all dependencies are in the correct state. For example (untested pseudo-example):
def __init__(self, *args, **kwargs):
self.framework.observe(self.on.config_changed, self._dep_changed)
self.framework.observe(self.on.relation_changed["db"], self._dep_changed) # or charm lib's database_created custom event
# also observe other events like storage
def _dep_changed(self, event: ops.BaseEvent):
if not self._config_good() or not self._database_ready():
self.unit.status = ops.BlockedStatus('stuff not ready')
return
# rest of code that depends on both those things
It's not a "framework" like the suggested Dependencies type, but it's explicit and manageable.
In discussion about holistic vs delta, one possibility that wallyworld brought up was the idea of an "event group" or "event context" where the charm would say to Juju give me this custom event-group-changed event if anything in the defined group has changed. I'm personally not sure we need this to be a Juju-specified thing, though modelling it in Juju might give us other advantages. Will discuss in Riga too.
Also, we recently added collect_app_status and collect_unit_status, which sort of encourages this kind of explicit "check your dependencies" kind of pattern but just for status setting. See CollectStatusEvent for details.
I think my previous comment is still a good summary here. This is closely related to the "holistic" event handling pattern, which we're going to document more clearly and make recommendations for in this cycle (next month), rather than doing some sort of "event dependency resolver".