mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-07-14 03:52:29 +00:00
49b670a791
Change errors.Cause to errors.Is, because github.com/cockroachdb/errors New() function create a error with error stack with depth 1, but github.com/pkg/errors's New() function create error with no depth. Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
254 lines
8.3 KiB
Go
254 lines
8.3 KiB
Go
/*
|
|
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 nodeagentconfig
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
. "github.com/onsi/gomega"
|
|
corev1api "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
|
"github.com/vmware-tanzu/velero/test"
|
|
. "github.com/vmware-tanzu/velero/test/e2e/test"
|
|
k8sutil "github.com/vmware-tanzu/velero/test/util/k8s"
|
|
veleroutil "github.com/vmware-tanzu/velero/test/util/velero"
|
|
)
|
|
|
|
type CachePVCTestCase struct {
|
|
TestCase
|
|
nodeAgentConfigMapName string
|
|
repoConfigMapName string
|
|
}
|
|
|
|
var CachePVCTest func() = TestFunc(&CachePVCTestCase{
|
|
nodeAgentConfigMapName: "node-agent-config-cache",
|
|
repoConfigMapName: "backup-repo-config-cache",
|
|
})
|
|
|
|
func (c *CachePVCTestCase) Init() error {
|
|
c.TestCase.Init()
|
|
c.CaseBaseName = "cache-pvc-" + c.UUIDgen
|
|
c.BackupName = "backup-" + c.CaseBaseName
|
|
c.RestoreName = "restore-" + c.CaseBaseName
|
|
c.NamespacesTotal = 1
|
|
c.NSIncluded = &[]string{}
|
|
for nsNum := 0; nsNum < c.NamespacesTotal; nsNum++ {
|
|
createNSName := fmt.Sprintf("%s-%00000d", c.CaseBaseName, nsNum)
|
|
*c.NSIncluded = append(*c.NSIncluded, createNSName)
|
|
}
|
|
|
|
c.VeleroCfg.UseNodeAgent = true
|
|
c.VeleroCfg.UseNodeAgentWindows = true
|
|
|
|
// Ensure Data Mover is used to trigger DataUpload/DataDownload pods
|
|
c.BackupArgs = []string{
|
|
"create", "--namespace", c.VeleroCfg.VeleroNamespace, "backup", c.BackupName,
|
|
"--include-namespaces", strings.Join(*c.NSIncluded, ","),
|
|
"--snapshot-volumes=true", "--snapshot-move-data",
|
|
}
|
|
|
|
c.RestoreArgs = []string{
|
|
"create", "--namespace", c.VeleroCfg.VeleroNamespace, "restore", c.RestoreName,
|
|
"--from-backup", c.BackupName,
|
|
}
|
|
|
|
c.TestMsg = &TestMSG{
|
|
Desc: "Validate dynamically provisioned Cache PVC for data mover pods",
|
|
FailedMSG: "Failed to apply and validate cache PVC configuration in data mover pods.",
|
|
Text: "Should dynamically provision a Cache PVC for data mover restore pods to avoid node root FS exhaustion.",
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *CachePVCTestCase) InstallVelero() error {
|
|
fmt.Println("Start to uninstall Velero")
|
|
if err := veleroutil.VeleroUninstall(c.Ctx, c.VeleroCfg); err != nil {
|
|
fmt.Printf("Fail to uninstall Velero: %s\n", err.Error())
|
|
return err
|
|
}
|
|
|
|
// 1. Construct node-agent ConfigMap (Define Cache PVC StorageClass and trigger threshold)
|
|
// Set residentThresholdInMB to 0 to force Velero to create a PVC even for tiny E2E test data.
|
|
nodeAgentConfigJSON := fmt.Sprintf(`{"cachePVC": {"residentThresholdInMB": 0, "storageClass": "%s"}}`, test.StorageClassName)
|
|
nodeAgentConfig := builder.ForConfigMap(c.VeleroCfg.VeleroNamespace, c.nodeAgentConfigMapName).
|
|
Data("node-agent-config", nodeAgentConfigJSON).Result()
|
|
|
|
// 2. Construct backup repository ConfigMap (Define cacheLimitMB)
|
|
repoConfigJSON := `{"cacheLimitMB": 2048}` // Set 2GB cache limit
|
|
repoConfig := builder.ForConfigMap(c.VeleroCfg.VeleroNamespace, c.repoConfigMapName).
|
|
Data(test.UploaderTypeKopia, repoConfigJSON).Result()
|
|
|
|
c.VeleroCfg.NodeAgentConfigMap = c.nodeAgentConfigMapName
|
|
c.VeleroCfg.BackupRepoConfigMap = c.repoConfigMapName
|
|
|
|
// Deploy Velero with the two Cache configuration ConfigMaps
|
|
return veleroutil.PrepareVelero(c.Ctx, c.CaseBaseName, c.VeleroCfg, nodeAgentConfig, repoConfig)
|
|
}
|
|
|
|
func (c *CachePVCTestCase) CreateResources() error {
|
|
for _, ns := range *c.NSIncluded {
|
|
if err := k8sutil.CreateNamespace(c.Ctx, c.Client, ns); err != nil {
|
|
return err
|
|
}
|
|
|
|
pvc, err := k8sutil.CreatePVC(c.Client, ns, "volume-1", test.StorageClassName, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vols := k8sutil.CreateVolumes(pvc.Name, []string{"volume-1"})
|
|
|
|
deployment := k8sutil.NewDeployment(
|
|
c.CaseBaseName,
|
|
ns,
|
|
1,
|
|
map[string]string{"app": "test"},
|
|
c.VeleroCfg.ImageRegistryProxy,
|
|
c.VeleroCfg.WorkerOS,
|
|
).WithVolume(vols).Result()
|
|
|
|
deployment, err = k8sutil.CreateDeployment(c.Client.ClientGo, ns, deployment)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create deployment")
|
|
}
|
|
|
|
if err := k8sutil.WaitForReadyDeployment(c.Client.ClientGo, deployment.Namespace, deployment.Name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// verifyCacheVolumeInPod correctly verifies that the Pod mounted a dynamic PVC instead of an emptyDir
|
|
func (c *CachePVCTestCase) verifyCacheVolumeInPod(pod corev1api.Pod) error {
|
|
for _, vol := range pod.Spec.Volumes {
|
|
if vol.PersistentVolumeClaim != nil {
|
|
// Velero dynamically provisioned cache volumes typically have 'cache' in the name
|
|
if strings.Contains(vol.PersistentVolumeClaim.ClaimName, "cache") || strings.Contains(vol.Name, "cache") {
|
|
return nil // Success: Found the dynamically provisioned Cache PVC
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("Dynamically provisioned Cache PVC not found in pod %s, feature failed!", pod.Name)
|
|
}
|
|
|
|
func (c *CachePVCTestCase) Backup() error {
|
|
if err := veleroutil.VeleroCmdExec(c.Ctx, c.VeleroCfg.VeleroCLI, c.BackupArgs); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("Waiting for backup to complete...")
|
|
|
|
// Wait for backup completion
|
|
wait.PollUntilContextTimeout(c.Ctx, 5*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
|
|
backup := new(velerov1api.Backup)
|
|
if err := c.VeleroCfg.ClientToInstallVelero.Kubebuilder.Get(
|
|
c.Ctx,
|
|
client.ObjectKey{Namespace: c.VeleroCfg.VeleroNamespace, Name: c.BackupName},
|
|
backup,
|
|
); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if backup.Status.Phase != velerov1api.BackupPhaseCompleted &&
|
|
backup.Status.Phase != velerov1api.BackupPhaseFailed &&
|
|
backup.Status.Phase != velerov1api.BackupPhasePartiallyFailed {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *CachePVCTestCase) Restore() error {
|
|
if err := veleroutil.VeleroCmdExec(c.Ctx, c.VeleroCfg.VeleroCLI, c.RestoreArgs); err != nil {
|
|
return err
|
|
}
|
|
|
|
restorePodList := new(corev1api.PodList)
|
|
|
|
wait.PollUntilContextTimeout(c.Ctx, 5*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
|
|
ddList := new(velerov2alpha1api.DataDownloadList)
|
|
if err := c.VeleroCfg.ClientToInstallVelero.Kubebuilder.List(
|
|
c.Ctx,
|
|
ddList,
|
|
&client.ListOptions{Namespace: c.VeleroCfg.VeleroNamespace},
|
|
); err != nil {
|
|
return false, err
|
|
} else if len(ddList.Items) <= 0 {
|
|
return false, nil
|
|
}
|
|
|
|
if err := c.VeleroCfg.ClientToInstallVelero.Kubebuilder.List(
|
|
c.Ctx,
|
|
restorePodList,
|
|
&client.ListOptions{
|
|
LabelSelector: labels.SelectorFromSet(map[string]string{
|
|
velerov1api.DataDownloadLabel: ddList.Items[0].Name,
|
|
}),
|
|
}); err != nil {
|
|
return false, err
|
|
} else if len(restorePodList.Items) <= 0 {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
|
|
fmt.Println("Start to verify restore data mover pod content.")
|
|
Expect(restorePodList.Items).ToNot(BeEmpty())
|
|
|
|
// Ensure the Data Mover pod is using a true PVC for caching
|
|
err := c.verifyCacheVolumeInPod(restorePodList.Items[0])
|
|
Expect(err).To(Succeed(), "Injected Cache PVC should exist in DataDownload Pod")
|
|
fmt.Println("Restore data mover pod content verification completed successfully.")
|
|
|
|
// Wait for restore completion
|
|
wait.PollUntilContextTimeout(c.Ctx, 5*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) {
|
|
restore := new(velerov1api.Restore)
|
|
if err := c.VeleroCfg.ClientToInstallVelero.Kubebuilder.Get(
|
|
c.Ctx,
|
|
client.ObjectKey{Namespace: c.VeleroCfg.VeleroNamespace, Name: c.RestoreName},
|
|
restore,
|
|
); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if restore.Status.Phase != velerov1api.RestorePhaseCompleted &&
|
|
restore.Status.Phase != velerov1api.RestorePhaseFailed &&
|
|
restore.Status.Phase != velerov1api.RestorePhasePartiallyFailed {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
|
|
return nil
|
|
}
|