mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-06 13:26:26 +00:00
Add a BSL controller to handle validation + update BSL status phase (#2674)
* Add BSL controller Signed-off-by: Carlisia <carlisia@vmware.com> * Add changelog Signed-off-by: Carlisia <carlisia@vmware.com> * Make update Signed-off-by: Carlisia <carlisia@vmware.com> * Update docs Signed-off-by: Carlisia <carlisia@vmware.com> * Add kubebuilder dependency Signed-off-by: Carlisia <carlisia@vmware.com> * Add export Signed-off-by: Carlisia <carlisia@vmware.com> * add kubebuilder binaries into velero builder image Signed-off-by: Ashish Amarnath <ashisham@vmware.com> * Reset velero dockerfile Signed-off-by: Carlisia <carlisia@vmware.com> * Consolidate all logic Signed-off-by: Carlisia <carlisia@vmware.com> * Add copyright header Signed-off-by: Carlisia <carlisia@vmware.com> * Clean up + add "last validated" column Signed-off-by: Carlisia <carlisia@vmware.com> * Better tests Signed-off-by: Carlisia <carlisia@vmware.com> * Add more tests Signed-off-by: Carlisia <carlisia@vmware.com> * Better logging Signed-off-by: Carlisia <carlisia@vmware.com> * Format Signed-off-by: Carlisia <carlisia@vmware.com> * Code reviews Signed-off-by: Carlisia <carlisia@vmware.com> * Address code review Signed-off-by: Carlisia <carlisia@vmware.com> * Remove redundant logic Signed-off-by: Carlisia <carlisia@vmware.com> Co-authored-by: Ashish Amarnath <ashisham@vmware.com>
This commit is contained in:
@@ -58,14 +58,14 @@ func NewCreateCommand(f client.Factory, use string) *cobra.Command {
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
Name string
|
||||
Provider string
|
||||
Bucket string
|
||||
Prefix string
|
||||
BackupSyncPeriod time.Duration
|
||||
Config flag.Map
|
||||
Labels flag.Map
|
||||
AccessMode *flag.Enum
|
||||
Name string
|
||||
Provider string
|
||||
Bucket string
|
||||
Prefix string
|
||||
BackupSyncPeriod, ValidationFrequency time.Duration
|
||||
Config flag.Map
|
||||
Labels flag.Map
|
||||
AccessMode *flag.Enum
|
||||
}
|
||||
|
||||
func NewCreateOptions() *CreateOptions {
|
||||
@@ -83,7 +83,8 @@ func (o *CreateOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&o.Provider, "provider", o.Provider, "name of the backup storage provider (e.g. aws, azure, gcp)")
|
||||
flags.StringVar(&o.Bucket, "bucket", o.Bucket, "name of the object storage bucket where backups should be stored")
|
||||
flags.StringVar(&o.Prefix, "prefix", o.Prefix, "prefix under which all Velero data should be stored within the bucket. Optional.")
|
||||
flags.DurationVar(&o.BackupSyncPeriod, "backup-sync-period", o.BackupSyncPeriod, "how often to ensure all Velero backups in object storage exist as Backup API objects in the cluster. Optional. Set this to `0s` to disable sync")
|
||||
flags.DurationVar(&o.BackupSyncPeriod, "backup-sync-period", o.BackupSyncPeriod, "how often to ensure all Velero backups in object storage exist as Backup API objects in the cluster. Optional. Set this to `0s` to disable sync. Default: 1 minute.")
|
||||
flags.DurationVar(&o.ValidationFrequency, "validation-frequency", o.ValidationFrequency, "how often to verify if the backup storage location is valid. Optional. Set this to `0s` to disable sync. Default 1 minute.")
|
||||
flags.Var(&o.Config, "config", "configuration key-value pairs")
|
||||
flags.Var(&o.Labels, "labels", "labels to apply to the backup storage location")
|
||||
flags.Var(
|
||||
@@ -119,12 +120,16 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error {
|
||||
}
|
||||
|
||||
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
var backupSyncPeriod *metav1.Duration
|
||||
var backupSyncPeriod, validationFrequency *metav1.Duration
|
||||
|
||||
if c.Flags().Changed("backup-sync-period") {
|
||||
backupSyncPeriod = &metav1.Duration{Duration: o.BackupSyncPeriod}
|
||||
}
|
||||
|
||||
if c.Flags().Changed("validation-frequency") {
|
||||
validationFrequency = &metav1.Duration{Duration: o.ValidationFrequency}
|
||||
}
|
||||
|
||||
backupStorageLocation := &velerov1api.BackupStorageLocation{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: f.Namespace(),
|
||||
@@ -139,9 +144,10 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
|
||||
Prefix: o.Prefix,
|
||||
},
|
||||
},
|
||||
Config: o.Config.Data(),
|
||||
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
|
||||
BackupSyncPeriod: backupSyncPeriod,
|
||||
Config: o.Config.Data(),
|
||||
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
|
||||
BackupSyncPeriod: backupSyncPeriod,
|
||||
ValidationFrequency: validationFrequency,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ import (
|
||||
snapshotv1beta1informers "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/informers/externalversions"
|
||||
snapshotv1beta1listers "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/listers/volumesnapshot/v1beta1"
|
||||
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/buildinfo"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -72,10 +71,10 @@ import (
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/util/managercontroller"
|
||||
"github.com/vmware-tanzu/velero/internal/velero"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
@@ -84,6 +83,7 @@ const (
|
||||
defaultMetricsAddress = ":8085"
|
||||
|
||||
defaultBackupSyncPeriod = time.Minute
|
||||
defaultStoreValidationFrequency = time.Minute
|
||||
defaultPodVolumeOperationTimeout = 240 * time.Minute
|
||||
defaultResourceTerminatingTimeout = 10 * time.Minute
|
||||
|
||||
@@ -125,7 +125,7 @@ var disableControllerList = []string{
|
||||
type serverConfig struct {
|
||||
pluginDir, metricsAddress, defaultBackupLocation string
|
||||
backupSyncPeriod, podVolumeOperationTimeout, resourceTerminatingTimeout time.Duration
|
||||
defaultBackupTTL time.Duration
|
||||
defaultBackupTTL, storeValidationFrequency time.Duration
|
||||
restoreResourcePriorities []string
|
||||
defaultVolumeSnapshotLocations map[string]string
|
||||
restoreOnly bool
|
||||
@@ -154,6 +154,7 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
defaultVolumeSnapshotLocations: make(map[string]string),
|
||||
backupSyncPeriod: defaultBackupSyncPeriod,
|
||||
defaultBackupTTL: defaultBackupTTL,
|
||||
storeValidationFrequency: defaultStoreValidationFrequency,
|
||||
podVolumeOperationTimeout: defaultPodVolumeOperationTimeout,
|
||||
restoreResourcePriorities: defaultRestorePriorities,
|
||||
clientQPS: defaultClientQPS,
|
||||
@@ -217,6 +218,7 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
command.Flags().StringSliceVar(&config.disabledControllers, "disable-controllers", config.disabledControllers, fmt.Sprintf("list of controllers to disable on startup. Valid values are %s", strings.Join(disableControllerList, ",")))
|
||||
command.Flags().StringSliceVar(&config.restoreResourcePriorities, "restore-resource-priorities", config.restoreResourcePriorities, "desired order of resource restores; any resource not in the list will be restored alphabetically after the prioritized resources")
|
||||
command.Flags().StringVar(&config.defaultBackupLocation, "default-backup-storage-location", config.defaultBackupLocation, "name of the default backup storage location")
|
||||
command.Flags().DurationVar(&config.storeValidationFrequency, "store-validation-frequency", config.storeValidationFrequency, "how often to verify if the storage is valid. Optional. Set this to `0s` to disable sync. Default 1 minute.")
|
||||
command.Flags().Var(&volumeSnapshotLocations, "default-volume-snapshot-locations", "list of unique volume providers and default volume snapshot location (provider1:location-01,provider2:location-02,...)")
|
||||
command.Flags().Float32Var(&config.clientQPS, "client-qps", config.clientQPS, "maximum number of requests per second by the server to the Kubernetes API once the burst limit has been reached")
|
||||
command.Flags().IntVar(&config.clientBurst, "client-burst", config.clientBurst, "maximum number of requests by the server to the Kubernetes API in a short period of time")
|
||||
@@ -296,7 +298,7 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||
}
|
||||
|
||||
var csiSnapClient *snapshotv1beta1client.Clientset
|
||||
if features.IsEnabled(api.CSIFeatureFlag) {
|
||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
csiSnapClient, err = snapshotv1beta1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
cancelFunc()
|
||||
@@ -359,21 +361,6 @@ func (s *server) run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.validateBackupStorageLocations(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetching from the server directly since at this point
|
||||
// the cache has not yet started
|
||||
bsl := &velerov1api.BackupStorageLocation{}
|
||||
if err := s.mgr.GetAPIReader().Get(context.Background(), kbclient.ObjectKey{
|
||||
Namespace: s.namespace,
|
||||
Name: s.config.defaultBackupLocation,
|
||||
}, bsl); err != nil {
|
||||
s.logger.WithError(errors.WithStack(err)).
|
||||
Warnf("A backup storage location named %s has been specified for the server to use by default, but no corresponding backup storage location exists. Backups with a location not matching the default will need to explicitly specify an existing location", s.config.defaultBackupLocation)
|
||||
}
|
||||
|
||||
if err := s.initRestic(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -427,14 +414,14 @@ func (s *server) veleroResourcesExist() error {
|
||||
|
||||
var veleroGroupVersion *metav1.APIResourceList
|
||||
for _, gv := range s.discoveryHelper.Resources() {
|
||||
if gv.GroupVersion == api.SchemeGroupVersion.String() {
|
||||
if gv.GroupVersion == velerov1api.SchemeGroupVersion.String() {
|
||||
veleroGroupVersion = gv
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if veleroGroupVersion == nil {
|
||||
return errors.Errorf("Velero API group %s not found. Apply examples/common/00-prereqs.yaml to create it.", api.SchemeGroupVersion)
|
||||
return errors.Errorf("Velero API group %s not found. Apply examples/common/00-prereqs.yaml to create it.", velerov1api.SchemeGroupVersion)
|
||||
}
|
||||
|
||||
foundResources := sets.NewString()
|
||||
@@ -443,13 +430,13 @@ func (s *server) veleroResourcesExist() error {
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for kind := range api.CustomResources() {
|
||||
for kind := range velerov1api.CustomResources() {
|
||||
if foundResources.Has(kind) {
|
||||
s.logger.WithField("kind", kind).Debug("Found custom resource")
|
||||
continue
|
||||
}
|
||||
|
||||
errs = append(errs, errors.Errorf("custom resource %s not found in Velero API group %s", kind, api.SchemeGroupVersion))
|
||||
errs = append(errs, errors.Errorf("custom resource %s not found in Velero API group %s", kind, velerov1api.SchemeGroupVersion))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
@@ -461,43 +448,6 @@ func (s *server) veleroResourcesExist() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBackupStorageLocations checks to ensure all backup storage locations exist
|
||||
// and have a compatible layout, and returns an error if not.
|
||||
func (s *server) validateBackupStorageLocations() error {
|
||||
s.logger.Info("Checking that all backup storage locations are valid")
|
||||
|
||||
pluginManager := clientmgmt.NewManager(s.logger, s.logLevel, s.pluginRegistry)
|
||||
defer pluginManager.CleanupClients()
|
||||
|
||||
// Fetching from the server directly since at this point
|
||||
// the cache has not yet started
|
||||
locations := &velerov1api.BackupStorageLocationList{}
|
||||
if err := s.mgr.GetAPIReader().List(context.Background(), locations, &kbclient.ListOptions{
|
||||
Namespace: s.namespace,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var invalid []string
|
||||
for _, location := range locations.Items {
|
||||
backupStore, err := persistence.NewObjectBackupStore(&location, pluginManager, s.logger)
|
||||
if err != nil {
|
||||
invalid = append(invalid, errors.Wrapf(err, "error getting backup store for location %q", location.Name).Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err := backupStore.IsValid(); err != nil {
|
||||
invalid = append(invalid, errors.Wrapf(err, "backup store for location %q is invalid", location.Name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalid) > 0 {
|
||||
return errors.Errorf("some backup storage locations are invalid: %s", strings.Join(invalid, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// - Custom Resource Definitions come before Custom Resource so that they can be
|
||||
// restored with their corresponding CRD.
|
||||
// - Namespaces go second because all namespaced resources depend on them.
|
||||
@@ -595,12 +545,12 @@ func (s *server) getCSISnapshotListers() (snapshotv1beta1listers.VolumeSnapshotL
|
||||
|
||||
// If CSI is enabled, check for the CSI groups and generate the listers
|
||||
// If CSI isn't enabled, return empty listers.
|
||||
if features.IsEnabled(api.CSIFeatureFlag) {
|
||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
_, err = s.discoveryClient.ServerResourcesForGroupVersion(snapshotv1beta1api.SchemeGroupVersion.String())
|
||||
switch {
|
||||
case apierrors.IsNotFound(err):
|
||||
// CSI is enabled, but the required CRDs aren't installed, so halt.
|
||||
s.logger.Fatalf("The '%s' feature flag was specified, but CSI API group [%s] was not found.", api.CSIFeatureFlag, snapshotv1beta1api.SchemeGroupVersion.String())
|
||||
s.logger.Fatalf("The '%s' feature flag was specified, but CSI API group [%s] was not found.", velerov1api.CSIFeatureFlag, snapshotv1beta1api.SchemeGroupVersion.String())
|
||||
case err == nil:
|
||||
// CSI is enabled, and the resources were found.
|
||||
// Instantiate the listers fully
|
||||
@@ -901,6 +851,22 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
s.logger.WithField("informer", informer).Info("Informer cache synced")
|
||||
}
|
||||
|
||||
storageLocationInfo := velero.StorageLocation{
|
||||
Client: s.mgr.GetClient(),
|
||||
Ctx: s.ctx,
|
||||
DefaultStorageLocation: s.config.defaultBackupLocation,
|
||||
DefaultStoreValidationFrequency: s.config.storeValidationFrequency,
|
||||
NewPluginManager: newPluginManager,
|
||||
NewBackupStore: persistence.NewObjectBackupStore,
|
||||
}
|
||||
if err := (&controller.BackupStorageLocationReconciler{
|
||||
Scheme: s.mgr.GetScheme(),
|
||||
StorageLocation: storageLocationInfo,
|
||||
Log: s.logger,
|
||||
}).SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", "BackupStorageLocation")
|
||||
}
|
||||
|
||||
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
|
||||
// by v2.0, the block from this line and including the `s.mgr.Start() will be
|
||||
// deprecated, since the manager auto-starts all the caches. Until then, we need to start the
|
||||
@@ -944,7 +910,7 @@ func NewCSIInformerFactoryWrapper(c snapshotv1beta1client.Interface) *CSIInforme
|
||||
// This is desirable for VolumeSnapshots, as we want to query for all VolumeSnapshots across all namespaces using this informer
|
||||
w := &CSIInformerFactoryWrapper{}
|
||||
|
||||
if features.IsEnabled(api.CSIFeatureFlag) {
|
||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
w.factory = snapshotv1beta1informers.NewSharedInformerFactoryWithOptions(c, 0)
|
||||
}
|
||||
return w
|
||||
@@ -952,14 +918,14 @@ func NewCSIInformerFactoryWrapper(c snapshotv1beta1client.Interface) *CSIInforme
|
||||
|
||||
// Start proxies the Start call to the CSI SharedInformerFactory.
|
||||
func (w *CSIInformerFactoryWrapper) Start(stopCh <-chan struct{}) {
|
||||
if features.IsEnabled(api.CSIFeatureFlag) {
|
||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
w.factory.Start(stopCh)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCacheSync proxies the WaitForCacheSync call to the CSI SharedInformerFactory.
|
||||
func (w *CSIInformerFactoryWrapper) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
|
||||
if features.IsEnabled(api.CSIFeatureFlag) {
|
||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
return w.factory.WaitForCacheSync(stopCh)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -31,6 +31,7 @@ var (
|
||||
{Name: "Provider"},
|
||||
{Name: "Bucket/Prefix"},
|
||||
{Name: "Phase"},
|
||||
{Name: "Last Validated"},
|
||||
{Name: "Access Mode"},
|
||||
}
|
||||
)
|
||||
@@ -64,11 +65,18 @@ func printBackupStorageLocation(location *velerov1api.BackupStorageLocation) []m
|
||||
status = "Unknown"
|
||||
}
|
||||
|
||||
lastValidated := location.Status.LastValidationTime
|
||||
LastValidatedStr := "Unknown"
|
||||
if lastValidated != nil {
|
||||
LastValidatedStr = lastValidated.String()
|
||||
}
|
||||
|
||||
row.Cells = append(row.Cells,
|
||||
location.Name,
|
||||
location.Spec.Provider,
|
||||
bucketAndPrefix,
|
||||
status,
|
||||
LastValidatedStr,
|
||||
accessMode,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user