Skip to content

Commit a6b170b

Browse files
kaovilaiclaude
andcommitted
Add E2E test for backup repository startup validation
This commit adds an E2E test to verify that backup repositories are properly validated against BSL configuration changes when Velero restarts. The test simulates a scenario where BSL configuration changes while Velero is not running and verifies that repositories are invalidated on startup with the correct error message. Test scenario: 1. Creates a backup to establish a BackupRepository 2. Scales down Velero deployment (simulating shutdown) 3. Modifies BSL configuration (changes prefix) 4. Scales up Velero deployment (simulating startup) 5. Verifies repository is invalidated with correct message 6. Restores original BSL configuration 7. Verifies repository recovers to Ready state Changes: - Added new E2E test file: test/e2e/bsl-mgmt/startup_validation.go - Registered test in test/e2e/e2e_suite_test.go - Added test label to GitHub workflow matrix for CI execution 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 10fee39 commit a6b170b

File tree

3 files changed

+284
-1
lines changed

3 files changed

+284
-1
lines changed

.github/workflows/e2e-test-kind.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ jobs:
6767
\"Basic && (ClusterResource || NodePort || StorageClass)\", \
6868
\"ResourceFiltering && !Restic\", \
6969
\"ResourceModifier || (Backups && BackupsSync) || PrivilegesMgmt || OrderedResources\", \
70-
\"(NamespaceMapping && Single && Restic) || (NamespaceMapping && Multiple && Restic)\"\
70+
\"(NamespaceMapping && Single && Restic) || (NamespaceMapping && Multiple && Restic)\", \
71+
\"BSL && BackupRepository && StartupValidation\"\
7172
]}" >> $GITHUB_OUTPUT
7273
7374
# Run E2E test against all Kubernetes versions on kind
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
Copyright the Velero contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package bslmgmt
17+
18+
import (
19+
"context"
20+
"flag"
21+
"fmt"
22+
"time"
23+
24+
"github.com/google/uuid"
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
appsv1api "k8s.io/api/apps/v1"
28+
"k8s.io/apimachinery/pkg/types"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
31+
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
32+
. "github.com/vmware-tanzu/velero/test"
33+
. "github.com/vmware-tanzu/velero/test/util/k8s"
34+
. "github.com/vmware-tanzu/velero/test/util/kibishii"
35+
. "github.com/vmware-tanzu/velero/test/util/velero"
36+
)
37+
38+
const (
39+
startupValidationTestNs = "bsl-startup-validation"
40+
)
41+
42+
// BackupRepositoryStartupValidation tests that backup repositories are validated
43+
// against BSL configuration on Velero startup and invalidated if configuration has changed
44+
func BackupRepositoryStartupValidation() {
45+
var (
46+
veleroCfg VeleroConfig
47+
)
48+
veleroCfg = VeleroCfg
49+
veleroCfg.UseVolumeSnapshots = false
50+
veleroCfg.UseNodeAgent = true
51+
52+
BeforeEach(func() {
53+
var err error
54+
flag.Parse()
55+
UUIDgen, err = uuid.NewRandom()
56+
Expect(err).To(Succeed())
57+
if InstallVelero {
58+
Expect(PrepareVelero(context.Background(), "BSL Startup Validation", veleroCfg)).To(Succeed())
59+
}
60+
})
61+
62+
AfterEach(func() {
63+
if CurrentSpecReport().Failed() && veleroCfg.FailFast {
64+
fmt.Println("Test case failed and fail fast is enabled. Skip resource clean up.")
65+
} else {
66+
By("Clean backups after test", func() {
67+
veleroCfg.ClientToInstallVelero = veleroCfg.DefaultClient
68+
DeleteAllBackups(context.Background(), &veleroCfg)
69+
})
70+
By(fmt.Sprintf("Delete sample workload namespace %s", startupValidationTestNs), func() {
71+
Expect(DeleteNamespace(context.Background(), *veleroCfg.ClientToInstallVelero, startupValidationTestNs,
72+
true)).To(Succeed(), fmt.Sprintf("failed to delete the namespace %q",
73+
startupValidationTestNs))
74+
})
75+
}
76+
})
77+
78+
When("BSL configuration changes while Velero is not running", func() {
79+
It("Should invalidate backup repositories on startup", func() {
80+
oneHourTimeout, ctxCancel := context.WithTimeout(context.Background(), time.Minute*60)
81+
defer ctxCancel()
82+
83+
backupName := "backup-" + UUIDgen.String()
84+
backupLocation := "default"
85+
86+
By("Create namespace for sample workload", func() {
87+
Expect(CreateNamespace(oneHourTimeout, *veleroCfg.ClientToInstallVelero, startupValidationTestNs)).To(Succeed())
88+
})
89+
90+
By("Deploy sample workload of Kibishii", func() {
91+
Expect(KibishiiPrepareBeforeBackup(
92+
oneHourTimeout,
93+
*veleroCfg.ClientToInstallVelero,
94+
veleroCfg.CloudProvider,
95+
startupValidationTestNs,
96+
veleroCfg.RegistryCredentialFile,
97+
veleroCfg.Features,
98+
veleroCfg.KibishiiDirectory,
99+
DefaultKibishiiData,
100+
veleroCfg.ImageRegistryProxy,
101+
veleroCfg.WorkerOS,
102+
)).To(Succeed())
103+
})
104+
105+
var BackupCfg BackupConfig
106+
BackupCfg.BackupName = backupName
107+
BackupCfg.Namespace = startupValidationTestNs
108+
BackupCfg.BackupLocation = backupLocation
109+
BackupCfg.UseVolumeSnapshots = false
110+
BackupCfg.DefaultVolumesToFsBackup = true
111+
112+
By("Backup sample workload to establish BackupRepository", func() {
113+
Expect(VeleroBackupNamespace(oneHourTimeout, veleroCfg.VeleroCLI,
114+
veleroCfg.VeleroNamespace, BackupCfg)).To(Succeed(), func() string {
115+
RunDebug(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace, BackupCfg.BackupName, "")
116+
return "Fail to backup workload"
117+
})
118+
})
119+
120+
By("Verify backup completed successfully", func() {
121+
Expect(WaitForBackupToBeCreated(context.Background(), backupName, 10*time.Minute, &veleroCfg)).To(Succeed())
122+
})
123+
124+
By("Verify BackupRepository is created and ready", func() {
125+
Expect(BackupRepositoriesCountShouldBe(context.Background(),
126+
veleroCfg.VeleroNamespace, startupValidationTestNs+"-"+backupLocation, 1)).To(Succeed())
127+
128+
// Get the BackupRepository and verify it's ready
129+
repoList := &velerov1api.BackupRepositoryList{}
130+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.List(oneHourTimeout, repoList,
131+
client.InNamespace(veleroCfg.VeleroNamespace))).To(Succeed())
132+
133+
var targetRepo *velerov1api.BackupRepository
134+
for i := range repoList.Items {
135+
if repoList.Items[i].Spec.VolumeNamespace == startupValidationTestNs {
136+
targetRepo = &repoList.Items[i]
137+
break
138+
}
139+
}
140+
Expect(targetRepo).NotTo(BeNil(), "BackupRepository not found")
141+
Expect(targetRepo.Status.Phase).To(Equal(velerov1api.BackupRepositoryPhaseReady))
142+
})
143+
144+
// Store original BSL configuration
145+
originalBSL := &velerov1api.BackupStorageLocation{}
146+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
147+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: backupLocation},
148+
originalBSL)).To(Succeed())
149+
150+
originalBucket := ""
151+
originalPrefix := ""
152+
if originalBSL.Spec.StorageType.ObjectStorage != nil {
153+
originalBucket = originalBSL.Spec.StorageType.ObjectStorage.Bucket
154+
originalPrefix = originalBSL.Spec.StorageType.ObjectStorage.Prefix
155+
}
156+
157+
By("Scale down Velero deployment to simulate shutdown", func() {
158+
deployment := &appsv1api.Deployment{}
159+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
160+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: "velero"},
161+
deployment)).To(Succeed())
162+
163+
zero := int32(0)
164+
deployment.Spec.Replicas = &zero
165+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Update(oneHourTimeout,
166+
deployment)).To(Succeed())
167+
168+
// Wait for deployment to scale down
169+
Eventually(func() int32 {
170+
d := &appsv1api.Deployment{}
171+
err := veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
172+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: "velero"},
173+
d)
174+
if err != nil {
175+
return -1
176+
}
177+
return d.Status.ReadyReplicas
178+
}, 2*time.Minute, 5*time.Second).Should(Equal(int32(0)))
179+
})
180+
181+
By("Modify BSL configuration (change prefix)", func() {
182+
bsl := &velerov1api.BackupStorageLocation{}
183+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
184+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: backupLocation},
185+
bsl)).To(Succeed())
186+
187+
if bsl.Spec.StorageType.ObjectStorage != nil {
188+
// Change the prefix to trigger validation failure
189+
bsl.Spec.StorageType.ObjectStorage.Prefix = originalPrefix + "-modified"
190+
}
191+
192+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Update(oneHourTimeout,
193+
bsl)).To(Succeed())
194+
})
195+
196+
By("Scale up Velero deployment to simulate startup", func() {
197+
deployment := &appsv1api.Deployment{}
198+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
199+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: "velero"},
200+
deployment)).To(Succeed())
201+
202+
one := int32(1)
203+
deployment.Spec.Replicas = &one
204+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Update(oneHourTimeout,
205+
deployment)).To(Succeed())
206+
207+
// Wait for deployment to scale up
208+
Eventually(func() int32 {
209+
d := &appsv1api.Deployment{}
210+
err := veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
211+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: "velero"},
212+
d)
213+
if err != nil {
214+
return -1
215+
}
216+
return d.Status.ReadyReplicas
217+
}, 2*time.Minute, 5*time.Second).Should(Equal(int32(1)))
218+
219+
// Give controller time to run startup validation
220+
time.Sleep(10 * time.Second)
221+
})
222+
223+
By("Verify BackupRepository is invalidated with correct message", func() {
224+
Eventually(func() string {
225+
repoList := &velerov1api.BackupRepositoryList{}
226+
err := veleroCfg.ClientToInstallVelero.Kubebuilder.List(oneHourTimeout, repoList,
227+
client.InNamespace(veleroCfg.VeleroNamespace))
228+
if err != nil {
229+
return ""
230+
}
231+
232+
for _, repo := range repoList.Items {
233+
if repo.Spec.VolumeNamespace == startupValidationTestNs {
234+
return repo.Status.Message
235+
}
236+
}
237+
return ""
238+
}, 2*time.Minute, 5*time.Second).Should(ContainSubstring("BSL configuration changed while Velero was not running"))
239+
})
240+
241+
By("Restore original BSL configuration", func() {
242+
bsl := &velerov1api.BackupStorageLocation{}
243+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Get(oneHourTimeout,
244+
types.NamespacedName{Namespace: veleroCfg.VeleroNamespace, Name: backupLocation},
245+
bsl)).To(Succeed())
246+
247+
if bsl.Spec.StorageType.ObjectStorage != nil {
248+
bsl.Spec.StorageType.ObjectStorage.Bucket = originalBucket
249+
bsl.Spec.StorageType.ObjectStorage.Prefix = originalPrefix
250+
}
251+
252+
Expect(veleroCfg.ClientToInstallVelero.Kubebuilder.Update(oneHourTimeout,
253+
bsl)).To(Succeed())
254+
})
255+
256+
By("Verify BackupRepository recovers to Ready state", func() {
257+
Eventually(func() velerov1api.BackupRepositoryPhase {
258+
repoList := &velerov1api.BackupRepositoryList{}
259+
err := veleroCfg.ClientToInstallVelero.Kubebuilder.List(oneHourTimeout, repoList,
260+
client.InNamespace(veleroCfg.VeleroNamespace))
261+
if err != nil {
262+
return ""
263+
}
264+
265+
for _, repo := range repoList.Items {
266+
if repo.Spec.VolumeNamespace == startupValidationTestNs {
267+
return repo.Status.Phase
268+
}
269+
}
270+
return ""
271+
}, 5*time.Minute, 10*time.Second).Should(Equal(velerov1api.BackupRepositoryPhaseReady))
272+
})
273+
274+
fmt.Printf("|| EXPECTED || - Backup repository startup validation test completed successfully\n")
275+
})
276+
})
277+
}

test/e2e/e2e_suite_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,11 @@ var _ = Describe(
607607
Label("BSL", "Deletion", "Restic", "AdditionalBSL"),
608608
BslDeletionWithRestic,
609609
)
610+
var _ = Describe(
611+
"Backup repositories are validated against BSL configuration on startup",
612+
Label("BSL", "BackupRepository", "StartupValidation"),
613+
BackupRepositoryStartupValidation,
614+
)
610615

611616
var _ = Describe(
612617
"Migrate resources between clusters by FileSystem backup",

0 commit comments

Comments
 (0)