Every application deployed by Juju is guaranteed to have exactly one leader at any time. This is true independent of the Charm authors’ actions; whether or not you implement the hooks or use the tools, the Juju controller will elect a leader when a charm is deployed. Units that hold leader status should not assume they will retain it, a new leader can be elected at any time.

Leadership provides a mechanism whereby multiple units of an application can make use of a single, shared, application data store when forming relations. This property is particularly useful where there are multiple units of an application deployed, but there are situations where it may be sensible to restrict a decision to a single (authoritative) unit. When peer relations are established, the application leader is able to both read and write to the application data on the peer relation. Followers can only read the application data on a peer relation.

Identifying a leader

Charm authors can identify if the unit executing the charm code is the current leader using the unit abstraction, for example:

def _on_config_changed(self, event):
    if not self.unit.is_leader()
    # do something

If the unit executing the code is not the leader, there is no facility to identify which other unit is the leader. This is by design, because such functionality could encourage development practices that are exposed to a higher risk of race conditions, or simply stale data should the leader change during, or shortly after such a function might return.

Leadership events

When a new application leader is elected, the Juju controller emits a leader-elected event at least once to the newly elected leader only. Callback methods bound to this event should take any relevant action for the application to act effectively as the leader (perhaps updating some shared configuration):

# ...
class DemoCharm(CharmBase):
    # ...
    def __init__(self, *args):
        # Handle the case where Juju elects a new application leader
        self.framework.observe(self.on.leader_elected, self._on_leader_elected)
        # ...

    def _on_leader_elected(self, event: LeaderElectedEvent) -> None:
        """Handle the leader-elected event"""
        logging.debug("A new leader was elected: %s", self.unit.name)
# ...

Immediately after the leader-elected is emitted, all units will receive a config-changed event. There is a more detailed example of responding to this event in the Relations section.