Merge branch 'main' into block-data-mover-backup-expose

This commit is contained in:
Lyndon-Li
2026-06-02 16:56:40 +08:00
19 changed files with 327 additions and 1217 deletions
+7
View File
@@ -595,6 +595,13 @@ func (b *backupReconciler) prepareBackupRequest(ctx context.Context, backup *vel
request.Status.ValidationErrors = append(request.Status.ValidationErrors, "include-resources, exclude-resources and include-cluster-resources are old filter parameters.\n"+
"They cannot be used with include-exclude policies.")
}
// namespacedFilterPolicies and clusterScopedFilterPolicy incompatible with old-style filters
if resourcePolicies != nil &&
(len(resourcePolicies.GetNamespacedFilterPolicies()) > 0 || resourcePolicies.GetClusterScopedFilterPolicy() != 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"+
"They cannot be used with namespace-scoped or fine-grained global filter policies.")
}
request.ResPolicies = resourcePolicies
return request
}
+236
View File
@@ -21,6 +21,7 @@ import (
"fmt"
"io"
"reflect"
"slices"
"sort"
"strings"
"testing"
@@ -34,6 +35,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@@ -2020,3 +2022,237 @@ func TestPatchResourceWorksWithStatus(t *testing.T) {
})
}
}
// TestPrepareBackupRequest_NamespacedFilterPoliciesIncompatibleWithOldFilters verifies
// that a backup referencing a ResourcePolicy ConfigMap with namespacedFilterPolicies
// produces a validation error when old-style resource filters are also set on the spec.
func TestPrepareBackupRequest_NamespacedFilterPoliciesIncompatibleWithOldFilters(t *testing.T) {
formatFlag := logging.FormatText
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
policyYAML := `version: v1
namespacedFilterPolicies:
- namespaces: ["production"]
resourceFilters:
- kinds: ["Deployment"]
names: ["api-server"]
`
policyConfigMap := &corev1api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "my-filter-policy",
Namespace: velerov1api.DefaultNamespace,
},
Data: map[string]string{"policy": policyYAML},
}
backup := defaultBackup().IncludedResources("deployments").Result()
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{
Kind: "configmap",
Name: "my-filter-policy",
}
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap)
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,
}
res := c.prepareBackupRequest(ctx, backup, logger)
require.NotEmpty(t, res.Status.ValidationErrors)
hasTargetError := slices.ContainsFunc(res.Status.ValidationErrors, func(e string) bool {
return strings.Contains(e, "namespace-scoped or fine-grained global filter policies")
})
assert.True(t, hasTargetError, "expected validation error about namespacedFilterPolicies incompatibility with old-style filters, got: %v", res.Status.ValidationErrors)
}
// 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.
func TestPrepareBackupRequest_ClusterScopedFilterPolicyIncompatibleWithOldFilters(t *testing.T) {
formatFlag := logging.FormatText
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
policyYAML := `version: v1
clusterScopedFilterPolicy:
resourceFilters:
- kinds: ["ClusterRole"]
names: ["my-app-*"]
`
policyConfigMap := &corev1api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-filter-policy",
Namespace: velerov1api.DefaultNamespace,
},
Data: map[string]string{"policy": policyYAML},
}
backup := defaultBackup().IncludedResources("clusterroles").Result()
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{
Kind: "configmap",
Name: "my-cluster-filter-policy",
}
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap)
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,
}
res := c.prepareBackupRequest(ctx, backup, logger)
require.NotEmpty(t, res.Status.ValidationErrors)
hasClusterError := slices.ContainsFunc(res.Status.ValidationErrors, func(e string) bool {
return strings.Contains(e, "namespace-scoped or fine-grained global filter policies")
})
assert.True(t, hasClusterError, "expected validation error about clusterScopedFilterPolicy incompatibility with old-style filters, got: %v", res.Status.ValidationErrors)
}
const (
namespacedFilterPolicyYAML = `version: v1
namespacedFilterPolicies:
- namespaces: ["production"]
resourceFilters:
- kinds: ["Deployment"]
names: ["api-server"]
`
clusterScopedFilterPolicyYAML = `version: v1
clusterScopedFilterPolicy:
resourceFilters:
- kinds: ["ClusterRole"]
names: ["my-app-*"]
`
bothFilterPoliciesYAML = `version: v1
namespacedFilterPolicies:
- namespaces: ["production"]
resourceFilters:
- kinds: ["Deployment"]
names: ["api-server"]
clusterScopedFilterPolicy:
resourceFilters:
- kinds: ["ClusterRole"]
names: ["my-app-*"]
`
)
// TestPrepareBackupRequest_FilterPoliciesWithNewFilters verifies that backups referencing
// a ResourcePolicy ConfigMap with namespacedFilterPolicies and/or clusterScopedFilterPolicy
// succeed when old-style resource filters are not set on the spec.
func TestPrepareBackupRequest_FilterPoliciesWithNewFilters(t *testing.T) {
tests := []struct {
name string
policyYAML string
policyConfigMapName string
backup *velerov1api.Backup
expectNamespacedPolicies int
expectClusterScopedPolicy bool
}{
{
name: "namespacedFilterPolicies only",
policyYAML: namespacedFilterPolicyYAML,
policyConfigMapName: "my-filter-policy",
backup: defaultBackup().StorageLocation("loc-1").Result(),
expectNamespacedPolicies: 1,
},
{
name: "clusterScopedFilterPolicy only",
policyYAML: clusterScopedFilterPolicyYAML,
policyConfigMapName: "my-cluster-filter-policy",
backup: defaultBackup().StorageLocation("loc-1").Result(),
expectClusterScopedPolicy: true,
},
{
name: "both filter policies",
policyYAML: bothFilterPoliciesYAML,
policyConfigMapName: "my-combined-filter-policy",
backup: defaultBackup().StorageLocation("loc-1").Result(),
expectNamespacedPolicies: 1,
expectClusterScopedPolicy: true,
},
{
name: "with new-style spec filters",
policyYAML: bothFilterPoliciesYAML,
policyConfigMapName: "my-combined-filter-policy",
backup: defaultBackup().
StorageLocation("loc-1").
IncludedNamespaceScopedResources("deployments").
IncludedClusterScopedResources("clusterroles").
Result(),
expectNamespacedPolicies: 1,
expectClusterScopedPolicy: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
formatFlag := logging.FormatText
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
policyConfigMap := &corev1api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: test.policyConfigMapName,
Namespace: velerov1api.DefaultNamespace,
},
Data: map[string]string{"policy": test.policyYAML},
}
test.backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{
Kind: "configmap",
Name: test.policyConfigMapName,
}
backupLocation := builder.ForBackupStorageLocation(velerov1api.DefaultNamespace, "loc-1").
Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, backupLocation, policyConfigMap)
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,
}
res := c.prepareBackupRequest(ctx, test.backup, logger)
defer res.WorkerPool.Stop()
assert.Empty(t, res.Status.ValidationErrors)
hasIncompatibilityError := slices.ContainsFunc(res.Status.ValidationErrors, func(e string) bool {
return strings.Contains(e, "namespace-scoped or fine-grained global filter policies")
})
assert.False(t, hasIncompatibilityError)
require.NotNil(t, res.ResPolicies)
assert.Len(t, res.ResPolicies.GetNamespacedFilterPolicies(), test.expectNamespacedPolicies)
if test.expectClusterScopedPolicy {
assert.NotNil(t, res.ResPolicies.GetClusterScopedFilterPolicy())
} else {
assert.Nil(t, res.ResPolicies.GetClusterScopedFilterPolicy())
}
})
}
}
+52 -48
View File
@@ -66,25 +66,26 @@ const (
// DataUploadReconciler reconciles a DataUpload object
type DataUploadReconciler struct {
client client.Client
kubeClient kubernetes.Interface
csiSnapshotClient snapshotter.SnapshotV1Interface
mgr manager.Manager
Clock clocks.WithTickerAndDelayedExecution
nodeName string
logger logrus.FieldLogger
snapshotExposerList map[velerov2alpha1api.SnapshotType]exposer.SnapshotExposer
dataPathMgr *datapath.Manager
vgdpCounter *exposer.VgdpCounter
loadAffinity []*kube.LoadAffinity
backupPVCConfig map[string]velerotypes.BackupPVC
podResources corev1api.ResourceRequirements
preparingTimeout time.Duration
metrics *metrics.ServerMetrics
cancelledDataUpload map[string]time.Time
dataMovePriorityClass string
podLabels map[string]string
podAnnotations map[string]string
client client.Client
kubeClient kubernetes.Interface
csiSnapshotClient snapshotter.SnapshotV1Interface
mgr manager.Manager
Clock clocks.WithTickerAndDelayedExecution
nodeName string
logger logrus.FieldLogger
snapshotExposerList map[velerov2alpha1api.SnapshotType]exposer.SnapshotExposer
dataPathMgr *datapath.Manager
vgdpCounter *exposer.VgdpCounter
loadAffinity []*kube.LoadAffinity
backupPVCConfig map[string]velerotypes.BackupPVC
podResources corev1api.ResourceRequirements
preparingTimeout time.Duration
metrics *metrics.ServerMetrics
cancelledDataUpload map[string]time.Time
dataMovePriorityClass string
podLabels map[string]string
podAnnotations map[string]string
snapshotMetadataServiceConfigs *velerotypes.CSISnapshotMetadataService
}
func NewDataUploadReconciler(
@@ -105,6 +106,7 @@ func NewDataUploadReconciler(
dataMovePriorityClass string,
podLabels map[string]string,
podAnnotations map[string]string,
snapshotMetadataServiceConfigs *velerotypes.CSISnapshotMetadataService,
) *DataUploadReconciler {
return &DataUploadReconciler{
client: client,
@@ -121,17 +123,18 @@ func NewDataUploadReconciler(
log,
),
},
dataPathMgr: dataPathMgr,
vgdpCounter: counter,
loadAffinity: loadAffinity,
backupPVCConfig: backupPVCConfig,
podResources: podResources,
preparingTimeout: preparingTimeout,
metrics: metrics,
cancelledDataUpload: make(map[string]time.Time),
dataMovePriorityClass: dataMovePriorityClass,
podLabels: podLabels,
podAnnotations: podAnnotations,
dataPathMgr: dataPathMgr,
vgdpCounter: counter,
loadAffinity: loadAffinity,
backupPVCConfig: backupPVCConfig,
podResources: podResources,
preparingTimeout: preparingTimeout,
metrics: metrics,
cancelledDataUpload: make(map[string]time.Time),
dataMovePriorityClass: dataMovePriorityClass,
podLabels: podLabels,
podAnnotations: podAnnotations,
snapshotMetadataServiceConfigs: snapshotMetadataServiceConfigs,
}
}
@@ -998,24 +1001,25 @@ func (r *DataUploadReconciler) setupExposeParam(du *velerov2alpha1api.DataUpload
}
return &exposer.CSISnapshotExposeParam{
SnapshotName: du.Spec.CSISnapshot.VolumeSnapshot,
SourceNamespace: du.Spec.SourceNamespace,
SourcePVCName: pvc.Name,
SourcePVName: pv.Name,
StorageClass: du.Spec.CSISnapshot.StorageClass,
HostingPodLabels: hostingPodLabels,
HostingPodAnnotations: hostingPodAnnotation,
HostingPodTolerations: hostingPodTolerations,
AccessMode: accessMode,
OperationTimeout: du.Spec.OperationTimeout.Duration,
ExposeTimeout: r.preparingTimeout,
VolumeSize: pvc.Spec.Resources.Requests[corev1api.ResourceStorage],
Affinity: r.loadAffinity,
BackupPVCConfig: r.backupPVCConfig,
Resources: r.podResources,
NodeOS: nodeOS,
PriorityClassName: r.dataMovePriorityClass,
DataMover: du.Spec.DataMover,
SnapshotName: du.Spec.CSISnapshot.VolumeSnapshot,
SourceNamespace: du.Spec.SourceNamespace,
SourcePVCName: pvc.Name,
SourcePVName: pv.Name,
StorageClass: du.Spec.CSISnapshot.StorageClass,
HostingPodLabels: hostingPodLabels,
HostingPodAnnotations: hostingPodAnnotation,
HostingPodTolerations: hostingPodTolerations,
AccessMode: accessMode,
OperationTimeout: du.Spec.OperationTimeout.Duration,
ExposeTimeout: r.preparingTimeout,
VolumeSize: pvc.Spec.Resources.Requests[corev1api.ResourceStorage],
Affinity: r.loadAffinity,
BackupPVCConfig: r.backupPVCConfig,
Resources: r.podResources,
NodeOS: nodeOS,
PriorityClassName: r.dataMovePriorityClass,
DataMover: du.Spec.DataMover,
SnapshotMetadataServiceConfigs: r.snapshotMetadataServiceConfigs,
}, nil
}
@@ -251,6 +251,7 @@ func initDataUploaderReconcilerWithError(needError ...error) (*DataUploadReconci
"", // dataMovePriorityClass
nil, // podLabels
nil, // podAnnotations
nil,
), nil
}
@@ -1513,6 +1514,7 @@ func TestDataUploadSetupExposeParam(t *testing.T) {
"upload-priority",
tt.args.customLabels,
tt.args.customAnnotations,
nil,
)
// Act