fix(seaweedfs): adopt legacy workloads in place across the 4.31 rename - #3282
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical upgrade regression introduced by the SeaweedFS chart version 4.31, which changed resource naming conventions. By detecting existing legacy workloads, the changes allow the system to adopt current StatefulSets and PVCs in place, avoiding the creation of duplicate, non-functional workload sets that previously caused pod anti-affinity deadlocks. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughSeaweedFS resources now use stable chart-based names, while the extra chart blocks unsafe upgrades when existing data is attached to renamed resources. Cleanup covers both naming schemes, tests validate naming and guard behavior, and a recovery runbook documents audit and remediation steps. ChangesSeaweedFS naming compatibility
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant HelmTemplate
participant KubernetesAPI
participant HelmRelease
HelmTemplate->>KubernetesAPI: lookup existing PVCs and StatefulSets
KubernetesAPI-->>HelmTemplate: return legacy/system naming evidence
HelmTemplate->>HelmRelease: render stable names or fail with re-bind instructions
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to pin SeaweedFS workload naming for legacy clusters upgrading to chart version 4.31+. It detects the presence of a legacy seaweedfs-master StatefulSet using Helm's lookup function and applies a fullnameOverride to prevent duplicate workloads and deadlocks. Unit tests are added to verify both the fresh-install behavior and the legacy naming override. Feedback on the changes highlights a potential naming collision if multiple SeaweedFS instances are deployed in the same namespace, suggesting a check on the app.kubernetes.io/instance label of the legacy StatefulSet to ensure it belongs to the current release.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| {{- $seaweedfsFullnameOverride := "" }} | ||
| {{- if lookup "apps/v1" "StatefulSet" .Release.Namespace "seaweedfs-master" }} | ||
| {{- $seaweedfsFullnameOverride = "seaweedfs" }} | ||
| {{- end }} |
There was a problem hiding this comment.
If multiple SeaweedFS instances are deployed in the same namespace, a fresh install of a second instance will mistakenly detect the legacy seaweedfs-master StatefulSet belonging to the first instance. This will cause the new instance to also set fullnameOverride: seaweedfs, leading to a critical naming collision.
To prevent this, verify that the found legacy StatefulSet actually belongs to the current release by checking its app.kubernetes.io/instance label.
{{- $seaweedfsFullnameOverride := "" }}
{{- $legacySTS := lookup "apps/v1" "StatefulSet" .Release.Namespace "seaweedfs-master" }}
{{- if and $legacySTS (eq (dig "metadata" "labels" "app.kubernetes.io/instance" "" $legacySTS) (printf "%s-system" .Release.Name)) }}
{{- $seaweedfsFullnameOverride = "seaweedfs" }}
{{- end }}1072d60 to
a5c8244
Compare
e7f85ae to
480e4fc
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/operations/seaweedfs-431-rename-recovery.md`:
- Around line 177-180: Make the S3 verification section executable by adding a
concrete aws s3 or SeaweedFS CLI command that reads or lists objects through the
seaweedfs-s3 endpoint, including the required endpoint and bucket/tenant
context. State the expected successful output so operators can confirm object
readability.
- Around line 68-70: Update the workload filtering in the scale-down and
deletion commands to avoid excluding all names beginning with “seaweedfs-”.
Exclude only the exact chart-named workload set, or explicitly identify the
duplicate workloads, so default release-based names such as seaweedfs-system-*
are processed.
- Around line 133-135: Update the D-split recovery commands that scale SeaweedFS
resources and clean up secrets to support non-default instance names. Derive
duplicate resource and secret names from the documented instance/release naming,
or select them by labels while explicitly excluding the legacy names. Preserve
the existing recovery behavior for the default seaweedfs-system instance.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cbc04dc5-baba-40ec-9162-169b8ebc2f67
📒 Files selected for processing (9)
docs/operations/seaweedfs-431-rename-recovery.mdpackages/extra/seaweedfs/templates/hooks/cleanup.yamlpackages/extra/seaweedfs/templates/seaweedfs.yamlpackages/extra/seaweedfs/tests/cleanup_test.yamlpackages/extra/seaweedfs/tests/fullname_override_named_test.yamlpackages/extra/seaweedfs/tests/fullname_override_test.yamlpackages/system/seaweedfs/tests/fullname_override_test.yamlpackages/system/seaweedfs/tests/s3_service_name_consumers_test.yamlpackages/system/seaweedfs/values.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/extra/seaweedfs/templates/hooks/cleanup.yaml
- packages/extra/seaweedfs/tests/cleanup_test.yaml
| # S3 endpoints resolve only to the adopted set's ready pods. | ||
| kubectl -n "$ns" get endpoints seaweedfs-s3 | ||
| # Objects are readable through S3 (bucket list via any tenant bucket). | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the S3 verification step executable.
The runbook requires confirming that objects are readable but provides no command, endpoint, or expected result. Add a concrete aws s3/SeaweedFS CLI check or document the exact manual procedure.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/operations/seaweedfs-431-rename-recovery.md` around lines 177 - 180,
Make the S3 verification section executable by adding a concrete aws s3 or
SeaweedFS CLI command that reads or lists objects through the seaweedfs-s3
endpoint, including the required endpoint and bucket/tenant context. State the
expected successful output so operators can confirm object readability.
|
I took this branch over and reworked the approach — sorry for the force-push, your cleanup-hook and the runbook base are still in, with The main change is dropping the adaptive I also dropped migration 52. Once the fix reconciles, Helm removes the duplicate StatefulSets itself, so the migration's only unique action was deleting the leftover PVCs — and that decision was made from instantaneous Your |
myasnikovdaniil
left a comment
There was a problem hiding this comment.
Second independent re-verification pass (two reviewers + a cross-model source read, exact-source-only). The core approach holds — the static fullnameOverride: seaweedfs pin is the right call over a render-time adaptive lookup (can't flip and re-duplicate), and the fail-closed S guard is sound; both unittest suites pass at HEAD (system 21/21, extra 17/17). Inline below: one design gap I'd resolve before merge (#1), then a cluster of default-instance-only bugs. Since block-and-rebind is the chosen path for fresh-1.5.x tenants, the Step 2 re-bind correctness (#2) is load-bearing.
Heads-up, not for this PR: adopt-in-place depends on the legacy 4.05 StatefulSet's immutable fields (spec.selector, spec.serviceName, spec.volumeClaimTemplates) matching the 4.31-with-pin render. The suite asserts the vCT name (data1) but not its labels/storageClass/accessModes, and no spec.upgrade.force is set — so a mismatch should wedge the HR (retries forever) rather than lose data. I'll validate this with a live 4.05→4.31 upgrade before the release-1.5 backport.
| {{- end }} | ||
| {{- end }} | ||
| {{- end }} | ||
| {{- if and $systemData (not $legacyData) }} |
There was a problem hiding this comment.
1. D-split renders and adopts silently — nothing routes the operator to Step 3. This gate is fail-closed for the pure-S case only. On a D-split tenant both legacy PVCs (data1-seaweedfs-volume-*) and system PVCs are present → $legacyData and $systemData are both true → and $systemData (not $legacyData) = false → the guard doesn't fire, the render proceeds, Helm adopts the legacy set and prunes the running seaweedfs-system-* set. Objects written through the split (system) volume servers are left on the leftover data1-seaweedfs-system-volume-* PVCs and become unreachable; filer metadata in the shared seaweedfs-db can reference fids with no live volume server.
The guard reads only PVC names (L130-138) and StatefulSet labels (L139-147) — no pod/replica state anywhere in L128-149. The only running-state read is the Step 1 audit script (docs/operations/seaweedfs-431-rename-recovery.md:38, --field-selector=status.phase=Running), and nothing forces it before an upgrade. So unlike S (blocked at render), D-split has no render-time gate.
Proposal: extend the existing StatefulSet scan to also block D-split, reusing the lookup already done — flag $systemRunning when a system-named seaweedfs volume StatefulSet reports .status.readyReplicas > 0, then fail on $legacyData AND $systemData AND $systemRunning pointing at Step 3. This is strictly safer than the dropped migration 52: 52 deleted PVCs off an instantaneous readyReplicas == 0, whereas this only blocks off a snapshot — worst case is an unnecessary block a later reconcile clears; it never deletes. WDYT — add the branch, or is there a reason to keep D-split rendering through?
There was a problem hiding this comment.
Added the branch. The guard now reads readyReplicas on the renamed volume StatefulSet and fails on legacyData AND systemData AND systemRunning, pointing at Step 3. D-wedged (zero ready) still renders through and adopts. Unit tests cover both. Agreed it's strictly safer than migration 52 — this only blocks, never deletes.
| # Scale down whatever 4.31 named this instance (any StatefulSet that is not the | ||
| # chart-named, pre-4.31 one). | ||
| for sts in $(kubectl -n "$ns" get sts -l app.kubernetes.io/name=seaweedfs -o name \ | ||
| | sed 's|statefulset.apps/||' | grep -v '^seaweedfs-'); do |
There was a problem hiding this comment.
2. Step 2 can't complete for the default instance (block-and-rebind depends on this). grep -v '^seaweedfs-' here drops the default instance's renamed workloads seaweedfs-system-master/filer/volume (they start with seaweedfs-), so the loop scales no StatefulSets. The PVC loop below then runs regardless and delete pvc (L88) blocks on the pvc-protection finalizer, because the still-running volume pod keeps data1-seaweedfs-system-volume-* mounted (patching the PV to Retain first doesn't help — pvc-protection is about the mounting pod, not the reclaim policy). Same filter at L115/L117 (drop-old-workloads) → those deletes are skipped too. It happens to work for a non-default instance (archive-system-seaweedfs-* doesn't start with seaweedfs-), so this is default-instance-only — the common case. Suggest selecting the renamed set explicitly (e.g. by app.kubernetes.io/instance=<release>-system, or excluding the exact chart-named names) instead of grep -v '^seaweedfs-'.
There was a problem hiding this comment.
Fixed. The grep -v '^seaweedfs-' was dropping the default instance's seaweedfs-system-* set. Replaced with grep -vE '^seaweedfs-(master|filer|volume)($|-)', which excludes only the pre-4.31 chart-named set and keeps both seaweedfs-system-* and <name>-system-seaweedfs-*. Same selector reused in Step 3.
|
|
||
| ```sh | ||
| ns=<tenant> | ||
| kubectl -n "$ns" scale sts seaweedfs-system-master seaweedfs-system-filer seaweedfs-system-volume --replicas=0 |
There was a problem hiding this comment.
3. Step 3 (and Step 4 at L155) hard-code the default instance name. This scales literal seaweedfs-system-master/filer/volume; L155 deletes literal seaweedfs-system-* secrets. For a non-default instance the duplicate set is <name>-system-seaweedfs-* and its secrets <name>-system-seaweedfs-*-cert, so these NotFound — the split isn't stopped / the secrets leak. Step 3 also isn't parameterized with $app the way Step 2 is (L64), and omits volume pools/zones. Same class as the Step 2 issue: Step 1 (audit) is instance-agnostic, only the action steps assume the default name.
There was a problem hiding this comment.
Fixed. Step 3 now selects the renamed set by that same filter instead of the literal names, and scales the renamed s3 Deployment by excluding seaweedfs-s3. Step 4's secret cleanup derives the names by suffix (-cert/-db-secret) minus the adopted seaweedfs-*, so both default and non-default instances are covered. Pools/zones fall out of the label selector.
| || echo "WARNING: SeaweedFS PVC cleanup failed for {{ .Release.Name }}; orphaned PVCs may remain" >&2 | ||
| echo "Deleting orphaned SeaweedFS cert/db secrets for {{ .Release.Name }}..." | ||
| kubectl delete secret -n {{ .Release.Namespace }} {{ .Release.Name }}-s3-ingress-tls {{ .Release.Name }}-system-admin-cert {{ .Release.Name }}-system-ca-cert {{ .Release.Name }}-system-client-cert {{ .Release.Name }}-system-filer-cert {{ .Release.Name }}-system-master-cert {{ .Release.Name }}-system-volume-cert {{ .Release.Name }}-system-worker-cert {{ .Release.Name }}-system-db-secret --ignore-not-found --wait=false \ | ||
| kubectl delete secret -n {{ .Release.Namespace }} {{ .Release.Name }}-s3-ingress-tls {{ .Release.Name }}-system-admin-cert {{ .Release.Name }}-system-ca-cert {{ .Release.Name }}-system-client-cert {{ .Release.Name }}-system-filer-cert {{ .Release.Name }}-system-master-cert {{ .Release.Name }}-system-volume-cert {{ .Release.Name }}-system-worker-cert {{ .Release.Name }}-system-db-secret seaweedfs-admin-cert seaweedfs-ca-cert seaweedfs-client-cert seaweedfs-filer-cert seaweedfs-master-cert seaweedfs-volume-cert seaweedfs-worker-cert seaweedfs-db-secret --ignore-not-found --wait=false \ |
There was a problem hiding this comment.
4. Cleanup hook misses a non-default fresh-install's secrets. This lists {{ .Release.Name }}-system-* and seaweedfs-* cert/db secrets (RBAC at L33-52, delete command here). But the real names are {{ include "seaweedfs.fullname" . }}-<comp>-cert (charts/seaweedfs/templates/cert/master-cert.yaml); for a non-default fresh install (archive) the system-chart fullname is archive-system-seaweedfs, so the secrets are archive-system-seaweedfs-*-cert — matching neither listed scheme. The "both schemes" coverage holds only for the default instance. (Single default tenant is fine; flagging for completeness.)
There was a problem hiding this comment.
Covered. The hook now computes seaweedfs.fullname of the child release (seaweedfs-system for the default, <name>-system-seaweedfs for a non-default one) and lists the renamed secrets off that, plus the adopted seaweedfs-*. Added a unit test on archive.
| # `<name>-system`, so every StatefulSet was renamed to `<name>-system-*`. Those | ||
| # names are immutable, so Helm could not rename in place — it stood up a second, | ||
| # empty set beside the running one while the data stayed on `data1-<name>-volume-*`. | ||
| # extra/seaweedfs overrides this with the release name it deploys under, which |
There was a problem hiding this comment.
5. This comment is inaccurate. extra/seaweedfs doesn't set fullnameOverride at all — the pin below (fullnameOverride: seaweedfs) is what applies, and the extra test asserts notExists: spec.values.seaweedfs.fullnameOverride. And if it did set it to the release name (seaweedfs-system), the fullname helper returns it verbatim → seaweedfs-system-* workloads — i.e. reintroduce the exact rename that caused the duplicate set, not "restore the pre-4.31 names." Worth correcting so nobody acts on it later.
There was a problem hiding this comment.
Corrected — it was left over from an earlier release-scoped draft. The comment now says extra/seaweedfs relies on this pin and does not re-set fullnameOverride.
Upstream chart 4.31 switched resource names from the chart name to the release name. The data-plane release is <name>-system, so every StatefulSet was renamed to <name>-system-*. StatefulSet names are immutable, so the upgrade could not rename in place: Helm stood up a second, empty set beside the running one while the data stayed on the original data1-seaweedfs-volume-* PVCs. Where the node count let both sets run, they shared one S3 Service and one filer metadata database, so the empty set answered reads and mixed volume IDs from an independent sequence into shared metadata. Pin fullnameOverride so the rendered names no longer follow the release name, matching the convention already used by other system packages (cozy-proxy, flux-operator, linstor-scheduler, victoria-metrics-operator). The s3-consumer suite pinned the name/fullname divergence this override removes by default, so it now re-creates that divergence explicitly and keeps guarding it regardless of the shipped default. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
With the chart-based names pinned back, an upgrade adopts the running workloads and their volumes in place. Before 4.31 the chart ignored the release name, so this holds for an instance running under any name: its data sits on the chart-named data1-seaweedfs-volume-* PVCs either way. Two states cannot be adopted and stop the render instead, each pointing the operator at the matching recovery step: - Fresh on 1.5.x: only the renamed volumes exist, so adopting the chart-based name would rename the workloads away from that data and bring up an empty cluster. Helm cannot move data between PVCs, so the operator is sent to the PV re-bind procedure. - D-split: both sets exist AND the renamed volume servers are live, so they may hold objects written through the split endpoint. Adopting would strand them, so the operator is sent to reconcile the split first. A duplicate that never served (zero ready replicas) is safe to adopt and renders through. The guard mirrors the chart's fullname helper to recognise what 4.31 named this instance (the helper appends the chart name when the release name does not contain it, then truncates), and matches the renamed volumes by shape plus the chart labels on the StatefulSet rather than reconstructing the exact name. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
…module delete The post-delete hook deleted only the secrets named after the renamed scheme, so a tenant running the adopted chart-based names leaked its cert and db secrets on delete. List both schemes, and derive the renamed name from the chart's fullname helper so an instance running under a non-default name (whose secrets are <name>-system-seaweedfs-*) is cleaned up too. Co-authored-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
Classify each tenant from its volume PVCs, re-bind the volumes of a tenant installed fresh on 1.5.x so the adopted names pick them up, stop a split cluster before upgrading it, and clear the leftovers the upgrade cannot remove itself. The recovery steps select the renamed set by a precise filter and derive secret names from the fullname helper, so they work for an instance running under a non-default name and for pooled or zoned volumes. Co-authored-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
480e4fc to
5dc363d
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/extra/seaweedfs/templates/seaweedfs.yaml (1)
138-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove redundant default guards from lookups.
Based on learnings for the Cozystack project, default dict guards (and by extension list guards) should not be used to protect against nil
lookupresults, as FluxCD renders against a live cluster where these structures are reliably returned. Furthermore, thedigfunction natively handlesnildictionaries safely, making(.metadata.labels | default dict)redundant.♻️ Proposed refactor
-{{- range (lookup "v1" "PersistentVolumeClaim" .Release.Namespace "").items | default list }} +{{- range (lookup "v1" "PersistentVolumeClaim" .Release.Namespace "").items }} {{- if and (hasPrefix "data1-" .metadata.name) (contains "-volume" .metadata.name) (contains "seaweedfs" .metadata.name) }} {{- if hasPrefix "data1-seaweedfs-volume" .metadata.name }} {{- $legacyData = true }} {{- else }} {{- $systemData = true }} {{- end }} {{- end }} {{- end }} -{{- range (lookup "apps/v1" "StatefulSet" .Release.Namespace "").items | default list }} -{{- if and (eq (dig "app.kubernetes.io/name" "" (.metadata.labels | default dict)) "seaweedfs") (contains "volume" .metadata.name) }} +{{- range (lookup "apps/v1" "StatefulSet" .Release.Namespace "").items }} +{{- if and (eq (dig "app.kubernetes.io/name" "" .metadata.labels) "seaweedfs") (contains "volume" .metadata.name) }}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/extra/seaweedfs/templates/seaweedfs.yaml` around lines 138 - 148, Remove the redundant list fallback from both PVC and StatefulSet lookup ranges, and pass metadata.labels directly to dig in the StatefulSet condition. Update the lookup expressions around the PVC/StatefulSet range blocks while preserving their existing filters and assignments.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/extra/seaweedfs/templates/seaweedfs.yaml`:
- Around line 138-148: Remove the redundant list fallback from both PVC and
StatefulSet lookup ranges, and pass metadata.labels directly to dig in the
StatefulSet condition. Update the lookup expressions around the PVC/StatefulSet
range blocks while preserving their existing filters and assignments.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2e118f6c-23d3-4761-b102-67948607edea
📒 Files selected for processing (10)
docs/operations/seaweedfs-431-rename-recovery.mdpackages/extra/seaweedfs/templates/hooks/cleanup.yamlpackages/extra/seaweedfs/templates/seaweedfs.yamlpackages/extra/seaweedfs/tests/cleanup_named_test.yamlpackages/extra/seaweedfs/tests/cleanup_test.yamlpackages/extra/seaweedfs/tests/fullname_override_named_test.yamlpackages/extra/seaweedfs/tests/fullname_override_test.yamlpackages/system/seaweedfs/tests/fullname_override_test.yamlpackages/system/seaweedfs/tests/s3_service_name_consumers_test.yamlpackages/system/seaweedfs/values.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/system/seaweedfs/values.yaml
- packages/system/seaweedfs/tests/fullname_override_test.yaml
- packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
- packages/extra/seaweedfs/tests/fullname_override_test.yaml
- packages/extra/seaweedfs/tests/cleanup_test.yaml
- packages/system/seaweedfs/tests/s3_service_name_consumers_test.yaml
|
Thanks for the pass — addressed all five inline, force-pushed:
On the heads-up: agreed the adopt-in-place depends on the legacy 4.05 StatefulSet's immutable |
|
I will try few upgrade paths today to verify it is working correctly, thank you. |
|
Created backport PR for
Please cherry-pick the changes locally and resolve any conflicts. git fetch origin backport-3282-to-release-1.5
git worktree add --checkout .worktree/backport-3282-to-release-1.5 backport-3282-to-release-1.5
cd .worktree/backport-3282-to-release-1.5
git reset --hard HEAD^
git cherry-pick -x 72db724d5fae4b7be1235c328a6b660a10e611f2 5dc363d3980bdfb54eaff3e98e522c58c0cea5e0
git push --force-with-lease |
…actually renames The class-S guard from #3282 lives in extra/seaweedfs, but that render is not in the path of a platform upgrade: the <name>-system HelmRelease pulls system/seaweedfs from a platform-managed ExternalArtifact, so bumping the platform upgrades the release directly from the new artifact and applies fullnameOverride: seaweedfs without extra/seaweedfs ever re-rendering. On a tenant installed fresh on 1.5.x that stood up an empty chart-named set beside the live release-named data, deleted the renamed s3 Service, and left S3 readable only because the empty set's pods cannot start. Worse, the damage flips the guard's own classification: creating the chart-named artifacts makes $legacyData true, so the guard then reports D-split and sends the operator to runbook Step 3 — which quiesces the renamed set, i.e. the one holding ALL the data. - Add the ENFORCING guard to system/seaweedfs (templates/naming-guard.yaml): the only render guaranteed to sit between a platform upgrade and the tenant's workloads. It also refuses to render if the fullnameOverride pin is ever lifted. - Tell S from D by PVC AGE when both generations exist: PVCs are never recreated in place (StatefulSets are, by the adoption hook), so the older volume-PVC generation is where the data was born. Renamed-older ⇒ class S (damaged variant, new Step 2a), never D-split. - Fail closed: the release namespace is a lookup canary; an upgrade that cannot see the cluster refuses instead of classifying blind. Client-side installs (CI lint/unittest) still render — they never touch live data. - Mirror the same classification in extra/seaweedfs so the operator-facing SeaweedFS application reports the correct class. - Tests now model the ExternalArtifact upgrade path in the chart it actually runs through (the old ones mocked lookups only in extra/seaweedfs, which is why this shipped green), including the damaged-S tenant observed live. - Runbook: add S-damaged audit classification (PVC age), Step 2a cleanup of the empty chart-named set, and the D-wedged anti-affinity stall workaround observed on 3-node clusters. helm-unittest 1.0.3 ignores release.isUpgrade, so the canary refusal is verified with 'helm template --is-upgrade' instead of a unit test. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… pinned fullname ClusterRoles and ClusterRoleBindings are cluster-scoped: one object per name, cluster-wide. This chart installs once per tenant, so every cluster-scoped name it renders must be unique per install. Four of them were not. Upstream 4.31 switched them from global.serviceAccountName to seaweedfs.fullname. That is fatal in combination with the #3282 pin: fullnameOverride: seaweedfs is what adopts the running NAMESPACED workloads across the rename, so fullname is by construction the SAME string in every tenant. Every SeaweedFS instance in the fleet therefore renders seaweedfs-objectstorage-provisioner, seaweedfs-rw-cr and their bindings — one object, five claimants. NOT a #3282 regression. Rendering v1.4.5, v1.5.3 and main for the same tenants: v1.4.5 tenant-root-seaweedfs-* tenant-named-seaweedfs-* unique v1.5.3 seaweedfs-system-* foo-system-seaweedfs-* COLLIDES (identical for every default-named instance; a non-default instance name was the only thing that escaped) main seaweedfs-* seaweedfs-* COLLIDES (nothing escapes: the pin removes the last distinguishing input) So the collision shipped in 1.5.0 with the 4.31 bump; #3282 widens it from "every default-named instance" to "every instance". Observed live on the upgrade stand: one ClusterRole/seaweedfs-objectstorage-provisioner annotated to a single owning tenant, four other tenants' COSI provisioners left with no RBAC, and tenant-dsplit's helm history recording Upgrade failed: original object ClusterRole "seaweedfs-objectstorage-provisioner" not found The binding is the sharp end: its subject names a ServiceAccount in the release namespace, so the single live ClusterRoleBinding grants cluster-wide secrets RBAC to whichever tenant reconciled last, and silently revokes it from the rest. Fix: put the four names back on global.seaweedfs.serviceAccountName, which extra/seaweedfs already sets to <namespace>-seaweedfs and which cosi-provisioner-sa-name.patch already uses for the ServiceAccount and the binding's subject — the cluster-scoped names were simply left behind. Three of the four come out byte-identical to pre-4.31, so a 1.4.x tenant adopts them in place; the fourth (rw-crb) replaces upstream's username-shaped system:serviceaccount:<sa>:default and is pruned from the release's own manifest. The namespaced pin is untouched: workloads still render seaweedfs-master/-filer/ -volume, so #3282's adoption is intact. Uniqueness now rests on a value, so assert it rather than trust it: templates/cluster-scoped-rbac-guard.yaml refuses to render when the name would not carry the release namespace. It sits behind the same namespace canary as the naming guard, because system/seaweedfs's own values.yaml ships a placeholder that only extra/seaweedfs overrides — a client-side render must not trip on it. Tests fail on unfixed code: cluster_scoped_names_test asserts two tenants get two names, and unpatched both render seaweedfs-objectstorage-provisioner (5 failed, 0 passed). They test the vendored chart templates directly, which is where the rename happens. The guard includes seaweedfs.compat before reading. extra/seaweedfs sets the OLD flat key global.serviceAccountName, and the shim is what folds it into the canonical global.seaweedfs.serviceAccountName; without it the guard compares against this chart's placeholder default and refuses every real upgrade. Caught by helm upgrade --dry-run=server against the stand, not by unit tests — the mocks never carry the flat key. Verified live with --dry-run=server on the stand: tenant-l (installed pre-4.31, never upgraded) already owns tenant-l-seaweedfs-objectstorage-provisioner and tenant-l-seaweedfs-rw-cr, and the fixed chart renders exactly those — adopted in place, no ownership error. tenant-root renders tenant-root-seaweedfs-* and also dry-runs clean. Leftovers on the release-based names that a 1.5.x tenant passed through are inert once every tenant is upgraded (no release renders them any more) but are not always pruned, since more than one release claimed them; the runbook adds the sweep. Review round 2: - The uniqueness guard used `contains .Release.Namespace $sa`, a substring test that accepts namespace `tenant-a` for service account `tenant-ab-seaweedfs` — a different namespace's. Anchored to hasPrefix "<namespace>-". It still asserts the VALUE the names are built from, not the rendered names, so it cannot catch the chart being reverted to name them after the pinned fullname; cluster_scoped_names_test.yaml covers that axis and is what actually bites. - Added the case that would have caught the compat bug: every test set the CANONICAL global.seaweedfs.serviceAccountName, while production sets the OLD FLAT global.serviceAccountName that only seaweedfs.compat folds in. Deleting the include left the suite passing 5/5 while every real upgrade refused. The new case sets the flat key; with the include removed it now fails with the production error. - Runbook sweep missed the fourth pre-4.31 name: 4.31 renamed the master-rw binding from upstream's username-shaped system:serviceaccount:<sa>:default to <sa>-rw-crb, and the old one carries neither suffix nor a tenant prefix, so the grep could not see it (live on the stand as system:serviceaccount:tenant-l-seaweedfs:default). Also documents the rolling- upgrade window: Helm prunes by name without checking ownership, so the first tenant onto the fixed chart deletes shared objects a not-yet-upgraded tenant is still bound through — COSI provisioning 403s until they reconcile, S3 traffic unaffected. Review round 3: - hasPrefix "<namespace>-" was still too loose. It accepts `tenant-a-b-seaweedfs` for namespace `tenant-a` — which is ALSO the natural value for namespace `tenant-a-b`, so two namespaces would accept one string and collide on the very object this guard exists to keep unique. extra/seaweedfs sets exactly `<namespace>-seaweedfs`, so assert equality with it and nothing looser. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…e path (#3339) ## What this PR does Cozystack v1.5.0 bumped the vendored SeaweedFS chart 4.0.405 → 4.31.0, which renamed every workload after the Helm release (`<name>-system-*`). StatefulSet names are immutable, so upgrades through 1.5.x stood up a second, empty set beside the running one instead of renaming. #3282 pinned `fullnameOverride: seaweedfs` so 1.6 adopts running workloads in place — this PR closes the remaining holes on that adoption path, found by driving a disposable 3-node cluster through a real v1.4.5 → v1.5.3 → main upgrade with SeaweedFS tenants planted in every reachable state. - **The naming guard moves into `packages/system/seaweedfs`** — the render a platform upgrade actually re-renders: the `<name>-system` HelmRelease pulls this chart from a platform-managed ExternalArtifact, so the previous guard in `extra/seaweedfs` was never in the path. Upgrading a fresh-1.5.x tenant therefore created an empty chart-named set beside its live data and flipped the guard's own legacy-data signal. `extra/` keeps a sibling copy for operator visibility; a bats parity suite pins the two detection blocks byte-identical. - **The guard refuses instead of guessing when both naming generations exist.** Nothing durable distinguishes a duplicate that never served from one that served and crashed: claim timestamps invert during the recovery runbook's own re-bind, `readyReplicas: 0` is a snapshot, and Helm birth order answers "which generation is original", not "is the other one empty". Exactly one generation present is decidable, and the render proceeds (or refuses as class S) on its own. - **Cluster-scoped RBAC is named per namespace again.** 4.31 named four cluster-scoped objects after the release — identical for every tenant — so all tenants collided on one ClusterRole/ClusterRoleBinding and only the last-reconciled tenant's COSI provisioner kept its RBAC. Names return to `global.seaweedfs.serviceAccountName`; three of four are byte-identical to pre-4.31 and adopt in place. - **The `seaweedfs-db` hand-over runs for every instance name.** Migration 43 compared the owning release against the literal `seaweedfs-system`, so an instance named `foo` was skipped and its CNPG Cluster — the filer metadata for every object in that tenant's S3 — was pruned on the next reconcile, PVC included. The comparison now matches the `-system` suffix (shared `lib/seaweedfs-db-adopt.sh`), and new migration 53 re-runs the hand-over for clusters already past 43, before anything re-renders. - **`hack/seaweedfs-naming-audit.sh` + `docs/operations/seaweedfs-431-rename-recovery.md`** — what the guard's refusal points operators at: read-only classification (`L` / `S` / `MIXED`, naming the candidate duplicate from relative PV vintage, never a clock window) and the recovery procedures. **Scope.** The supported SeaweedFS deployment is the tenant module, which hardcodes the instance name (`packages/apps/tenant/templates/seaweedfs.yaml`); a tenant only enables or disables it. The runbook and its selectors are scoped to that name; instances created directly against the API under other names are classified by the audit but routed to escalation. Zone/pool keys of ~40 or more characters fall outside the guard's reconstruction — an accepted limit, recorded in `_naming.tpl` and the runbook. **Upgrade impact.** 1.4.x → 1.6: no manual steps — one generation, adopted in place. 1.5.x → 1.6 with SeaweedFS: migration 53 protects every `seaweedfs-db` first; then the upgrade **refuses** for any tenant holding both naming generations until the operator resolves the duplicate. This is deliberate — which generation holds the data is not decidable from inside a render, and guessing wrong destroys it. Release notes should present the refusal as expected behavior. **Testing.** 55 chart unit tests across both packages (including new MultiZone zone-component guard cases — the suite previously had no zone shapes), 11 audit bats, 8 guard-parity bats, migration bats. Validated end-to-end on a disposable 3-node cluster driven v1.4.5 → v1.5.3 → main with five tenants covering: never-saw-4.31 (renders untouched), fresh-1.5.x (refused as class S), wedged duplicate, split duplicate, and a non-default-named instance (its database survives only with this fix). The audit classifies all five correctly, and its two independent signals — revision-1 birth scheme and relative PV vintage — agree on every MIXED tenant. Related: #3282 (fullnameOverride pin), #3335 (etcd adoption backup gate — separate, also required for the 1.5.x→1.6 path). ### Screenshots Not a UI change. ### Downstream repositories - [x] No downstream repository is affected by this change Walked the trigger map against the diff: no package added/renamed under `packages/{apps,extra}/`, the `packages/core/platform/values.yaml` change is only the migrations `targetVersion` bump (no `spec.components.platform.values.*` key changes), no variant/bundle/component changes, no asset renames, no ApplicationDefinition semantic changes. ### Release note ```release-note fix(seaweedfs): the 1.6 upgrade no longer renames a SeaweedFS instance away from its data. The naming guard now runs in the chart a platform upgrade actually re-renders and refuses when both pre- and post-4.31 naming generations exist; hack/seaweedfs-naming-audit.sh and docs/operations/seaweedfs-431-rename-recovery.md guide recovery, and the refusal is expected for tenants that passed through 1.5.x. Cluster-scoped COSI RBAC is named per namespace again (the 4.31 release-based names collided across tenants), and the seaweedfs-db hand-over runs for every instance name — previously an instance not named `seaweedfs` had its filer metadata database pruned on upgrade; new migration 53 repairs clusters that already ran the old hand-over. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added fail-closed upgrade protections for SeaweedFS naming migrations (mixed/damaged classification), including safer behavior when cluster visibility is limited. * Added cluster-scoped RBAC uniqueness safeguards to prevent cross-tenant name collisions during rendering. * Improved SeaweedFS database adoption/repair migrations and strengthened post-delete cleanup ownership checks. * **Bug Fixes** * Hardened SeaweedFS 4.31 rename recovery and PV rebind flow, including reclaim policy preservation and long/non-default instance-name edge cases. * **Documentation** * Expanded the SeaweedFS 4.31 rename-recovery runbook with clarified auditing, verification, and escalation. * **Tests** * Added/expanded integration and Helm rendering tests for the above scenarios and refusal/fail-closed behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Backport of #3339 onto release-1.5, on top of the #3282 adopt-in-place fix (#3326). Three differences from main, all forced by what release-1.5 does not carry: * The repair migration is renumbered 53 -> 45 and stamps 46, since release-1.5 tops out at migration 44 with targetVersion 45. platform values.yaml goes to targetVersion 46. * Migrations 43 and 45 keep release-1.5's inline labelled stamp instead of lib/cozystack-version.sh. That helper is the shared stamp refactor (#2980), whose backport (#3065) was closed, so it does not exist on this branch. Only lib/seaweedfs-db-adopt.sh -- the part that actually carries the fix -- is sourced. * The extra/seaweedfs post-delete cleanup hook and its three tests are dropped. The hook arrived with #3092, a breaking change that was never backported, so there is nothing here for #3339's changes to it to apply to. Comments in lib/seaweedfs-db-adopt.sh and the recovery runbook that promised the hook would reclaim an orphaned volume are corrected to say it does not on 1.5.x. The migration renumbering is carried through the shared helper, the bats suite (STAMP 54 -> 46) and the runbook prose. Verified locally: helm unittest green for both seaweedfs charts (51 + 16 assertions) including both blind-upgrade guard canaries, and 34 bats assertions across migration-seaweedfs-db-adopt, seaweedfs-naming-audit and seaweedfs-guard-parity. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> (cherry picked from commit 88e9fdcbbbff8aeab317b08557ca6bd6c8b5706d)
Backport of #3339 onto release-1.5, on top of the #3282 adopt-in-place fix (#3326). Three differences from main, all forced by what release-1.5 does not carry: * The repair migration is renumbered 53 -> 45 and stamps 46, since release-1.5 tops out at migration 44 with targetVersion 45. platform values.yaml goes to targetVersion 46. * Migrations 43 and 45 keep release-1.5's inline labelled stamp instead of lib/cozystack-version.sh. That helper is the shared stamp refactor (#2980), whose backport (#3065) was closed, so it does not exist on this branch. Only lib/seaweedfs-db-adopt.sh -- the part that actually carries the fix -- is sourced. * The extra/seaweedfs post-delete cleanup hook and its three tests are dropped. The hook arrived with #3092, a breaking change that was never backported, so there is nothing here for #3339's changes to it to apply to. Comments in lib/seaweedfs-db-adopt.sh and the recovery runbook that promised the hook would reclaim an orphaned volume are corrected to say it does not on 1.5.x. The migration renumbering is carried through the shared helper, the bats suite (STAMP 54 -> 46) and the runbook prose. Verified locally: helm unittest green for both seaweedfs charts (51 + 16 assertions) including both blind-upgrade guard canaries, and 34 bats assertions across migration-seaweedfs-db-adopt, seaweedfs-naming-audit and seaweedfs-guard-parity. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> (cherry picked from commit 88e9fdcbbbff8aeab317b08557ca6bd6c8b5706d)
What this PR does
Fixes a v1.5.0 upgrade regression where a tenant ends up with two SeaweedFS workload sets.
Root cause. v1.5.0 bumped the vendored SeaweedFS chart
4.0.405 → 4.31.0. Upstream changed resource naming: before 4.31 the chart named workloads after the chart (seaweedfs-*, ignoring the release name), and 4.31 names them after the release. The data-plane release is<name>-system, so every StatefulSet wanted to becomeseaweedfs-system-*. StatefulSet names are immutable, so Helm could not rename in place — it stood up a second, empty set beside the running one while the data stayed on the originaldata1-seaweedfs-volume-*PVCs. Where the node count let both sets run, they shared oneseaweedfs-s3Service (identical pod labels) and oneseaweedfs-dbfiler metadata database, so the empty set answered reads and the two master sets mixed volume IDs from independent sequences into one metadata table.Fix. Pin
fullnameOverrideinsystem/seaweedfsvalues so the rendered names stop following the release name, which is the convention already used bycozy-proxy,flux-operator,grafana-operator,linstor-schedulerandvictoria-metrics-operator. The names go back to what every pre-1.5 cluster already runs, so the upgrade adopts the running workloads and their volumes in place. No data migration, no platform migration.The one case that cannot be adopted. A tenant installed fresh on 1.5.x wrote its data under the renamed workloads, so its volumes are not the chart-named ones. Pinning the chart name there would rename the workloads away from that data and bring up an empty cluster beside it. Helm cannot move data between PVCs, so
extra/seaweedfsrefuses to render for such a tenant and points at the recovery runbook, which re-binds the existing PVs onto the adopted PVC names (no copying — the claim is renamed, the volume is not touched). The guard is fail-closed: it fires only when renamed volumes are present and no adopted ones are.Two naming subtleties the guard has to respect, both verified by rendering the shipped 1.5 chart:
SeaweedFSis a user-creatable kind) also stored its data ondata1-seaweedfs-volume-*. Such a tenant is adopted, not blocked.archivetherefore wrote todata1-archive-system-seaweedfs-volume-*, and a long name loses the chart name from its tail entirely. The guard matches volumes by shape and by the chart labels on the StatefulSet instead of reconstructing those names, so both are caught.On the duplicate set. Once the fix reconciles, the
seaweedfs-system-*StatefulSets and Deployments are no longer part of the release and Helm removes them on its own. What it cannot remove — PVCs it did not template, and cert-manager Secrets that carry no owner reference — is inert, and the runbook clears it explicitly. A pre-upgrade migration was considered and dropped: it would duplicate what Helm already does, while its only unique action (deleting PVCs) cannot distinguish "the duplicate never served" from "it served and is currently down", and any transient delete failure underset -ewould fail the platform's pre-upgrade hook and block delivery of this very fix.Testing
helm unittest—system/seaweedfs21 tests,extra/seaweedfs17 tests. The guard'slookupbranches are covered by mocking the Kubernetes provider (the pattern already used incore/platformandsystem/cozystack-api): adopted-only, renamed-only, both sets, zoned volumes, a non-default instance name, a truncated long name, and an unrelateddata1-*-volume-*PVC that must not trip the guard. The s3-consumer suite now re-creates the name/fullname divergence explicitly, so it keeps guarding that regression regardless of the shipped default.The remaining regression test for this class of bug is an upgrade E2E (seed a pre-4.31 named set, upgrade, assert a single set with data intact) — no fresh-install E2E or
helm templatecan reproduce an upgrade over pre-existing state. That belongs in the chainsaw upgrade lane and is a follow-up.Screenshots
N/A — no UI changes.
Release note
Summary by CodeRabbit