Skip to content

fix(seaweedfs): adopt legacy workloads in place across the 4.31 rename - #3282

Merged
myasnikovdaniil merged 4 commits into
mainfrom
fix/seaweedfs-fullname-adopt
Jul 16, 2026
Merged

fix(seaweedfs): adopt legacy workloads in place across the 4.31 rename#3282
myasnikovdaniil merged 4 commits into
mainfrom
fix/seaweedfs-fullname-adopt

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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 become seaweedfs-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 original data1-seaweedfs-volume-* PVCs. Where the node count let both sets run, they shared one seaweedfs-s3 Service (identical pod labels) and one seaweedfs-db filer 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 fullnameOverride in system/seaweedfs values so the rendered names stop following the release name, which is the convention already used by cozy-proxy, flux-operator, grafana-operator, linstor-scheduler and victoria-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/seaweedfs refuses 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:

  • Before 4.31 the chart ignored the release name, so an instance running under a non-default name (SeaweedFS is a user-creatable kind) also stored its data on data1-seaweedfs-volume-*. Such a tenant is adopted, not blocked.
  • On 4.31 the fullname helper appends the chart name when the release name does not contain it, and the component helper truncates to 56 characters. A fresh 1.5.x instance named archive therefore wrote to data1-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 under set -e would fail the platform's pre-upgrade hook and block delivery of this very fix.

Testing

helm unittestsystem/seaweedfs 21 tests, extra/seaweedfs 17 tests. The guard's lookup branches are covered by mocking the Kubernetes provider (the pattern already used in core/platform and system/cozystack-api): adopted-only, renamed-only, both sets, zoned volumes, a non-default instance name, a truncated long name, and an unrelated data1-*-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 template can 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

fix(seaweedfs): adopt existing SeaweedFS workloads and volumes in place when upgrading past the 4.31 chart rename, instead of standing up a second, empty cluster beside them

Summary by CodeRabbit

  • New Features
    • Added an audit & recovery runbook for SeaweedFS 4.31 rename impacts.
  • Bug Fixes
    • Stabilized SeaweedFS workload/PVC naming across upgrades, including non-default instance names.
    • Added preflight safeguards to prevent upgrade scenarios that could split or wedge existing data.
    • Updated post-delete cleanup to remove secrets from both legacy and current naming schemes.
  • Tests
    • Expanded Helm unit coverage for naming guards, cleanup behavior, and S3 service name consumers.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Workload Adoption Logic: Implemented a conditional check using the Helm lookup function to detect legacy SeaweedFS StatefulSets, ensuring smooth upgrades by pinning fullnameOverride when necessary.
  • Upgrade Regression Fix: Prevented the creation of duplicate, conflicting SeaweedFS workload sets during the upgrade to chart version 4.31 by maintaining legacy naming conventions for existing clusters.
  • Test Coverage: Added comprehensive unit tests to verify that fullnameOverride correctly toggles naming behavior, ensuring native naming for fresh installs and legacy compatibility for existing ones.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files labels Jul 14, 2026
@dosubot dosubot Bot added the backport Should change be backported on previous release label Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

SeaweedFS 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.

Changes

SeaweedFS naming compatibility

