-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix database relation state handling #75
Conversation
I believe there's a similar issue with database_provides—if a relation is created via database_provides but no database is requested, the relation will be included in mysql-router-k8s-operator/src/relations/database_provides.py Lines 135 to 143 in 68d723c
|
003ab21
to
a093aa2
Compare
PR is partially completed—I believe (Requested review early in the hopes of speeding up final review later since this issue currently affects openstack charmers' testing) |
c31bbc4
to
f5e644b
Compare
4a3e003
to
5a1757b
Compare
src/relations/database_provides.py
Outdated
"""Database & user has not been provided to related application charm""" | ||
|
||
|
||
class _CreatedUser(_Relation): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels a little counterintuitive: a _CreatedUser
is actually a subclass of a _Relation
same thing with _RequestedUser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about it feels counterintuitive—the name, that it's a subclass of _Relation
, or something else?
Also here's the docstring for context
"""Related application charm that has been provided with a database & user"""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name is definitely a big part of it. when i saw it in reconcile_users
, i was surprised that it was being compared to a Relation
. i expected it to be a class that encapsulates Users, but it's not quite storing any user info (except a method delete_user
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the name is confusing
do you have ideas for better names? I struggled to come up with names for them
would something like _RelationThatRequestedUser
and _RelationWithCreatedUser
be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed in c7d24df—if anyone thinks of better names happy to rename again
@@ -180,3 +183,28 @@ def reconcile_users( | |||
if relation not in requested_users: | |||
relation.delete_user(shell=shell) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it may be a good idea to log out users created and users deleted to the debug-log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're already logged in lower-level methods (e.g. shell.create_application_database_and_user
and self._set_databag
)
# At least one relation is active—do not report about inactive relations | ||
return | ||
for exception in exceptions: | ||
if isinstance(exception, remote_databag.IncompleteDatabag): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be a good idea to add a suffix Exception
so that we know that remote_databag.IncompleteDatabagException
is an exception (without having to trace back to the definition of the class to see that it's a subclass of an exception)
same applies to all subclasses of StatusException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems a bit verbose to me. I think that's something that should be handled by the IDE instead of the naming (e.g. hovering over IncompleteDatabag
shows it's a StatusException
). Somewhat analogous, we don't generally name something like requested_users
as requested_users_list
Also, I think it's fairly clear from the context—exceptions
has type annotation list[status_exception.StatusException]
, the variable is called exception
, and the only other usage of these classes (besides isinstance(exception, )
) is in an except
statement
interface: data_interfaces.DatabaseRequires | data_interfaces.DatabaseProvides, | ||
relation: ops.Relation, | ||
) -> None: | ||
super().__init__(interface.fetch_relation_data()[relation.id]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
6ef7f4e
to
3ebbc46
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, some generators adoption!
self._local_databag = relation.data[interface.local_app] | ||
for key in ("database", "username", "password", "endpoints"): | ||
if key not in self._local_databag: | ||
raise _UserNotCreated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be useful to extent _UserNotCreated
to include the missing key in the exception message, e.g.:
raise _UserNotCreated | |
raise _UserNotCreated(f"missing {key}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose not to include it since it's not being used (_UserNotCreated
is always caught & it's not parsing the message) and since it would be incomplete—the exception would only include the first missing key
@@ -201,35 +198,31 @@ def _patch_service(self, *, name: str, ro_port: int, rw_port: int) -> None: | |||
|
|||
def reconcile_database_relations(self, event=None) -> None: | |||
"""Handle database requires/provides events.""" | |||
workload_ = self.get_workload(event=event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why use of post _
? afaik, that's commonly usud to avoid clashes with python keywords
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conflict with workload.py
(import workload
)
Issue
Fixes #73, fixes #78
Database relations can be in many states:
Before this PR, the
_Relation
class could be in multiple states and some properties could be accessed but some properties would cause an exception if accessed depending on the state. In other words, which properties were available was implicit.For example, these properties
mysql-router-k8s-operator/src/relations/database_requires.py
Lines 44 to 62 in 92b029e
can only be accessed if
mysql-router-k8s-operator/src/relations/database_requires.py
Lines 64 to 66 in 92b029e
is False and these conditions are met
mysql-router-k8s-operator/src/relations/database_requires.py
Lines 102 to 108 in 92b029e
Solution
Create different
_Relation
classes for different states so that all properties/methods on the class can be callede.g.
mysql-router-k8s-operator/src/relations/database_provides.py
Line 57 in 5a1757b
mysql-router-k8s-operator/src/relations/database_provides.py
Line 103 in 5a1757b
Also raise exceptions during the
__init__
of those objects so that we don't have to check for exceptional cases (e.g. required key missing from relation databag, relation is breaking) when using the object—if the object initializes successfully, all of its properties/methods are safe to use