The Problem:
In charming complex applications such as Istio, there are often several different types of relations that the charm needs to support. Each of these relation types may have many (dozens or more) applications that are set up in a deployment. If something goes wrong with one of the related applications, how can that error message be propagated up to an administrator without blocking the entire functionality of the charm, and without a charm author losing their sanity?
As a concrete example, this represents a possible istio-pilot
deployment, with one failed relation highlighted in purple:
Existing Solutions
Right now, we handle this in serialized-data-interface in a very blunt manner:
class Operator(CharmBase):
def __init__(self, *args):
super().__init__(*args)
try:
self.interfaces = get_interfaces(self)
except NoVersionsListed as err:
self.model.unit.status = WaitingStatus(str(err))
return
except NoCompatibleVersions as err:
self.model.unit.status = BlockedStatus(str(err))
return
else:
self.model.unit.status = ActiveStatus()
This is non-ideal for one very big reason: if one app sends bad data, that blocks the entire charm from working until the issue is resolved by an administrator. We could get clever in charm code, and write something like this:
class Operator(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.ingress = SerializedDataInterface(self, "ingress")
self.ingress_auth = SerializedDataInterface(self, "ingress-auth")
self.framework.observe(self.on.ingress_relation_changed, self.ingress)
self.framework.observe(self.on.ingress_auth_relation_changed, self.ingress_auth)
def ingress(self, event):
for app in self.ingress.valid_apps:
# do something
if self.ingress.invalid_apps:
self.unit.status = BlockedStatus("Invalid relations: " + ", ".join(app.name for app in self.ingress.invalid_apps))
def ingress_auth(self, event):
for app in self.ingress_auth.valid_apps:
# do something
if self.ingress_auth.invalid_apps:
self.unit.status = BlockedStatus("Invalid relations: " + ", ".join(app.name for app in self.ingress_auth.invalid_apps))
But what happens if both of those need to set BlockedStatus
? What code sets ActiveStatus
after the issue is fixed? These issues could be addressed by forcing charm authors to write a lot of code at the expense of their sanity, all of which would be obsoleted by the following proposal.
Proposed Solution
Juju should allow setting relation-level statuses, that will play nicely with tools such as juju wait
. The high-level picture is something like this:
$ juju status --relations
Model Controller Cloud/Region Version SLA Timestamp
demo uk8s microk8s/localhost 2.9.16 unsupported 12:34:56-00:00
App Version Status Scale Charm Store Channel Rev OS Address Message
istio-pilot res:oci-image@4707912 active 1 minio charmstore stable 55 kubernetes 10.1.1.1
app1 res:oci-image@7654321 active 1 mlflow-server charmstore stable 9 kubernetes
app2 res:oci-image@1234567 active 1 mlflow-server charmstore stable 2 kubernetes
Unit Workload Agent Address Ports Message
istio-pilot/0* active idle 10.1.1.1 8080/TCP
app1/0* active idle 10.1.1.2 5000/TCP
app2/0* active idle 10.1.1.3 5000/TCP
Relation provider Requirer Interface Type Message
istio-pilot:ingress app1:ingress ingress regular
istio-pilot:ingress app2:ingress ingress regular BlockedStatus("app2 sent invalid data")
Tooling such as juju wait
could propagate the error state and display an error, as it does with regular charm statuses today.
From a charm code point of view, this could be as simple as something like:
self.model.relations['ingress']['app2'].status = BlockedStatus('app2 sent invalid data')
That way, the charm code can still set self.model.app.status = ActiveStatus()
to represent that the istio-pilot
workload is running fine, and the ingress
and ingress-auth
bits of code don’t have to know or care about that status.
FAQ
- How would this work with rich statuses in Juju?
- These features would be complementary. We wouldn’t want to have the user interpret a free-form JSON blob for example, but allowing a charm to set a relation status with rich statuses would work well. However, the interface for a charm author would probably end up being mediated via the
ops
library, which would probably expose something like the aboveself.model.relations['ingress']['app2'].status
anyway.
- These features would be complementary. We wouldn’t want to have the user interpret a free-form JSON blob for example, but allowing a charm to set a relation status with rich statuses would work well. However, the interface for a charm author would probably end up being mediated via the