Skip to content

fix(relations): skip database request without a database name - #1897

Draft
marceloneppel wants to merge 3 commits into
canonical:16/edgefrom
marceloneppel:fix/database-requested-missing-database-name
Draft

fix(relations): skip database request without a database name#1897
marceloneppel wants to merge 3 commits into
canonical:16/edgefrom
marceloneppel:fix/database-requested-missing-database-name

Conversation

@marceloneppel

@marceloneppel marceloneppel commented Aug 11, 2026

Copy link
Copy Markdown
Member

Issue

On a leader unit, a database_requested event that was deferred while Patroni was unavailable gets replayed by ops on every subsequent dispatch. If the relation has gone away in the meantime, ops supplies a dead relation whose remote application cannot be resolved, so the requested database name reads back as absent. _collect_databases coerced that missing value to an empty string, and _on_database_requested then indexed it, raising an uncaught IndexError: string index out of range.

ops replays deferred notices before the hook's own event, and only drops a notice once its handler returns. The exception therefore aborts every later hook on that unit permanently, actions included. The leader can no longer provision client relations or refresh endpoints, so any client relation that had not yet been served stays unserved. The unit itself sits in error on a failed hook.

Reported downstream as ISREQ-3825.

Solution

Return early from _collect_databases when the relation no longer carries a database name, instead of coercing the missing value to an empty string. The handler already treats a None return as "nothing to do", so the stale notice drains on its next replay. Note this only applies once the fixed revision is running: a unit already wedged on an older revision needs to be refreshed and have its failed hook cleared before the guard can execute.

An empty name is not reachable from a live requirer: Juju drops a databag key whose value is an empty string, and the library only emits database_requested when that key is added. The guard therefore only affects the replayed-stale-notice path. The remaining database[-1] sites in this module are already protected by truthiness checks and are left unchanged.

Adds a regression test that drives the handler with an absent database name, which reproduces the IndexError without the guard.

Checklist

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

A database_requested event deferred while Patroni is unavailable is replayed by
ops on every later dispatch. If the relation has gone away in the meantime, ops
hands the handler a dead relation whose remote application cannot be resolved,
so the requested database name reads back as absent. The handler coerced that to
an empty string and then indexed it, raising an uncaught IndexError.

Because ops replays deferred notices before the hook's own event, and only drops
a notice once its handler returns, that exception aborted every subsequent hook
on the leader unit permanently. No client relation could be provisioned and no
endpoint refreshed, so related applications stayed blocked without credentials
even though the cluster itself was healthy.

Bail out when the name is absent rather than coercing it, so the stale notice
drains on its next replay and the unit recovers on its own.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
The regression test only checked that the handler stopped short of creating a
database or user. A guard that logged and then deferred again would satisfy that
while leaving the notice queued forever, so the unit would still never recover -
the failure this change exists to prevent went unasserted.

Returning without deferring is what lets ops drop the notice, so assert it
directly.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
Pinning only the absent name left the guard's shape untested: a variant checking
`is None` passes every test while still walking an empty string into the index
that raised in production. Covering both values locks in the behaviour the fix
depends on rather than the particular expression used to get it.

Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
@alnvdl-work

alnvdl-work commented Aug 17, 2026

Copy link
Copy Markdown

@marceloneppel

We reported the same issue in two places:

Should we update #1859 with the learnings from ISREQ-3825 and link to #1859 in this PR?

@marceloneppel

Copy link
Copy Markdown
Member Author

We reported the same issue in two places:

Should we update #1859 with the learnings from ISREQ-3825 and link to #1859 in this PR?

Hi, @alnvdl-work! Yes, we should.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants