Skip to content

e2e: make the StorageClass definitions configurable - #10588

Merged
kaovilai merged 1 commit into
velero-io:mainfrom
krishhna24:e2e-configurable-storage-class
Oct 9, 2026
Merged

kaovilai merged 1 commit into
velero-io:mainfrom
krishhna24:e2e-configurable-storage-class

Conversation

@krishhna24

@krishhna24 krishhna24 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Addresses the hardcoded e2e StorageClass handling described in #8765.

What changes

--storage-class-file and --storage-class-file-2, which configure the StorageClass definition rather than its name. Both default to testdata/storage-class/<provider>.yaml, so with neither set the behaviour is exactly as it is today.

The class names stay constants. Every spec keeps referring to e2e-storage-class and e2e-storage-class-2, and only the definition varies. That is what lets kind point at a hostpath.csi.k8s.io class without any spec knowing about it.

An empty --storage-class-file-2 runs without a second StorageClass. Which specs need one cannot be determined in BeforeSuite, since the label filter is not visible yet, so the cases that map between two classes skip with a message naming the flag.

Why this shape

The first version of this PR configured the names and adopted classes that already existed in the cluster. @blackpiglet pointed out that a name cannot change the provisioner, which is the thing kind actually needs, and that adopting a pre-existing class is what forces the ownership machinery around BeforeSuite and AfterSuite. Applying our own manifest avoids all of it: the suite owns everything it created, teardown stays unconditional, and it matches how volume-snapshot-class and the VGSClass from #10582 already work, one file per situation.

#10592 then landed CreateStorageClassFromYaml, which takes a yaml path and a name override and is called twice from the same file. That fixed the file-versus-constant coupling in InstallStorageClasses independently of this PR, and it is why this one is now 96 insertions where the earlier version was 155: no Provided booleans, no existence check, no early-fail, and DeleteStorageClass already swallows NotFound.

One thing that did not go away. STORAGE_CLASS_FILE_2 goes through an $(origin) check in test/Makefile, because an empty value means "run without a second class" and a flag that is always passed cannot express "not set". If a separate boolean would be preferred, that is a two line change.

Testing

kind v1.36.1, MinIO as the object store, Velero built from this branch, Basic && (ClusterResource || NodePort || StorageClass) in every case:

result
no flags 5/5, 168s
STORAGE_CLASS_FILE set to a hostpath manifest 5/5, 173s. Log: Install storage class e2e-storage-class with /tmp/hostpath-sc.yaml then e2e-storage-class-2 with ../testdata/storage-class/kind.yaml
STORAGE_CLASS_FILE_2= 4 pass, 1 skipped with this case maps between two StorageClasses; it needs --storage-class-file-2
STORAGE_CLASS_FILE set to a path that does not exist fails in BeforeSuite in 0.012s with failed to read storage class yaml /tmp/does-not-exist.yaml: no such file or directory

I ran that last one because the failure I would expect from this change is a missing file quietly falling back to the provider file, which would leave the flag looking like it worked while testing something else. It does not fall back.

make lint, go vet ./test/... and gofmt are clean.

Context: this is part of the CSI-on-kind e2e work in #7507.

@krishhna24
krishhna24 requested a review from a team as a code owner September 26, 2026 13:23
@netlify

netlify Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

👷 Deploy request for velero pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit c7031a9

krishhna24 added a commit to krishhna24/velero that referenced this pull request Sep 26, 2026
Signed-off-by: krishhna24 <krishhnatupedev@gmail.com>
@codecov

codecov Bot commented Sep 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kaovilai kaovilai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nodeAgentConfigs is initialized before the E2E flags are parsed, so these fields retain the default StorageClass names even when --storage-class and --storage-class-2 are provided. Could we construct this configuration inside Init() after parsing, so the configured values propagate to this test too?

@krishhna24

krishhna24 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Good catch, that literal is evaluated before flag.Parse, so LoadAffinity would have kept the defaults. Moved it into Init(). It was the only package-level spot reading the names; CachePVCTestCase already builds its config there.

Re-ran Basic && (ClusterResource || NodePort || StorageClass) with both flags set, still 5/5. LoadAffinity itself I could not run on kind since it needs --snapshot-move-data and the kind class is still local-path, so that one is by inspection.

@shubham-pampattiwar

Copy link
Copy Markdown
Member

Really nice work on this one. The creation path catch is the part I'd highlight. Just swapping the constants for flags would have looked fine and been quietly broken, with the specs going off to find the new name while the apply still created e2e-storage-class. Building both classes from a single definition is the right fix, and decoding into a typed StorageClass rather than string-replacing saves us from a provider file that happens to carry a parameter called name. That's the kind of thing that's much cheaper to catch now than six months from now.

The node-agent-config.go spot was good too. I went through the other readers of those two vars and that was the only one initializing at package level, so you got all of it.

On your question: let's do both follow-ups here rather than splitting them. They're both about what the suite creates, and the flag doesn't buy us much until the first one lands, since pointing it at csi-hostpath-sc is really why we want it.

One suggestion on that first one. Skip-if-exists covers half the problem. AfterSuite deletes both names unconditionally (test/e2e/e2e_suite_test.go:826 and :835), so we'd stop clobbering csi-hostpath-sc on the way in and still delete it on the way out. It might be easier to think of it as ownership: if you passed the flag, the class is yours, so we neither create nor delete it, and we fail early with a clear message if it isn't there. No flag, everything behaves exactly as it does today. One rule, both ends covered.

For the second, I'd avoid trying to detect which specs need class 2. BeforeSuite runs before you can see what the label filter selected, so there's nothing reliable to check. Opt-out is simpler: an empty --storage-class-2 means don't create it, and the four places that use it (nodeagentconfig, resourcepolicies, storage-class-changing, migration) skip with a message explaining why.

Flag names look fine to me, no need to churn on those. Thanks for writing up the timings and the before/after comparison in the description, that made this much easier to review.

krishhna24 added a commit to krishhna24/velero that referenced this pull request Sep 28, 2026
Signed-off-by: krishhna24 <krishhnatupedev@gmail.com>
@krishhna24
krishhna24 force-pushed the e2e-configurable-storage-class branch from 8264903 to d4ee6a9 Compare September 28, 2026 06:23
@krishhna24

Copy link
Copy Markdown
Contributor Author

Both follow-ups are in, done as ownership like you suggested.

If --storage-class or --storage-class-2 is set, the class belongs to whoever set it up: not created, not deleted, and BeforeSuite fails early with a message if it is missing. Unset, everything behaves as before. Empty --storage-class-2 runs without a second class and the four cases that need one skip with a message saying how to enable them.

One thing that fell out while wiring it up: the Makefile was passing --storage-class on every run with a default value, so "set" was always true and the default path broke immediately, before any spec ran. The names are no longer defaulted in the Makefile and the flags are only passed when the caller sets them. A flag that is always passed cannot express "not set", which is really the same point you were making about AfterSuite.

Ran four cases on kind with Basic && (ClusterResource || NodePort || StorageClass):

result
no flags 5/5 in 177s, classes created and deleted as before
STORAGE_CLASS_2= 4 pass, 1 skipped: this case needs a second StorageClass; set --storage-class-2 to run it
STORAGE_CLASS=owned-by-me, pre-created on hostpath 5/5, and the class is still in the cluster after teardown while the suite-created second class is gone
STORAGE_CLASS=does-not-exist fails in 0.013s with storage class does-not-exist was configured but does not exist in the cluster; create it first or drop the flag to let the tests create their own

Rebased on main. Description updated to match.

@blackpiglet

Copy link
Copy Markdown
Contributor

Thanks for working on this!

Configuring just the SC names (--storage-class / --storage-class-2) brings up a few tricky issues: it doesn't allow changing the provisioner or parameters, and targeting existing cluster SCs creates complex ownership problems around creation and teardown in BeforeSuite / AfterSuite.

Instead of configuring names and adopting existing cluster SCs, what about letting users provide a customized StorageClass manifest (e.g. --storage-class-file)?

  • Clean lifecycle: E2E applies it in BeforeSuite and deletes it in AfterSuite (just like volume-snapshot-class). No ownership heuristics, and no risk of deleting cluster-preexisting resources.
  • Full flexibility: Users can define the actual provisioner and parameters (e.g., hostpath CSI on kind), not just the name.
  • Handling 1 vs. 2 SCs: Standard tests use the first SC. The few tests that verify remapping (storage-class-changing, resourcepolicies, etc.) can dynamically check if 2 SCs were provided and Skip() if only 1 was given—matching how multi-BSL and multi-cluster tests already behave.

This keeps the interface down to a single flag while making E2E lifecycle management much simpler and safer. What do you think?

@krishhna24

Copy link
Copy Markdown
Contributor Author

Thanks, that is a fair point about the provisioner. Renaming cannot change it, which is the gap I flagged in the description: on kind the class is still local-path whatever it is called, so pointing at an existing csi-hostpath-sc is the only way to get a CSI-backed class today, and that is what the ownership rules are there for.

A manifest flag would solve that more directly, and I agree the lifecycle gets simpler if the suite always creates and deletes what it applied, with no ownership rules at all.

The current shape is what @shubham-pampattiwar asked for on this PR, so I would rather you two agree on the interface than have me guess between you. Happy to switch to --storage-class-file; the change is fairly contained, mostly InstallStorageClasses and the flag wiring, and the skip behaviour for the second class works the same either way.

One thing worth keeping whichever way it goes: InstallStorageClasses currently takes the first class name from the provider file and derives the second by string-replacing a constant, so the two are coupled by an invariant nothing enforces. That is a live bug independent of the interface, and this PR fixes it by building both from one definition.

@shubham-pampattiwar

Copy link
Copy Markdown
Member

@blackpiglet is right, and I want to be clear that this is me changing my mind rather than @krishhna24 guessing wrong. The ownership model on this PR is there because I asked for it.

The thing that convinced me is the provisioner point. Renaming a class cannot change it, so on kind the class stays rancher.io/local-path whatever we call it. That means names only get us a CSI-backed class by adopting the csi-hostpath-sc that csi-driver-host-path ships, and adopting a class we did not create is what forced all the machinery: the Provided booleans, the existence check, the early failure, the $(origin) guard in the Makefile.

We do not actually need to adopt anything. We can apply our own class with provisioner: hostpath.csi.k8s.io and the suite owns everything it created again, which makes teardown unconditional and lets all of that go. It also matches how volume-snapshot-class and the VGSClass from #10582 already work, one file per situation. And with a manifest the name just comes from metadata.name, so the name flag stops being needed at all.

@krishhna24, sorry for the churn, two rounds on a model we are now dropping. The good news is most of it survives:

  • Keep the creation path fix. InstallStorageClasses taking the first name from the file and deriving the second by string-replacing a constant is a real bug independent of the interface, and you are right to call that out.
  • Keep the skip behaviour for the second class, it works the same way with files.
  • Drop the Provided booleans, StorageClassExists, the early-fail, and the Makefile $(origin) guard.
  • --storage-class-file and --storage-class-file-2, both defaulting to testdata/storage-class/<provider>.yaml so unset behaves exactly as today. Empty for the second one means run without it.

Should come out smaller than what is there now. @blackpiglet does that match what you had in mind?

@krishhna24

Copy link
Copy Markdown
Contributor Author

Conflicts here are from #10592, which rewrote sc.go and InstallStorageClasses.

Worth flagging: that PR already fixes the creation path this one was carrying. CreateStorageClassFromYaml decodes into a typed StorageClass and takes a name override, and InstallStorageClasses now calls it twice from the same file, so the file-versus-constant coupling is gone on main.

