Sync CSI API objects during backup sync (#2496)

* add changelog

Signed-off-by: Ashish Amarnath <ashisham@vmware.com>

* Sync CSI API volumesnapshotcontents during backup sync

Signed-off-by: Ashish Amarnath <ashisham@vmware.com>
pull/2509/head
Ashish Amarnath 2020-05-06 07:00:05 -07:00 committed by GitHub
parent 8671a639c9
commit 577e87d1b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 11 deletions

View File

@ -0,0 +1 @@
sync backups' CSI API objects into the cluster as part of the backup sync controller

View File

@ -1,5 +1,5 @@
/*
Copyright 2017, 2019 the Velero contributors.
Copyright 2017, 2019, 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.
@ -615,6 +615,8 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.sharedInformerFactory.Velero().V1().BackupStorageLocations().Lister(),
s.config.backupSyncPeriod,
s.namespace,
s.csiSnapshotClient,
s.kubeClient,
s.config.defaultBackupLocation,
newPluginManager,
s.logger,

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 the Velero contributors.
Copyright 2017, 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.
@ -20,14 +20,17 @@ import (
"encoding/json"
"time"
snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kuberrs "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/features"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"github.com/vmware-tanzu/velero/pkg/label"
@ -42,6 +45,8 @@ type backupSyncController struct {
backupLocationClient velerov1client.BackupStorageLocationsGetter
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter
backupLister velerov1listers.BackupLister
csiSnapshotClient *snapshotterClientSet.Clientset
kubeClient kubernetes.Interface
backupStorageLocationLister velerov1listers.BackupStorageLocationLister
namespace string
defaultBackupLocation string
@ -58,6 +63,8 @@ func NewBackupSyncController(
backupStorageLocationLister velerov1listers.BackupStorageLocationLister,
syncPeriod time.Duration,
namespace string,
csiSnapshotClient *snapshotterClientSet.Clientset,
kubeClient kubernetes.Interface,
defaultBackupLocation string,
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
logger logrus.FieldLogger,
@ -77,6 +84,8 @@ func NewBackupSyncController(
defaultBackupSyncPeriod: syncPeriod,
backupLister: backupLister,
backupStorageLocationLister: backupStorageLocationLister,
csiSnapshotClient: csiSnapshotClient,
kubeClient: kubeClient,
// use variables to refer to these functions so they can be
// replaced with fakes for testing.
@ -262,6 +271,34 @@ func (c *backupSyncController) run() {
log.Debug("Synced pod volume backup into cluster")
}
}
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
// we are syncing these objects only to ensure that the storage snapshots are cleaned up
// on backup deletion or expiry.
log.Info("Syncing CSI volumesnapshotcontents in backup")
snapConts, err := backupStore.GetCSIVolumeSnapshotContents(backupName)
if err != nil {
log.WithError(errors.WithStack(err)).Error("Error getting CSI volumesnapshotcontents for this backup from backup store")
continue
}
log.Infof("Syncing %d CSI volumesnapshotcontents in backup", len(snapConts))
for _, snapCont := range snapConts {
// TODO: Reset ResourceVersion prior to persisting VolumeSnapshotContents
snapCont.ResourceVersion = ""
created, err := c.csiSnapshotClient.SnapshotV1beta1().VolumeSnapshotContents().Create(snapCont)
switch {
case err != nil && kuberrs.IsAlreadyExists(err):
log.Debugf("volumesnapshotcontent %s already exists in cluster", snapCont.Name)
continue
case err != nil && !kuberrs.IsAlreadyExists(err):
log.WithError(errors.WithStack(err)).Errorf("Error syncing volumesnapshotcontent %s into cluster", snapCont.Name)
continue
default:
log.Infof("Created CSI volumesnapshotcontent %s", created.Name)
}
}
}
}
c.deleteOrphanedBackups(location.Name, backupStoreBackups, log)

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 the Velero contributors.
Copyright 2017, 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.
@ -345,6 +345,8 @@ func TestBackupSyncControllerRun(t *testing.T) {
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient
nil, // kubeClient
"",
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
velerotest.NewLogger(),
@ -568,6 +570,8 @@ func TestDeleteOrphanedBackups(t *testing.T) {
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient
nil, // kubeClient
"",
nil, // new plugin manager func
velerotest.NewLogger(),
@ -659,6 +663,8 @@ func TestStorageLabelsInDeleteOrphanedBackups(t *testing.T) {
sharedInformers.Velero().V1().BackupStorageLocations().Lister(),
time.Duration(0),
test.namespace,
nil, // csiSnapshotClient
nil, // kubeClient
"",
nil, // new plugin manager func
velerotest.NewLogger(),

View File

@ -1,12 +1,32 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
/*
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 mocks
import io "io"
import mock "github.com/stretchr/testify/mock"
import persistence "github.com/vmware-tanzu/velero/pkg/persistence"
import v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
import volume "github.com/vmware-tanzu/velero/pkg/volume"
import (
io "io"
mock "github.com/stretchr/testify/mock"
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
persistence "github.com/vmware-tanzu/velero/pkg/persistence"
volume "github.com/vmware-tanzu/velero/pkg/volume"
)
// BackupStore is an autogenerated mock type for the BackupStore type
type BackupStore struct {
@ -253,3 +273,13 @@ func (_m *BackupStore) PutRestoreResults(backup string, restore string, results
return r0
}
func (_m *BackupStore) GetCSIVolumeSnapshots(backup string) ([]*snapshotv1beta1api.VolumeSnapshot, error) {
panic("Not implemented")
return nil, nil
}
func (_m *BackupStore) GetCSIVolumeSnapshotContents(backup string) ([]*snapshotv1beta1api.VolumeSnapshotContent, error) {
panic("Not implemented")
return nil, nil
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
Copyright 2018, 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.
@ -24,6 +24,7 @@ import (
"strings"
"time"
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kerrors "k8s.io/apimachinery/pkg/util/errors"
@ -58,7 +59,8 @@ type BackupStore interface {
GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error)
GetPodVolumeBackups(name string) ([]*velerov1api.PodVolumeBackup, error)
GetBackupContents(name string) (io.ReadCloser, error)
// TODO(nrb-csi): Any Get methods relevant to the CSI VolumeSnapshots should be added with the client-side PRs.
GetCSIVolumeSnapshots(name string) ([]*snapshotv1beta1api.VolumeSnapshot, error)
GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1beta1api.VolumeSnapshotContent, error)
// BackupExists checks if the backup metadata file exists in object storage.
BackupExists(bucket, backupName string) (bool, error)
@ -324,6 +326,42 @@ func decode(jsongzReader io.Reader, into interface{}) error {
return nil
}
func (s *objectBackupStore) GetCSIVolumeSnapshots(name string) ([]*snapshotv1beta1api.VolumeSnapshot, error) {
res, err := tryGet(s.objectStore, s.bucket, s.layout.getCSIVolumeSnapshotKey(name))
if err != nil {
return nil, err
}
if res == nil {
// this indicates that the no CSI volumesnapshots were prensent in the backup
return nil, nil
}
defer res.Close()
var csiSnaps []*snapshotv1beta1api.VolumeSnapshot
if err := decode(res, &csiSnaps); err != nil {
return nil, err
}
return csiSnaps, nil
}
func (s *objectBackupStore) GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1beta1api.VolumeSnapshotContent, error) {
res, err := tryGet(s.objectStore, s.bucket, s.layout.getCSIVolumeSnapshotContentsKey(name))
if err != nil {
return nil, err
}
if res == nil {
// this indicates that the no CSI volumesnapshotcontents were prensent in the backup
return nil, nil
}
defer res.Close()
var snapConts []*snapshotv1beta1api.VolumeSnapshotContent
if err := decode(res, &snapConts); err != nil {
return nil, err
}
return snapConts, nil
}
func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.PodVolumeBackup, error) {
// if the podvolumebackups file doesn't exist, we don't want to return an error, since
// a legacy backup or a backup with no pod volume backups would not have this file, so