Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support configure BSL CR to indicate which one is the default #3092

Merged
merged 12 commits into from
Dec 8, 2020
1 change: 1 addition & 0 deletions changelogs/unreleased/3092-jenting
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: support configures BackupStorageLocation custom resources to indicate which one is the default
8 changes: 8 additions & 0 deletions config/crd/bases/velero.io_backupstoragelocations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ spec:
- JSONPath: .metadata.creationTimestamp
name: Age
type: date
- JSONPath: .spec.default
description: Default backup storage location
name: Default
type: boolean
group: velero.io
names:
kind: BackupStorageLocation
Expand Down Expand Up @@ -83,6 +87,10 @@ spec:
type: string
description: Config is for provider-specific configuration fields.
type: object
default:
description: Default indicates this location is the default backup storage
location.
type: boolean
objectStorage:
description: ObjectStorageLocation specifies the settings necessary
to connect to a provider's object storage.
Expand Down
2 changes: 1 addition & 1 deletion config/crd/crds/crds.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pkg/apis/velero/v1/backupstoragelocation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type BackupStorageLocationSpec struct {

StorageType `json:",inline"`

// Default indicates this location is the default backup storage location.
// +optional
Default bool `json:"default,omitempty"`

// AccessMode defines the permissions for the backup storage location.
// +optional
AccessMode BackupStorageLocationAccessMode `json:"accessMode,omitempty"`
Expand Down Expand Up @@ -96,6 +100,7 @@ type BackupStorageLocationStatus struct {
// +kubebuilder:printcolumn:name="Last Validated",type="date",JSONPath=".status.lastValidationTime",description="LastValidationTime is the last time the backup store location was validated"
// +kubebuilder:printcolumn:name="Access Mode",type="string",JSONPath=".spec.accessMode",description="Permissions for the backup storage location"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Default",type="boolean",JSONPath=".spec.default",description="Default backup storage location"

// BackupStorageLocation is a location where Velero stores backup objects
type BackupStorageLocation struct {
Expand Down
8 changes: 7 additions & 1 deletion pkg/builder/backup_storage_location_builder.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2017, 2019 the Velero contributors.
Copyright 2020 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.
Expand Down Expand Up @@ -83,6 +83,12 @@ func (b *BackupStorageLocationBuilder) Prefix(val string) *BackupStorageLocation
return b
}

// Default sets the BackupStorageLocation's is default or not
func (b *BackupStorageLocationBuilder) Default(isDefault bool) *BackupStorageLocationBuilder {
b.object.Spec.Default = isDefault
return b
}

// AccessMode sets the BackupStorageLocation's access mode.
func (b *BackupStorageLocationBuilder) AccessMode(accessMode velerov1api.BackupStorageLocationAccessMode) *BackupStorageLocationBuilder {
b.object.Spec.AccessMode = accessMode
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/cli/backuplocation/backup_location.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewCommand(f client.Factory) *cobra.Command {
NewCreateCommand(f, "create"),
NewDeleteCommand(f, "delete"),
NewGetCommand(f, "get"),
NewSetCommand(f, "set"),
)

return c
Expand Down
22 changes: 22 additions & 0 deletions pkg/cmd/cli/backuplocation/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type CreateOptions struct {
Name string
Provider string
Bucket string
DefaultBackupStorageLocation bool
Prefix string
BackupSyncPeriod, ValidationFrequency time.Duration
Config flag.Map
Expand All @@ -85,6 +86,7 @@ func NewCreateOptions() *CreateOptions {
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.BoolVar(&o.DefaultBackupStorageLocation, "default", o.DefaultBackupStorageLocation, "Sets this new location to be the new default backup storage location. Optional.")
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. 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.")
Expand Down Expand Up @@ -162,6 +164,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
},
},
Config: o.Config.Data(),
Default: o.DefaultBackupStorageLocation,
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
BackupSyncPeriod: backupSyncPeriod,
ValidationFrequency: validationFrequency,
Expand All @@ -177,6 +180,25 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
return err
}

if o.DefaultBackupStorageLocation {
// There is one and only one default backup storage location.
// Disable the origin default backup storage location.
locations := new(velerov1api.BackupStorageLocationList)
if err := kbClient.List(context.Background(), locations, &kbclient.ListOptions{Namespace: f.Namespace()}); err != nil {
return errors.WithStack(err)
}
for _, location := range locations.Items {
if !location.Spec.Default {
continue
}
location.Spec.Default = false
if err := kbClient.Update(context.Background(), &location, &kbclient.UpdateOptions{}); err != nil {
return errors.WithStack(err)
}
break
}
}

if err := kbClient.Create(context.Background(), backupStorageLocation, &kbclient.CreateOptions{}); err != nil {
return errors.WithStack(err)
}
Expand Down
26 changes: 24 additions & 2 deletions pkg/cmd/cli/backuplocation/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

func NewGetCommand(f client.Factory, use string) *cobra.Command {
var listOptions metav1.ListOptions
var showDefaultOnly bool

c := &cobra.Command{
Use: use,
Expand All @@ -45,28 +46,49 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {

locations := new(velerov1api.BackupStorageLocationList)
if len(args) > 0 {
location := &velerov1api.BackupStorageLocation{}
for _, name := range args {
location := &velerov1api.BackupStorageLocation{}
err = kbClient.Get(context.Background(), kbclient.ObjectKey{
Namespace: f.Namespace(),
Name: name,
}, location)
cmd.CheckError(err)
locations.Items = append(locations.Items, *location)

if showDefaultOnly {
if location.Spec.Default {
locations.Items = append(locations.Items, *location)
}
} else {
locations.Items = append(locations.Items, *location)
}
}
} else {
err := kbClient.List(context.Background(), locations, &kbclient.ListOptions{
Namespace: f.Namespace(),
Raw: &listOptions,
})
cmd.CheckError(err)

if showDefaultOnly {
for i := 0; i < len(locations.Items); i++ {
if locations.Items[i].Spec.Default {
continue
}
if i != len(locations.Items)-1 {
copy(locations.Items[i:], locations.Items[i+1:])
i = i - 1
}
locations.Items = locations.Items[:len(locations.Items)-1]
}
}
}

_, err = output.PrintWithFormat(c, locations)
cmd.CheckError(err)
},
}

c.Flags().BoolVar(&showDefaultOnly, "default", false, "Displays the current default backup storage location.")
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.")

output.BindFlags(c.Flags())
Expand Down
115 changes: 115 additions & 0 deletions pkg/cmd/cli/backuplocation/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright 2020 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 backuplocation

import (
"context"
"fmt"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

kbclient "sigs.k8s.io/controller-runtime/pkg/client"

velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
)

func NewSetCommand(f client.Factory, use string) *cobra.Command {
o := NewSetOptions()

c := &cobra.Command{
Use: use + " NAME",
Short: "Set a backup storage location",
Args: cobra.ExactArgs(1),
Run: func(c *cobra.Command, args []string) {
cmd.CheckError(o.Complete(args, f))
cmd.CheckError(o.Run(c, f))
},
}

o.BindFlags(c.Flags())

return c
}

type SetOptions struct {
Name string
DefaultBackupStorageLocation bool
}

func NewSetOptions() *SetOptions {
return &SetOptions{}
}

func (o *SetOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.DefaultBackupStorageLocation, "default", o.DefaultBackupStorageLocation, "Sets this new location to be the new default backup storage location. Optional.")
}

func (o *SetOptions) Complete(args []string, f client.Factory) error {
o.Name = args[0]
return nil
}

func (o *SetOptions) Run(c *cobra.Command, f client.Factory) error {
kbClient, err := f.KubebuilderClient()
if err != nil {
return err
}

location := &velerov1api.BackupStorageLocation{}
err = kbClient.Get(context.Background(), kbclient.ObjectKey{
Namespace: f.Namespace(),
Name: o.Name,
}, location)
if err != nil {
return errors.WithStack(err)
}

if o.DefaultBackupStorageLocation {
// There is one and only one default backup storage location.
// Disable the origin default backup storage location.
locations := new(velerov1api.BackupStorageLocationList)
if err := kbClient.List(context.Background(), locations, &kbclient.ListOptions{Namespace: f.Namespace()}); err != nil {
return errors.WithStack(err)
}
for _, location := range locations.Items {
if !location.Spec.Default {
continue
}
if location.Name == o.Name {
// Do not update if the origin default BSL is the current one.
break
}
location.Spec.Default = false
if err := kbClient.Update(context.Background(), &location, &kbclient.UpdateOptions{}); err != nil {
return errors.WithStack(err)
}
break
}
}

location.Spec.Default = o.DefaultBackupStorageLocation
if err := kbClient.Update(context.Background(), location, &kbclient.UpdateOptions{}); err != nil {
return errors.WithStack(err)
}

fmt.Printf("Backup storage location %q configured successfully.\n", o.Name)
return nil
}
3 changes: 1 addition & 2 deletions pkg/cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func NewCommand(f client.Factory) *cobra.Command {
command.Flags().BoolVar(&config.restoreOnly, "restore-only", config.restoreOnly, "Run in a mode where only restores are allowed; backups, schedules, and garbage-collection are all disabled. DEPRECATED: this flag will be removed in v2.0. Use read-only backup storage locations instead.")
command.Flags().StringSliceVar(&config.disabledControllers, "disable-controllers", config.disabledControllers, fmt.Sprintf("List of controllers to disable on startup. Valid values are %s", strings.Join(controller.DisableableControllers, ",")))
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().StringVar(&config.defaultBackupLocation, "default-backup-storage-location", config.defaultBackupLocation, "Name of the default backup storage location. DEPRECATED: this flag will be removed in v2.0. Use \"velero backup-location set --default\" instead.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if both of these are set? We should document that behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me summarize the scenario I've thought:

  1. During the upgrade, the BSL controller sets the defaulted BSL according to the BSL name because none of the BSLs be marked as default.
  2. The user could change the defaulted BSL by velero CLI.
  3. Now that we have the default BSL. If the user changes the velero server --default-backup-storage-location, it takes no effect anymore. The only way to configure which BSL is the default is to change BSL CR (velero CLI or kubectl edit backupstoragelocations).

The only way we can't prevent is the user directly edit BSL CRs to have more than one BSLs having .spec.default=true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That behavior sounds to me like it's not deprecating this flag, but making it entirely a no-op. To me, that's worse than removing the flag, because if it's present, users will expect it to work.

Deprecation means that it should still work, but is not the preferred method and will be removed.

Ideally, I think the flag should remain until complete removal and cause the server to execute similar logic to what the command does by altering the CRDs. This would happen at server startup, so it's a "one-time" operation, but the users can still use the new command to alter the default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I think the flag should remain until complete removal and cause the server to execute similar logic to what the command does by altering the CRDs. This would happen at server startup, so it's a "one-time" operation, but the users can still use the new command to alter the default.

sure, I'll make server-side and client-side change both work.

Copy link
Contributor Author

@jenting jenting Dec 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think another use case that might break the user's expectation, for example, we have 2 BSLs, one is default and the other one is secondary.

  1. During the upgrade, the BSL controller sets BSL default as default BSL.
  2. The user changes the defaulted BSL to secondary by Velero CLI.
  3. The Velero pod restart due to deleting the pod, then at velero server startup, it'll set the default BSL back as the default.

It means that every time the velero pod start/restart, it might change the default BSL setting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means that every time the velero pod start/restart, it might change the default BSL setting.

Yeah, that's a very good point. That's definitely not something we want, because that will send data to the wrong place based on other configuration changes. Perhaps the server-level option sets the default BSL once and only once, for migration purposes, then it doesn't work anymore?

How about we address these in a follow up PR to keep this one on track? I think the implementation here is pretty good, but the challenges for making it backwards compatible look trickier than I first thought and probably warrant their own set of focused changes.

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.")
Expand Down Expand Up @@ -713,7 +713,6 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger,
s.logLevel,
newPluginManager,
s.config.defaultBackupLocation,
s.metrics,
s.config.formatFlag.Parse(),
)
Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/util/output/backup_storage_location_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
{Name: "Phase"},
{Name: "Last Validated"},
{Name: "Access Mode"},
{Name: "Default"},
}
)

Expand All @@ -50,6 +51,11 @@ func printBackupStorageLocation(location *velerov1api.BackupStorageLocation) []m
Object: runtime.RawExtension{Object: location},
}

