Skip to content

[DPE-10203] fix(async-replication): reference the offer secret by id on the consumer - #1838

Closed
marceloneppel wants to merge 7 commits into
dpe-10203-async-replication-fixesfrom
dpe-10203-consumer-secret-by-id
Closed

[DPE-10203] fix(async-replication): reference the offer secret by id on the consumer#1838
marceloneppel wants to merge 7 commits into
dpe-10203-async-replication-fixesfrom
dpe-10203-consumer-secret-by-id

Conversation

@marceloneppel

@marceloneppel marceloneppel commented Jul 8, 2026

Copy link
Copy Markdown
Member

Issue

DPE-10203 (stacked on #1837): the consumer/standby side of async replication fetched the offer's shared secret with get_secret(id=..., label=SECRET_LABEL), registering a consumer-side alias under the legacy shared label. Juju keeps that alias reserved even after the remote secret is gone, which is the other half of the post-dead-DC deadlock: a former standby that is later promoted can neither read nor create a secret under that label.

Solution

Consumer-side and robustness fixes:

  • reference the offer secret purely by the id published in relation data — no label, so no consumer-side alias is ever registered and nothing can go stale;
  • drop the legacy SECRET_LABEL constant from production code: nothing attaches it anymore, and the tests that guard against reintroducing it use the literal string (the integration test already did, to stay black-box);
  • tolerate an unreadable dead peer in all relation-databag reads, so the teardown path reconciles instead of crashing hooks;
  • fall back to the current unit count when planned_units() fails on the goal-state read (which errors while a force-removed SAAS churns), keeping the _patroni property usable in every hook.

The owner-side label (OFFER_SECRET_LABEL) stays by design: the owner needs a stable handle to its own secret across hooks, one that survives the loss of the relation data carrying the published id — exactly what a dead-DC teardown destroys.

Checklist

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

@github-actions github-actions Bot added the Libraries: Out of sync The charm libs used are out-of-sync label Jul 8, 2026
@marceloneppel
marceloneppel force-pushed the dpe-10203-async-replication-fixes branch from ef98cd6 to 93e8dce Compare July 8, 2026 18:55
…he consumer

The standby/consumer side read the shared offer secret with a fixed
Juju label, which registers a consumer-side alias under that label.
Juju keeps the alias reserved even after the remote secret is
force-removed during a dead-DC teardown, so a former standby is left
carrying a stale alias it can neither read nor release. The owner-side
OFFER_SECRET_LABEL split already breaks the promotion deadlock, but the
consumer alias remains an avoidable latent hazard whose safe reuse
depends on unverified Juju relabel behaviour.

Referencing the secret purely by the id already published in the
relation databag — as the MySQL async-replication charm does — means no
consumer alias is ever registered and nothing can go stale, closing the
DPE-10203 failure class on the consumer side too. This is backward
compatible: the id is already present in primary-cluster-data and no
peer depends on the alias existing.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
…on reads

After a dead-DC teardown the remote app/unit databag on the dying cross-model
async relation raises ModelError ("permission denied") on any read. Only
_get_primary_cluster was guarded; the sibling reads were not, so they crashed the
hooks that keep a promoted cluster reconciling — replication-offer-relation-joined
and database-peers-relation-changed both died, leaving units wedged in error and
blocking re-replication to a fresh cluster.

Route every async-relation databag read through a shared _safe_databag_get helper
(and a shared _remote_unit_addresses for the two identical endpoint list
comprehensions) that treats an unreadable databag as key-absent, matching the
existing _get_primary_cluster behaviour, so the teardown always reconciles instead
of crashing.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
ops implements Application.planned_units() via the goal-state hook command, which
fails ("saas application ... not found") while a cross-model SAAS force-removed
during a dead-DC teardown still lingers in goal-state. Every caller — the _patroni
property construction, the degraded-status check, the synchronous-node count and the
backups pre-checks — crashed its hook, cascading through the _patroni property to
wedge the promoted cluster and block re-replication to a fresh cluster.

Route them through a single guarded _planned_units property that falls back to the
current unit count when goal-state is unavailable, so hooks reconcile instead of
crashing (DPE-10203 Issue B).

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
@marceloneppel
marceloneppel force-pushed the dpe-10203-consumer-secret-by-id branch from 6fc12bd to bb22825 Compare July 8, 2026 19:07
Merge the fixes branch after its sync with 16/edge (single-kernel lib
16.3.5 update_config adoption #1849 among 28 commits), so the stacked
PR's diff and CI run against the current base.

The charm-side sync moved the DPE-10203 stale-standby fix to the new
update_config seam; this merge carries that over. The stacked branch's
own planned-units regression tests are kept at the tail of the test
file, replacing the worker-config tests the upstream refactor deleted.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
…s' into dpe-10203-consumer-secret-by-id

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
Pull the stale-counter reconciliation fix (clear even after a new
relation forms) so the stacked PR validates against the current base.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
The consumer now references the offer secret purely by id, so the
legacy shared label is no longer attached anywhere by this charm —
keeping the constant "only to assert we never reintroduce it" was dead
weight in production code for a test-only concern. The tests that guard
against reintroduction use the literal string instead, the same
black-box approach the integration test already takes.

The owner-side OFFER_SECRET_LABEL stays: it is the owner's stable
handle to its own secret across hooks and survives the loss of the
relation data that carries the published id — exactly what a dead-DC
teardown destroys.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
@marceloneppel marceloneppel changed the title Dpe 10203 consumer secret by [DPE-10203] fix(async-replication): reference the offer secret by id on the consumer Aug 21, 2026
@marceloneppel marceloneppel added the bug Something isn't working as expected label Aug 21, 2026
@marceloneppel

Copy link
Copy Markdown
Member Author

Superseded by #1913 — the consumer-by-id change is included there; the label constants are gone entirely (labelless design).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working as expected Libraries: Out of sync The charm libs used are out-of-sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant