Skip to content

Commit

Permalink
Generate VolumeInfo.
Browse files Browse the repository at this point in the history
Remove CSI VolumeSnapshot listter and the informer.
Add download the VolumeInfos metadata for backup.

Signed-off-by: Xun Jiang <[email protected]>
  • Loading branch information
Xun Jiang committed Nov 21, 2023
1 parent 939dd71 commit d82c4c9
Show file tree
Hide file tree
Showing 23 changed files with 1,364 additions and 181 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/7100-blackpiglet
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generate VolumeInfo for backup.
1 change: 1 addition & 0 deletions config/crd/v1/bases/velero.io_downloadrequests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ spec:
- RestoreItemOperations
- CSIBackupVolumeSnapshots
- CSIBackupVolumeSnapshotContents
- BackupVolumeInfos
type: string
name:
description: Name is the name of the Kubernetes resource with
Expand Down
2 changes: 1 addition & 1 deletion config/crd/v1/crds/crds.go

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions design/pv_backup_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type VolumeInfo struct {
// CSISnapshotInfo is used for displaying the CSI snapshot status
type CSISnapshotInfo struct {
SnapshotHandle string // It's the storage provider's snapshot ID for CSI.
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
Size int64 // The snapshot corresponding volume size.

Driver string // The name of the CSI driver.
VSCName string // The name of the VolumeSnapshotContent.
Expand All @@ -70,7 +70,7 @@ type CSISnapshotInfo struct {
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
type SnapshotDataMovementInfo struct {
DataMover string // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
UploaderType string // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover.
UploaderType string // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`.
RetainedSnapshot string // The name or ID of the snapshot associated object(SAO). SAO is used to support local snapshots for the snapshot data mover, e.g. it could be a VolumeSnapshot for CSI snapshot data moign/pv_backup_info.
SnapshotHandle string // It's the filesystem repository's snapshot ID.

Expand All @@ -79,7 +79,6 @@ type SnapshotDataMovementInfo struct {
// VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status.
type VeleroNativeSnapshotInfo struct {
SnapshotHandle string // It's the storage provider's snapshot ID for the Velero-native snapshot.
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.

VolumeType string // The cloud provider snapshot volume type.
VolumeAZ string // The cloud provider snapshot volume's availability zones.
Expand All @@ -89,11 +88,12 @@ type VeleroNativeSnapshotInfo struct {
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
type PodVolumeBackupInfo struct {
SnapshotHandle string // It's the file-system uploader's snapshot ID for PodVolumeBackup.
Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
Size int64 // The snapshot corresponding volume size.

UploaderType string // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover.
UploaderType string // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
VolumeName string // The PVC's corresponding volume name used by Pod: https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48
PodName string // The Pod name mounting this PVC. The format should be <namespace-name>/<pod-name>.
PodName string // The Pod name mounting this PVC.
PodNamespace string // The Pod namespace.
NodeName string // The PVB-taken k8s node's name.
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/velero/v1/download_request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type DownloadRequestSpec struct {
}

// DownloadTargetKind represents what type of file to download.
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents;BackupVolumeInfos
type DownloadTargetKind string

const (
Expand All @@ -41,6 +41,7 @@ const (
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
)

// DownloadTarget is the specification for what kind of file to download, and the name of the
Expand Down
5 changes: 3 additions & 2 deletions pkg/backup/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
req := &Request{
Backup: defaultBackup().Result(),
SkippedPVTracker: NewSkipPVTracker(),
PVMap: map[string]PvcPvInfo{},
}
backupFile := bytes.NewBuffer([]byte{})

Expand All @@ -84,8 +85,8 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
builder.ForDeployment("zoo", "raz").Result(),
),
test.PVs(
builder.ForPersistentVolume("bar").Result(),
builder.ForPersistentVolume("baz").Result(),
builder.ForPersistentVolume("bar").ClaimRef("foo", "pvc1").Result(),
builder.ForPersistentVolume("baz").ClaimRef("bar", "pvc2").Result(),
),
}
for _, resource := range apiResources {
Expand Down
37 changes: 37 additions & 0 deletions pkg/backup/item_backupper.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
namespace = metadata.GetNamespace()

if groupResource == kuberesource.PersistentVolumes {
if err := ib.addVolumeInfo(obj, log); err != nil {
backupErrs = append(backupErrs, err)
}

Check warning on line 255 in pkg/backup/item_backupper.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/item_backupper.go#L254-L255

Added lines #L254 - L255 were not covered by tests

if err := ib.takePVSnapshot(obj, log); err != nil {
backupErrs = append(backupErrs, err)
}
Expand Down Expand Up @@ -685,6 +689,39 @@ func (ib *itemBackupper) unTrackSkippedPV(obj runtime.Unstructured, groupResourc
}
}

func (ib *itemBackupper) addVolumeInfo(obj runtime.Unstructured, log logrus.FieldLogger) error {
pv := new(corev1api.PersistentVolume)
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pv)
if err != nil {
log.WithError(err).Warnf("Fail to convert PV")
return err
}

Check warning on line 698 in pkg/backup/item_backupper.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/item_backupper.go#L696-L698

Added lines #L696 - L698 were not covered by tests

if ib.backupRequest.PVMap == nil {
ib.backupRequest.PVMap = make(map[string]PvcPvInfo)
}

pvcName := ""
pvcNamespace := ""
if pv.Spec.ClaimRef != nil {
pvcName = pv.Spec.ClaimRef.Name
pvcNamespace = pv.Spec.ClaimRef.Namespace

ib.backupRequest.PVMap[pvcNamespace+"/"+pvcName] = PvcPvInfo{
PVCName: pvcName,
PVCNamespace: pvcNamespace,
PV: *pv,
}
}

ib.backupRequest.PVMap[pv.Name] = PvcPvInfo{
PVCName: pvcName,
PVCNamespace: pvcNamespace,
PV: *pv,
}
return nil
}

// convert the input object to PV/PVC and get the PV name
func getPVName(obj runtime.Unstructured, groupResource schema.GroupResource) (string, error) {
if groupResource == kuberesource.PersistentVolumes {
Expand Down
54 changes: 54 additions & 0 deletions pkg/backup/item_backupper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package backup
import (
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/volume"

"github.com/stretchr/testify/assert"
corev1api "k8s.io/api/core/v1"
Expand Down Expand Up @@ -237,3 +239,55 @@ func TestRandom(t *testing.T) {
err2 := runtime.DefaultUnstructuredConverter.FromUnstructured(o, pvc)
t.Logf("err1: %v, err2: %v", err1, err2)
}

func TestAddVolumeInfo(t *testing.T) {
tests := []struct {
name string
pv *corev1api.PersistentVolume
expectedVolumeInfo map[string]PvcPvInfo
}{
{
name: "PV has ClaimRef",
pv: builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
expectedVolumeInfo: map[string]PvcPvInfo{
"testPV": {
PVCName: "testPVC",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
},
"testNS/testPVC": {
PVCName: "testPVC",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
},
},
},
{
name: "PV has no ClaimRef",
pv: builder.ForPersistentVolume("testPV").Result(),
expectedVolumeInfo: map[string]PvcPvInfo{
"testPV": {
PVCName: "",
PVCNamespace: "",
PV: *builder.ForPersistentVolume("testPV").Result(),
},
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ib := itemBackupper{}
ib.backupRequest = new(Request)
ib.backupRequest.VolumeInfos.VolumeInfos = make([]volume.VolumeInfo, 0)

pvObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pv)
require.NoError(t, err)
logger := logrus.StandardLogger()

err = ib.addVolumeInfo(&unstructured.Unstructured{Object: pvObj}, logger)
require.NoError(t, err)
require.Equal(t, tc.expectedVolumeInfo, ib.backupRequest.PVMap)
})
}
}
8 changes: 8 additions & 0 deletions pkg/backup/pv_skip_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ type SkippedPV struct {
Reasons []PVSkipReason `json:"reasons"`
}

func (s *SkippedPV) SerializeSkipReasons() string {
ret := ""
for _, reason := range s.Reasons {
ret = ret + reason.Approach + ": " + reason.Reason + ";"
}
return ret
}

type PVSkipReason struct {
Approach string `json:"approach"`
Reason string `json:"reason"`
Expand Down
12 changes: 12 additions & 0 deletions pkg/backup/pv_skip_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestSummary(t *testing.T) {
Expand Down Expand Up @@ -41,3 +42,14 @@ func TestSummary(t *testing.T) {
}
assert.Equal(t, expected, tracker.Summary())
}

func TestSerializeSkipReasons(t *testing.T) {
tracker := NewSkipPVTracker()
//tracker.Track("pv5", "", "skipped due to policy")
tracker.Track("pv3", podVolumeApproach, "it's set to opt-out")
tracker.Track("pv3", csiSnapshotApproach, "not applicable for CSI ")

for _, skippedPV := range tracker.Summary() {
require.Equal(t, "csiSnapshot: not applicable for CSI ;podvolume: it's set to opt-out;", skippedPV.SerializeSkipReasons())
}
}
12 changes: 12 additions & 0 deletions pkg/backup/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"fmt"
"sort"

corev1api "k8s.io/api/core/v1"

"github.com/vmware-tanzu/velero/internal/hook"
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
Expand Down Expand Up @@ -52,6 +54,16 @@ type Request struct {
itemOperationsList *[]*itemoperation.BackupOperation
ResPolicies *resourcepolicies.Policies
SkippedPVTracker *skipPVTracker
// A map contains the backup-included PV detail content.
// The key is PV name or PVC name(The format is PVC-namespace/PVC-name)
PVMap map[string]PvcPvInfo
VolumeInfos volume.VolumeInfos
}

type PvcPvInfo struct {
PVCName string
PVCNamespace string
PV corev1api.PersistentVolume
}

// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
Expand Down
21 changes: 9 additions & 12 deletions pkg/backup/snapshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"

snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/sets"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -17,25 +16,23 @@ import (

// Common function to update the status of CSI snapshots
// returns VolumeSnapshot, VolumeSnapshotContent, VolumeSnapshotClasses referenced
func UpdateBackupCSISnapshotsStatus(client kbclient.Client, volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister, backup *velerov1api.Backup, backupLog logrus.FieldLogger) (volumeSnapshots []snapshotv1api.VolumeSnapshot, volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass) {
func UpdateBackupCSISnapshotsStatus(client kbclient.Client, globalCRClient kbclient.Client, backup *velerov1api.Backup, backupLog logrus.FieldLogger) (volumeSnapshots []snapshotv1api.VolumeSnapshot, volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass) {

Check warning on line 19 in pkg/backup/snapshots.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/snapshots.go#L19

Added line #L19 was not covered by tests
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
backupLog.Info("backup SnapshotMoveData is set to true, skip VolumeSnapshot resource persistence.")
} else if features.IsEnabled(velerov1api.CSIFeatureFlag) {
selector := label.NewSelectorForBackup(backup.Name)
vscList := &snapshotv1api.VolumeSnapshotContentList{}

if volumeSnapshotLister != nil {
tmpVSs, err := volumeSnapshotLister.List(label.NewSelectorForBackup(backup.Name))
if err != nil {
backupLog.Error(err)
}
for _, vs := range tmpVSs {
volumeSnapshots = append(volumeSnapshots, *vs)
}
vsList := new(snapshotv1api.VolumeSnapshotList)
err := globalCRClient.List(context.TODO(), vsList, &kbclient.ListOptions{
LabelSelector: label.NewSelectorForBackup(backup.Name),
})
if err != nil {
backupLog.Error(err)

Check warning on line 31 in pkg/backup/snapshots.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/snapshots.go#L26-L31

Added lines #L26 - L31 were not covered by tests
}
volumeSnapshots = append(volumeSnapshots, vsList.Items...)

Check warning on line 33 in pkg/backup/snapshots.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/snapshots.go#L33

Added line #L33 was not covered by tests

err := client.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector})
if err != nil {
if err := client.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector}); err != nil {

Check warning on line 35 in pkg/backup/snapshots.go

View check run for this annotation

Codecov / codecov/patch

pkg/backup/snapshots.go#L35

Added line #L35 was not covered by tests
backupLog.Error(err)
}
if len(vscList.Items) >= 0 {
Expand Down
15 changes: 15 additions & 0 deletions pkg/builder/volume_snapshot_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package builder

import (
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -68,7 +69,21 @@ func (v *VolumeSnapshotBuilder) BoundVolumeSnapshotContentName(vscName string) *
return v
}

// SourcePVC set the built VolumeSnapshot's spec.Source.PersistentVolumeClaimName.
func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
v.object.Spec.Source.PersistentVolumeClaimName = &name
return v
}

// RestoreSize set the built VolumeSnapshot's status.RestoreSize.
func (v *VolumeSnapshotBuilder) RestoreSize(size string) *VolumeSnapshotBuilder {
resourceSize := resource.MustParse(size)
v.object.Status.RestoreSize = &resourceSize
return v
}

// VolumeSnapshotClass set the built VolumeSnapshot's spec.VolumeSnapshotClassName value.
func (v *VolumeSnapshotBuilder) VolumeSnapshotClass(name string) *VolumeSnapshotBuilder {
v.object.Spec.VolumeSnapshotClassName = &name
return v
}
16 changes: 16 additions & 0 deletions pkg/builder/volume_snapshot_content_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func (v *VolumeSnapshotContentBuilder) DeletionPolicy(policy snapshotv1api.Delet
return v
}

// VolumeSnapshotRef sets the built VolumeSnapshotContent's spec.VolumeSnapshotRef value.
func (v *VolumeSnapshotContentBuilder) VolumeSnapshotRef(namespace, name string) *VolumeSnapshotContentBuilder {
v.object.Spec.VolumeSnapshotRef = v1.ObjectReference{
APIVersion: "snapshot.storage.k8s.io/v1",
Expand All @@ -68,3 +69,18 @@ func (v *VolumeSnapshotContentBuilder) VolumeSnapshotRef(namespace, name string)
}
return v
}

// VolumeSnapshotClassName sets the built VolumeSnapshotContent's spec.VolumeSnapshotClassName value.
func (v *VolumeSnapshotContentBuilder) VolumeSnapshotClassName(name string) *VolumeSnapshotContentBuilder {
v.object.Spec.VolumeSnapshotClassName = &name
return v
}

// ObjectMeta applies functional options to the VolumeSnapshotContent's ObjectMeta.
func (v *VolumeSnapshotContentBuilder) ObjectMeta(opts ...ObjectMetaOpt) *VolumeSnapshotContentBuilder {
for _, opt := range opts {
opt(v.object)
}

return v
}
4 changes: 4 additions & 0 deletions pkg/client/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
k8scheme "k8s.io/client-go/kubernetes/scheme"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"

snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -158,6 +159,9 @@ func (f *factory) KubebuilderClient() (kbclient.Client, error) {
if err := apiextv1.AddToScheme(scheme); err != nil {
return nil, err
}
if err := snapshotv1api.AddToScheme(scheme); err != nil {
return nil, err
}

Check warning on line 164 in pkg/client/factory.go

View check run for this annotation

Codecov / codecov/patch

pkg/client/factory.go#L163-L164

Added lines #L163 - L164 were not covered by tests
kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{
Scheme: scheme,
})
Expand Down
Loading

0 comments on commit d82c4c9

Please sign in to comment.