From 62aa70219bb4acef06dcf5c512ea7763f5d451b1 Mon Sep 17 00:00:00 2001 From: dongqingcc Date: Thu, 26 Feb 2026 17:10:28 +0800 Subject: [PATCH 1/2] Add e2e test case for PR 9255 Signed-off-by: dongqingcc --- test/e2e/e2e_suite_test.go | 5 + .../resource-filtering/wildcard_namespaces.go | 127 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 test/e2e/resource-filtering/wildcard_namespaces.go diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index f0d1c9c2e..981aeee4c 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -494,6 +494,11 @@ var _ = Describe( Label("ResourceFiltering", "IncludeNamespaces", "Restore"), RestoreWithIncludeNamespaces, ) +var _ = Describe( + "Velero test on backup/restore with wildcard namespaces", + Label("ResourceFiltering", "WildcardNamespaces"), + BackupWithWildcardNamespaces, +) var _ = Describe( "Velero test on include resources from the cluster backup", Label("ResourceFiltering", "IncludeResources", "Backup"), diff --git a/test/e2e/resource-filtering/wildcard_namespaces.go b/test/e2e/resource-filtering/wildcard_namespaces.go new file mode 100644 index 000000000..670d1fe26 --- /dev/null +++ b/test/e2e/resource-filtering/wildcard_namespaces.go @@ -0,0 +1,127 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filtering + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + . "github.com/vmware-tanzu/velero/test/e2e/test" + . "github.com/vmware-tanzu/velero/test/util/k8s" +) + +// WildcardNamespaces tests the inclusion and exclusion of namespaces using wildcards +// introduced in PR #9255 (Issue #1874). +type WildcardNamespaces struct { + TestCase + namespacesToInclude []string + namespacesToExclude []string +} + +var BackupWithWildcardNamespaces func() = TestFunc(&WildcardNamespaces{}) + +func (w *WildcardNamespaces) Init() error { + Expect(w.TestCase.Init()).To(Succeed()) + + // Use CaseBaseName as a prefix for all namespaces so that the base + // TestCase.Destroy() and TestCase.Clean() can automatically garbage-collect them. + w.CaseBaseName = "wildcard-ns-" + w.UUIDgen + w.BackupName = "backup-" + w.CaseBaseName + w.RestoreName = "restore-" + w.CaseBaseName + + // Define specific namespaces to test both wildcard include and exclude + w.namespacesToInclude = []string{ + w.CaseBaseName + "-inc-wild-1", // Will be matched by *-inc-wild-* + w.CaseBaseName + "-inc-wild-2", // Will be matched by *-inc-wild-* + w.CaseBaseName + "-exact-inc", // Exact match + } + w.namespacesToExclude = []string{ + w.CaseBaseName + "-exc-wild-1", // Excluded by *-exc-wild-* + w.CaseBaseName + "-exc-wild-2", // Excluded by *-exc-wild-* + } + + w.TestMsg = &TestMSG{ + Desc: "Backup and restore with wildcard namespaces", + Text: "Should correctly backup and restore namespaces matching the include wildcard, and skip the exclude wildcard", + FailedMSG: "Failed to properly filter namespaces using wildcards", + } + + // Construct wildcard strings + incWildcard := fmt.Sprintf("%s-inc-wild-*", w.CaseBaseName) + excWildcard := fmt.Sprintf("%s-exc-wild-*", w.CaseBaseName) + exactInc := fmt.Sprintf("%s-exact-inc", w.CaseBaseName) + + // In backup args, we intentionally include the `excWildcard` in the include-namespaces + // to verify that `exclude-namespaces` correctly overrides it. + w.BackupArgs = []string{ + "create", "--namespace", w.VeleroCfg.VeleroNamespace, "backup", w.BackupName, + "--include-namespaces", fmt.Sprintf("%s,%s,%s", incWildcard, exactInc, excWildcard), + "--exclude-namespaces", excWildcard, + "--default-volumes-to-fs-backup", "--wait", + } + + w.RestoreArgs = []string{ + "create", "--namespace", w.VeleroCfg.VeleroNamespace, "restore", w.RestoreName, + "--from-backup", w.BackupName, "--wait", + } + + return nil +} + +func (w *WildcardNamespaces) CreateResources() error { + allNamespaces := append(w.namespacesToInclude, w.namespacesToExclude...) + + for _, ns := range allNamespaces { + By(fmt.Sprintf("Creating namespace %s", ns), func() { + Expect(CreateNamespace(w.Ctx, w.Client, ns)).To(Succeed(), fmt.Sprintf("Failed to create namespace %s", ns)) + }) + + // Create a ConfigMap in each namespace to verify resource restoration + cmName := "configmap-" + ns + By(fmt.Sprintf("Creating ConfigMap %s in namespace %s", cmName, ns), func() { + _, err := CreateConfigMap(w.Client.ClientGo, ns, cmName, map[string]string{"wildcard-test": "true"}, nil) + Expect(err).To(Succeed(), fmt.Sprintf("Failed to create configmap in namespace %s", ns)) + }) + } + return nil +} + +func (w *WildcardNamespaces) Verify() error { + // 1. Verify included namespaces and their resources were fully restored + for _, ns := range w.namespacesToInclude { + By(fmt.Sprintf("Checking included namespace %s exists", ns), func() { + _, err := GetNamespace(w.Ctx, w.Client, ns) + Expect(err).To(Succeed(), fmt.Sprintf("Included namespace %s should exist after restore", ns)) + + _, err = GetConfigMap(w.Client.ClientGo, ns, "configmap-"+ns) + Expect(err).To(Succeed(), fmt.Sprintf("ConfigMap in included namespace %s should exist", ns)) + }) + } + + // 2. Verify excluded namespaces were NOT restored + for _, ns := range w.namespacesToExclude { + By(fmt.Sprintf("Checking excluded namespace %s does NOT exist", ns), func() { + _, err := GetNamespace(w.Ctx, w.Client, ns) + Expect(err).To(HaveOccurred(), fmt.Sprintf("Excluded namespace %s should NOT exist after restore", ns)) + Expect(apierrors.IsNotFound(err)).To(BeTrue(), "Error should be NotFound") + }) + } + return nil +} From d315bca32b979f3c01b8c80ecdb81f9a3b9fa3c9 Mon Sep 17 00:00:00 2001 From: dongqingcc Date: Thu, 5 Mar 2026 11:23:57 +0800 Subject: [PATCH 2/2] add namespace wildcard test case for restore Signed-off-by: dongqingcc --- test/e2e/e2e_suite_test.go | 2 +- .../resource-filtering/wildcard_namespaces.go | 84 +++++++++++-------- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 981aeee4c..1632e79eb 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -497,7 +497,7 @@ var _ = Describe( var _ = Describe( "Velero test on backup/restore with wildcard namespaces", Label("ResourceFiltering", "WildcardNamespaces"), - BackupWithWildcardNamespaces, + WildcardNamespacesTest, ) var _ = Describe( "Velero test on include resources from the cluster backup", diff --git a/test/e2e/resource-filtering/wildcard_namespaces.go b/test/e2e/resource-filtering/wildcard_namespaces.go index 670d1fe26..c3cd50071 100644 --- a/test/e2e/resource-filtering/wildcard_namespaces.go +++ b/test/e2e/resource-filtering/wildcard_namespaces.go @@ -28,65 +28,72 @@ import ( ) // WildcardNamespaces tests the inclusion and exclusion of namespaces using wildcards -// introduced in PR #9255 (Issue #1874). +// introduced in PR #9255 (Issue #1874). It verifies filtering at both Backup and Restore stages. type WildcardNamespaces struct { - TestCase - namespacesToInclude []string - namespacesToExclude []string + TestCase // Inherit from basic TestCase instead of FilteringCase to customize a single flow + restoredNS []string + excludedByBackupNS []string + excludedByRestoreNS []string } -var BackupWithWildcardNamespaces func() = TestFunc(&WildcardNamespaces{}) +// Register as a single E2E test +var WildcardNamespacesTest func() = TestFunc(&WildcardNamespaces{}) func (w *WildcardNamespaces) Init() error { Expect(w.TestCase.Init()).To(Succeed()) - // Use CaseBaseName as a prefix for all namespaces so that the base - // TestCase.Destroy() and TestCase.Clean() can automatically garbage-collect them. w.CaseBaseName = "wildcard-ns-" + w.UUIDgen w.BackupName = "backup-" + w.CaseBaseName w.RestoreName = "restore-" + w.CaseBaseName - // Define specific namespaces to test both wildcard include and exclude - w.namespacesToInclude = []string{ - w.CaseBaseName + "-inc-wild-1", // Will be matched by *-inc-wild-* - w.CaseBaseName + "-inc-wild-2", // Will be matched by *-inc-wild-* - w.CaseBaseName + "-exact-inc", // Exact match - } - w.namespacesToExclude = []string{ - w.CaseBaseName + "-exc-wild-1", // Excluded by *-exc-wild-* - w.CaseBaseName + "-exc-wild-2", // Excluded by *-exc-wild-* - } + // 1. Define namespaces for different filtering lifecycle scenarios + nsIncBoth := w.CaseBaseName + "-inc-both" // Included in both backup and restore + nsExact := w.CaseBaseName + "-exact" // Included exactly without wildcards + nsIncExc := w.CaseBaseName + "-inc-exc" // Included in backup, but excluded during restore + nsBakExc := w.CaseBaseName + "-test-bak" // Excluded during backup + + // Group namespaces for validation + w.restoredNS = []string{nsIncBoth, nsExact} + w.excludedByRestoreNS = []string{nsIncExc} + w.excludedByBackupNS = []string{nsBakExc} w.TestMsg = &TestMSG{ Desc: "Backup and restore with wildcard namespaces", - Text: "Should correctly backup and restore namespaces matching the include wildcard, and skip the exclude wildcard", + Text: "Should correctly filter namespaces using wildcards during both backup and restore stages", FailedMSG: "Failed to properly filter namespaces using wildcards", } - // Construct wildcard strings - incWildcard := fmt.Sprintf("%s-inc-wild-*", w.CaseBaseName) - excWildcard := fmt.Sprintf("%s-exc-wild-*", w.CaseBaseName) - exactInc := fmt.Sprintf("%s-exact-inc", w.CaseBaseName) + // 2. Setup Backup Args + backupIncWildcard1 := fmt.Sprintf("%s-inc-*", w.CaseBaseName) // Matches nsIncBoth, nsIncExc + backupIncWildcard2 := fmt.Sprintf("%s-test-*", w.CaseBaseName) // Matches nsBakExc + backupExcWildcard := fmt.Sprintf("%s-test-bak", w.CaseBaseName) // Excludes nsBakExc + nonExistentWildcard := "non-existent-ns-*" // Tests zero-match boundary condition - // In backup args, we intentionally include the `excWildcard` in the include-namespaces - // to verify that `exclude-namespaces` correctly overrides it. w.BackupArgs = []string{ "create", "--namespace", w.VeleroCfg.VeleroNamespace, "backup", w.BackupName, - "--include-namespaces", fmt.Sprintf("%s,%s,%s", incWildcard, exactInc, excWildcard), - "--exclude-namespaces", excWildcard, + // Use broad wildcards for inclusion to bypass Velero CLI's literal string collision validation + "--include-namespaces", fmt.Sprintf("%s,%s,%s,%s", backupIncWildcard1, backupIncWildcard2, nsExact, nonExistentWildcard), + "--exclude-namespaces", backupExcWildcard, "--default-volumes-to-fs-backup", "--wait", } + // 3. Setup Restore Args + restoreExcWildcard := fmt.Sprintf("%s-*-exc", w.CaseBaseName) // Excludes nsIncExc + w.RestoreArgs = []string{ "create", "--namespace", w.VeleroCfg.VeleroNamespace, "restore", w.RestoreName, - "--from-backup", w.BackupName, "--wait", + "--from-backup", w.BackupName, + "--include-namespaces", fmt.Sprintf("%s,%s,%s", backupIncWildcard1, nsExact, nonExistentWildcard), + "--exclude-namespaces", restoreExcWildcard, + "--wait", } return nil } func (w *WildcardNamespaces) CreateResources() error { - allNamespaces := append(w.namespacesToInclude, w.namespacesToExclude...) + allNamespaces := append(w.restoredNS, w.excludedByRestoreNS...) + allNamespaces = append(allNamespaces, w.excludedByBackupNS...) for _, ns := range allNamespaces { By(fmt.Sprintf("Creating namespace %s", ns), func() { @@ -104,8 +111,8 @@ func (w *WildcardNamespaces) CreateResources() error { } func (w *WildcardNamespaces) Verify() error { - // 1. Verify included namespaces and their resources were fully restored - for _, ns := range w.namespacesToInclude { + // 1. Verify namespaces that should be successfully restored + for _, ns := range w.restoredNS { By(fmt.Sprintf("Checking included namespace %s exists", ns), func() { _, err := GetNamespace(w.Ctx, w.Client, ns) Expect(err).To(Succeed(), fmt.Sprintf("Included namespace %s should exist after restore", ns)) @@ -115,11 +122,20 @@ func (w *WildcardNamespaces) Verify() error { }) } - // 2. Verify excluded namespaces were NOT restored - for _, ns := range w.namespacesToExclude { - By(fmt.Sprintf("Checking excluded namespace %s does NOT exist", ns), func() { + // 2. Verify namespaces excluded during Backup + for _, ns := range w.excludedByBackupNS { + By(fmt.Sprintf("Checking namespace %s excluded by backup does NOT exist", ns), func() { _, err := GetNamespace(w.Ctx, w.Client, ns) - Expect(err).To(HaveOccurred(), fmt.Sprintf("Excluded namespace %s should NOT exist after restore", ns)) + Expect(err).To(HaveOccurred(), fmt.Sprintf("Namespace %s excluded by backup should NOT exist after restore", ns)) + Expect(apierrors.IsNotFound(err)).To(BeTrue(), "Error should be NotFound") + }) + } + + // 3. Verify namespaces excluded during Restore + for _, ns := range w.excludedByRestoreNS { + By(fmt.Sprintf("Checking namespace %s excluded by restore does NOT exist", ns), func() { + _, err := GetNamespace(w.Ctx, w.Client, ns) + Expect(err).To(HaveOccurred(), fmt.Sprintf("Namespace %s excluded by restore should NOT exist after restore", ns)) Expect(apierrors.IsNotFound(err)).To(BeTrue(), "Error should be NotFound") }) }