That makes the rework smaller again. With yamlPath already a parameter, the file-based version is mostly choosing which path to pass. Still holding for @blackpiglet to confirm the shape, then I will rebase and push the reduced version rather than resolving conflicts against code we are removing.

@shubham-pampattiwar

Copy link
Copy Markdown
Member

Please rebase and resolve conflicts @krishhna24

The e2e suite creates both of its StorageClasses from the provider's file
under testdata/storage-class, so running against a cluster whose volumes
need a different provisioner or parameters means editing test data every
other job also reads. On kind that is the only way to get a class a CSI
driver can snapshot.

Take the definition as a flag instead. --storage-class-file and
--storage-class-file-2 name a manifest each, and the suite keeps creating
and deleting what it applied, so nothing adopts resources it did not make.

The class names stay fixed. Every spec refers to e2e-storage-class and
e2e-storage-class-2, and CreateStorageClassFromYaml already takes a name
override, so only the definition needs to vary.

An empty --storage-class-file-2 runs without a second StorageClass, and
the three cases that map between two of them skip with a message saying
how to enable them. Which specs need it cannot be worked out in
BeforeSuite, since the label filter is not visible there yet, so this is
expressed as an opt-out rather than detection.

Signed-off-by: krishhna24 <krishhnatupedev@gmail.com>
@krishhna24
krishhna24 force-pushed the e2e-configurable-storage-class branch from d4ee6a9 to c7031a9 Compare October 5, 2026 02:44
@krishhna24

Copy link
Copy Markdown
Contributor Author

Rebased. Rather than merging the ownership code and then deleting it, I reset onto main and rebuilt it as the file-based version, so this is one commit and 95 insertions where the old one was 155.

Two things main gave us. #10592 added CreateStorageClassFromYaml, which takes a yaml path and a name override, so the class names stay constants: every spec keeps referring to e2e-storage-class and only the definition varies. That removes the name flags entirely, along with the Provided booleans, StorageClassExists and the early-fail. DeleteStorageClass also swallows NotFound now, so teardown stays unconditional with no extra logic.

Gates on kind, Basic && (ClusterResource || NodePort || StorageClass) each time:

result
no flags 5/5, 168s
STORAGE_CLASS_FILE set to a hostpath manifest 5/5, 173s. Log: Install storage class e2e-storage-class with /tmp/hostpath-sc.yaml then e2e-storage-class-2 with ../testdata/storage-class/kind.yaml
STORAGE_CLASS_FILE_2= 4 pass, 1 skipped with this case maps between two StorageClasses; it needs --storage-class-file-2
STORAGE_CLASS_FILE set to a path that does not exist fails in BeforeSuite in 0.012s with failed to read storage class yaml /tmp/does-not-exist.yaml: no such file or directory

I ran that last one because the failure I would expect from this change is a missing file quietly falling back to the provider file, which would leave the flag looking like it worked while testing something else. It does not fall back.

One thing I did not manage to drop: STORAGE_CLASS_FILE_2 still goes through an $(origin) check in test/Makefile. An empty value means run without a second class, and a flag that is always passed cannot express "not set", so the Makefile only passes it when the caller sets it. If you would rather have a separate boolean for that, it is a two line change.

@krishhna24

Copy link
Copy Markdown
Contributor Author

PTAL ! @shubham-pampattiwar @kaovilai

@krishhna24 krishhna24 changed the title e2e: make the StorageClass names configurable e2e: make the StorageClass definitions configurable Oct 8, 2026
@krishhna24

Copy link
Copy Markdown
Contributor Author

Noticed the title and description here were still the ownership version, which is probably confusing if you opened this expecting what @blackpiglet asked for. Both now match the branch. No code change, still c7031a978.

@kaovilai kaovilai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's good. Thank you for your pull request.

@kaovilai

kaovilai commented Oct 9, 2026

Copy link
Copy Markdown
Member

This test only changes. I think this is safe to merge post FC.

@kaovilai
kaovilai merged commit 8c59d54 into velero-io:main Oct 9, 2026
71 of 72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants