Contents:
Set unit status
To set unit status, the charm should assign an instance of a StatusBase
subclass to the Unit.status
property such as ActiveStatus
. For example:
def _on_install(self, event: ops.InstallEvent):
self.unit.status = ops.MaintenanceStatus("Configuring Prometheus")
# ... perform install actions ...
self.unit.status = ops.ActiveStatus()
If you’re curious to know how this works under the hood:
When you’re assigning Unit.status
, this uses Juju’s status-set
hook tool.
Set application status
Setting application status is similar: assign it to the Application.status
property (this uses status-set --application
under the hood). Only the leader unit can set application status. For example:
def _on_config_changed(self, event: ops.ConfigChangedEvent):
if not self.unit.is_leader():
return
if "port" not in self.model.config:
self.app.status = ops.BlockedStatus('"port" required')
return
# ... update port ...
self.app.status = ops.ActiveStatus()
If a charm sets application status explicitly, that status will appear as the application status in juju status
output. However, if a charm does not set application status, the application status displayed is the highest-priority status of all the units (priorities are listed above).
Charms for applications that are intended to scale up to more than one unit should usually set application status explicitly. For example, a horizontally-scaled web app charm could remain up even if 1 of 3 units is not responding; the overall application is still “active”.
Evaluate charm status across multiple components
As of version 2.5.0, ops
includes two events that allow a charm to automatically set status at the end of every hook. These are collect_app_status
and collect_unit_status
, both of which are of CollectStatusEvent
type.
These events are triggered by the ops
framework at the end of every successful hook to collect statuses for evaluation (collect_app_status
will only be triggered on the leader unit). If any statuses were added by the event handlers using add_status
, the framework will choose the highest-priority status and set that as the status.
The status-collecting events allow a charm to provide a status for each component of the charm, and let the framework figure out which status to set (and show in juju status
output). The system is flexible – a charm can have multiple collect_unit_status
(or collect_app_status
handlers), and each handler can add one or more statuses for evaluation.
Here is an example of a charm with two components, a “database” and a “web app” component. The database component adds collect_unit_status
handlers for two sub-components: relation status, and config status. The web app component has a single handler for config status:
class StatustestCharm(ops.CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.database = Database(self)
self.webapp = Webapp(self)
class Database(ops.Object):
"""Database component."""
def __init__(self, charm: ops.CharmBase):
super().__init__(charm, "database")
self.framework.observe(charm.on.config_changed, self._on_config_changed)
# Note that you can have multiple collect_status observers even
# within a single component, as shown here. Alternatively, we could
# do both of these tests within a single handler.
self.framework.observe(charm.on.collect_unit_status, self._on_collect_db_status)
self.framework.observe(charm.on.collect_unit_status,
self._on_collect_config_status)
def _on_collect_db_status(self, event: ops.CollectStatusEvent):
if 'db' not in self.model.relations:
event.add_status(ops.BlockedStatus('please integrate with database'))
return
event.add_status(ops.ActiveStatus())
def _on_collect_config_status(self, event: ops.CollectStatusEvent):
message = self._validate_config()
if message is not None:
event.add_status(ops.BlockedStatus(message))
return
event.add_status(ops.ActiveStatus())
def _on_config_changed(self, event):
if self._validate_config() is not None:
return
mode = self.model.config["database_mode"]
logger.info("Database using mode %r", mode)
def _validate_config(self) -> typing.Optional[str]:
"""Validate charm config for the database component.
Return an error message if the config is incorrect, None if it's valid.
"""
if "database_mode" not in self.model.config:
return '"database_mode" required'
return None
class Webapp(ops.Object):
"""Web app component."""
def __init__(self, charm: ops.CharmBase):
super().__init__(charm, "webapp")
self.framework.observe(charm.on.config_changed, self._on_config_changed)
self.framework.observe(charm.on.collect_unit_status, self._on_collect_status)
def _on_collect_status(self, event: ops.CollectStatusEvent):
message = self._validate_config()
if message is not None:
event.add_status(ops.BlockedStatus(message))
return
event.add_status(ops.ActiveStatus())
def _on_config_changed(self, event):
if self._validate_config() is not None:
return
mode = self.model.config["port"]
logger.info("Web app using port %d", mode)
def _validate_config(self) -> typing.Optional[str]:
"""Validate charm config for the web app component.
Return an error message if the config is incorrect, None if it's valid.
"""
if "port" not in self.model.config:
return '"port" required'
return None