Layer / File(s) Summary
Chart-based workload naming
packages/system/seaweedfs/values.yaml, packages/system/seaweedfs/tests/*
The system chart fixes the seaweedfs fullname and tests stable workload, PVC, selector, and S3 consumer names.
Data-preserving upgrade guard
packages/extra/seaweedfs/templates/seaweedfs.yaml, packages/extra/seaweedfs/tests/fullname_override*.yaml
The extra chart detects legacy and system PVC/StatefulSet naming through lookup, rejects unsafe states, and tests adoption, duplicate, zoned, truncated, and unrelated-resource cases.
Dual naming cleanup coverage
packages/extra/seaweedfs/templates/hooks/cleanup.yaml, packages/extra/seaweedfs/tests/cleanup*.yaml
Cleanup RBAC and secret deletion commands cover both release-based and chart-based secret names.
Rename audit and recovery procedures
docs/operations/seaweedfs-431-rename-recovery.md
The runbook documents tenant classification, PV/PVC rebinding, split-cluster recovery, leftover cleanup, reconciliation, and verification.

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
Loading

Possibly related PRs

Suggested labels: kind/regression, area/extra

Suggested reviewers: lexfrei, androndo, sircthulhu, lllamnyp, ivanhunters

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing the SeaweedFS 4.31 rename by adopting legacy workloads in place.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/seaweedfs-fullname-adopt

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +114 to +117
{{- $seaweedfsFullnameOverride := "" }}
{{- if lookup "apps/v1" "StatefulSet" .Release.Namespace "seaweedfs-master" }}
{{- $seaweedfsFullnameOverride = "seaweedfs" }}
{{- end }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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 }}

@myasnikovdaniil
myasnikovdaniil force-pushed the fix/seaweedfs-fullname-adopt branch from 1072d60 to a5c8244 Compare July 14, 2026 09:38
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/L This PR changes 100-499 lines, ignoring generated files labels Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e7f85ae and 480e4fc.

📒 Files selected for processing (9)
  • docs/operations/seaweedfs-431-rename-recovery.md
  • packages/extra/seaweedfs/templates/hooks/cleanup.yaml
  • packages/extra/seaweedfs/templates/seaweedfs.yaml
  • packages/extra/seaweedfs/tests/cleanup_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_test.yaml
  • packages/system/seaweedfs/tests/fullname_override_test.yaml
  • packages/system/seaweedfs/tests/s3_service_name_consumers_test.yaml
  • packages/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

Comment thread docs/operations/seaweedfs-431-rename-recovery.md Outdated
Comment thread docs/operations/seaweedfs-431-rename-recovery.md Outdated
Comment on lines +177 to +180
# 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).
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@lexfrei

Copy link
Copy Markdown
Contributor

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 Co-authored-by.

The main change is dropping the adaptive lookup in favour of pinning fullnameOverride in system/seaweedfs values, which is what the other system packages already do (cozy-proxy, flux-operator, linstor-scheduler, ...). Two things pushed me there. First, before 4.31 the chart ignored the release name entirely, so an instance running under any name kept its data on the chart-named data1-seaweedfs-volume-* — the adaptive rule would have blocked those tenants instead of adopting them. Second, on 4.31 the fullname helper appends the chart name when the release name does not contain it (a fresh archive instance wrote data1-archive-system-seaweedfs-volume-*) and then truncates to 56 chars, so any guard that reconstructs those names misses tenants. The guard now matches volumes by shape plus the chart labels on the StatefulSet, and is fail-closed: a tenant installed fresh on 1.5.x cannot be adopted, so the render stops and points at the PV re-bind runbook rather than silently bringing up an empty cluster next to its data.

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 readyReplicas == 0, which cannot tell "the duplicate never served" from "it served and is currently down". On top of that, the deletes run under set -e without || true, so any transient API error would fail the platform's pre-upgrade hook and block delivery of the fix itself. The leftovers are inert, so the runbook clears them explicitly instead.

Your lookup-is-untestable note turned out not to hold, by the way: helm-unittest can mock it via kubernetesProvider (already used in core/platform and system/cozystack-api), so all the guard branches are covered now.

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-'.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 \

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/system/seaweedfs/values.yaml Outdated
# `<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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/extra/seaweedfs/templates/seaweedfs.yaml (1)

138-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove 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 lookup results, as FluxCD renders against a live cluster where these structures are reliably returned. Furthermore, the dig function natively handles nil dictionaries 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

📥 Commits

Reviewing files that changed from the base of the PR and between 480e4fc and 5dc363d.

📒 Files selected for processing (10)
  • docs/operations/seaweedfs-431-rename-recovery.md
  • packages/extra/seaweedfs/templates/hooks/cleanup.yaml
  • packages/extra/seaweedfs/templates/seaweedfs.yaml
  • packages/extra/seaweedfs/tests/cleanup_named_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_test.yaml
  • packages/system/seaweedfs/tests/fullname_override_test.yaml
  • packages/system/seaweedfs/tests/s3_service_name_consumers_test.yaml
  • packages/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

@lexfrei

Copy link
Copy Markdown
Contributor

Thanks for the pass — addressed all five inline, force-pushed:

  1. D-split — added the branch you proposed. The guard reads readyReplicas on the renamed volume StatefulSet and fails on legacyData AND systemData AND systemRunning → Step 3. D-wedged (zero ready) still adopts. Strictly block-only, never deletes.
  2. Step 2 filtergrep -v '^seaweedfs-' was dropping the default instance's own renamed set; replaced with grep -vE '^seaweedfs-(master|filer|volume)($|-)'.
  3. Step 3/4 — select the renamed set by that same filter, derive secret names by suffix; no hardcoded default name, pools/zones covered.
  4. Cleanup hook — derives seaweedfs.fullname of the child release, so a non-default instance's <name>-system-seaweedfs-* secrets are listed too; unit test on archive.
  5. values.yaml comment — corrected (leftover from a release-scoped draft).

On the heads-up: agreed the adopt-in-place depends on the legacy 4.05 StatefulSet's immutable spec.selector / serviceName / volumeClaimTemplates matching the 4.31-with-pin render, and a mismatch wedges the HR (retries forever) rather than losing data. The suite pins only the vCT name today. I can add selector / serviceName / vCT-label assertions to the adopt test now — say the word — but your live 4.05→4.31 upgrade is the real proof before the release-1.5 backport.

@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

I will try few upgrade paths today to verify it is working correctly, thank you.

@myasnikovdaniil
myasnikovdaniil merged commit 4a00b50 into main Jul 16, 2026
17 of 21 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the fix/seaweedfs-fullname-adopt branch July 16, 2026 17:04
@github-actions

Copy link
Copy Markdown

Created backport PR for release-1.5:

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

myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
…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>
myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
… 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>
myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
…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 -->
myasnikovdaniil added a commit that referenced this pull request Jul 31, 2026
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)
myasnikovdaniil added a commit that referenced this pull request Jul 31, 2026
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) backport Should change be backported on previous release kind/bug Categorizes issue or PR as related to a bug size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants