mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-18 22:14:29 +00:00
Merge branch 'main' into block-uploader-snapshot-operations
This commit is contained in:
@@ -80,6 +80,11 @@ const (
|
||||
// timeout value for backup to plugins.
|
||||
ResourceTimeoutAnnotation = "velero.io/resource-timeout"
|
||||
|
||||
// GlobalBackupVolumePolicyConfigMapAnnotation is the annotation key used to record the
|
||||
// name of the cluster-wide global backup volume policies ConfigMap that contributed to a
|
||||
// backup, so that `velero backup describe` can surface it.
|
||||
GlobalBackupVolumePolicyConfigMapAnnotation = "velero.io/global-backup-volume-policy-configmap"
|
||||
|
||||
// AsyncOperationIDLabel is the label key used to identify the async operation ID
|
||||
AsyncOperationIDLabel = "velero.io/async-operation-id"
|
||||
|
||||
|
||||
@@ -125,6 +125,16 @@ type RestoreSpec struct {
|
||||
// +nullable
|
||||
ResourceModifier *corev1api.TypedLocalObjectReference `json:"resourceModifier,omitempty"`
|
||||
|
||||
// ResourcePolicy specifies the reference to a ConfigMap containing resource
|
||||
// filter policies for this restore. The ConfigMap can contain a
|
||||
// namespacedFilterPolicies section that specifies per-namespace resource type
|
||||
// filters, label selectors, and resource name patterns, and a
|
||||
// clusterScopedFilterPolicy section for per-kind filtering of cluster-scoped
|
||||
// resources. The ConfigMap format is the same as for BackupSpec.ResourcePolicy.
|
||||
// +optional
|
||||
// +nullable
|
||||
ResourcePolicy *corev1api.TypedLocalObjectReference `json:"resourcePolicy,omitempty"`
|
||||
|
||||
// UploaderConfig specifies the configuration for the restore.
|
||||
// +optional
|
||||
// +nullable
|
||||
|
||||
@@ -1415,6 +1415,11 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) {
|
||||
*out = new(corev1.TypedLocalObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ResourcePolicy != nil {
|
||||
in, out := &in.ResourcePolicy, &out.ResourcePolicy
|
||||
*out = new(corev1.TypedLocalObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.UploaderConfig != nil {
|
||||
in, out := &in.UploaderConfig, &out.UploaderConfig
|
||||
*out = new(UploaderConfigForRestore)
|
||||
|
||||
@@ -19,6 +19,7 @@ package builder
|
||||
import (
|
||||
"time"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
@@ -171,3 +172,12 @@ func (b *RestoreBuilder) ItemOperationTimeout(timeout time.Duration) *RestoreBui
|
||||
b.object.Spec.ItemOperationTimeout.Duration = timeout
|
||||
return b
|
||||
}
|
||||
|
||||
// ResourcePoliciesConfigmap sets the Restore's resource policies configmap.
|
||||
func (b *RestoreBuilder) ResourcePoliciesConfigmap(name string) *RestoreBuilder {
|
||||
b.object.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{
|
||||
Kind: "configmap",
|
||||
Name: name,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRestoreBuilder_ResourcePoliciesConfigmap(t *testing.T) {
|
||||
restore := ForRestore("velero", "my-restore").
|
||||
ResourcePoliciesConfigmap("my-policy-cm").
|
||||
Result()
|
||||
|
||||
assert.Equal(t, "velero", restore.Namespace)
|
||||
assert.Equal(t, "my-restore", restore.Name)
|
||||
assert.NotNil(t, restore.Spec.ResourcePolicy)
|
||||
assert.Equal(t, "configmap", restore.Spec.ResourcePolicy.Kind)
|
||||
assert.Equal(t, "my-policy-cm", restore.Spec.ResourcePolicy.Name)
|
||||
assert.Equal(t, (*string)(nil), restore.Spec.ResourcePolicy.APIGroup)
|
||||
}
|
||||
@@ -145,42 +145,43 @@ var (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
PluginDir string
|
||||
MetricsAddress string
|
||||
DefaultBackupLocation string // TODO(2.0) Deprecate defaultBackupLocation
|
||||
BackupSyncPeriod time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
ResourceTerminatingTimeout time.Duration
|
||||
DefaultBackupTTL time.Duration
|
||||
DefaultVGSLabelKey string
|
||||
StoreValidationFrequency time.Duration
|
||||
DefaultCSISnapshotTimeout time.Duration
|
||||
DefaultItemOperationTimeout time.Duration
|
||||
ResourceTimeout time.Duration
|
||||
RestoreResourcePriorities types.Priorities
|
||||
DefaultVolumeSnapshotLocations flag.Map
|
||||
RestoreOnly bool
|
||||
DisabledControllers []string
|
||||
ClientQPS float32
|
||||
ClientBurst int
|
||||
ClientPageSize int
|
||||
ProfilerAddress string
|
||||
LogLevel *logging.LevelFlag
|
||||
LogFormat *logging.FormatFlag
|
||||
RepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
ItemOperationSyncFrequency time.Duration
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
MaxConcurrentK8SConnections int
|
||||
DefaultSnapshotMoveData bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
CredentialsDirectory string
|
||||
BackupRepoConfig string
|
||||
RepoMaintenanceJobConfig string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
PluginDir string
|
||||
MetricsAddress string
|
||||
DefaultBackupLocation string // TODO(2.0) Deprecate defaultBackupLocation
|
||||
BackupSyncPeriod time.Duration
|
||||
PodVolumeOperationTimeout time.Duration
|
||||
ResourceTerminatingTimeout time.Duration
|
||||
DefaultBackupTTL time.Duration
|
||||
DefaultVGSLabelKey string
|
||||
StoreValidationFrequency time.Duration
|
||||
DefaultCSISnapshotTimeout time.Duration
|
||||
DefaultItemOperationTimeout time.Duration
|
||||
ResourceTimeout time.Duration
|
||||
RestoreResourcePriorities types.Priorities
|
||||
DefaultVolumeSnapshotLocations flag.Map
|
||||
RestoreOnly bool
|
||||
DisabledControllers []string
|
||||
ClientQPS float32
|
||||
ClientBurst int
|
||||
ClientPageSize int
|
||||
ProfilerAddress string
|
||||
LogLevel *logging.LevelFlag
|
||||
LogFormat *logging.FormatFlag
|
||||
RepoMaintenanceFrequency time.Duration
|
||||
GarbageCollectionFrequency time.Duration
|
||||
ItemOperationSyncFrequency time.Duration
|
||||
DefaultVolumesToFsBackup bool
|
||||
UploaderType string
|
||||
MaxConcurrentK8SConnections int
|
||||
DefaultSnapshotMoveData bool
|
||||
DisableInformerCache bool
|
||||
ScheduleSkipImmediately bool
|
||||
CredentialsDirectory string
|
||||
BackupRepoConfig string
|
||||
RepoMaintenanceJobConfig string
|
||||
ItemBlockWorkerCount int
|
||||
ConcurrentBackups int
|
||||
GlobalBackupVolumePoliciesConfigMap string
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *Config {
|
||||
@@ -275,4 +276,10 @@ func (c *Config) BindFlags(flags *pflag.FlagSet) {
|
||||
c.ConcurrentBackups,
|
||||
"Number of backups to process concurrently. Default is one. Optional.",
|
||||
)
|
||||
flags.StringVar(
|
||||
&c.GlobalBackupVolumePoliciesConfigMap,
|
||||
"global-backup-volume-policies-configmap",
|
||||
c.GlobalBackupVolumePoliciesConfigMap,
|
||||
"The name of a ConfigMap in the Velero install namespace holding global backup volume policies that are merged into every backup. Optional.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetDefaultConfig(t *testing.T) {
|
||||
@@ -17,3 +18,14 @@ func TestBindFlags(t *testing.T) {
|
||||
config.BindFlags(pflag.CommandLine)
|
||||
assert.Equal(t, 1, config.ItemBlockWorkerCount)
|
||||
}
|
||||
|
||||
func TestGlobalBackupVolumePoliciesConfigMapFlag(t *testing.T) {
|
||||
config := GetDefaultConfig()
|
||||
// Opt-in: defaults to empty.
|
||||
assert.Empty(t, config.GlobalBackupVolumePoliciesConfigMap)
|
||||
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
config.BindFlags(flags)
|
||||
require.NoError(t, flags.Parse([]string{"--global-backup-volume-policies-configmap", "global-volume-policy"}))
|
||||
assert.Equal(t, "global-volume-policy", config.GlobalBackupVolumePoliciesConfigMap)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import (
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
"github.com/vmware-tanzu/velero/internal/storage"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
@@ -390,6 +391,14 @@ func (s *server) setupBeforeControllerRun() error {
|
||||
if err := setDefaultBackupLocation(s.ctx, client, s.namespace, s.config.DefaultBackupLocation, s.logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the global backup volume policies ConfigMap early, so misconfigurations fail fast.
|
||||
if s.config.GlobalBackupVolumePoliciesConfigMap != "" {
|
||||
if _, err := resourcepolicies.GetGlobalResourcePolicies(client, s.namespace, s.config.GlobalBackupVolumePoliciesConfigMap, s.logger); err != nil {
|
||||
return err
|
||||
}
|
||||
s.logger.WithField("configmap", s.config.GlobalBackupVolumePoliciesConfigMap).Info("Loaded global backup volume policies")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -671,6 +680,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.config.ItemBlockWorkerCount,
|
||||
s.config.ConcurrentBackups,
|
||||
s.crClient,
|
||||
s.config.GlobalBackupVolumePoliciesConfigMap,
|
||||
).SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", constant.ControllerBackup)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ func DescribeBackup(
|
||||
DescribeFineGrainedFilterPolicies(ctx, kbClient, d, backup)
|
||||
}
|
||||
|
||||
DescribeGlobalVolumePolicy(d, backup)
|
||||
|
||||
if backup.Spec.UploaderConfig != nil && backup.Spec.UploaderConfig.ParallelFilesUpload > 0 {
|
||||
d.Println()
|
||||
DescribeUploaderConfigForBackup(d, backup.Spec)
|
||||
@@ -136,6 +138,19 @@ func DescribeResourcePolicies(d *Describer, resPolicies *corev1api.TypedLocalObj
|
||||
d.Printf("\tName:\t%s\n", resPolicies.Name)
|
||||
}
|
||||
|
||||
// DescribeGlobalVolumePolicy describes the cluster-wide global backup volume policies
|
||||
// ConfigMap that contributed to the backup, if any.
|
||||
func DescribeGlobalVolumePolicy(d *Describer, backup *velerov1api.Backup) {
|
||||
name := backup.Annotations[velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation]
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
d.Println()
|
||||
d.Printf("Global volume policies:\n")
|
||||
d.Printf("\tType:\t%s\n", resourcepolicies.ConfigmapRefType)
|
||||
d.Printf("\tName:\t%s\n", name)
|
||||
}
|
||||
|
||||
// DescribeFineGrainedFilterPolicies describes cluster-scoped and namespace-scoped filter policies if present
|
||||
func DescribeFineGrainedFilterPolicies(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup) {
|
||||
if backup.Spec.ResourcePolicy == nil {
|
||||
|
||||
@@ -72,6 +72,34 @@ func TestDescribeResourcePolicies(t *testing.T) {
|
||||
assert.Equal(t, expect, d.buf.String())
|
||||
}
|
||||
|
||||
func TestDescribeGlobalVolumePolicy(t *testing.T) {
|
||||
newDescriber := func() *Describer {
|
||||
d := &Describer{out: &tabwriter.Writer{}, buf: &bytes.Buffer{}}
|
||||
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
|
||||
return d
|
||||
}
|
||||
|
||||
// No annotation: nothing is printed.
|
||||
d := newDescriber()
|
||||
DescribeGlobalVolumePolicy(d, builder.ForBackup("velero", "b").Result())
|
||||
d.out.Flush()
|
||||
assert.Empty(t, d.buf.String())
|
||||
|
||||
// Annotation present: ConfigMap name is surfaced.
|
||||
d = newDescriber()
|
||||
backup := builder.ForBackup("velero", "b").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation, "global-volume-policy")).
|
||||
Result()
|
||||
DescribeGlobalVolumePolicy(d, backup)
|
||||
d.out.Flush()
|
||||
expect := `
|
||||
Global volume policies:
|
||||
Type: configmap
|
||||
Name: global-volume-policy
|
||||
`
|
||||
assert.Equal(t, expect, d.buf.String())
|
||||
}
|
||||
|
||||
func TestDescribeBackupSpec(t *testing.T) {
|
||||
input1 := builder.ForBackup("test-ns", "test-backup-1").
|
||||
IncludedNamespaces("inc-ns-1", "inc-ns-2").
|
||||
|
||||
@@ -60,6 +60,8 @@ func DescribeBackupInSF(
|
||||
DescribeFineGrainedFilterPoliciesInSF(ctx, kbClient, d, backup)
|
||||
}
|
||||
|
||||
DescribeGlobalVolumePolicyInSF(d, backup)
|
||||
|
||||
status := backup.Status
|
||||
if len(status.ValidationErrors) > 0 {
|
||||
d.Describe("validationErrors", status.ValidationErrors)
|
||||
@@ -699,6 +701,19 @@ func DescribeResourcePoliciesInSF(d *StructuredDescriber, resPolicies *corev1api
|
||||
d.Describe("resourcePolicies", policiesInfo)
|
||||
}
|
||||
|
||||
// DescribeGlobalVolumePolicyInSF describes the global backup volume policies ConfigMap that
|
||||
// contributed to the backup, if any, in structured format.
|
||||
func DescribeGlobalVolumePolicyInSF(d *StructuredDescriber, backup *velerov1api.Backup) {
|
||||
name := backup.Annotations[velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation]
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
d.Describe("globalVolumePolicies", map[string]any{
|
||||
"type": resourcepolicies.ConfigmapRefType,
|
||||
"name": name,
|
||||
})
|
||||
}
|
||||
|
||||
func describeResultInSF(m map[string]any, result results.Result) {
|
||||
m["velero"], m["cluster"], m["namespace"] = []string{}, []string{}, []string{}
|
||||
|
||||
|
||||
@@ -627,6 +627,27 @@ func TestDescribeResourcePoliciesInSF(t *testing.T) {
|
||||
assert.True(t, reflect.DeepEqual(sd.output, expect))
|
||||
}
|
||||
|
||||
func TestDescribeGlobalVolumePolicyInSF(t *testing.T) {
|
||||
// No annotation: nothing is added to the output.
|
||||
sd := &StructuredDescriber{output: make(map[string]any), format: ""}
|
||||
DescribeGlobalVolumePolicyInSF(sd, builder.ForBackup("velero", "b").Result())
|
||||
assert.Empty(t, sd.output)
|
||||
|
||||
// Annotation present: the ConfigMap name is surfaced.
|
||||
sd = &StructuredDescriber{output: make(map[string]any), format: ""}
|
||||
backup := builder.ForBackup("velero", "b").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.GlobalBackupVolumePolicyConfigMapAnnotation, "global-volume-policy")).
|
||||
Result()
|
||||
DescribeGlobalVolumePolicyInSF(sd, backup)
|
||||
expectGlobal := map[string]any{
|
||||
"globalVolumePolicies": map[string]any{
|
||||
"type": "configmap",
|
||||
"name": "global-volume-policy",
|
||||
},
|
||||
}
|
||||
assert.True(t, reflect.DeepEqual(sd.output, expectGlobal))
|
||||
}
|
||||
|
||||
func TestDescribeBackupResultInSF(t *testing.T) {
|
||||
input := results.Result{
|
||||
Velero: []string{"msg-1", "msg-2"},
|
||||
|
||||
@@ -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