mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-20 15:04:17 +00:00
feat(backup): add global backup volume policies
Signed-off-by: chlins <chlins.zhang@gmail.com>
This commit is contained in:
@@ -84,32 +84,33 @@ var autoExcludeClusterScopedResources = []string{
|
||||
}
|
||||
|
||||
type backupReconciler struct {
|
||||
ctx context.Context
|
||||
logger logrus.FieldLogger
|
||||
discoveryHelper discovery.Helper
|
||||
backupper pkgbackup.Backupper
|
||||
kbClient kbclient.Client
|
||||
clock clock.WithTickerAndDelayedExecution
|
||||
backupLogLevel logrus.Level
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupTracker BackupTracker
|
||||
defaultBackupLocation string
|
||||
defaultVolumesToFsBackup bool
|
||||
defaultBackupTTL time.Duration
|
||||
defaultVGSLabelKey string
|
||||
defaultCSISnapshotTimeout time.Duration
|
||||
resourceTimeout time.Duration
|
||||
defaultItemOperationTimeout time.Duration
|
||||
defaultSnapshotLocations map[string]string
|
||||
metrics *metrics.ServerMetrics
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
formatFlag logging.Format
|
||||
credentialFileStore credentials.FileStore
|
||||
maxConcurrentK8SConnections int
|
||||
defaultSnapshotMoveData bool
|
||||
globalCRClient kbclient.Client
|
||||
itemBlockWorkerCount int
|
||||
concurrentBackups int
|
||||
ctx context.Context
|
||||
logger logrus.FieldLogger
|
||||
discoveryHelper discovery.Helper
|
||||
backupper pkgbackup.Backupper
|
||||
kbClient kbclient.Client
|
||||
clock clock.WithTickerAndDelayedExecution
|
||||
backupLogLevel logrus.Level
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupTracker BackupTracker
|
||||
defaultBackupLocation string
|
||||
defaultVolumesToFsBackup bool
|
||||
defaultBackupTTL time.Duration
|
||||
defaultVGSLabelKey string
|
||||
defaultCSISnapshotTimeout time.Duration
|
||||
resourceTimeout time.Duration
|
||||
defaultItemOperationTimeout time.Duration
|
||||
defaultSnapshotLocations map[string]string
|
||||
metrics *metrics.ServerMetrics
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
formatFlag logging.Format
|
||||
credentialFileStore credentials.FileStore
|
||||
maxConcurrentK8SConnections int
|
||||
defaultSnapshotMoveData bool
|
||||
globalCRClient kbclient.Client
|
||||
itemBlockWorkerCount int
|
||||
concurrentBackups int
|
||||
globalVolumePoliciesConfigMap string
|
||||
}
|
||||
|
||||
func NewBackupReconciler(
|
||||
@@ -138,34 +139,36 @@ func NewBackupReconciler(
|
||||
itemBlockWorkerCount int,
|
||||
concurrentBackups int,
|
||||
globalCRClient kbclient.Client,
|
||||
globalVolumePoliciesConfigMap string,
|
||||
) *backupReconciler {
|
||||
b := &backupReconciler{
|
||||
ctx: ctx,
|
||||
discoveryHelper: discoveryHelper,
|
||||
backupper: backupper,
|
||||
clock: &clock.RealClock{},
|
||||
logger: logger,
|
||||
backupLogLevel: backupLogLevel,
|
||||
newPluginManager: newPluginManager,
|
||||
backupTracker: backupTracker,
|
||||
kbClient: kbClient,
|
||||
defaultBackupLocation: defaultBackupLocation,
|
||||
defaultVolumesToFsBackup: defaultVolumesToFsBackup,
|
||||
defaultBackupTTL: defaultBackupTTL,
|
||||
defaultVGSLabelKey: defaultVGSLabelKey,
|
||||
defaultCSISnapshotTimeout: defaultCSISnapshotTimeout,
|
||||
resourceTimeout: resourceTimeout,
|
||||
defaultItemOperationTimeout: defaultItemOperationTimeout,
|
||||
defaultSnapshotLocations: defaultSnapshotLocations,
|
||||
metrics: metrics,
|
||||
backupStoreGetter: backupStoreGetter,
|
||||
formatFlag: formatFlag,
|
||||
credentialFileStore: credentialStore,
|
||||
maxConcurrentK8SConnections: maxConcurrentK8SConnections,
|
||||
defaultSnapshotMoveData: defaultSnapshotMoveData,
|
||||
itemBlockWorkerCount: itemBlockWorkerCount,
|
||||
concurrentBackups: max(concurrentBackups, 1),
|
||||
globalCRClient: globalCRClient,
|
||||
ctx: ctx,
|
||||
discoveryHelper: discoveryHelper,
|
||||
backupper: backupper,
|
||||
clock: &clock.RealClock{},
|
||||
logger: logger,
|
||||
backupLogLevel: backupLogLevel,
|
||||
newPluginManager: newPluginManager,
|
||||
backupTracker: backupTracker,
|
||||
kbClient: kbClient,
|
||||
defaultBackupLocation: defaultBackupLocation,
|
||||
defaultVolumesToFsBackup: defaultVolumesToFsBackup,
|
||||
defaultBackupTTL: defaultBackupTTL,
|
||||
defaultVGSLabelKey: defaultVGSLabelKey,
|
||||
defaultCSISnapshotTimeout: defaultCSISnapshotTimeout,
|
||||
resourceTimeout: resourceTimeout,
|
||||
defaultItemOperationTimeout: defaultItemOperationTimeout,
|
||||
defaultSnapshotLocations: defaultSnapshotLocations,
|
||||
metrics: metrics,
|
||||
backupStoreGetter: backupStoreGetter,
|
||||
formatFlag: formatFlag,
|
||||
credentialFileStore: credentialStore,
|
||||
maxConcurrentK8SConnections: maxConcurrentK8SConnections,
|
||||
defaultSnapshotMoveData: defaultSnapshotMoveData,
|
||||
itemBlockWorkerCount: itemBlockWorkerCount,
|
||||
concurrentBackups: max(concurrentBackups, 1),
|
||||
globalCRClient: globalCRClient,
|
||||
globalVolumePoliciesConfigMap: globalVolumePoliciesConfigMap,
|
||||
}
|
||||
b.updateTotalBackupMetric()
|
||||
return b
|
||||
@@ -587,9 +590,13 @@ func (b *backupReconciler) prepareBackupRequest(ctx context.Context, backup *vel
|
||||
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified")
|
||||
}
|
||||
|
||||
resourcePolicies, err := resourcepolicies.GetResourcePoliciesFromBackup(*request.Backup, b.kbClient, logger)
|
||||
resourcePolicies, err := resourcepolicies.GetResourcePoliciesFromBackupWithGlobal(
|
||||
*request.Backup, b.kbClient, b.globalVolumePoliciesConfigMap, request.Namespace, logger)
|
||||
if err != nil {
|
||||
request.Status.ValidationErrors = append(request.Status.ValidationErrors, err.Error())
|
||||
} else if b.globalVolumePoliciesConfigMap != "" {
|
||||
// Record the contributing global volume policies ConfigMap so `velero backup describe` can surface it.
|
||||
request.Annotations[velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation] = b.globalVolumePoliciesConfigMap
|
||||
}
|
||||
if resourcePolicies != nil && resourcePolicies.GetIncludeExcludePolicy() != nil && collections.UseOldResourceFilters(request.Spec) {
|
||||
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n"+
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
@@ -2076,6 +2077,100 @@ namespacedFilterPolicies:
|
||||
assert.True(t, hasTargetError, "expected validation error about namespacedFilterPolicies incompatibility with old-style filters, got: %v", res.Status.ValidationErrors)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_GlobalVolumePolicies verifies that the cluster-wide global backup
|
||||
// volume policies are merged into the request and that the contributing ConfigMap is recorded
|
||||
// on the backup so `velero backup describe` can surface it.
|
||||
func TestPrepareBackupRequest_GlobalVolumePolicies(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
globalCM := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "global-volume-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policies.yaml": `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
storageClass:
|
||||
- gp2
|
||||
action:
|
||||
type: skip
|
||||
`},
|
||||
}
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, globalCM,
|
||||
builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "loc-1").Result())
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
logger: logger,
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
defaultBackupLocation: "loc-1",
|
||||
globalVolumePoliciesConfigMap: "global-volume-policy",
|
||||
}
|
||||
|
||||
backup := defaultBackup().StorageLocation("loc-1").Result()
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
// The global volume policies must load cleanly (no policy-related validation error).
|
||||
for _, e := range res.Status.ValidationErrors {
|
||||
assert.NotContains(t, e, "global backup volume policies")
|
||||
}
|
||||
require.NotNil(t, res.ResPolicies)
|
||||
assert.Equal(t, "global-volume-policy", res.Annotations[velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation])
|
||||
|
||||
action, err := res.ResPolicies.GetMatchAction(resourcepolicies.VolumeFilterData{
|
||||
PersistentVolume: &corev1api.PersistentVolume{Spec: corev1api.PersistentVolumeSpec{StorageClassName: "gp2"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, action)
|
||||
assert.Equal(t, resourcepolicies.Skip, action.Type)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_GlobalVolumePolicies_LoadError verifies that when the configured
|
||||
// global backup volume policies ConfigMap cannot be loaded, a validation error is recorded and
|
||||
// the contributing-ConfigMap annotation is not set on the backup.
|
||||
func TestPrepareBackupRequest_GlobalVolumePolicies_LoadError(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
// No ConfigMap with this name exists, so loading the global policies fails.
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t,
|
||||
builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "loc-1").Result())
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
logger: logger,
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
defaultBackupLocation: "loc-1",
|
||||
globalVolumePoliciesConfigMap: "missing-global-volume-policy",
|
||||
}
|
||||
|
||||
backup := defaultBackup().StorageLocation("loc-1").Result()
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
// The failure to load the global policies must surface as a validation error.
|
||||
var hasGlobalPolicyError bool
|
||||
for _, e := range res.Status.ValidationErrors {
|
||||
if strings.Contains(e, "global backup volume policies") {
|
||||
hasGlobalPolicyError = true
|
||||
}
|
||||
}
|
||||
assert.True(t, hasGlobalPolicyError, "expected a validation error about global backup volume policies, got: %v", res.Status.ValidationErrors)
|
||||
// The annotation is only set when the policies load successfully.
|
||||
assert.Empty(t, res.Annotations[velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation])
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_ClusterScopedFilterPolicyIncompatibleWithOldFilters verifies
|
||||
// that a backup referencing a ResourcePolicy ConfigMap with clusterScopedFilterPolicy
|
||||
// produces a validation error when old-style resource filters are also set on the spec.
|
||||
|
||||
Reference in New Issue
Block a user