mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 11:46:06 +00:00
Merge branch 'main' into block-data-mover-backup-expose
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user