isDefault := ""
if location.Spec.Default {
isDefault = "true"
nrb marked this conversation as resolved.
Show resolved Hide resolved
}

bucketAndPrefix := location.Spec.ObjectStorage.Bucket
if location.Spec.ObjectStorage.Prefix != "" {
bucketAndPrefix += "/" + location.Spec.ObjectStorage.Prefix
Expand Down Expand Up @@ -78,6 +84,7 @@ func printBackupStorageLocation(location *velerov1api.BackupStorageLocation) []m
status,
LastValidatedStr,
accessMode,
isDefault,
)

return []metav1.TableRow{row}
Expand Down
11 changes: 11 additions & 0 deletions pkg/controller/backup_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
snapshotv1beta1listers "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/listers/volumesnapshot/v1beta1"

"github.com/vmware-tanzu/velero/internal/storage"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
"github.com/vmware-tanzu/velero/pkg/discovery"
Expand Down Expand Up @@ -345,6 +346,16 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
// default storage location if not specified
if request.Spec.StorageLocation == "" {
request.Spec.StorageLocation = c.defaultBackupLocation

locationList, err := storage.ListBackupStorageLocations(context.Background(), c.kbClient, request.Namespace)
if err == nil {
for _, location := range locationList.Items {
if location.Spec.Default {
request.Spec.StorageLocation = location.Name
break
}
}
}
}

if request.Spec.DefaultVolumesToRestic == nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/backup_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func TestDefaultBackupTTL(t *testing.T) {
}

func TestProcessBackupCompletions(t *testing.T) {
defaultBackupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Bucket("store-1").Result()
defaultBackupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Default(true).Bucket("store-1").Result()

now, err := time.Parse(time.RFC1123Z, time.RFC1123Z)
require.NoError(t, err)
Expand Down
Loading