Bob and Alice are two charms. Bob wishes Alice to know that he’s ready for something. Bob and Alice maybe are already related, but abusing that relation to ‘wake up’ the other charm and send some simple payload not necessarily ‘about’ that relation feels iffy.
So what about we create another relation, only for that specific signal to go through? A channel, so to say. We want to do that on Alice and Bob, and maybe we want to have multiple separate channels. That’s a lot of code duplication.
Hereby we present
signals, a charm lib to facilitate setting up endpoints by which to send simple notifications from one charm to the other.
metadata.yaml, in both charms, an endpoint with interface
requires: signals: interface: signals
It does not matter whether you put requires or provides; both charms could even have the same role. Signals can go both ways, there’s no real semantics to the role in this context.
Technically the interface name also does not matter, so long as it’s the same in both charms.
Now add to your
from charms.signals.v0.signals import Signals, SignalEvent class MyCharm(CharmBase): def __init__(...): self.signals = Signals(endpoint='signals')
Your charm is now ready to send and receive signals via the
Sending a signal
You send a signal by:
self.signals.send(name='ping', payload='hello signals')
By default this will send the signal on all available channels for the
signals endpoint. So if multiple charms are related to the emitter, they will all receive the ‘ping’ signal (with the same payload).
To target a specific relation instead, you can pass a
Relation instance to the
relation: Relation = self.get_relation() self.signals.send(name='ping', payload='hello signals', relation=relation)
This means only the remote units belonging to that relation will receive the signal.
Receiving a signal
You receive a signal by observing the
You do so by adding to your charm’s
The idea is that if you have multiple signal channels, say, one to notify one another of readiness, another to notify one another of some service status, you can idiomatically do:
requires: ready: interface: signals service: interface: signals
self.ready = Signals(self, 'ready') self.service = Signals(self, 'service') self.framework.observe(self.ready.on.receive, self._on_ready_receive) self.framework.observe(self.service.on.receive, self._on_service_receive)
Name and payload
A signal consists of a
name, which charms can use to identify different signal types sent through the same channel, and a
payload, whose semantics will presumably differ depending on the type. It can be any string.
_on_signal_receive implementation will look like:
def _on_signal_receive(self, event: SignalEvent): if event.name == 'pong': self.pong_received(event.payload) if event.name == 'cheese': self.smile(event.payload) else: raise ValueError(event.name, 'not a known signal type')
Now. What are you waiting for?
charmcraft fetch-lib charms.signals.v0.signals