Skip to content

fix: handle concurrent updates when marking obsolete secret revisions#21779

Merged
hpidcock merged 5 commits into
juju:3.6from
wallyworld:robust-secret-updates
Feb 16, 2026
Merged

fix: handle concurrent updates when marking obsolete secret revisions#21779
hpidcock merged 5 commits into
juju:3.6from
wallyworld:robust-secret-updates

Conversation

@wallyworld
Copy link
Copy Markdown
Member

@wallyworld wallyworld commented Feb 13, 2026

When saving metadata for a secret consumer, any obsolete secret revisions are deleted.
There's a race where saving a new revision concurrently with updating a secret consumer record could result in the new secret revision being marked as obsolete.

This also could result in a mismatch between the max revision number and the latest revision attr on the parent metadata doc. This caused a "state changing too quickly" error due to an assertion used in processing the obsolete revisions.

The fixes are:

  • to always use the actual latest revision number in the assertion, rather than the max revision number from the collection
  • if needed, reset a consumed revision back to obsolete=false if there's been a concurrent update when updating a consumer record. This doesn't rely on assertions but uses the passed in data. We don't have a way to do it with assertions at this point.

New tests

  • TestSaveSecretConsumerRevisionMismatch
  • TestSaveSecretConsumerConcurrentUpdate
  • TestUpdateConcurrentSaveSecretConsumer

Also, a drive by fix to the firewaller unit tests.

QA steps

Not really easy to reproduce what happened on site. Unit tests were created to simulate the issue.

Links

Issue: Fixes #21778, #21783

Jira card: JUJU-9206
Jira card: JUJU-9211

@wallyworld wallyworld force-pushed the robust-secret-updates branch from d32395b to 487fe1b Compare February 16, 2026 03:11
@wallyworld wallyworld changed the title feat: allow missing revisions when updating obsolete revisions fix: handle concurrent updates when marking obsolete secret revisions Feb 16, 2026
@wallyworld wallyworld force-pushed the robust-secret-updates branch from 9ad3d5a to 32149db Compare February 16, 2026 03:55
@wallyworld wallyworld force-pushed the robust-secret-updates branch from 32149db to 62c8e67 Compare February 16, 2026 03:55
@hpidcock hpidcock merged commit b859d96 into juju:3.6 Feb 16, 2026
22 checks passed
jujubot added a commit that referenced this pull request Feb 23, 2026
#21795

Merge 3.6

Bring forward dependabot changes

#21751 [from anvial/36-40-transition-guide](f8067fd)
#21760 [from anvial/fix-doc-sidemenu](41f8ba9)
#21557 [from raineszm/fix/int-options-represented-a…](81cff4d)

Drop:
#21741 [from wallyworld/limit-apiport-again](627aab5)

The shell tests are retained as jenkins job config is derived from main - test config allows tests to be targetted.

Drop mongo work:
#21788 [from wallyworld/fix-backup](236532c)
#21779 [from wallyworld/robust-secret-updates](b859d96)
#21782 [from manadart/3.6-log-ops-on-errors](70a3817)

Also drop commits that were backported from 4.0 to 3.6 and brought forward again in the merge.

The api port expose feature was dropped (needs reimplementing). But one change from that PR was re-implemented - we don't want to allow the controller app to be unexposed.

```
# Conflicts:
# .github/workflows/upgrade.yml
# agent/errors/errors.go
# api/agent/instancemutater/mocks/caller_mock.go
# api/agent/secretsdrain/mocks/facade_mock.go
# api/agent/uniter/resource.go
# api/agent/uniter/resource_test.go
# api/apiclient.go
# api/apiclient_test.go
# api/apiclient_whitebox_test.go
# api/base/mocks/caller_mock.go
# api/base/mocks/clientfacade_mock.go
# api/client/charms/downloader.go
# api/client/charms/downloader_test.go
# api/client/client/client.go
# api/client/modelupgrader/mocks/apibase_mock.go
# api/client/modelupgrader/package_test.go
# api/client/modelupgrader/upgrader.go
# api/client/modelupgrader/upgrader_test.go
# api/client/resources/client.go
# api/client/resources/client_upload_test.go
# api/connection.go
# api/controller/crossmodelrelations/crossmodelrelations.go
# api/controller/crossmodelsecrets/crossmodelsecrets.go
# api/controller/migrationtarget/client.go
# api/controller/usersecretsdrain/mocks/facade_mock.go
# api/http/http.go
# apiserver/apiserver.go
# apiserver/apiserver_test.go
# apiserver/facades/client/application/application.go
# apiserver/facades/client/application/application_test.go
# apiserver/facades/controller/firewaller/firewaller.go
# apiserver/facades/controller/firewaller/firewaller_unit_test.go
# apiserver/httpattachment/attachment.go
# apiserver/registration_proxy_mock_test.go
# cmd/juju/application/bundle/bundle_test.go
# cmd/juju/application/deployer/mocks/deploy_mock.go
# cmd/juju/commands/bootstrap_test.go
# cmd/juju/common/controller.go
# cmd/juju/common/controller_test.go
# cmd/jujud/agent/bootstrap.go
# cmd/jujud/agent/bootstrap_test.go
# cmd/jujud/agent/controllercharm.go
# cmd/modelcmd/mocks/api_mock.go
# environs/jujutest/livetests.go
# go.mod
# go.sum
# internal/container/broker/instance_broker.go
# internal/provider/common/bootstrap.go
# internal/provider/dummy/environs.go
# internal/provider/ec2/local_test.go
# internal/provider/kubernetes/specs/builder.go
# internal/proxy/proxy.go
# internal/proxy/testing/proxy.go
# internal/worker/caasapplicationprovisioner/application.go
# internal/worker/caasapplicationprovisioner/application_test.go
# internal/worker/caasapplicationprovisioner/mocks/runner_mock.go
# internal/worker/caasapplicationprovisioner/worker.go
# internal/worker/caasfirewallersidecar/mocks/api_base_mock.go
# internal/worker/caasoperator/mocks/apibase.go
# internal/worker/containerbroker/mocks/base_mock.go
# internal/worker/firewaller/firewaller.go
# internal/worker/firewaller/firewaller_test.go
# internal/worker/instancemutater/mocks/base_mock.go
# rpc/client.go
# rpc/jsoncodec/codec.go
# rpc/jsoncodec/codec_test.go
# rpc/jsoncodec/conn.go
# rpc/rpc_test.go
# rpc/server.go
# state/application_ports.go
# state/backups/db.go
# state/backups/db_dump_test.go
# state/backups/db_info_test.go
# state/enableha.go
# state/enableha_test.go
# state/export_test.go
# state/machine_ports.go
# state/resources.go
# state/secrets.go
# state/secrets_test.go
# state/txns.go
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants