Repository navigation
fix(workloadmonitor): stop doubling the bucket- prefix in bucket Workload names - #5083
Conversation
…load names A bucket's Workload was named "bucket-" + the BucketClaim name, and a Bucket application names its claim after its release, which already starts with "bucket-". A bucket an operator knows as cozy-backups showed up as bucket-bucket-cozy-backups in every billing usage report. Add the prefix only when the claim name does not carry it. Claims from other charts, such as Harbor's <release>-registry, keep their names. Existing Workloads under the doubled name are owned by their claim and would otherwise live on next to the new one, metering the bucket twice. When the replacement is reconciled, take the legacy Workload's recorded sizes as the starting point, so the rename leaves no gap in the metered size, and delete the legacy Workload. Only a Workload owned by the same BucketClaim is touched. Billing sees the renamed Workload as a new consumer: a usage report spanning the upgrade lists the bucket once under each name. Fixes #5014 Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
…claim With Bucket applications "x" and "bucket-x" in one namespace, the legacy name of the first, bucket-bucket-x, is the new name of the second. When the second reconciled first, CreateOrUpdate took over the first one's legacy Workload, which already carried bucket x's size, and appended a second owner. The first claim then found its own UID among the owners, treated the object as its legacy Workload and deleted it, and the second bucket's Workload came back without a size. Leave a Workload alone while it is owned by a different BucketClaim, log it and emit a BucketWorkloadNameTaken warning on the monitor. In the case above the other claim renames its Workload away on its own reconcile, and the name is free on the next pass. The same refusal keeps two claims that map to one name, such as "x" and "bucket-x", from sharing one Workload. Pin the edge cases the migration depends on: the reconcile order above in both directions, a claim without the prefix keeping its own Workload, and a recorded size taking precedence over the legacy one. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
…not an error The legacy Workload can disappear between the lookup and the delete, for example through owner garbage collection, and the delete ignores NotFound for that reason. Reconcile only logs per-claim errors, so check reconcileBucketClaimForMonitor's own result with a delete that reports NotFound. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
… Workload On a live cluster the cache records the BucketClaim kind in a Workload's owner reference, so a claim's own Workload passes through the "owned by another claim" check on every reconcile, and only the own-UID match keeps it from being skipped. The fake client leaves that kind empty, so no test exercised it. Seed a Workload whose owner reference carries the kind and the claim's UID, and expect it to be updated with no warning event. Pin the physical size carry-over alongside the logical one, and word the warning event so it holds when two claims map to one name for good. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
…does not own The adoption check only refused a Workload owned by another BucketClaim, and the kind test in it had no case that could tell it apart: a bucket Workload only ever has BucketClaim owners. Refuse any Workload that has owners when the claim is not among them, which needs no kind at all, and keep taking over one that has lost every owner reference, since nothing else would ever release its name. Pin the ownerless case. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
…hared with another claim A controller rolled back to the doubled prefix runs CreateOrUpdate on bucket-bucket-x, which by then is the renamed Workload of the Bucket application "bucket-x", and appends claim bucket-x as a second owner. After the next upgrade claim bucket-x took that object for its own legacy Workload: it carried the other bucket's size over and deleted the object, and the other bucket's Workload came back without a size. Treat a legacy Workload as the claim's own only when the claim is its sole owner. When it has other owners, take neither its sizes nor the object: drop the claim's owner reference and leave the rest. Delete the legacy Workload with UID and resourceVersion preconditions, so a Workload that took over the name between the lookup and the delete survives; a conflict is not an error and the next reconcile retries. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
…ile the new one is taken When the new Workload name was held by another owner the claim was skipped altogether, so its legacy Workload stopped being refreshed and billing kept reading the last size it had recorded. That happens briefly with Bucket applications "x" and "bucket-x" until the first one has migrated, and for good with a claim "x" outside the Bucket application next to the application "x", which map to the same name. Keep reconciling the bucket under its legacy name instead, without migrating, until the new name is free. Skip it only when the legacy name is held by another owner as well, which is the only option left for a claim without the prefix, whose two names coincide; the warning event then says that the bucket is not metered. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
…refer legacy sizes on takeover A BucketClaim deleted with --cascade=orphan and recreated leaves its bucket-bucket-<name> Workload without owners. The migration recognised a legacy Workload only by the claim's UID among its owners, so that object stayed next to the new one with a frozen size and the bucket was metered twice. Treat an ownerless legacy Workload that carries the monitor's label as the claim's: delete it with the same preconditions, but take no sizes from it, since they may predate the claim. The size carry-over kept whatever the Workload at the new name had recorded. When that Workload was not yet owned by the claim, an ownerless leftover, its sizes were not written for this bucket, yet they won over the migrating legacy Workload's. Prefer the recorded sizes only when the claim already owned the Workload. Assisted-by: LLM Signed-off-by: Andrey Kolkov <[email protected]>
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
LGTM with non-blocking notes.
The de-prefix fix and the one-way Workload-rename migration hold up across every ownership case I enumerated. go build/go vet/go test ./internal/controller/ pass on the head and on a fresh merge with current main, every CI check-run on the head is green, and a per-term mutation sweep over the diff turns up no coverage gap. Nothing here blocks the merge. Two minor notes and one design observation follow.
Findings
- [MINOR]
internal/controller/workloadmonitor_controller.go:792, legacy-release failure is log-only (no Event/status)
- [MINOR]
internal/controller/workloadmonitor_controller.go:679, unprefixed claim colliding with a held bucket- is silently unmetered
Design note (non-blocking)
reconcileBucketClaimForMonitor took five fix commits in this PR and now carries three ownership heuristics: a held-name fallback, a refuse-to-adopt, and a shared-owner release. Each one closes a concrete bug an earlier revision had. Commit a51ad0ed5 shows the first migration deleting a live bucket's Workload, so these branches are load-bearing rather than speculative, and every Workload write and delete sits in this one function, which puts the check beside the action rather than offsite. The thing to weigh is that all of this machinery exists to make a billing name human-readable. A smaller shape would sidestep the collision class above entirely: keep the object identity and expose the human name as a label the billing report reads, or run a one-shot platform migration and then use the new name unconditionally. Worth a look for a follow-up, not something this PR needs.
Checked and correct
- Size precedence
:741/:743: a recorded size on an owned Workload is kept, a stale size on an ownerless new-name object is discarded, and the legacy size is carried on a fresh rename. No silent double or collapse across the combinations. - Owner filter
:783(OwnerReferences[:0]in-place compaction, withorigtaken as a prior DeepCopy) and the delete preconditions:780(UID+ResourceVersion) touch only the inspected object, so a Workload that took the name since the lookup survives. A swallowed Conflict converges on the guaranteed 60s requeue. - No path I walked deletes a live bucket's Workload by mistake, and cache-stale reads converge through the preconditions.
Caveats
golangci-lintdid not run locally (toolchain pin); CIgolangciis green on the head.- The tests' fake client ignores the UID precondition on the legacy delete and checks only ResourceVersion, so the UID half of
:780is not pinned by any test. The real apiserver enforces both, so there is no defect, just a test that promises a little more than it exercises.
| legacy.OwnerReferences = owners | ||
| err = r.Patch(ctx, legacy, client.MergeFromWithOptions(orig, client.MergeFromWithOptimisticLock{})) | ||
| } | ||
| if client.IgnoreNotFound(err) != nil && !apierrors.IsConflict(err) { |
There was a problem hiding this comment.
[MINOR] legacy-release failure is log-only (no Event/status)
When the legacy-Workload delete (sole/orphan) or owner-ref patch (shared) returns anything other than NotFound or Conflict, the code logs and returns, and the reconcile loop logs again and continues, no Warning Event, no status condition. A transient failure self-heals on the 60s requeue, but a persistent one (a writer holding the ResourceVersion, or missing delete/patch RBAC on Workload) leaves the bucket metered twice under both the legacy and the new name with no operator-visible signal except controller logs. The held-name paths already emit a BucketWorkloadNameTaken Warning; consider an equivalent Warning on a repeated legacy-release failure so the double-metering surfaces in kubectl describe.
| r.Recorder.Eventf(monitor, corev1.EventTypeWarning, "BucketWorkloadNameTaken", | ||
| "Workload %s for BucketClaim %s is owned by another object; the bucket is not metered", name, bc.Name) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
[MINOR] unprefixed claim colliding with a held bucket- is silently unmetered
A BucketClaim whose name does not start with bucket- now resolves to the same Workload name as a Bucket application one bucket- level down: claim x and application release bucket-x both map to bucket-x, whereas before this change they were distinct (bucket-x vs bucket-bucket-x). If the application holds bucket-x first, the unprefixed claim takes the legacyHeld branch (legacyName == name, :666) and returns without creating a Workload, so that bucket is not metered while the application exists. The only signal is a BucketWorkloadNameTaken Warning re-emitted every 60s; WorkloadMonitor.Status carries nothing. No in-tree producer emits an unprefixed metered claim (the Bucket app always prefixes, Harbor's unprefixed claim is unmonitored), so this needs operator-made resources, hence minor, but the name-collision class is new in this change.
## What this PR does Backports to `release-1.7` from `main`, picked by the rules of the [backport policy](https://github.com/cozystack/cozystack/blob/main/docs/release/backports.md) in merge order. A merged pull request comes as its own commits, picked with `git cherry-pick -x` so each keeps its author, message and sign-offs, and a merge commit that carries the original's merge message with a `Backported-from:` trailer naming it; a squash-merged one comes as one pick. `backport-batch verify` checks that shape. A pick listed as changed differs from the commit it came from; `git range-diff <merge>^1..<merge>^2 <batch merge>^1..<batch merge>^2` shows how. A conflict or a failing check here is a [backport task](https://github.com/cozystack/cozystack/blob/main/docs/release/backport-tasks/README.md). - [#4756](#4756) `fix(backupstrategy-controller): run backup clients on a package-owned image`, 2 commits, changed in the pick: `48d0cb2958a5` - [#4867](#4867) `fix(e2e): keep chainsaw asserts polling through transient API errors`, 1 commit - [#4888](#4888) `fix(ci): dispatch frozen-line builds with GITHUB_TOKEN`, 1 commit - [#4985](#4985) `fix(etcd): stop counting canceled watch streams as failed gRPC requests`, 1 commit - [#5005](#5005) `fix(tcp-balancer): cap HAProxy fds and threads by the pod's own limits`, 1 commit - [#4917](#4917) `fix(kubernetes): run Proxmox CCM v0.16.1 and pin the Proxmox images by digest`, 2 commits - [#4974](#4974) `fix(kubernetes-nodes): plan autoscaler capacity on the cores the VM actually gets`, 1 commit - [#4971](#4971) `fix(e2e): keep the Proxmox tenant kubeconfig per run and remove it`, 2 commits - [#4970](#4970) `fix(e2e): read the KubernetesNodes pool release in the HelmRelease guard`, 3 commits - [#4972](#4972) `fix(e2e): validate the leak allowlist against every suite layout`, 3 commits - [#5082](#5082) `fix(marketplace): pin a tapped repository to a digest instead of a mutable tag`, 15 commits, changed in the pick: `a31ae9090928`, `b326e0badd62`, `a3d883b4bf5b`, `f8509450f7ca` - [#4004](#4004) `fix(dashboard): link a tenant row to its detail page`, 2 commits - [#5212](#5212) `fix(cilium): pass the CiliumNode list to jq on stdin in the IPAM hook`, 1 commit - [#5213](#5213) `fix(kubernetes): move the OIDC users out of the bootstrap Job's script`, 1 commit - [#5208](#5208) `fix(clickhouse): restore the archive a Backup was taken as`, 3 commits - [#5230](#5230) `fix(backups): re-render the default backup objects when the S3 endpoint changes`, 8 commits - [#5133](#5133) `fix(securitygroups): project a membership-only group to a policy Cilium accepts`, 1 commit - [#5091](#5091) `fix(postgres): render the declared databases in a stable order`, 1 commit - [#5123](#5123) `fix(mariadb-operator): derive the cluster name from the platform domain`, 2 commits - [#5151](#5151) `fix(redis): run one Sentinel when Redis runs a single pod`, 5 commits - [#5124](#5124) `fix(lineage, kubernetes): resolve CAPK node VMs to their cluster`, 2 commits - [#5184](#5184) `fix(api): read pool usage under the quota key the controller counts`, 1 commit - [#5190](#5190) `fix(platform): remove the dead registries value and patch-containerd Secret`, 2 commits - [#5211](#5211) `fix(postgres): fail a backup job as soon as its cnpg Backup fails`, 1 commit - [#5155](#5155) `fix(postgres): refuse system databases and never drop them`, 1 commit - [#5207](#5207) `fix(mariadb): fail backup and restore jobs whose operator Job failed`, 5 commits - [#5024](#5024) `fix(kubernetes, monitoring): send tenant cluster metrics to the tenant's vminsert`, 1 commit ### Maintainer overrides - [#5083](#5083) `fix(workloadmonitor): stop doubling the bucket- prefix in bucket Workload names`: `backport/skip`, left out - [#4973](#4973) `fix(kubernetes-nodes): refuse a partial resources spec on every pool`: `backport/skip`, left out - [#5180](#5180) `fix(tests): keep two unit tests green after the release image restamp`: `backport/skip`, left out ### Skipped - Rule 1, already on `release-1.7`: 19 - Rule 2, the title type is not `fix` or `security`, or the scope is `deps`: 26 - Rule 3, a breaking change: 5 - Rule 4, touches platform migrations, `targetVersion` or a CRD, or makes a sizeable API change: 2 <details><summary>Rule 1, already on `release-1.7`</summary> - [#4815](#4815) `fix(vm-disk): name the importer error in the disk's WorkloadsReady message`, `its 2 commits picked, the last in 4323363` - [#4293](#4293) `fix(fluxcd): keep Helm uninstall working during flux-plunger recovery`, `its 1 commit picked, the last in 0210407` - [#4791](#4791) `fix(kubernetes): keep NFS publish from hanging on a dead server`, `its 1 commit picked, the last in fa88bd1` - [#4830](#4830) `fix(multus): drop the CPU limit and make resources configurable`, `its 2 commits picked, the last in 363c842` - [#4865](#4865) `fix(kafka): pass the container limits to the Kafka JVMs`, `its 3 commits picked, the last in fc38a54` - [#4866](#4866) `fix(linstor): mark volumes restored from a snapshot DRBD-initialized`, `its 1 commit picked, the last in f9550b7` - [#4819](#4819) `fix(seaweedfs): build SeaweedFS in-tree with a bounded S3 read-ahead`, `its 5 commits picked, the last in 7f4c346` - [#3427](#3427) `fix(backups): prepare standalone VMDisk restore targets and add VM backup/restore e2e round-trips`, `its 3 commits picked, the last in 4ac0cd5` - [#4889](#4889) `fix(monitoring): send alerts to every Alertmanager replica`, `its 1 commit picked, the last in 5033d53` - [#4929](#4929) `fix(backups): gate every default Strategy CR on its CRD`, `its 2 commits picked, the last in dee1eef` - [#4914](#4914) `fix(fluxcd): close the flux-plunger review follow-ups for the hook, drain and Warnings`, `its 7 commits picked, the last in a8e4916` - [#4939](#4939) `fix(cluster-api): resync capi-operator every minute`, `its 1 commit picked, the last in cc8d6a4` - [#5027](#5027) `fix(hack): keep the backup stack in the vminstance selective install`, `its 1 commit picked, the last in 8ba8386` - [#4955](#4955) `fix(vm-disk): let CDI adopt the PVC a Velero restore recreates`, `its 2 commits picked, the last in 691d7bc` - [#5071](#5071) `fix(marketplace): do not install a connected repository's components on connect`, `its 2 commits picked, the last in 25f8f11` - [#5086](#5086) `fix(tenant): stop the gateway release waiting on TenantGateway listeners and certificates`, `its 1 commit picked, the last in 18b2249` - [#5087](#5087) `fix(controller): carry healthCheckExprs and waitStrategy to existing releases`, `its 1 commit picked, the last in d2a452e` - [#5088](#5088) `fix(ingress-nginx): drop the admission-webhook policy a rebase brought back`, `its 1 commit picked, the last in cf00b41` - [#5142](#5142) `fix(release): keep release bodies within GitHub's size limit`, `its 1 commit picked, the last in c740515` </details> <details><summary>Rule 2, the title type is not `fix` or `security`, or the scope is `deps`</summary> - [#4845](#4845) `docs(governance): welcome new contributors and link LF sanctions guidance`, `type docs` - [#4881](#4881) `feat(bucket): add a ui flag to skip the s3manager web UI`, `type feat` - [#4899](#4899) `chore: link the FOSSA license scan for CLOMonitor`, `type chore` - [#4898](#4898) `feat(backups): back up Valkey with the Redis strategy`, `type feat` - [#4940](#4940) `docs(release): add changelog for v1.6.5`, `type docs` - [#4861](#4861) `feat(release): sign the release assets and attach a source SBOM`, `type feat` - [#3780](#3780) `docs: move the operator guides out of docs/ to where readers find them`, `type docs` - [#4936](#4936) `docs(release): check changelog claims against the code`, `type docs` - [#4959](#4959) `docs(release): point changelog check B at the Talos installer profile`, `type docs` - [#3777](#3777) `docs(agents): define where a new document belongs`, `type docs` - [#5025](#5025) `test(harbor): pin the resources of the nginx proxy and the portal`, `type test` - [#5039](#5039) `feat(keycloak): load additional CA files from Secret or ConfigMap keys`, `type feat` - [#4860](#4860) `feat(kubernetes-nodes): trust a factory CA in the Proxmox template script`, `type feat` - [#5090](#5090) `ci(e2e): end the Proxmox lane's SSH key with a newline`, `type ci` - [#4969](#4969) `test(kubernetes): pin RetryOnFailure on the Proxmox csi release`, `type test` - [#5000](#5000) `test(kubernetes): name the Proxmox fixture's node pool <cluster>-<pool>`, `type test` - [#5168](#5168) `docs(agents): steer agents away from Helm lookup`, `type docs` - [#4997](#4997) `feat(release): attach an SBOM to every release image`, `type feat` - [#3440](#3440) `ci(api-review-gate): publish the gate as a commit status`, `type ci` - [#5034](#5034) `docs: move the repo-only guides onto the website`, `type docs` - [#5215](#5215) `feat(monitoring-agents): export PVC labels from kube-state-metrics`, `type feat` - [#5217](#5217) `feat(monitoring-agents): keep application identity in container log streams`, `type feat` - [#5171](#5171) `feat(api): expose HelmRelease failure state on Application status`, `type feat` - [#5237](#5237) `feat(operator): let the platform source trust a private registry CA`, `type feat` - [#5106](#5106) `feat(release): make backports automatic with backport-batch`, `type feat` - [#4983](#4983) `feat(api): add the reservation evaluator for tenant quotas`, `type feat` </details> <details><summary>Rule 3, a breaking change</summary> - [#4790](#4790) `fix(platform)!: refuse a networking variant change on a live cluster`, `! in the title` - [#4796](#4796) `fix(vm-disk)!: refuse a golden-image clone CDI would leave unbound`, `! in the title` - [#5085](#5085) `fix(kubernetes, opensearch, foundationdb)!: restrict managed application images`, `! in the title` - [#4818](#4818) `fix(keycloak)!: make the KMS proxy keep its keys across restarts`, `! in the title` - [#5167](#5167) `fix(monitoring-agents)!: isolate merged log payload under log_processed`, `! in the title` </details> <details><summary>Rule 4, touches platform migrations, `targetVersion` or a CRD, or makes a sizeable API change</summary> - [#4941](#4941) `fix(backups): keep the backupstrategy-controller Helm release under the Secret size limit`, `a sizeable API change (breaking-change: [v1alpha1] spec.spec.template: field "metadata" was removed, and 5 more), a CRD` - [#5223](#5223) `fix(backups): report sizeBytes for MariaDB and CNPG backup artifacts`, `a CRD` </details> ```backport-batch {"line":"release-1.7","deferred":[]} ``` ### Screenshots See the original pull requests. ### Downstream repositories - [ ] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note fix(backupstrategy-controller): the ClickHouse (Altinity) and RabbitMQ backup strategy Pods now run a dedicated `backup-http-client` image (alpine + curl + jq + ca-certificates, non-root, ~7 MB) instead of the 349 MB `platform-migrations` image. Air-gapped mirrors need the new `ghcr.io/cozystack/cozystack/backup-http-client` repository. fix(e2e): a chainsaw assert in the e2e suites keeps polling through a transient API error such as an etcd leader change, instead of failing the suite long before its timeout. fix(ci): use GITHUB_TOKEN to start the initial packages build when freezing a release line fix(etcd): the tenant etcd alert etcdHighNumberOfFailedGRPCRequests now counts only the gRPC codes that the etcd mixin and the platform copy of the alert count, so the Canceled watch streams of a Kubernetes API server that stops no longer fire it on every etcd member. fix(tcp-balancer): HAProxy now takes its fd and thread caps from the replica's memory and CPU limits instead of the node, so a t1.nano balancer is no longer OOMKilled on nodes with many CPUs. maxconn follows the memory limit at about 16 connections per MiB, 2035 on t1.nano, and the pods roll once on upgrade. fix(kubernetes): the Proxmox cloud-controller-manager now runs v0.16.1, and the Proxmox CCM and CSI driver images are pinned by digest. The v0.15.0 tag referenced before was re-pushed upstream to a build that panics on start, which left every new Proxmox worker with the uninitialized taint. fix(marketplace): a tapped repository is pinned to a digest instead of following a mutable tag, so moving the tag upstream no longer changes what runs in the cluster; connect accepts oci://...@sha256:... and an optional keyless cosign verify policy (anchored to an exact identity and preserved across re-connects), and with verify flags the CLI verifies the signature and pins the verified digest at tap time. fix(dashboard): tenant name in the tenant list links to its detail page now (where Delete is), and top level tenants get their Edit button back. fix(cilium): the IPAM migration hook no longer fails with "Argument list too long" on clusters with more than a few nodes, which blocked the cilium upgrade and every package that depends on networking. fix(kubernetes): the OIDC bootstrap Job no longer fails to start for a tenant cluster with about 240 or more oidc.users, the bindings now come from a ConfigMap the Job mounts. fix(clickhouse): a RestoreJob now restores the archive its Backup was taken as, not the newest one. New Backups record the archive name, Backups taken before this release are restored best effort from the newest archive stamped no later than their takenAt, with a warning in the restore pod log. A ClickHouse BackupJob created again under a name whose Backup still exists now fails instead of reporting that Backup as its result. fix(backups): the backup controller now re-renders the Velero BSL, the default backup strategies and the platform bucket's credentials Secret when their S3 endpoint is stale, so after the platform bucket's BucketAccess is re-granted for a new publishing.host, backups move to the new endpoint without forcing releases by hand. fix(securitygroups): a SecurityGroup with attachments and no rules no longer leaves an invalid CiliumNetworkPolicy behind; its backing policy is now accepted by Cilium and still allows or denies nothing. fix(postgres): the init script renders the declared databases in a stable order, so an upgrade that leaves the databases unchanged no longer rewrites the init-script Secret. fix(mariadb-operator): the MariaDB operator now uses the platform's networking.clusterDomain instead of a hard-coded cozy.local, so replication and User/Database/Grant reconciliation work on clusters with a non-default cluster domain. fix(redis): an instance with `replicas: 1` now runs one Sentinel instead of three, without a Sentinel PodDisruptionBudget; on upgrade, the PDB the operator created earlier is deleted. Instances with two replicas or more are unchanged. fix(lineage, kubernetes): the virt-launcher pods of Kubernetes worker nodes are now resolved to their Kubernetes application and marked as managed by Cozystack, so a SecurityGroup attached to a Kubernetes cluster now applies to its worker nodes, not only to its control plane. fix(api): tenant quota admission now counts the parent pool's usage of extended resources and hugepages, so a child tenant can no longer get a share the parent has already used. After upgrading, updates to existing tenants may be rejected if their quota for these resources exceeds the parent's remaining budget. This includes updates that leave the quota unchanged. fix(postgres): a Postgres BackupJob now fails as soon as its CloudNativePG Backup fails, with the recorded error, instead of after 30 minutes, and a BackupJob waiting on a CloudNativePG Backup reports a WaitingForCNPGBackup condition with the Backup's phase. fix(postgres): databases now refuses the PostgreSQL system databases postgres, template0 and template1, and the init job never runs DROP DATABASE on them. A system database a previous version took over is handed back to the superuser on the next upgrade (event trigger removed, ownership and public schema restored, its <db>_admin and <db>_readonly roles dropped). A release that declares one of them must remove the entry before upgrading. fix(mariadb): a MariaDB BackupJob or RestoreJob whose operator Job failed now ends Failed instead of Succeeded, and a failed backup no longer creates a Ready Backup without an archive. Backups created by failed runs before this fix are not removed; restoring from one can fail or select another dump from the shared prefix. fix(kubernetes, monitoring): the vmagent of tenant Kubernetes clusters now writes to the tenant's vminsert and labels series with the owning tenant instead of `cozy-monitoring`. Previously no tenant cluster metric reached the tenant's Monitoring. ```
## What this PR does Backports to `release-1.7` from `main`, picked by the rules of the [backport policy](https://github.com/cozystack/cozystack/blob/main/docs/release/backports.md) in merge order. A merged pull request comes as its own commits, picked with `git cherry-pick -x` so each keeps its author, message and sign-offs, and a merge commit that carries the original's merge message with a `Backported-from:` trailer naming it; a squash-merged one comes as one pick. `backport-batch verify` checks that shape. A pick listed as changed differs from the commit it came from; `git range-diff <merge>^1..<merge>^2 <batch merge>^1..<batch merge>^2` shows how. A conflict or a failing check here is a [backport task](https://github.com/cozystack/cozystack/blob/main/docs/release/backport-tasks/README.md). - [#5284](#5284) `fix(seaweedfs): bump the COSI driver to v0.4.0 for the lost-grant fix`, 1 commit ### Maintainer overrides - [#5083](#5083) `fix(workloadmonitor): stop doubling the bucket- prefix in bucket Workload names`: `backport/skip`, left out - [#4973](#4973) `fix(kubernetes-nodes): refuse a partial resources spec on every pool`: `backport/skip`, left out - [#5180](#5180) `fix(tests): keep two unit tests green after the release image restamp`: `backport/skip`, left out ### Skipped - Rule 1, already on `release-1.7`: 46 - Rule 2, the title type is not `fix` or `security`, or the scope is `deps`: 31 - Rule 3, a breaking change: 5 - Rule 4, touches platform migrations, `targetVersion` or a CRD, or makes a sizeable API change: 2 <details><summary>Rule 1, already on `release-1.7`</summary> - [#4815](#4815) `fix(vm-disk): name the importer error in the disk's WorkloadsReady message`, `its 2 commits picked, the last in 4323363` - [#4756](#4756) `fix(backupstrategy-controller): run backup clients on a package-owned image`, `its 2 commits picked, the last in 4643366` - [#4293](#4293) `fix(fluxcd): keep Helm uninstall working during flux-plunger recovery`, `its 1 commit picked, the last in 0210407` - [#4791](#4791) `fix(kubernetes): keep NFS publish from hanging on a dead server`, `its 1 commit picked, the last in fa88bd1` - [#4830](#4830) `fix(multus): drop the CPU limit and make resources configurable`, `its 2 commits picked, the last in 363c842` - [#4865](#4865) `fix(kafka): pass the container limits to the Kafka JVMs`, `its 3 commits picked, the last in fc38a54` - [#4866](#4866) `fix(linstor): mark volumes restored from a snapshot DRBD-initialized`, `its 1 commit picked, the last in f9550b7` - [#4867](#4867) `fix(e2e): keep chainsaw asserts polling through transient API errors`, `its 1 commit picked, the last in df75adc` - [#4819](#4819) `fix(seaweedfs): build SeaweedFS in-tree with a bounded S3 read-ahead`, `its 5 commits picked, the last in 7f4c346` - [#3427](#3427) `fix(backups): prepare standalone VMDisk restore targets and add VM backup/restore e2e round-trips`, `its 3 commits picked, the last in 4ac0cd5` - [#4889](#4889) `fix(monitoring): send alerts to every Alertmanager replica`, `its 1 commit picked, the last in 5033d53` - [#4929](#4929) `fix(backups): gate every default Strategy CR on its CRD`, `its 2 commits picked, the last in dee1eef` - [#4914](#4914) `fix(fluxcd): close the flux-plunger review follow-ups for the hook, drain and Warnings`, `its 7 commits picked, the last in a8e4916` - [#4888](#4888) `fix(ci): dispatch frozen-line builds with GITHUB_TOKEN`, `its 1 commit picked, the last in bf81e2d` - [#4939](#4939) `fix(cluster-api): resync capi-operator every minute`, `its 1 commit picked, the last in cc8d6a4` - [#5027](#5027) `fix(hack): keep the backup stack in the vminstance selective install`, `its 1 commit picked, the last in 8ba8386` - [#4955](#4955) `fix(vm-disk): let CDI adopt the PVC a Velero restore recreates`, `its 2 commits picked, the last in 691d7bc` - [#4985](#4985) `fix(etcd): stop counting canceled watch streams as failed gRPC requests`, `its 1 commit picked, the last in 38e4089` - [#5005](#5005) `fix(tcp-balancer): cap HAProxy fds and threads by the pod's own limits`, `its 1 commit picked, the last in 7a3d0b0` - [#5071](#5071) `fix(marketplace): do not install a connected repository's components on connect`, `its 2 commits picked, the last in 25f8f11` - [#5086](#5086) `fix(tenant): stop the gateway release waiting on TenantGateway listeners and certificates`, `its 1 commit picked, the last in 18b2249` - [#5087](#5087) `fix(controller): carry healthCheckExprs and waitStrategy to existing releases`, `its 1 commit picked, the last in d2a452e` - [#5088](#5088) `fix(ingress-nginx): drop the admission-webhook policy a rebase brought back`, `its 1 commit picked, the last in cf00b41` - [#5142](#5142) `fix(release): keep release bodies within GitHub's size limit`, `its 1 commit picked, the last in c740515` - [#4917](#4917) `fix(kubernetes): run Proxmox CCM v0.16.1 and pin the Proxmox images by digest`, `its 2 commits picked, the last in 8ff6dca` - [#4974](#4974) `fix(kubernetes-nodes): plan autoscaler capacity on the cores the VM actually gets`, `its 1 commit picked, the last in d68a163` - [#4971](#4971) `fix(e2e): keep the Proxmox tenant kubeconfig per run and remove it`, `its 2 commits picked, the last in 77227c5` - [#4970](#4970) `fix(e2e): read the KubernetesNodes pool release in the HelmRelease guard`, `its 3 commits picked, the last in 6b17a76` - [#4972](#4972) `fix(e2e): validate the leak allowlist against every suite layout`, `its 3 commits picked, the last in 7ec550b` - [#5082](#5082) `fix(marketplace): pin a tapped repository to a digest instead of a mutable tag`, `its 15 commits picked, the last in 9cff86f` - [#4004](#4004) `fix(dashboard): link a tenant row to its detail page`, `its 2 commits picked, the last in bd5e0b5` - [#5212](#5212) `fix(cilium): pass the CiliumNode list to jq on stdin in the IPAM hook`, `its 1 commit picked, the last in b28a2e2` - [#5213](#5213) `fix(kubernetes): move the OIDC users out of the bootstrap Job's script`, `its 1 commit picked, the last in 02dd8e5` - [#5208](#5208) `fix(clickhouse): restore the archive a Backup was taken as`, `its 3 commits picked, the last in 9ded5c5` - [#5230](#5230) `fix(backups): re-render the default backup objects when the S3 endpoint changes`, `its 8 commits picked, the last in 490eab5` - [#5133](#5133) `fix(securitygroups): project a membership-only group to a policy Cilium accepts`, `its 1 commit picked, the last in b7c50a5` - [#5091](#5091) `fix(postgres): render the declared databases in a stable order`, `its 1 commit picked, the last in b658dff` - [#5123](#5123) `fix(mariadb-operator): derive the cluster name from the platform domain`, `its 2 commits picked, the last in 17702dd` - [#5151](#5151) `fix(redis): run one Sentinel when Redis runs a single pod`, `its 5 commits picked, the last in 5524a41` - [#5124](#5124) `fix(lineage, kubernetes): resolve CAPK node VMs to their cluster`, `its 2 commits picked, the last in c908560` - [#5184](#5184) `fix(api): read pool usage under the quota key the controller counts`, `its 1 commit picked, the last in 18c17af` - [#5190](#5190) `fix(platform): remove the dead registries value and patch-containerd Secret`, `its 2 commits picked, the last in 617f889` - [#5211](#5211) `fix(postgres): fail a backup job as soon as its cnpg Backup fails`, `its 1 commit picked, the last in 6645e21` - [#5155](#5155) `fix(postgres): refuse system databases and never drop them`, `its 1 commit picked, the last in 6f1bb80` - [#5207](#5207) `fix(mariadb): fail backup and restore jobs whose operator Job failed`, `its 5 commits picked, the last in ef3de55` - [#5024](#5024) `fix(kubernetes, monitoring): send tenant cluster metrics to the tenant's vminsert`, `its 1 commit picked, the last in 423a75b` </details> <details><summary>Rule 2, the title type is not `fix` or `security`, or the scope is `deps`</summary> - [#4845](#4845) `docs(governance): welcome new contributors and link LF sanctions guidance`, `type docs` - [#4881](#4881) `feat(bucket): add a ui flag to skip the s3manager web UI`, `type feat` - [#4899](#4899) `chore: link the FOSSA license scan for CLOMonitor`, `type chore` - [#4898](#4898) `feat(backups): back up Valkey with the Redis strategy`, `type feat` - [#4940](#4940) `docs(release): add changelog for v1.6.5`, `type docs` - [#4861](#4861) `feat(release): sign the release assets and attach a source SBOM`, `type feat` - [#3780](#3780) `docs: move the operator guides out of docs/ to where readers find them`, `type docs` - [#4936](#4936) `docs(release): check changelog claims against the code`, `type docs` - [#4959](#4959) `docs(release): point changelog check B at the Talos installer profile`, `type docs` - [#3777](#3777) `docs(agents): define where a new document belongs`, `type docs` - [#5025](#5025) `test(harbor): pin the resources of the nginx proxy and the portal`, `type test` - [#5039](#5039) `feat(keycloak): load additional CA files from Secret or ConfigMap keys`, `type feat` - [#4860](#4860) `feat(kubernetes-nodes): trust a factory CA in the Proxmox template script`, `type feat` - [#5090](#5090) `ci(e2e): end the Proxmox lane's SSH key with a newline`, `type ci` - [#4969](#4969) `test(kubernetes): pin RetryOnFailure on the Proxmox csi release`, `type test` - [#5000](#5000) `test(kubernetes): name the Proxmox fixture's node pool <cluster>-<pool>`, `type test` - [#5168](#5168) `docs(agents): steer agents away from Helm lookup`, `type docs` - [#4997](#4997) `feat(release): attach an SBOM to every release image`, `type feat` - [#3440](#3440) `ci(api-review-gate): publish the gate as a commit status`, `type ci` - [#5034](#5034) `docs: move the repo-only guides onto the website`, `type docs` - [#5215](#5215) `feat(monitoring-agents): export PVC labels from kube-state-metrics`, `type feat` - [#5217](#5217) `feat(monitoring-agents): keep application identity in container log streams`, `type feat` - [#5171](#5171) `feat(api): expose HelmRelease failure state on Application status`, `type feat` - [#5237](#5237) `feat(operator): let the platform source trust a private registry CA`, `type feat` - [#5106](#5106) `feat(release): make backports automatic with backport-batch`, `type feat` - [#4983](#4983) `feat(api): add the reservation evaluator for tenant quotas`, `type feat` - [#5264](#5264) `feat(release): keep a line's backport record and reports across batch pull requests`, `type feat` - [#5260](#5260) `ci(e2e): skip final cleanup on disposable PR runners`, `type ci` - [#5046](#5046) `feat(backups): record the RDB snapshot location and size for Redis backups`, `type feat` - [#5214](#5214) `feat(observability-proxy): serve tenant namespace logs through an authorizing proxy`, `type feat` - [#5282](#5282) `test(release): isolate report deduplication from workflow attempts`, `type test` </details> <details><summary>Rule 3, a breaking change</summary> - [#4790](#4790) `fix(platform)!: refuse a networking variant change on a live cluster`, `! in the title` - [#4796](#4796) `fix(vm-disk)!: refuse a golden-image clone CDI would leave unbound`, `! in the title` - [#5085](#5085) `fix(kubernetes, opensearch, foundationdb)!: restrict managed application images`, `! in the title` - [#4818](#4818) `fix(keycloak)!: make the KMS proxy keep its keys across restarts`, `! in the title` - [#5167](#5167) `fix(monitoring-agents)!: isolate merged log payload under log_processed`, `! in the title` </details> <details><summary>Rule 4, touches platform migrations, `targetVersion` or a CRD, or makes a sizeable API change</summary> - [#4941](#4941) `fix(backups): keep the backupstrategy-controller Helm release under the Secret size limit`, `a sizeable API change (breaking-change: [v1alpha1] spec.spec.template: field "metadata" was removed, and 5 more), a CRD` - [#5223](#5223) `fix(backups): report sizeBytes for MariaDB and CNPG backup artifacts`, `a CRD` </details> ```backport-batch {"version":2,"line":"release-1.7","built":{"main":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","line":"3cd9d4c220b519972677c7ff756ae6c4db07ef4a","newer":"","policy":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","batch":"e27d6630a0008381f0bc105a87d117dfcc29680f","tool":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","run":"https://github.com/cozystack/cozystack/actions/runs/43209950053","at":"2026-11-12T11:33:16Z","options":{"noWorkflowFiles":true,"mode":"new"}},"report":"9ca0285a0bb30a09736f6a430878b3ce0d32a2811639861e80daa549f208d7b3","closing":null,"deferred":[],"dropped":[],"stale":[]} ``` ```backport-batch-report {"format":1,"id":"9ca0285a0bb30a09736f6a430878b3ce0d32a2811639861e80daa549f208d7b3","line":"release-1.7","built":{"main":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","line":"3cd9d4c220b519972677c7ff756ae6c4db07ef4a","newer":"","policy":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","batch":"e27d6630a0008381f0bc105a87d117dfcc29680f","tool":"76a34d3e53a14c39d8df63eec6271c2eaae5ffea","run":"https://github.com/cozystack/cozystack/actions/runs/43209950053","at":"2026-11-12T11:33:16Z","options":{"noWorkflowFiles":true,"mode":"new"}},"overrides":{"backport/include":[],"backport/skip":[4973,5083,5180]},"primary":{"picked":[5284],"missing":[],"deferred":[],"parked":[],"skipped":{"on-line":[3427,4004,4293,4756,4791,4815,4819,4830,4865,4866,4867,4888,4889,4914,4917,4929,4939,4955,4970,4971,4972,4974,4985,5005,5024,5027,5071,5082,5086,5087,5088,5091,5123,5124,5133,5142,5151,5155,5184,5190,5207,5208,5211,5212,5213,5230],"before-start":[],"type":[3440,3777,3780,4845,4860,4861,4881,4898,4899,4936,4940,4959,4969,4983,4997,5000,5025,5034,5039,5046,5090,5106,5168,5171,5214,5215,5217,5237,5260,5264,5282],"breaking":[4790,4796,4818,5085,5167],"touches":[4941,5223],"newer":[],"skip-label":[4973,5083,5180],"dropped":[]}},"overlays":{"dropped":[],"stale":[],"changed":[]},"diag":{},"other":[]} ``` ### Screenshots See the original pull requests. ### Downstream repositories - [ ] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note fix(seaweedfs): bucket users granted together could get S3 keys that never work (403 InvalidAccessKeyId). COSI driver is bumped to v0.4.0, which writes each grant to per-identity IAM file. ```
… claim is provisioned (#5096) ## What this PR does A Bucket app reports WorkloadsReady=True while its BucketClaim is never provisioned. On a v1.6.5 cluster a tenant got a BucketClaim for bucket class `tenant-root`, the root tenant there runs no SeaweedFS, and for three days the claim had no status while the objectstorage controller kept emitting `FailedCreateBucket` with `bucketclasses.objectstorage.k8s.io "tenant-root" not found`. The Bucket app showed Ready and WorkloadsReady the whole time. The WorkloadMonitor controller already writes a Workload per BucketClaim with `Operational` set from `status.bucketReady`, but the monitor itself counts only pods. The bucket chart renders its monitor with `replicas: 0` and `minReplicas: 0`, so the monitor stays operational whatever happens to the claim. The monitor now names every selected BucketClaim that is not provisioned in `status.message`, sets `status.reason` to the new `BucketClaimNotReady` and turns `Operational` false, the same way it already reports a DataVolume that is not ready. A DataVolume verdict keeps the fields when both apply. BucketClaims are listed on every pass, so a stored BucketClaim verdict is recomputed rather than kept like an unread DataVolume verdict, and it clears once the claim is provisioned. Any monitor whose selector picks a BucketClaim follows its readiness, which is what the Workload objects already say. The Application then shows WorkloadsReady=False with that message and reason. Its Ready condition keeps following the HelmRelease, as `pkg/registry/apps/application/rest.go` intends. Three new tests cover a provisioned claim, a claim not provisioned with and without a class, a claim outside the selector, the verdict clearing after provisioning with and without a DataVolume reader, and the DataVolume verdict winning over the BucketClaim one. Against the controller on main the four not-provisioned and clearing cases fail and the rest pass. `go test ./internal/controller/...` and `go vet` pass. `make generate` does not run on my machine, gengo fails to parse the go1.27.1 standard library, so I ran the controller-gen `object` and `crd` steps of `hack/update-codegen.sh` for `./api/v1alpha1/...` directly. The deepcopy file is unchanged and the generated WorkloadMonitor CRD matches `packages/system/cozystack-controller/definitions/cozystack.io_workloadmonitors.yaml`. I have not run the change on a live cluster. #5083 changes the Workload names of the same BucketClaims and does not touch the monitor status, so the two do not overlap. ### Screenshots Not a UI change. ### Downstream repositories The controller, one exported constant and its tests. No CRD schema, values key, default, telemetry metric or package change, so no trigger map entry applies. - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note fix(workloadmonitor): a Bucket app reports WorkloadsReady=False with reason BucketClaimNotReady while its BucketClaim is not provisioned, instead of WorkloadsReady=True. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Workload monitors now report when a selected bucket claim is not provisioned and mark the monitor as non-operational. Status messages include the bucket class when available and list multiple unprovisioned claims in sorted order. * **Bug Fixes** * The bucket-claim status clears when the claim is provisioned or no longer selected. When DataVolume information is available, its status takes precedence over the bucket-claim status. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What this PR does
Fixes #5014.
A bucket's
Workloadwas named"bucket-" + <BucketClaim name>, and a Bucket application names its claim after its release, which already starts withbucket-. A bucket an operator knows ascozy-backupstherefore appeared asbucket-bucket-cozy-backups, and that string is theworkloadof the bucket's consumer in every billing usage report.bucket-prefix is now added only when the claim name does not already carry it. A Bucket application's claimbucket-cozy-backupsgives theWorkloadbucket-cozy-backups; a claim without the prefix, such asmy-bucket, still givesbucket-my-bucket. In the tree today every monitored BucketClaim comes from the Bucket application (Harbor's registry claim carries no labels its monitors select, so it has noWorkloadon either side of this change).Workloads by name: they are owned by their BucketClaim and live as long as it does. Without a migration the oldbucket-bucket-…object would stay next to the new one and the bucket would be metered twice. When the newWorkloadis reconciled, the controller looks up the legacy name and, only if the same BucketClaim (matched by UID) is its sole owner, uses its recorded sizes as the starting point and then deletes it, with UID and resourceVersion preconditions so aWorkloadthat took over the name in the meantime survives. If the legacyWorkloadhas other owners as well, it is another bucket's renamedWorkloadthat a rolled-back controller attached this claim to: the controller takes neither its sizes nor the object and only drops its own owner reference. A legacyWorkloadwith no owners left (a claim deleted with--cascade=orphanand recreated) that still carries this monitor's label is deleted the same way, without taking its sizes. Sizes already recorded at the new name win over the legacy ones only when the claim already owned thatWorkload. Carrying the sizes over matters because the controller deliberately keeps the last known size when the object-store metrics are unavailable; a freshWorkloadwould otherwise report no size until the next successful poll, which billing reads as an empty bucket.xandbucket-xin one namespace, the legacy name of the first (bucket-bucket-x) is the new name of the second. The controller never takes over aWorkloadthat has owners when the claim is not one of them (in practice another BucketClaim). While the new name is held that way, it keeps metering the bucket under its legacy name, without migrating, and emits aBucketWorkloadNameTakenwarning on the monitor. For Bucket applications the other claim renames itsWorkloadaway on its own reconcile and the migration completes on the next pass. Only when the legacy name is held as well — which for a claim without the prefix, whose two names coincide, means a claimxoutside the Bucket application next to the applicationx— is the bucket skipped, and the warning says it is not metered. Without this, whichever claim reconciled first decided whether the second bucket ended up with the first one's size or with no size at all. The same rule keeps two claims that map to one name (xandbucket-x) from sharing aWorkload.Rollback. The migration is one-way. A controller rolled back to a previous version recreates
bucket-bucket-<name>next to the renamedbucket-<name>, which its monitor keeps alive, so the bucket is metered twice until thebucket-<name>Workloads of Bucket applications are deleted by hand. Upgrading again after such a rollback converges without deleting another bucket'sWorkload(see above).Known limitation. Taking over an ownerless
Workloadkeeps the sizes recorded on it as the last known ones, as the controller always has. If such an object was left behind by another bucket (for examplekubectl delete bucketclaim --cascade=orphan), its sizes count for the new claim until the next successful size poll.Effect on billing, worth knowing before the upgrade:
Workloadnames are the identity billing integrates over. A bucket's consumer changes name once, at the upgrade, so a usage report whose window spans the upgrade lists the bucket twice: under the old name up to the switch and under the new one after it. The totals are right; the split is cosmetic. Reports for windows entirely before or after the upgrade are unaffected.Tests (
internal/controller/workloadmonitor_controller_test.go):TestBucketWorkloadName: the naming rule for a Bucket application claim, a plain claim and a Harbor claim.TestReconcileBucketApp_NoDoublePrefix: a Bucket application getsbucket-cozy-backupsand nobucket-bucket-cozy-backups.TestReconcileBucketApp_ReplacesLegacyWorkload: an existingbucket-bucket-cozy-backupsowned by the claim is deleted, and its recorded logical and physical sizes (no metrics endpoint in the test) are carried over to the newWorkload.TestReconcileBucketApp_KeepsLegacyNameOwnedByAnotherClaim: aWorkloadunder the legacy name owned by a different claim is left alone and its size is not taken.TestReconcileBucketApps_LegacyNameIsAnotherAppsNewName: Bucket applicationsxandbucket-xwith their legacyWorkloads, reconciled in both orders, end withbucket-xandbucket-bucket-x, each with one owner and its own size, and the legacybucket-bucket-bucket-xgone.TestReconcileBucketClaim_DoesNotAdoptAnotherClaimsWorkload: a claim whose name is held by another claim'sWorkloadleaves it untouched, emitsBucketWorkloadNameTakenand is metered under its legacy name.TestReconcileBucketClaim_KeepsMeteringUnderLegacyNameWhileNewNameIsHeld: the legacyWorkloadkeeps being updated, with its size and owner, across repeated reconciles while the new name is held.TestReconcileBucketClaim_UnprefixedClaimWithHeldNameIsSkippedandTestReconcileBucketClaim_BothNamesHeldIsSkipped: when no free name is left, nothing is taken over and the event says the bucket is not metered.TestReconcileBucketClaim_UnprefixedClaimKeepsItsWorkload: for a claim without the prefix, where the legacy and the new name coincide, its ownWorkloadand size survive repeated reconciles.TestReconcileBucketApp_RecordedSizeWinsOverLegacy: a size already recorded on the newWorkloadtakes precedence over the legacy one.TestReconcileBucketClaim_UpdatesItsOwnWorkload: a claim's ownWorkload, with its owner reference carrying theBucketClaimkind as a live cluster's cache records it, keeps being updated and raises no warning (the fake client leaves that kind empty on objects the controller creates, so the other tests do not exercise this).TestReconcileBucketClaim_AdoptsOwnerlessWorkload: aWorkloadthat lost all owner references is taken over by the claim whose name it carries, rather than blocking that name forever.TestReconcileBucketApp_ReleasesSharedLegacyWorkload: a legacyWorkloadthat another claim also owns (the rollback case) keeps its size and its other owner; only this claim's reference is dropped, and its size is not carried over.TestReconcileBucketApp_KeepsWorkloadThatReplacedLegacy: aWorkloadthat replaces the legacy one under the same name between the lookup and the delete survives.TestReconcileBucketApp_RemovesOrphanedLegacyWorkload,TestReconcileBucketApp_KeepsOrphanOfAnotherMonitor: an ownerless legacyWorkloadwith this monitor's label is removed without carrying its sizes; one labelled for another monitor is left alone.TestReconcileBucketApp_LegacySizeWinsOverOwnerlessNewWorkload: a stale size on an ownerlessWorkloadat the new name loses to the migrating legacyWorkload's size.TestReconcileBucketApp_LegacyAlreadyGone: a legacyWorkloadthat disappears between the lookup and the delete (NotFound) does not fail the per-claim reconcile, and the size is still carried over.Reverting the naming rule, the legacy delete, the size carry-over, the owner check, the refusal to adopt, the same-name guard, the size precedence ignoring NotFound on the legacy delete, the own-claim match in the adoption check, the ownerless case, the physical size carry-over, the sole-owner rule for legacy Workloads, the delete preconditions, the legacy-name fallback or its own held check, the ownerless-legacy rule or the size precedence on takeover each fails one of these.
go test ./internal/... -count=1andgo vet ./internal/controller/pass.Screenshots
N/A — no UI changes.
Downstream repositories
Walked the trigger map in
docs/agents/contributing.mdagainst the diff, which touches onlyinternal/controller/workloadmonitor_controller.goand its test: no package, values, schema, CRD, variant,hack/tooling, telemetry metric or cozy-proxy contract changes.Release note
Summary by CodeRabbit
bucket-prefix.