wordpress-charmers team mailing list archive
-
wordpress-charmers team
-
Mailing list archive
-
Message #00580
Re: [Merge] ~stub/charm-k8s-wordpress:mysql-relation into charm-k8s-wordpress:master
Some inline comments, and you have a test failure in test_is_config_valid.
Haven't tested yet, but will do so shortly.
Diff comments:
> diff --git a/src/charm.py b/src/charm.py
> index edade47..efe9026 100755
> --- a/src/charm.py
> +++ b/src/charm.py
> @@ -115,20 +117,20 @@ class WordpressCharm(CharmBase):
> self.framework.observe(self.on.update_status, self.on_config_changed)
> self.framework.observe(self.on.wordpress_initialise, self.on_wordpress_initialise)
>
> + self.db = MySQLClient(self, 'db')
> + self.framework.observe(self.on.db_relation_created, self.on_db_relation_created)
> + self.framework.observe(self.on.db_relation_broken, self.on_db_relation_broken)
> + self.framework.observe(self.db.on.database_changed, self.on_database_changed)
> +
> + c = self.model.config
Might be worth using "config" rather that "c" here as you do later in the same file.
> self.state.set_default(
> - initialised=False, valid=False,
> + initialised=False, valid=False, has_db_relation=False,
> + db_host=c["db_host"], db_name=c["db_name"], db_user=c["db_user"], db_password=c["db_password"]
> )
> -
> - self.wordpress = Wordpress(self.model.config)
> + self.wordpress = Wordpress(c)
>
> def on_config_changed(self, event):
> - is_valid = self.is_valid_config()
> - if not is_valid:
> - return
> -
> - self.configure_pod()
> - if not self.state.initialised:
> - self.on.wordpress_initialise.emit()
> + self.config_changed()
>
> def on_wordpress_initialise(self, event):
> wordpress_needs_configuring = False
> @@ -161,6 +163,40 @@ class WordpressCharm(CharmBase):
> logger.info("Wordpress workload pod is ready and configured")
> self.model.unit.status = ActiveStatus()
>
> + def on_db_relation_created(self, event):
Can you add docstrings for this and other functions you're adding? I realise not all of them in this charm have them, but it'd be good to make sure the ones we're adding do have them.
> + self.state.has_db_relation = True
> + self.state.db_host = None
> + self.state.db_name = None
> + self.state.db_user = None
> + self.state.db_password = None
> + self.config_changed()
> +
> + def on_db_relation_broken(self, event):
> + self.state.has_db_relation = False
> + self.config_changed()
> +
> + def on_database_changed(self, event):
> + self.state.db_host = event.host
> + self.state.db_name = event.database
> + self.state.db_user = event.user
> + self.state.db_password = event.password
> + self.config_changed()
> +
> + def config_changed(self):
> + if not self.state.has_db_relation:
> + self.state.db_host = self.model.config["db_host"] or None
> + self.state.db_name = self.model.config["db_name"] or None
> + self.state.db_user = self.model.config["db_user"] or None
> + self.state.db_password = self.model.config["db_password"] or None
> +
> + is_valid = self.is_valid_config()
> + if not is_valid:
> + return
> +
> + self.configure_pod()
> + if not self.state.initialised:
> + self.on.wordpress_initialise.emit()
> +
> def configure_pod(self):
> # Only the leader can set_spec().
> if self.model.unit.is_leader():
> diff --git a/src/mysql.py b/src/mysql.py
> new file mode 100644
> index 0000000..2cbab87
> --- /dev/null
> +++ b/src/mysql.py
> @@ -0,0 +1,166 @@
> +"""
> +MySQL endpoing implementation for the Operator Framework.
endpoint
> +
> +Ported to the Operator Framework from the canonical-osm Reactive
> +charms at https://git.launchpad.net/canonical-osm
> +"""
> +
> +import logging
> +
> +import ops.charm
> +import ops.framework
> +import ops.model
> +
> +
> +__all__ = ["MySQLClient", "MySQLClientEvents", "MySQLRelationEvent", "MySQLDatabaseChangedEvent"]
> +
> +
> +class _MySQLConnectionDetails(object):
> + database: str = None
> + host: str = None
> + port: int = 3306
> + user: str = None
> + password: str = None
> + root_password: str = None
> + connection_string: str = None
> + sanitized_connection_string: str = None # With no secrets, for logging.
> + is_available: bool = False
> +
> + def __init__(self, relation: ops.model.Relation, unit: ops.model.Unit):
> + reldata = relation.data.get(unit, {})
> + self.database = reldata.get("database", None)
> + self.host = reldata.get("host", None)
> + self.port = int(reldata.get("port", 3306))
> + self.user = reldata.get("user", None)
> + self.password = reldata.get("password", None)
> + self.root_password = reldata.get("root_password", None)
> +
> + if all([self.database, self.host, self.port, self.user, self.password, self.root_password]):
> + self.sanitized_connection_string = (
> + f"host={self.host} port={self.port} dbname={self.database} user={self.user}"
> + )
> + self.connection_string = (
> + self.sanitized_connection_string + f" password={self.password} root_password={self.root_password}"
> + )
> + else:
> + self.sanitized_connection_string = None
> + self.connection_string = None
> + self.is_available = self.connection_string is not None
> +
> +
> +class MySQLRelationEvent(ops.charm.RelationEvent):
> + def __init__(self, *args, **kw):
> + super().__init__(*args, **kw)
> + self._conn = _MySQLConnectionDetails(self.relation, self.unit)
> +
> + @property
> + def is_available(self) -> bool:
> + """True if the database is available for use."""
> + return self._conn.is_available
> +
> + @property
> + def connection_string(self) -> str:
> + """The connection string, if available, or None.
> +
> + The connection string will be in the format:
> +
> + 'host={host} port={port} dbname={database} user={user} password={password} root_password={root_password}'
> + """
> + return self._conn.connection_string
> +
> + @property
> + def database(self) -> str:
> + """The name of the provided database, or None."""
> + return self._conn.database
> +
> + @property
> + def host(self) -> str:
> + """The host for the provided database, or None."""
> + return self._conn.host
> +
> + @property
> + def port(self) -> int:
> + """The port to the provided database."""
> + # If not available, returns the default port of 3306.
> + return self._conn.port
> +
> + @property
> + def user(self) -> str:
> + """The username for the provided database, or None."""
> + return self._conn.user
> +
> + @property
> + def password(self) -> str:
> + """The password for the provided database, or None."""
> + return self._conn.password
> +
> + @property
> + def root_password(self) -> str:
> + """The password for the root user, or None."""
> + return self._conn.root_password
> +
> + def restore(self, snapshot) -> None:
> + super().restore(snapshot)
> + self._conn = _MySQLConnectionDetails(self.relation, self.unit)
> +
> +
> +class MySQLDatabaseChangedEvent(MySQLRelationEvent):
> + """The database connection details on the relation have changed.
> +
> + This event is emitted when the database first becomes available
> + for use, when the connection details have changed, and when it
> + becomes unavailable.
> + """
> +
> + pass
> +
> +
> +class MySQLClientEvents(ops.framework.ObjectEvents):
> + database_changed = ops.framework.EventSource(MySQLDatabaseChangedEvent)
> +
> +
> +class MySQLClient(ops.framework.Object):
> + """Requires side of a MySQL Endpoint"""
> +
> + on = MySQLClientEvents()
> + _state = ops.framework.StoredState()
> +
> + relation_name: str = None
> + log: logging.Logger = None
> +
> + def __init__(self, charm: ops.charm.CharmBase, relation_name: str):
> + super().__init__(charm, relation_name)
> +
> + self.relation_name = relation_name
> + self.log = logging.getLogger("mysql.client.{}".format(relation_name))
> + self._state.set_default(rels={})
> +
> + self.framework.observe(charm.on[relation_name].relation_changed, self._on_changed)
> + self.framework.observe(charm.on[relation_name].relation_broken, self._on_broken)
> +
> + def _on_changed(self, event: ops.charm.RelationEvent) -> None:
> + if event.unit is None:
> + return # Ignore application relation data events.
> +
> + prev_conn_str = self._state.rels.get(event.relation.id, None)
> + new_cd = _MySQLConnectionDetails(event.relation, event.unit)
> + new_conn_str = new_cd.connection_string
> +
> + if prev_conn_str != new_conn_str:
> + self._state.rels[event.relation.id] = new_conn_str
> + if new_conn_str is None:
> + self.log.info(f"Database on relation {event.relation.id} is no longer available.")
> + else:
> + self.log.info(
> + f"Database on relation {event.relation.id} available at {new_cd.sanitized_connection_string}."
> + )
> + self.on.database_changed.emit(relation=event.relation, app=event.app, unit=event.unit)
> +
> + def _on_broken(self, event: ops.charm.RelationEvent) -> None:
> + self.log.info(f"Database relation {event.relation.id} is gone.")
> + prev_conn_str = self._state.rels.get(event.relation.id, None)
> + if event.relation.id in self._state.rels:
> + del self._state.rels[event.relation.id]
> + if prev_conn_str is None:
> + return
> + self.on.database_changed.emit(relation=event.relation, app=event.app, unit=None)
--
https://code.launchpad.net/~stub/charm-k8s-wordpress/+git/charm-k8s-wordpress/+merge/395826
Your team Wordpress Charmers is requested to review the proposed merge of ~stub/charm-k8s-wordpress:mysql-relation into charm-k8s-wordpress:master.
References