Change-Id: I6abc64755bff428543fdb22bd04ad217b1fa5ee9
5.2 KiB
How-To Write a relation handler
A relation handler gives the charm a consistent method of interacting with relation interfaces. It can also encapsulate common interface tasks, this removes the need for duplicate code across multiple charms.
This how-to will walk through the steps to write a database relation handler for the requires side.
In this database interface the database charm expects the client to provide the name of the database(s) to be created. To model this the relation handler will require the charm to specify the database name(s) when the class is instantiated.
class DBHandler(RelationHandler):
"""Handler for DB relations."""
def __init__(
self,
charm: ops.charm.CharmBase,str,
relation_name:
callback_f: Callable,str] = None,
databases: List[-> None:
) """Run constructor."""
self.databases = databases
super().__init__(charm, relation_name, callback_f)
The handler initialises the interface with the database names and also sets up an observer for relation changed events.
def setup_event_handler(self) -> ops.charm.Object:
"""Configure event handlers for a MySQL relation."""
"Setting up DB event handler")
logger.debug(# Lazy import to ensure this lib is only required if the charm
# has this relation.
import charms.sunbeam_mysql_k8s.v0.mysql as mysql
= mysql.MySQLConsumer(
db self.charm, self.relation_name, databases=self.databases
)= self.relation_name.replace("-", "_")
_rname = getattr(
db_relation_event self.charm.on, f"{_rname}_relation_changed"
)self.framework.observe(db_relation_event, self._on_database_changed)
return db
The method runs when the changed event is seen and checks whether all required data has been provided. If it is then it calls back to the charm, if not then no action is taken.
def _on_database_changed(self, event: ops.framework.EventBase) -> None:
"""Handle database change events."""
= self.interface.databases()
databases f"Received databases: {databases}")
logger.info(if not self.ready:
return
self.callback_f(event)
@property
def ready(self) -> bool:
"""Whether the handler is ready for use."""
try:
# Nothing to wait for
return bool(self.interface.databases())
except (AttributeError, KeyError):
return False
The ready property is common across all handlers and allows the charm to check the state of any relation in a consistent way.
The relation handlers also provide a context which can be used when rendering templates. ASO places each relation context in its own namespace.
def context(self) -> dict:
"""Context containing database connection data."""
try:
= self.interface.databases()
databases except (AttributeError, KeyError):
return {}
if not databases:
return {}
= {}
ctxt = {
conn_data "database_host": self.interface.credentials().get("address"),
"database_password": self.interface.credentials().get("password"),
"database_user": self.interface.credentials().get("username"),
"database_type": "mysql+pymysql",
}
for db in self.interface.databases():
= {"database": db}
ctxt[db]
ctxt[db].update(conn_data)= (
connection "{database_type}://{database_user}:{database_password}"
"@{database_host}/{database}")
if conn_data.get("database_ssl_ca"):
= connection + "?ssl_ca={database_ssl_ca}"
connection if conn_data.get("database_ssl_cert"):
= connection + (
connection "&ssl_cert={database_ssl_cert}"
"&ssl_key={database_ssl_key}")
"connection"] = str(connection.format(
ctxt[db][**ctxt[db]))
return ctxt
Configuring Charm to use custom relation handler
The base class will add the default relation handlers for any interfaces which do not yet have a handler. Therefore the custom handler is added to the list and then passed to the super method. The base charm class will see a handler already exists for database and not add the default one.
class MyCharm(sunbeam_charm.OSBaseOperatorAPICharm):
"""Charm the service."""
def get_relation_handlers(self, handlers=None) -> List[
sunbeam_rhandlers.RelationHandler]:"""Relation handlers for the service."""
= handlers or []
handlers if self.can_add_handler("database", handlers):
self.db = sunbeam_rhandlers.DBHandler(
self, "database", self.configure_charm, self.databases
)self.db)
handlers.append(= super().get_relation_handlers(handlers)
handlers return handlers