-
Notifications
You must be signed in to change notification settings - Fork 119
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
Add Relation.active
attribute to indicate if a relation is active or currently breaking
#958
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -361,6 +361,15 @@ def _should_use_controller_storage(db_path: Path, meta: CharmMeta) -> bool: | |
return False | ||
|
||
|
||
def _get_breaking_relation_id() -> Optional[int]: | ||
"""Returns the relation id of the currently breaking relation, if applicable.""" | ||
if not os.environ.get("JUJU_HOOK_NAME", "").endswith("-relation-broken"): | ||
# Not the relation-broken event | ||
return None | ||
|
||
return int(os.environ.get("JUJU_RELATION_ID")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not actually correct, as |
||
|
||
|
||
def main(charm_class: Type[ops.charm.CharmBase], | ||
use_juju_for_storage: Optional[bool] = None): | ||
"""Setup the charm and dispatch the observed event. | ||
|
@@ -391,8 +400,10 @@ def main(charm_class: Type[ops.charm.CharmBase], | |
else: | ||
actions_metadata = None | ||
|
||
breaking_relation_id = _get_breaking_relation_id() | ||
|
||
meta = CharmMeta.from_yaml(metadata, actions_metadata) | ||
model = ops.model.Model(meta, model_backend) | ||
model = ops.model.Model(meta, model_backend, breaking_relation_id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's not required, but let's pass in optional arguments using their keywords, to avoid accidental breakage if we add more and change the order. So |
||
|
||
charm_state_path = charm_dir / CHARM_STATE_FILE | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,12 +107,14 @@ class Model: | |
from any class that derives from Object. | ||
""" | ||
|
||
def __init__(self, meta: 'ops.charm.CharmMeta', backend: '_ModelBackend'): | ||
def __init__(self, meta: 'ops.charm.CharmMeta', backend: '_ModelBackend', | ||
breaking_relation_id: Optional[int] = None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be more elegant if we attached this information to the event instead of the model. breaking-relation-id is deceptive as it suggests any relation ID could be that of a relation that is breaking, while in fact the only relation we could possibly know to be breaking is 'the current one', aka 'the one the current event is about'. That also spares us to add model.relation.get_active because there's only ever going to be one relation in breaking state: the one we get from the event we're observing. I propose the following api:
this feels a bit more There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that said, this would make it more complicated to access the same information from code that doesn't directly have access to the event object. Maybe this was the whole point of this exercise? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah the motivation for me with this issue is a case where, if possible, not being tied to the event would be nice. I had tried to implement a nicely encapsulated getter on a relation charm lib, but found since it might be called during There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right though that this is event-specific behaviour. But then since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree with Andrew here: we specifically want this info to be available on the |
||
self._cache = _ModelCache(meta, backend) | ||
self._backend = backend | ||
self._unit = self.get_unit(self._backend.unit_name) | ||
relations: Dict[str, 'ops.RelationMeta'] = meta.relations | ||
self._relations = RelationMapping(relations, self.unit, self._backend, self._cache) | ||
self._relations = RelationMapping(relations, self.unit, self._backend, self._cache, | ||
breaking_relation_id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar here: let's use |
||
self._config = ConfigData(self._backend) | ||
resources: Iterable[str] = meta.resources | ||
self._resources = Resources(list(resources), self._backend) | ||
|
@@ -667,7 +669,8 @@ class RelationMapping(Mapping[str, List['Relation']]): | |
"""Map of relation names to lists of :class:`Relation` instances.""" | ||
|
||
def __init__(self, relations_meta: Dict[str, 'ops.RelationMeta'], our_unit: 'Unit', | ||
backend: '_ModelBackend', cache: '_ModelCache'): | ||
backend: '_ModelBackend', cache: '_ModelCache', | ||
breaking_relation_id: Optional[int] = None): | ||
self._peers: Set[str] = set() | ||
for name, relation_meta in relations_meta.items(): | ||
if relation_meta.role.is_peer(): | ||
|
@@ -676,6 +679,7 @@ def __init__(self, relations_meta: Dict[str, 'ops.RelationMeta'], our_unit: 'Uni | |
self._backend = backend | ||
self._cache = cache | ||
self._data: _RelationMapping_Raw = {r: None for r in relations_meta} | ||
self._breaking_relation_id = breaking_relation_id | ||
|
||
def __contains__(self, key: str): | ||
return key in self._data | ||
|
@@ -692,8 +696,10 @@ def __getitem__(self, relation_name: str) -> List['Relation']: | |
if not isinstance(relation_list, list): | ||
relation_list = self._data[relation_name] = [] # type: ignore | ||
for rid in self._backend.relation_ids(relation_name): | ||
active = rid != self._breaking_relation_id | ||
relation = Relation(relation_name, rid, is_peer, | ||
self._our_unit, self._backend, self._cache) | ||
self._our_unit, self._backend, self._cache, | ||
active) | ||
relation_list.append(relation) | ||
return relation_list | ||
|
||
|
@@ -1244,15 +1250,18 @@ class Relation: | |
For subordinate relations, this set will include only one unit: the principal unit. | ||
data: A :class:`RelationData` holding the data buckets for each entity | ||
of a relation. Accessed via eg Relation.data[unit]['foo'] | ||
active: Indicates whether this relation is active (True) or inactive (False, because | ||
this relation is currently part of a relation-broken event) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These attributes have now been type-hinted and their docstrings moved to under the type hint so Sphinx picks it up for each attribute, so I believe this will conflict with the class Relation:
...
active: bool
"""Indicates whether this relation is active.
This will be ``False`` if the current event is a ``relation-broken`` event
associated with this relation.
""" |
||
""" | ||
|
||
def __init__( | ||
self, relation_name: str, relation_id: int, is_peer: bool, our_unit: Unit, | ||
backend: '_ModelBackend', cache: '_ModelCache'): | ||
backend: '_ModelBackend', cache: '_ModelCache', active: bool = True): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this one, I'd probably use a required (non-keyword) argument -- that is, don't default to That would have caught the fact that we've forgotten here to specify |
||
self.name = relation_name | ||
self.id = relation_id | ||
self.app: Optional[Application] = None | ||
self.units: Set[Unit] = set() | ||
self.active = active | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could/should we evaluate it here instead of propagating? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I'm not sure what you mean - can you restate this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on the one hand it would greatly reduce the amount of code changes, on the other it is nice that code this 'deep' in ops is profoundly unaware of juju-specific envvars. It'd be nice if the only pieces of code doing envvar unpacking were in ops.main. How we propagate that info tastefully and in a future-proof manner, that is the question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it would be "simpler", but I definitely prefer doing as much env var handling as possible in |
||
|
||
if is_peer: | ||
# For peer relations, both the remote and the local app are the same. | ||
|
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.
We don't actually use
JUJU_HOOK_NAME
in ops anymore, we use the newerJUJU_DISPATCH_PATH
. However, that's already parsed by the_Dispatcher
class here, and the event name (with-
already replaced with_
) is available asdispatcher.event_name
. So I suggest we do this inmain()
: