feat(kubernetes): propagate remote-accessible LINSTOR StorageClasses to tenant clusters - #2872
Conversation
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughThis PR updates StorageClass propagation for tenant clusters, renders tenant StorageClasses from a map, and adds matching unit and e2e validation. It also refreshes related API, schema, and README wording. ChangesStorageClass Auto-Propagation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
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 introduces automatic propagation of remote-accessible LINSTOR StorageClasses from the management cluster to tenant Kubernetes clusters. By dynamically discovering these classes, it enables users to utilize multiple storage tiers within tenant environments. The implementation ensures backward compatibility by maintaining the legacy 'kubevirt' alias and provides robust fallback mechanisms for dry-run scenarios, while explicitly excluding node-local classes to prevent migration issues. 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
|
There was a problem hiding this comment.
Code Review
This pull request implements StorageClass propagation from the infrastructure cluster to the tenant cluster, ensuring remote-accessible LINSTOR classes are auto-propagated under the same name while filtering out node-local classes and retaining the legacy 'kubevirt' alias. The changes include updates to Helm templates, schemas, documentation, and new unit and E2E tests. The feedback suggests quoting templated string values in the Helm templates to prevent potential YAML parsing issues.
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.
| kind: StorageClass | ||
| metadata: | ||
| name: kubevirt | ||
| name: {{ $name }} |
| allowVolumeExpansion: true | ||
| parameters: | ||
| infraStorageClassName: {{ .Values.storageClass }} | ||
| infraStorageClassName: {{ $m.infraStorageClass }} |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@packages/apps/kubernetes/templates/helmreleases/csi.yaml`:
- Line 37: The condition is using the wrong parameter key; update the Helm
template conditional that references $params to check "allowRemoteVolumeAccess"
(without the "linstor.csi.linbit.com/" prefix) instead of
"linstor.csi.linbit.com/allowRemoteVolumeAccess". Specifically, modify the
expression that currently reads (index $params
"linstor.csi.linbit.com/allowRemoteVolumeAccess" | default "true") to use (index
$params "allowRemoteVolumeAccess" | default "true") so the overall if-statement
with $sc.provisioner and the comparison to "false" reflects LINSTOR CSI’s actual
StorageClass parameter name.
🪄 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: 24fcad57-0972-4254-973e-b58e67942905
📒 Files selected for processing (12)
api/apps/v1alpha1/kubernetes/types.gohack/e2e-apps/run-kubernetes.shpackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/helmreleases/csi.yamlpackages/apps/kubernetes/tests/csi_test.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/system/kubernetes-rd/cozyrds/kubernetes.yamlpackages/system/kubevirt-csi-node/Makefilepackages/system/kubevirt-csi-node/templates/deploy.yamlpackages/system/kubevirt-csi-node/tests/storageclasses_test.yamlpackages/system/kubevirt-csi-node/values.yaml
| {{- $out := dict }} | ||
| {{- range $sc := (lookup "storage.k8s.io/v1" "StorageClass" "" "").items }} | ||
| {{- $params := $sc.parameters | default dict }} | ||
| {{- if and (eq $sc.provisioner "linstor.csi.linbit.com") (ne (index $params "linstor.csi.linbit.com/allowRemoteVolumeAccess" | default "true") "false") }} |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the correct StorageClass parameter name in LINSTOR CSI driver to control remote volume access? Check LINSTOR CSI driver documentation for allowRemoteVolumeAccess parameter.
💡 Result:
The correct StorageClass parameter name to control remote volume access in the LINSTOR CSI driver is allowRemoteVolumeAccess [1][2]. You can specify this parameter in your StorageClass definition to control whether volumes can be attached to nodes where they do not have a physical replica (i.e., diskless access) [1][2]. Example usage: apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: linstor-remote-access-example provisioner: linstor.csi.linbit.com parameters: allowRemoteVolumeAccess: "false"... This parameter supports boolean values (e.g., "true" or "false") [1][2]. Additionally, it can accept more complex configuration values, such as YAML-formatted strings, to define fine-grained policies based on Kubernetes topology labels [1][3][4]. For instance, you can restrict remote access to specific zones or nodes [3][4]: allowRemoteVolumeAccess: | - fromSame: - topology.kubernetes.io/zone If not explicitly defined, this parameter defaults to "true" [1]. Note that all StorageClass parameters for the LINSTOR CSI driver are case-insensitive [1].
Citations:
- 1: https://github.com/piraeusdatastore/linstor-csi/blob/master/CHANGELOG.md
- 2: https://linbit.com/blog/performance-tuning-for-linstor-persistent-storage-in-kubernetes/
- 3: https://context7.com/piraeusdatastore/linstor-csi/llms.txt
- 4: Anti-affinity and allowRemoteVolumeAccess broken at some point piraeusdatastore/piraeus-operator#680
Fix LINSTOR remote-access StorageClass parameter key.
In packages/apps/kubernetes/templates/helmreleases/csi.yaml (line 37), the template checks $params["linstor.csi.linbit.com/allowRemoteVolumeAccess"], but LINSTOR CSI’s StorageClass parameter is allowRemoteVolumeAccess (defaults to "true" when unset). Adjust the lookup to use allowRemoteVolumeAccess without the linstor.csi.linbit.com/ prefix so the condition reflects the intended remote/diskless access behavior.
🤖 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/apps/kubernetes/templates/helmreleases/csi.yaml` at line 37, The
condition is using the wrong parameter key; update the Helm template conditional
that references $params to check "allowRemoteVolumeAccess" (without the
"linstor.csi.linbit.com/" prefix) instead of
"linstor.csi.linbit.com/allowRemoteVolumeAccess". Specifically, modify the
expression that currently reads (index $params
"linstor.csi.linbit.com/allowRemoteVolumeAccess" | default "true") to use (index
$params "allowRemoteVolumeAccess" | default "true") so the overall if-statement
with $sc.provisioner and the comparison to "false" reflects LINSTOR CSI’s actual
StorageClass parameter name.
bd65bc4 to
61c3776
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@hack/e2e-apps/run-kubernetes.sh`:
- Line 209: Remove the EXIT trap that invokes the _tenant_snapshot_on_fail
function (at line 209 and also at lines 643-644) as this violates the
repository's e2e shell scripting conventions which prohibit EXIT and RETURN
traps. Instead of relying on the trap for teardown/snapshot logic, explicitly
invoke the _tenant_snapshot_on_fail function at the appropriate failure handling
points in the script's control flow where snapshot capture is actually needed,
making the failure-path invocation explicit rather than trap-based.
🪄 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: 1e4fc28b-f2e7-4b1b-a413-1ce33f8333b0
📒 Files selected for processing (12)
api/apps/v1alpha1/kubernetes/types.gohack/e2e-apps/run-kubernetes.shpackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/helmreleases/csi.yamlpackages/apps/kubernetes/tests/csi_test.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/system/kubernetes-rd/cozyrds/kubernetes.yamlpackages/system/kubevirt-csi-node/Makefilepackages/system/kubevirt-csi-node/templates/deploy.yamlpackages/system/kubevirt-csi-node/tests/storageclasses_test.yamlpackages/system/kubevirt-csi-node/values.yaml
✅ Files skipped from review due to trivial changes (5)
- packages/apps/kubernetes/values.yaml
- api/apps/v1alpha1/kubernetes/types.go
- packages/apps/kubernetes/values.schema.json
- packages/system/kubernetes-rd/cozyrds/kubernetes.yaml
- packages/apps/kubernetes/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/system/kubevirt-csi-node/tests/storageclasses_test.yaml
- packages/apps/kubernetes/tests/csi_test.yaml
- packages/apps/kubernetes/templates/helmreleases/csi.yaml
- packages/system/kubevirt-csi-node/values.yaml
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🤖 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 `@hack/e2e-apps/run-kubernetes.sh`:
- Line 209: Remove the EXIT trap that invokes the _tenant_snapshot_on_fail
function (at line 209 and also at lines 643-644) as this violates the
repository's e2e shell scripting conventions which prohibit EXIT and RETURN
traps. Instead of relying on the trap for teardown/snapshot logic, explicitly
invoke the _tenant_snapshot_on_fail function at the appropriate failure handling
points in the script's control flow where snapshot capture is actually needed,
making the failure-path invocation explicit rather than trap-based.
🪄 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: 1e4fc28b-f2e7-4b1b-a413-1ce33f8333b0
📒 Files selected for processing (12)
api/apps/v1alpha1/kubernetes/types.gohack/e2e-apps/run-kubernetes.shpackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/helmreleases/csi.yamlpackages/apps/kubernetes/tests/csi_test.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/system/kubernetes-rd/cozyrds/kubernetes.yamlpackages/system/kubevirt-csi-node/Makefilepackages/system/kubevirt-csi-node/templates/deploy.yamlpackages/system/kubevirt-csi-node/tests/storageclasses_test.yamlpackages/system/kubevirt-csi-node/values.yaml
✅ Files skipped from review due to trivial changes (5)
- packages/apps/kubernetes/values.yaml
- api/apps/v1alpha1/kubernetes/types.go
- packages/apps/kubernetes/values.schema.json
- packages/system/kubernetes-rd/cozyrds/kubernetes.yaml
- packages/apps/kubernetes/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/system/kubevirt-csi-node/tests/storageclasses_test.yaml
- packages/apps/kubernetes/tests/csi_test.yaml
- packages/apps/kubernetes/templates/helmreleases/csi.yaml
- packages/system/kubevirt-csi-node/values.yaml
🛑 Comments failed to post (1)
hack/e2e-apps/run-kubernetes.sh (1)
209-209: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Remove EXIT-trap usage in e2e script flow.
This introduces an
EXITtrap, which conflicts with the repository’s e2e shell conventions and can create brittle control flow in failure handling. Prefer explicit failure-path invocation instead of trap-based teardown/snapshot logic.As per coding guidelines,
**/*.{bats,sh}changes must use noEXIT/RETURNtraps.Also applies to: 643-644
🤖 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 `@hack/e2e-apps/run-kubernetes.sh` at line 209, Remove the EXIT trap that invokes the _tenant_snapshot_on_fail function (at line 209 and also at lines 643-644) as this violates the repository's e2e shell scripting conventions which prohibit EXIT and RETURN traps. Instead of relying on the trap for teardown/snapshot logic, explicitly invoke the _tenant_snapshot_on_fail function at the appropriate failure handling points in the script's control flow where snapshot capture is actually needed, making the failure-path invocation explicit rather than trap-based.Source: Coding guidelines
|
LGTM ! |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM (REQUEST_CHANGES)
Business context
Tenant Kubernetes clusters previously got a single hardcoded kubevirt StorageClass. This auto-discovers the management cluster's remote-accessible LINSTOR classes via Helm lookup and propagates each into the tenant under the same name, keeping the legacy kubevirt class as a non-default alias.
I verified the design on the points that matter most. Tenant isolation is not widened: the kubevirt-csi-driver defaults to AllowAll enforcement, so a tenant could already reference any infra StorageClass; this change only auto-creates the convenience classes. Evidence: packages/apps/kubernetes/images/kubevirt-csi-driver/main.go:190-192 (empty enforcement string -> AllowAll: true). The filter parameter key linstor.csi.linbit.com/allowRemoteVolumeAccess is correct for this project: it matches the parameter form the LINSTOR classes actually use here, so a bare-key concern does not apply. Evidence: hack/e2e-post-install-prep.sh:82,95. Both helm-unittest suites (132 + 3) and the new e2e assertions render and pass locally.
One blocker remains.
Blockers
B1 - Fallback default can select the kubevirt alias pointing at a non-existent infra class
File: packages/apps/kubernetes/templates/helmreleases/csi.yaml:47-54
Evidence: The kubevirt alias (infraStorageClass: $defaultName, line 48) is inserted into $out before the default is chosen. When neither the configured storageClass nor replicated is among the propagated classes, the default is picked by keys $out | sortAlpha | first (line 54), and that candidate set still contains kubevirt. If every propagated class name sorts after kubevirt (for example nvme, ssd, standard), kubevirt becomes the tenant default with infraStorageClassName: <defaultName> (for example replicated), which by assumption does not exist on the management cluster.
Impact: The tenant's default StorageClass then provisions against a non-existent infra class - default PVCs stay Pending with no error surfaced, while the real working classes are left non-default. This is the feature's own target configuration (operators running multiple custom tiers without a replicated class). The standard path (a remote replicated class present) is unaffected and is covered by e2e, so this is not a regression - but the new fallback has the information to avoid the broken default and does not.
Fix: Choose the default among the propagated classes before inserting the kubevirt alias, or exclude kubevirt from the sortAlpha candidate set.
Non-blocking
-
Lookup result is invisible to the Flux release digest. Evidence:
packages/apps/kubernetes/templates/helmreleases/csi.yaml:36. The propagated set is computed from live cluster state vialookup, but helm-controller re-renders on a digest of chart plus values that does not includelookupoutput. StorageClasses added to or removed from the management cluster after a tenant exists will not propagate or be pruned on a normal reconcile - only when thekubernetesHelmRelease inputs otherwise change. This limits the auto-discover behavior to create/update time. Consider routing the discovered set throughvaluesFrom(a controller-maintained ConfigMap) so changes enter the digest, or documenting the limitation. -
allowRemoteVolumeAccessabsent is treated as remote. Evidence:packages/apps/kubernetes/templates/helmreleases/csi.yaml:38(| default "true"). A LINSTOR class whose layer list has no network-replication layer (for examplelayerList: storagewithout DRBD) is effectively node-local even with the parameter unset, so it would be propagated as if remote and its volumes could not follow a live-migrated VM. The project's own node-local class setsallowRemoteVolumeAccess: "false"explicitly, so in-convention setups are safe; operator-authored classes that omit the flag are the gap. Consider also gating on the layer list, or documenting the requirement. -
Filter logic has no unit coverage. Evidence:
packages/apps/kubernetes/tests/csi_test.yaml(header comment). helm-unittest has no live cluster, so the provisioner andallowRemoteVolumeAccessfilter is exercised only by e2e; the unit tests cover the fallback map only. This is acknowledged in the test, and is acceptable, but the core selection has no fast-feedback coverage. -
Quote templated scalars. Evidence:
packages/system/kubevirt-csi-node/templates/deploy.yaml:274,282.name: {{ $name }}andinfraStorageClassName: {{ $m.infraStorageClass }}render unquoted. StorageClass names are DNS-1123 so the risk is low, but| quoteis the safer default. -
Upgrade caveat lives only in the PR description. Evidence: PR body "Upgrade note". The instruction that a manually-created tenant StorageClass named
replicatedmust be deleted before upgrade (to avoid a Helm ownership conflict) is operator-facing and is not in any persistent doc; I found no sibling website docs PR. Worth a durable note.
| {{- $chosen := "" }} | ||
| {{- if hasKey $out $defaultName }}{{ $chosen = $defaultName }} | ||
| {{- else if hasKey $out "replicated" }}{{ $chosen = "replicated" }} | ||
| {{- else }}{{ $chosen = (keys $out | sortAlpha | first) }}{{ end }} |
There was a problem hiding this comment.
B1 (blocker): the sortAlpha | first fallback runs after the kubevirt alias was inserted (line 48), so kubevirt is a default candidate. If every propagated class name sorts after kubevirt (e.g. nvme, ssd), kubevirt becomes the tenant default with infraStorageClassName: <defaultName> pointing at an infra class that is not present -> default PVCs stay Pending silently. Pick the default among the propagated classes before adding the alias, or exclude kubevirt from the candidate set.
| {{- $out := dict }} | ||
| {{- range $sc := (lookup "storage.k8s.io/v1" "StorageClass" "" "").items }} | ||
| {{- $params := $sc.parameters | default dict }} | ||
| {{- if and (eq $sc.provisioner "linstor.csi.linbit.com") (ne (index $params "linstor.csi.linbit.com/allowRemoteVolumeAccess" | default "true") "false") }} |
There was a problem hiding this comment.
Non-blocking: | default "true" treats an absent allowRemoteVolumeAccess as remote-accessible. A class with no network-replication layer (e.g. layerList: storage without DRBD) is effectively node-local even when the flag is unset, and would be propagated as if remote. In-convention classes set the flag explicitly, so this only affects operator-authored classes that omit it.
| {{- /* Propagate remote-accessible LINSTOR infra StorageClasses to the tenant under the same name. */}} | ||
| {{- $defaultName := .Values.storageClass | default "replicated" }} | ||
| {{- $out := dict }} | ||
| {{- range $sc := (lookup "storage.k8s.io/v1" "StorageClass" "" "").items }} |
There was a problem hiding this comment.
Non-blocking: lookup output is not part of the Flux release digest, so helm-controller will not re-render when the management cluster's StorageClass set changes. New classes will not propagate (and removed ones will not prune) on a normal reconcile - only when the HelmRelease inputs otherwise change. Consider valuesFrom for the discovered set, or document the create/update-time limitation.
| kind: StorageClass | ||
| metadata: | ||
| name: kubevirt | ||
| name: {{ $name }} |
There was a problem hiding this comment.
Nit: quote templated scalars - name: {{ $name | quote }} here and infraStorageClassName: {{ $m.infraStorageClass | quote }} on line 282. DNS-1123 names make this low-risk, but quoting is the safer default.
|
Aleksei Sviridkin (@lexfrei) addressed in 59d22f7:
All helm-unittest suites pass (132 kubernetes + 3 kubevirt-csi-node); On the two CodeRabbit bot findings: the prefixed |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM (REQUEST_CHANGES) — re-review on the current head. The B1 fix is correct, but the behavior it fixes has no automated regression coverage, and that path is the feature's own target configuration.
What's resolved since the previous review
- B1 fixed (verified by inspection). Default selection now runs before the
kubevirtalias is inserted into$out, sokeys $out | sortAlpha | firstchooses among the propagated classes only — thekubevirtalias can no longer be auto-selected as the tenant default.csi.yamlrule order confirms it. - Non-blocking #4 addressed:
nameandinfraStorageClassNameare now| quoted. - Non-blocking #5 addressed: the upgrade caveat now lives in
packages/apps/kubernetes/README.md(durable doc), not just the PR body. - Non-blocking #1 and #2 are now documented in that same README note (render-time
lookupsemantics; absentallowRemoteVolumeAccesstreated as remote).
Blocker
B1-test: the fallback-default fix ships with no test pinning it
The fix is correct, but nothing guards it, and the branch it lives in — propagated classes present, none named the configured storageClass, none named replicated — is the exact multi-tier config this feature targets, with zero automated coverage today:
- helm-unittest can't reach it. With no live cluster,
lookupreturns empty, so$outis empty and every unit test falls into thereplicatedfallback. The two existing unit tests only exercise that fallback map — not the propagated-classes branch where B1 lived. - e2e doesn't cover it either. The e2e provisions a setup where a remote
replicatedclass is present, so the no-replicatedmulti-tier branch is never exercised.
So the regression that was just fixed can silently return. Since the logic is lookup-gated and not reachable by helm-unittest, the regression belongs in e2e: add a case that provisions >=2 remote LINSTOR classes with none named replicated (ideally names sorting after kubevirt, e.g. nvme/ssd), then asserts:
- exactly one tenant default StorageClass,
- the default is one of the propagated classes — never the
kubevirtalias, - the
kubevirtalias exists withdefault: false.
That locks the fix and documents the intended behavior for the multi-tier configuration. Everything else is ready.
…to tenant clusters Tenant Kubernetes clusters previously exposed a single hardcoded "kubevirt" StorageClass mapped to one infra class. Discover all remote-accessible LINSTOR infra StorageClasses (provisioner linstor.csi.linbit.com, allowRemoteVolumeAccess != false) via Helm lookup and propagate each to the tenant under the same name. - csi.yaml builds a storageClasses map filtered to remote LINSTOR classes; node-local classes (e.g. "local") are excluded because the backing volume cannot follow a live-migrated worker VM. - The class named by .Values.storageClass (default "replicated") is marked the tenant default; the legacy "kubevirt" StorageClass is kept as a non-default alias so existing tenant PVCs keep binding. - A non-destructive fallback preserves the legacy mapping when the lookup returns empty (dry-run), so tenant StorageClasses are never wiped on a transient reconcile. - infraStorageClassEnforcement is intentionally left disabled (AllowAll) to avoid breaking tenants that already self-served custom StorageClasses under the current default. Refs: #2094, #1424 Assisted-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
- csi_test.yaml: assert the fallback storageClasses map yields a default "replicated" plus the non-default "kubevirt" alias, and that the alias tracks the storageClass selector. - kubevirt-csi-node: add a Makefile test target (otherwise the package is silently skipped by the unit-test runner) and storageclasses_test covering per-entry rendering, the single-default invariant, and the absence of any enforcement allowList in driver-config. - run-kubernetes.sh: verify in e2e that "replicated" propagates as the default kubevirt-CSI class, the "kubevirt" alias is retained, and the node-local "local" infra class is filtered out. Refs: #2094 Assisted-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…lt SC The legacy "kubevirt" StorageClass alias was inserted into the candidate map before the default was chosen, so the sortAlpha fallback could pick it when propagated class names sort after "kubevirt" (e.g. nvme, ssd) and no configured/replicated class is present. That made the tenant default point at an infraStorageClass that may not exist on the infra cluster, leaving default PVCs Pending. Choose the default from the propagated set before adding the alias. Also quote the templated StorageClass name and infraStorageClassName in kubevirt-csi-node, and document the propagation upgrade caveat, render-time evaluation limitation, and allowRemoteVolumeAccess assumption in the kubernetes README. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…B1) Pin the PR #2872 B1 fix: when the management cluster exposes only remote LINSTOR StorageClasses whose names sort after "kubevirt" and none is named the configured storageClass (default "replicated"), the tenant default must be chosen among the propagated classes and never the legacy "kubevirt" alias. helm-unittest cannot reach this branch (no live cluster -> lookup returns empty -> the storageClasses map collapses to the "replicated" fallback), so it is exercised in e2e via a single server-side dry-run render of the kubernetes chart against the live management cluster: two remote LINSTOR classes are added and "replicated" is removed for the one render, then the rendered -csi HelmRelease storageClasses map is asserted to carry exactly one default among the propagated classes, never the "kubevirt" alias. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
59d22f7 to
da084ef
Compare
|
Aleksei Sviridkin (@lexfrei) B1-test addressed in Added an e2e regression ( It provisions two remote LINSTOR classes sorting after Rebased onto |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the test-completeness blocker is resolved.
The B1 fallback-default fix is intact at the current head: in csi.yaml, $chosen is picked before the kubevirt alias is inserted into $out, so keys $out | sortAlpha | first operates on a candidate set that never contains kubevirt — the alias can no longer become the tenant default.
And there is now a real regression test for the exact path I flagged. The new e2e case (hack/e2e-apps/kubernetes-latest.bats + verify_storageclass_fallback_default in run-kubernetes.sh) provisions two remote LINSTOR classes whose names sort after kubevirt, removes replicated for a single server-side dry-run render (so neither the configured class nor replicated is in the propagated set, forcing the sortAlpha | first branch), and asserts exactly one default, that it is nvme/ssd and never the kubevirt alias, and that the alias is present but non-default. It correctly exercises the lookup-gated branch that helm-unittest cannot reach — helm install --dry-run=server runs Helm lookup against the live cluster — and restores the management-cluster StorageClasses inline before any assertion can exit, per the e2e teardown rules. That is exactly the multi-tier, no-replicated coverage the fix needed.
Code-level checks (Build, Analyze (go), CodeQL, generated-code, pre-commit, DCO) are green; E2E is the separate merge gate and will exercise this new case. Approving.
…ites - qdrant: add the post-delete PVC-reclaim guard (#3059) — delete the CR and poll the ordinal-anchored data PVC to absence via the error op (retries transient API errors internally), with a PVC/jobs/pods dump on failure; Chainsaw auto-cleanup alone would never notice a leaked StatefulSet-templated PVC - kuberture: make the split-horizon negative log assertions fail loud — under set -e a pipeline beginning with ! suppresses errexit, so both leak checks silently passed (the same vacuous-negation bug main fixed in 12fe325) - foundationdb: retry the one-shot generations.reconciled read for up to 2m — the operator stamps it after the post-bounce uptime gate, so it can trail the fdbcli-derived health fields the previous assert converged on (a79f800) - kubernetes-latest: add the version-independent SC-fallback-default Test (#2872 B1) calling verify_storageclass_fallback_default from the shared lib; a separate Test doc so it runs regardless of the heavy bringup Test's outcome, safe under the suite-wide parallel: 1 while it temporarily mutates cluster-scoped StorageClasses Assisted-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…2826) ## What this PR does Migrates the E2E **app** test suite from BATS to [Kyverno Chainsaw](https://github.com/kyverno/chainsaw) — a declarative, Kubernetes-native E2E framework (CNCF, part of the Kyverno project; successor to KUTTL) — and wires it into CI as the replacement for the per-app BATS loop. This grows the original pilot (postgres + bucket) into the full suite. All 22 app suites are ported under `hack/e2e-chainsaw/` and the `hack/e2e-apps/*.bats` files are removed: `postgres`, `bucket`, `mariadb`, `mongodb`, `redis`, `qdrant`, `clickhouse`, `kafka`, `etcd`, `openbao`, `harbor`, `foundationdb`, `external-dns`, `kuberture`, `vminstance`, `gateway`, `kubernetes-latest`, `kubernetes-previous`, `kubernetes-oidc-system`, `kubernetes-oidc-customconfig`, `securitygroup`, `serviceexposure`. **Approach** - **Declarative** suites assert on `status.conditions` and concrete fields. The `timeout N sh -ec "until kubectl get ..."` + `kubectl wait` pair that appeared ~100 times collapses into a single `assert` that polls existence and state together, with a structured diff on failure and automatic `events`/`describe`/`podLogs` capture via `catch` (previously only `harbor.bats` did this, by hand). - **Imperative** suites keep their logic in `script` steps: `openbao` init/unseal, `kuberture` external-dns split-horizon probes, `vminstance`, the `gateway` admission/impersonation cases, and `kubernetes-latest`/`previous`, which wrap the relocated `hack/e2e-chainsaw/_lib/run-kubernetes.sh` verbatim (Kamaji bring-up, LB/NFS/ouroboros checks). - `gateway` tests derive the tenant apex from the namespace `namespace.cozystack.io/host` label at runtime, so they are host-independent. **CI** - The `e2e` job now runs `chainsaw test hack/e2e-chainsaw/` via a new `test-chainsaw` target and uploads the JUnit `chainsaw-report.xml`. - The `chainsaw` binary is added to the e2e-sandbox image. - `install-cozystack` and `test-openapi` stay BATS (cluster bootstrap + OpenAPI checks). - The per-app 3-retry loop is dropped — assertion polling replaces the fixed-timeout flakiness it papered over. **Validation** Ran against a development cluster: the DB/app, storage, and VM suites pass; the three suites that depend on platform features not present on that cluster (`gateway`, `kuberture`, `external-dns`) and the two heavyweight `kubernetes-*` suites are exercised by this PR's CI run on a freshly installed platform. Note for reviewers: service-port asserts use the `(ports[*].port)` projection form rather than a number-literal filter (`` ports[?port == `N`] ``), because Chainsaw v0.2.15 mis-evaluates JMESPath number-literal comparisons. **2026-07-09: reconciled with main (~520 commits of drift)** - etcd suite re-ported to the v1alpha2 operator contracts (#2859): `readyMembers=3` gate, pod-label/`/scale`/WorkloadMonitor/metrics/defrag contracts, plus the `ETCD_E2E_S3_ROUNDTRIP`-gated backup round-trip driving `examples/backups/etcd` - 12 new `ingress-hostname-policy` gateway cases ported (apex derived at runtime from the tenant-root namespace label) - post-branch bats drift folded in: `mariadb-single` webhook guard, kafka `c1.small` presets, bucket/harbor 2m BucketClaim fail-fast, bucket readonly-denial promoted to a hard fail (cosi-driver v0.3.1), qdrant PVC-reclaim guard (#3059), kuberture fail-loud negations, SC-fallback-default test (#2872 B1) - 4 suites that only exist on main since the branch was cut are ported: `kubernetes-oidc-system`, `kubernetes-oidc-customconfig`, `securitygroup`, `serviceexposure` - CI seams: `nightly.yaml` converted to the chainsaw invocation, `run-kubernetes.sh` reconciled (Talos/CABPT waits, tenant drain, LINSTOR pool wait, talos-image-cache under `_lib/`), `e2e-capture-dataplane.sh` replicated in the Chainsaw global catch ### Release note ```release-note fix(kubernetes): tenant Kubernetes teardown no longer hangs when the cluster has no working nodes — the pre-delete hook now bounds its wait for the in-tenant HelmReleases and force-clears their Flux finalizers on timeout (#3271) ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * End-to-end testing is now driven by a unified Chainsaw suite (platform apps, Kubernetes, networking, security groups, service exposure). * Test Impact Analysis selects affected suites, with an option to run the full suite. * CI produces a JUnit report artifact and collects suite-scoped diagnostics on failure. * **Bug Fixes** * CI E2E now fails fast and emits clearer Kubernetes/HelmRelease and sandbox bucket state diagnostics. * Improved teardown reliability with bounded wait + finalizer handling to avoid hangs. * **Documentation** * Updated E2E testing docs and local guidance to reflect the new Chainsaw-based workflow and suite paths. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What this PR does
Tenant Kubernetes clusters previously exposed a single hardcoded
kubevirtStorageClass mapped to one infrastructure class (.Values.storageClass). Users running multiple storage tiers on the management cluster could not reach them from inside tenant clusters (#2094, #1424).This change auto-discovers the management cluster's remote-accessible LINSTOR StorageClasses (
provisioner: linstor.csi.linbit.comwithallowRemoteVolumeAccess != "false") via Helmlookupand propagates each to tenant clusters under the same name. No Go changes — the kubevirt-csi-driver already supports multiple infra StorageClasses natively.Behavior
packages/apps/kubernetescsi.yamlbuilds astorageClassesmap filtered to remote-accessible LINSTOR classes and passes it to thekubevirt-csi-nodechild chart.kubevirt-csi-noderenders one tenant StorageClass per entry (provisioner: csi.kubevirt.io,infraStorageClassName: <name>,bus: scsi)..Values.storageClass(defaultreplicated) is marked the tenant default; exactly one default is guaranteed.kubevirtStorageClass is retained as a non-default alias, so existing tenant PVCs keep binding with no migration.lookupreturns empty (e.g. dry-run), a non-destructive fallback preserves the legacyreplicated+kubevirtmapping — tenant StorageClasses are never wiped on a transient reconcile.Scope decisions
local,allowRemoteVolumeAccess: false) are excluded because the backing volume cannot follow a live-migrated worker-node VM. Other provisioners (NFS, etc.) are out of scope.infraStorageClassEnforcementis intentionally left at the currentAllowAlldefault. Enabling the allowList would break tenants that have already self-served custom StorageClasses against the management cluster — exactly the users this feature serves. Tightening tenant isolation is left to a separate, discovery-based change.This is a focused, additive alternative to #2095 (kept open), scoped per the LINSTOR-only / no-breakage decisions above.
Upgrade note
If you previously created a tenant StorageClass named
replicatedby hand, delete it before upgrading — otherwise the child Helm release hits an ownership conflict adopting the now-managedreplicatedclass.Tests
kubernetes) and per-entry rendering + single-default invariant + absence of an enforcement block (kubevirt-csi-node; newMakefiletesttarget so the package is no longer skipped by the runner).run-kubernetes.sh): assertsreplicatedpropagates as the default kubevirt-CSI class, thekubevirtalias is retained, and node-locallocalis filtered out.Release note
Summary by CodeRabbit
StorageClassentries now auto-propagate into tenant clusters using the same name.StorageClassrendering now supports multiple configurable classes with deterministic default selection.StorageClassprovisioner, parameters, default annotation, and legacykubevirtalias compatibility.storageClasscases.testtarget.storageClassdescriptions and upgrade caveats across charts and schemas.