Add checkpoint for groupsnapshot data consistency (#134637)

* Add checkpoint for groupsnapshot data consistency

Signed-off-by: Penghao <pewang@redhat.com>

* fix: using exist helpers check data consistency

Signed-off-by: Penghao <pewang@redhat.com>

---------

Signed-off-by: Penghao <pewang@redhat.com>
This commit is contained in:
Penghao
2025-11-05 00:20:15 +08:00
committed by GitHub
parent af70ebcbf5
commit 27a81144b2
3 changed files with 225 additions and 92 deletions

View File

@@ -130,7 +130,8 @@ type SnapshottableTestDriver interface {
GetSnapshotClass(ctx context.Context, config *PerTestConfig, parameters map[string]string) *unstructured.Unstructured
}
type VoulmeGroupSnapshottableTestDriver interface {
// VolumeGroupSnapshottableTestDriver represents an interface for a TestDriver that supports DynamicGroupSnapshot
type VolumeGroupSnapshottableTestDriver interface {
TestDriver
// GetVolumeGroupSnapshotClass returns a VolumeGroupSnapshotClass to create group snapshot.
GetVolumeGroupSnapshotClass(ctx context.Context, config *PerTestConfig, parameters map[string]string) *unstructured.Unstructured

View File

@@ -64,7 +64,7 @@ type VolumeGroupSnapshotResource struct {
// CreateVolumeGroupSnapshot creates a VolumeGroupSnapshotClass with given SnapshotDeletionPolicy and a VolumeGroupSnapshot
// from the VolumeGroupSnapshotClass using a dynamic client.
// Returns the unstructured VolumeGroupSnapshotClass and VolumeGroupSnapshot objects.
func CreateVolumeGroupSnapshot(ctx context.Context, sDriver VoulmeGroupSnapshottableTestDriver, config *PerTestConfig, pattern TestPattern, groupName string, pvcNamespace string, timeouts *framework.TimeoutContext, parameters map[string]string) (*unstructured.Unstructured, *unstructured.Unstructured, *unstructured.Unstructured) {
func CreateVolumeGroupSnapshot(ctx context.Context, sDriver VolumeGroupSnapshottableTestDriver, config *PerTestConfig, pattern TestPattern, groupName string, pvcNamespace string, timeouts *framework.TimeoutContext, parameters map[string]string) (*unstructured.Unstructured, *unstructured.Unstructured, *unstructured.Unstructured) {
defer ginkgo.GinkgoRecover()
var err error
if pattern.SnapshotType != VolumeGroupSnapshot {
@@ -120,7 +120,7 @@ func (r *VolumeGroupSnapshotResource) CleanupResource(ctx context.Context, timeo
}
// CreateVolumeGroupSnapshotResource creates a VolumeGroupSnapshotResource object with the given parameters.
func CreateVolumeGroupSnapshotResource(ctx context.Context, sDriver VoulmeGroupSnapshottableTestDriver, config *PerTestConfig, pattern TestPattern, pvcName string, pvcNamespace string, timeouts *framework.TimeoutContext, parameters map[string]string) *VolumeGroupSnapshotResource {
func CreateVolumeGroupSnapshotResource(ctx context.Context, sDriver VolumeGroupSnapshottableTestDriver, config *PerTestConfig, pattern TestPattern, pvcName string, pvcNamespace string, timeouts *framework.TimeoutContext, parameters map[string]string) *VolumeGroupSnapshotResource {
vgsClass, snapshot, vgsc := CreateVolumeGroupSnapshot(ctx, sDriver, config, pattern, pvcName, pvcNamespace, timeouts, parameters)
vgs := &VolumeGroupSnapshotResource{
Config: config,

View File

@@ -20,35 +20,42 @@ import (
"context"
"crypto/sha256"
"fmt"
"strings"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/feature"
"k8s.io/kubernetes/test/e2e/framework"
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
e2estatefulset "k8s.io/kubernetes/test/e2e/framework/statefulset"
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
admissionapi "k8s.io/pod-security-admission/api"
)
type volumeGroupSnapshottableTest struct {
config *storageframework.PerTestConfig
pods []*v1.Pod
volumeGroup [3][]*storageframework.VolumeResource
snapshots []*storageframework.VolumeGroupSnapshotResource
numPods int
numVolumes int
config *storageframework.PerTestConfig
statefulSet *appsv1.StatefulSet
pods []*v1.Pod
volumeResources []*storageframework.VolumeResource
snapshots []*storageframework.VolumeGroupSnapshotResource
numReplicas int
}
// VolumeGroupSnapshottableTestSuite represents a test suite for testing volume group snapshot functionality.
type VolumeGroupSnapshottableTestSuite struct {
tsInfo storageframework.TestSuiteInfo
}
// InitVolumeGroupSnapshottableTestSuite initializes the test suite for volume group snapshottable functionality.
func InitVolumeGroupSnapshottableTestSuite() storageframework.TestSuite {
patterns := []storageframework.TestPattern{
storageframework.VolumeGroupSnapshotDelete,
@@ -56,6 +63,8 @@ func InitVolumeGroupSnapshottableTestSuite() storageframework.TestSuite {
return InitCustomGroupSnapshottableTestSuite(patterns)
}
// InitCustomGroupSnapshottableTestSuite initializes a custom test suite for volume group snapshottable tests
// with the provided test patterns.
func InitCustomGroupSnapshottableTestSuite(patterns []storageframework.TestPattern) storageframework.TestSuite {
return &VolumeGroupSnapshottableTestSuite{
tsInfo: storageframework.TestSuiteInfo{
@@ -69,143 +78,225 @@ func InitCustomGroupSnapshottableTestSuite(patterns []storageframework.TestPatte
}
}
// SkipUnsupportedTests skips tests if the driver does not support group snapshots.
func (s *VolumeGroupSnapshottableTestSuite) SkipUnsupportedTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) {
// Check preconditions.
dInfo := driver.GetDriverInfo()
ok := false
_, ok = driver.(storageframework.VoulmeGroupSnapshottableTestDriver)
_, ok = driver.(storageframework.VolumeGroupSnapshottableTestDriver)
if !dInfo.Capabilities[storageframework.CapVolumeGroupSnapshot] || !ok {
e2eskipper.Skipf("Driver %q does not support group snapshots - skipping", dInfo.Name)
}
}
// GetTestSuiteInfo returns the test suite information for the VolumeGroupSnapshottableTestSuite.
func (s *VolumeGroupSnapshottableTestSuite) GetTestSuiteInfo() storageframework.TestSuiteInfo {
return s.tsInfo
}
// DefineTests defines the test cases for the VolumeGroupSnapshottableTestSuite.
func (s *VolumeGroupSnapshottableTestSuite) DefineTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) {
labelKey := "group"
labelValue := "test-group"
f := framework.NewDefaultFramework("volumegroupsnapshottable")
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
ginkgo.Describe("VolumeGroupSnapshottable", func() {
var tk *e2ekubectl.TestKubeconfig
ginkgo.BeforeEach(func() {
tk = e2ekubectl.NewTestKubeconfig(framework.TestContext.CertDir, framework.TestContext.Host, framework.TestContext.KubeConfig, framework.TestContext.KubeContext, framework.TestContext.KubectlPath, f.Namespace.Name)
})
ginkgo.Context("", func() {
var (
snapshottableDriver storageframework.VoulmeGroupSnapshottableTestDriver
snapshottableDriver storageframework.VolumeGroupSnapshottableTestDriver
cs clientset.Interface
groupTest *volumeGroupSnapshottableTest
)
init := func(ctx context.Context) {
snapshottableDriver = driver.(storageframework.VoulmeGroupSnapshottableTestDriver)
snapshottableDriver = driver.(storageframework.VolumeGroupSnapshottableTestDriver)
cs = f.ClientSet
config := driver.PrepareTest(ctx, f)
groupTest = &volumeGroupSnapshottableTest{
config: config,
volumeGroup: [3][]*storageframework.VolumeResource{},
snapshots: []*storageframework.VolumeGroupSnapshotResource{},
pods: []*v1.Pod{},
numPods: 1,
numVolumes: 3,
config: config,
volumeResources: []*storageframework.VolumeResource{},
snapshots: []*storageframework.VolumeGroupSnapshotResource{},
pods: []*v1.Pod{},
numReplicas: 3,
}
}
createGroupLabel := func(ctx context.Context, pvc *v1.PersistentVolumeClaim, labelKey, labelValue string) {
if pvc.Labels == nil {
pvc.Labels = map[string]string{}
createStatefulSetAndVolumes := func(ctx context.Context) {
// Create volume resource which includes storage class
volumeResource := storageframework.CreateVolumeResource(ctx, driver, groupTest.config, pattern, s.GetTestSuiteInfo().SupportedSizeRange)
groupTest.volumeResources = append(groupTest.volumeResources, volumeResource)
// Create StatefulSet with volumeClaimTemplates
statefulSetName := fmt.Sprintf("statefulset-vgs-%s", f.Namespace.Name)
replicas := int32(groupTest.numReplicas)
statefulSet := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: statefulSetName,
Namespace: f.Namespace.Name,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": statefulSetName,
},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": statefulSetName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test-container",
Image: e2epod.GetDefaultTestImage(),
Command: []string{"sleep", "3600"},
VolumeMounts: []v1.VolumeMount{
{
Name: "data",
MountPath: "/mnt/data",
},
},
},
},
},
},
VolumeClaimTemplates: []v1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
Labels: map[string]string{
labelKey: labelValue,
},
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
},
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse(s.GetTestSuiteInfo().SupportedSizeRange.Min),
},
},
StorageClassName: &volumeResource.Sc.Name,
},
},
},
},
}
pvc.Labels[labelKey] = labelValue
_, err := cs.CoreV1().PersistentVolumeClaims(pvc.GetNamespace()).Update(ctx, pvc, metav1.UpdateOptions{})
framework.ExpectNoError(err, "failed to update PVC %s", pvc.Name)
}
createPodsAndVolumes := func(ctx context.Context) {
for i := 0; i < groupTest.numPods; i++ {
framework.Logf("Creating resources for pod %d/%d", i, groupTest.numPods-1)
for j := 0; j < groupTest.numVolumes; j++ {
volume := storageframework.CreateVolumeResource(ctx, driver, groupTest.config, pattern, s.GetTestSuiteInfo().SupportedSizeRange)
groupTest.volumeGroup[i] = append(groupTest.volumeGroup[i], volume)
createGroupLabel(ctx, volume.Pvc, labelKey, labelValue)
var err error
groupTest.statefulSet, err = cs.AppsV1().StatefulSets(f.Namespace.Name).Create(ctx, statefulSet, metav1.CreateOptions{})
framework.ExpectNoError(err, "failed to create StatefulSet")
}
pvcs := []*v1.PersistentVolumeClaim{}
for _, volume := range groupTest.volumeGroup[i] {
pvcs = append(pvcs, volume.Pvc)
}
// Create a pod with multiple volumes
podConfig := e2epod.Config{
NS: f.Namespace.Name,
PVCs: pvcs,
SeLinuxLabel: e2epv.SELinuxLabel,
}
pod, err := e2epod.MakeSecPod(&podConfig)
framework.ExpectNoError(err, "failed to create pod")
// Wait for StatefulSet to be ready
e2estatefulset.WaitForRunningAndReady(ctx, cs, replicas, groupTest.statefulSet)
// Get the pods created by StatefulSet
for i := 0; i < groupTest.numReplicas; i++ {
podName := fmt.Sprintf("%s-%d", statefulSetName, i)
pod, err := cs.CoreV1().Pods(f.Namespace.Name).Get(ctx, podName, metav1.GetOptions{})
framework.ExpectNoError(err, "failed to get StatefulSet pod %s", podName)
groupTest.pods = append(groupTest.pods, pod)
}
}
writeTestDataToVolumes := func(ctx context.Context) map[string]string {
mountPath := "/mnt/data"
writePath := fmt.Sprintf("%s/testfile", mountPath)
originalMntTestData := make(map[string]string)
ginkgo.By("writing test data to all StatefulSet volumes")
for i, pod := range groupTest.pods {
pod, err := cs.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
framework.Failf("Failed to create pod-%d [%+v]. Error: %v", i, pod, err)
}
if err = e2epod.WaitForPodRunningInNamespace(ctx, cs, pod); err != nil {
framework.Failf("Failed to wait for pod-%d [%+v] to turn into running status. Error: %v", i, pod, err)
}
// For StatefulSet, each pod has one PVC named "data-{statefulset-name}-{index}"
pvcName := fmt.Sprintf("data-%s-%d", groupTest.statefulSet.Name, i)
testData := fmt.Sprintf("HelloFromStatefulSetPVC%d", i)
originalMntTestData[pvcName] = testData
err := tk.WriteFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, writePath, testData)
framework.ExpectNoError(err, "failed to write test data to StatefulSet pod %s", pod.Name)
}
return originalMntTestData
}
cleanup := func(ctx context.Context) {
for _, pod := range groupTest.pods {
framework.Logf("Deleting pod %s", pod.Name)
err := e2epod.DeletePodWithWait(ctx, cs, pod)
framework.ExpectNoError(err, "failed to delete pod %s", pod.Name)
if groupTest.statefulSet != nil {
framework.Logf("Deleting StatefulSet %s", groupTest.statefulSet.Name)
err := cs.AppsV1().StatefulSets(f.Namespace.Name).Delete(ctx, groupTest.statefulSet.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "failed to delete StatefulSet %s", groupTest.statefulSet.Name)
}
for _, group := range groupTest.volumeGroup {
for _, volume := range group {
framework.Logf("Deleting volume %s", volume.Pvc.Name)
err := volume.CleanupResource(ctx)
framework.ExpectNoError(err, "failed to delete volume %s", volume.Pvc.Name)
for _, volumeResource := range groupTest.volumeResources {
if volumeResource != nil {
framework.Logf("Deleting volume resource")
err := volumeResource.CleanupResource(ctx)
framework.ExpectNoError(err, "failed to delete volume resource")
}
}
}
ginkgo.It("should create snapshots for multiple volumes in a pod", func(ctx context.Context) {
ginkgo.It("should create snapshots for StatefulSet volumes and verify data consistency after restore", func(ctx context.Context) {
init(ctx)
createPodsAndVolumes(ctx)
createStatefulSetAndVolumes(ctx)
ginkgo.DeferCleanup(cleanup)
snapshot := storageframework.CreateVolumeGroupSnapshotResource(ctx, snapshottableDriver, groupTest.config, pattern, labelValue, groupTest.volumeGroup[0][0].Pvc.GetNamespace(), f.Timeouts, map[string]string{"deletionPolicy": pattern.SnapshotDeletionPolicy.String()})
originalMntTestData := writeTestDataToVolumes(ctx)
snapshot := storageframework.CreateVolumeGroupSnapshotResource(ctx, snapshottableDriver, groupTest.config, pattern, labelValue, f.Namespace.Name, f.Timeouts, map[string]string{"deletionPolicy": pattern.SnapshotDeletionPolicy.String()})
groupTest.snapshots = append(groupTest.snapshots, snapshot)
ginkgo.By("verifying the snapshots in the group are ready to use")
status := snapshot.VGS.Object["status"]
err := framework.Gomega().Expect(status).NotTo(gomega.BeNil())
framework.ExpectNoError(err, "failed to get status of group snapshot")
gomega.Expect(status).ShouldNot(gomega.BeNil(), "failed to get status of group snapshot")
volumeListMap := snapshot.VGSContent.Object["status"].(map[string]interface{})
err = framework.Gomega().Expect(volumeListMap).NotTo(gomega.BeNil())
framework.ExpectNoError(err, "failed to get volume snapshot list")
gomega.Expect(volumeListMap).ShouldNot(gomega.BeNil(), "failed to get group snapshot list")
volumeSnapshotInfoList := volumeListMap["volumeSnapshotInfoList"].([]interface{})
err = framework.Gomega().Expect(volumeSnapshotInfoList).NotTo(gomega.BeNil())
framework.ExpectNoError(err, "failed to get volume snapshot list")
err = framework.Gomega().Expect(len(volumeSnapshotInfoList)).To(gomega.Equal(groupTest.numVolumes))
framework.ExpectNoError(err, "failed to get volume snapshot list")
claimSize := groupTest.volumeGroup[0][0].Pvc.Spec.Resources.Requests.Storage().String()
gomega.Expect(volumeSnapshotInfoList).ShouldNot(gomega.BeNil(), "failed to get group snapshot handle list")
gomega.Expect(volumeSnapshotInfoList).Should(gomega.HaveLen(groupTest.numReplicas), "failed to verify snapshot handle list length")
claimSize := s.GetTestSuiteInfo().SupportedSizeRange.Min
ginkgo.By("creating restored PVCs from snapshots")
restoredPVCs := []*v1.PersistentVolumeClaim{}
volumeHandleToPVCName := make(map[string]string)
// First, create mapping from volume handles to original PVC names for StatefulSet
for i := 0; i < groupTest.numReplicas; i++ {
pvcName := fmt.Sprintf("data-%s-%d", groupTest.statefulSet.Name, i)
pvc, err := cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Get(ctx, pvcName, metav1.GetOptions{})
framework.ExpectNoError(err, "failed to get PVC %s", pvcName)
pv, err := cs.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
framework.ExpectNoError(err, "failed to get PV for PVC %s", pvcName)
volumeHandle := pv.Spec.CSI.VolumeHandle
volumeHandleToPVCName[volumeHandle] = pvcName
}
for _, info := range volumeSnapshotInfoList {
// Create a PVC from the snapshot
volumeHandle := info.(map[string]interface{})["volumeHandle"].(string)
err = framework.Gomega().Expect(volumeHandle).NotTo(gomega.BeNil())
framework.ExpectNoError(err, "failed to get volume handle from volume")
if volumeHandle == "" {
framework.Failf("volumeHandle missing for volume snapshot %v", info)
}
uid := snapshot.VGS.Object["metadata"].(map[string]interface{})["uid"].(string)
err = framework.Gomega().Expect(uid).NotTo(gomega.BeNil())
framework.ExpectNoError(err, "failed to get uuid from content")
gomega.Expect(uid).NotTo(gomega.BeNil(), "failed to get uuid from content")
volumeSnapshotName := fmt.Sprintf("snapshot-%x", sha256.Sum256([]byte(
uid+volumeHandle)))
// Use original PVC name as base for restored PVC name
originalPVCName := volumeHandleToPVCName[volumeHandle]
restoredPVCName := fmt.Sprintf("restored-%s", originalPVCName)
pvc := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
StorageClassName: &groupTest.volumeGroup[0][0].Sc.Name,
StorageClassName: &groupTest.volumeResources[0].Sc.Name,
ClaimSize: claimSize,
Name: restoredPVCName,
}, f.Namespace.Name)
group := "snapshot.storage.k8s.io"
@@ -216,21 +307,62 @@ func (s *VolumeGroupSnapshottableTestSuite) DefineTests(driver storageframework.
Name: volumeSnapshotName,
}
volSrc := v1.VolumeSource{
Ephemeral: &v1.EphemeralVolumeSource{
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
Spec: pvc.Spec,
},
},
}
pvc, err = cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc, metav1.CreateOptions{})
pvc, err := cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(ctx, pvc, metav1.CreateOptions{})
framework.ExpectNoError(err, "failed to create PVC from snapshot")
pod := StartInPodWithVolumeSource(ctx, cs, volSrc, pvc.Namespace, "snapshot-pod", "sleep 300", groupTest.config.ClientNodeSelection)
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, cs, pod)
framework.ExpectNoError(e2epod.WaitTimeoutForPodRunningInNamespace(ctx, cs, pod.Name, pod.Namespace, f.Timeouts.PodStartSlow), "Pod did not start in expected time")
restoredPVCs = append(restoredPVCs, pvc)
}
ginkgo.DeferCleanup(func(ctx context.Context) {
for _, pvc := range restoredPVCs {
framework.Logf("Deleting restored PVC %s", pvc.Name)
err := cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Delete(ctx, pvc.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "failed to delete restored PVC %s", pvc.Name)
}
})
ginkgo.By("creating single pod with all restored volumes")
restoredPodConfig := e2epod.Config{
NS: f.Namespace.Name,
PVCs: restoredPVCs,
SeLinuxLabel: e2epv.SELinuxLabel,
}
restoredPod, err := e2epod.MakeSecPod(&restoredPodConfig)
framework.ExpectNoError(err, "failed to create restored pod config")
restoredPod, err = cs.CoreV1().Pods(f.Namespace.Name).Create(ctx, restoredPod, metav1.CreateOptions{})
framework.ExpectNoError(err, "failed to create restored pod")
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, cs, restoredPod)
framework.ExpectNoError(e2epod.WaitTimeoutForPodRunningInNamespace(ctx, cs, restoredPod.Name, restoredPod.Namespace, f.Timeouts.PodStartSlow), "Restored pod did not start in expected time")
ginkgo.By("verifying data consistency for all restored StatefulSet volumes")
var dataConsistencyCheckErrors []string
for volumeIndex, pvc := range restoredPVCs {
dataPath := "/mnt/volume"
mountPath := fmt.Sprintf("%s%d", dataPath, volumeIndex+1)
readPath := fmt.Sprintf("%s/testfile", mountPath)
// Extract original PVC name from restored PVC name
const restoredPrefix = "restored-"
if !strings.HasPrefix(pvc.Name, restoredPrefix) {
framework.Failf("unexpected restored PVC name format: %s, expected prefix %s", pvc.Name, restoredPrefix)
}
originalPVCName := pvc.Name[len(restoredPrefix):]
expectedData, ok := originalMntTestData[originalPVCName]
if !ok {
framework.Failf("no test data found for original PVC %s", originalPVCName)
}
restoredData, err := tk.ReadFileViaContainer(restoredPod.Name, restoredPod.Spec.Containers[0].Name, readPath)
if err != nil || !strings.Contains(restoredData, expectedData) {
dataConsistencyCheckErrors = append(dataConsistencyCheckErrors, fmt.Sprintf("volume %s (from %s), expectedData: %s, restoredData: %s : err: %v", pvc.Name, originalPVCName, expectedData, restoredData, err))
} else {
framework.Logf("data consistency verified for StatefulSet volume %s (from %s): found expected data '%s'", pvc.Name, originalPVCName, expectedData)
}
}
if len(dataConsistencyCheckErrors) > 0 {
framework.Logf("data verification failed for one or more volumes: %v", dataConsistencyCheckErrors)
}
gomega.Expect(dataConsistencyCheckErrors).Should(gomega.BeEmpty(), "failed to check data consistency")
})
})
})