Skip to content

[DPE-10841] feat(database): own the client relation events, drop the endpoint bridge (4/7) - #219

Draft
marceloneppel wants to merge 1 commit into
feat/02-db-endpointsfrom
feat/02-db-events
Draft

[DPE-10841] feat(database): own the client relation events, drop the endpoint bridge (4/7)#219
marceloneppel wants to merge 1 commit into
feat/02-db-endpointsfrom
feat/02-db-events

Conversation

@marceloneppel

@marceloneppel marceloneppel commented Aug 17, 2026

Copy link
Copy Markdown
Member

Issue

The manager from PRs 1–3 has no observers, and ConfigManager still reaches back into the charm to refresh endpoints and is still handed a user hash the charm derives from provider state.

Solution

Adds events/database.py, which owns DatabaseProvides, observes database-requested, relation-departed and relation-broken, and constructor-injects the interface into DatabaseManager — the same shape as the TLS handler and its certificate requirers.

Each handler keeps its readiness guard and its work in one observer. defer() is per-observer, so splitting a guard from the action it protects lets a deferred action retry alone against state its guard never re-checked. That is the same failure mode as the TLS reload race in canonical/postgresql-k8s-operator#1618.

The two substrates' guards differ and both are kept. A request waits on the cluster being initialised plus, on VM, a started member and a reachable primary; on K8s, primary_endpoint_ready. A removal waits on the same predicate on VM, but on K8s settles for a started member. The K8s handler also wrote the primary Service endpoint, URI and TLS fields inline before update_endpoints ran; that write is preserved so databag write ordering is unchanged, though it is redundant — the provider library emits database_requested on the leader only, and update_endpoints publishes the same values immediately after.

Two smaller divergences are deliberate: the endpoint refresh after a request narrows to the requesting relation (both charms refreshed every relation, and the no-argument refresh inside update_config still rewrites all of them in the same hook), and a VM leader missing from the peer data yields :5432 rather than the charm's None:5432 for the read-write endpoint — a broken-input corner either way.

One parity gap is fixed up-stack rather than here: the requested_entity_secret_content read needs the ModelError guard the charms wrapped their whole _get_credentials in, because the vendored property raises while a cross-model grant is still pending. Adding the guard to this branch would force a rebase of the three test branches above, so it rides 7/7.

Seam changes:

  • ConfigManager takes the DatabaseManager and calls update_endpoints() itself. The refresh_endpoints charm bridge is deleted.
  • update_config's user_hash parameter is gone; the manager derives it from the same relation data the user-databases map comes from. relations_user_databases_map stays injected — its computation lives in charm.py and diverges between substrates, so it belongs to the core event-loop phase.
  • AbstractPostgreSQLCharm gains set_unit_status, update_config and primary_endpoint. The first is permanent (it gates status writes on charm_refresh priority, and charm_refresh is not a migration target); the other two are reclaimed by later phases.

Handler tests are in PR 7. tests/unit/test_config_manager.py is updated here because the constructor and update_config signature change in this PR.

Stacked on 3/7. Next: 5/7.

Checklist

  • I have added or updated any relevant documentation.
  • I have cleaned any remaining cloud resources from my accounts.

…bridge (4/6)

Wires the client relation into the library: the handler owns DatabaseProvides,
observes database-requested, relation-departed and relation-broken, and
constructor-injects the interface into the manager the way the TLS handler
already injects its certificate requirers.

Each handler keeps its readiness guard and its work in the same observer. defer()
is per-observer, so a guard split across observers would let a deferred action
retry alone against state its guard never re-checked.

With endpoint publishing lib-side, the config manager calls the database manager
directly and the refresh_endpoints charm bridge goes away, as does the injected
user hash, which is now derived from the same relation data the map is. The
charms gain bridges for the refresh-aware status write, the config re-render and
the substrate's primary lookup, all of which their own migration phases reclaim.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant