Merge branch 'main' of https://github.com/qiuming-best/velero into kopia-parallelism
commit
c2d4495efe
|
@ -7,7 +7,7 @@ jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v6.0.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days. If a Velero team member has requested log or more information, please provide the output of the shared commands."
|
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days. If a Velero team member has requested log or more information, please provide the output of the shared commands."
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Generate VolumeInfo for backup.
|
|
@ -53,6 +53,7 @@ spec:
|
||||||
- RestoreItemOperations
|
- RestoreItemOperations
|
||||||
- CSIBackupVolumeSnapshots
|
- CSIBackupVolumeSnapshots
|
||||||
- CSIBackupVolumeSnapshotContents
|
- CSIBackupVolumeSnapshotContents
|
||||||
|
- BackupVolumeInfos
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
description: Name is the name of the Kubernetes resource with
|
description: Name is the name of the Kubernetes resource with
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -61,7 +61,7 @@ type VolumeInfo struct {
|
||||||
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||||
type CSISnapshotInfo struct {
|
type CSISnapshotInfo struct {
|
||||||
SnapshotHandle string // It's the storage provider's snapshot ID for CSI.
|
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.
|
Driver string // The name of the CSI driver.
|
||||||
VSCName string // The name of the VolumeSnapshotContent.
|
VSCName string // The name of the VolumeSnapshotContent.
|
||||||
|
@ -70,7 +70,7 @@ type CSISnapshotInfo struct {
|
||||||
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
||||||
type SnapshotDataMovementInfo struct {
|
type SnapshotDataMovementInfo struct {
|
||||||
DataMover string // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
|
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.
|
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.
|
SnapshotHandle string // It's the filesystem repository's snapshot ID.
|
||||||
|
|
||||||
|
@ -79,7 +79,6 @@ type SnapshotDataMovementInfo struct {
|
||||||
// VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status.
|
// VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status.
|
||||||
type VeleroNativeSnapshotInfo struct {
|
type VeleroNativeSnapshotInfo struct {
|
||||||
SnapshotHandle string // It's the storage provider's snapshot ID for the Velero-native snapshot.
|
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.
|
VolumeType string // The cloud provider snapshot volume type.
|
||||||
VolumeAZ string // The cloud provider snapshot volume's availability zones.
|
VolumeAZ string // The cloud provider snapshot volume's availability zones.
|
||||||
|
@ -89,11 +88,12 @@ type VeleroNativeSnapshotInfo struct {
|
||||||
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
||||||
type PodVolumeBackupInfo struct {
|
type PodVolumeBackupInfo struct {
|
||||||
SnapshotHandle string // It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
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
|
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.
|
NodeName string // The PVB-taken k8s node's name.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,30 +19,37 @@ type JSONPatch struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *JSONPatch) ToString() string {
|
func (p *JSONPatch) ToString() string {
|
||||||
if addQuotes(p.Value) {
|
if addQuotes(&p.Value) {
|
||||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addQuotes(value string) bool {
|
func addQuotes(value *string) bool {
|
||||||
if value == "" {
|
if *value == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// if value is escaped, remove escape and add quotes
|
||||||
|
// this is useful for scenarios where boolean, null and numbers are required to be set as string.
|
||||||
|
if strings.HasPrefix(*value, "\"") && strings.HasSuffix(*value, "\"") {
|
||||||
|
*value = strings.TrimPrefix(*value, "\"")
|
||||||
|
*value = strings.TrimSuffix(*value, "\"")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// if value is null, then don't add quotes
|
// if value is null, then don't add quotes
|
||||||
if value == "null" {
|
if *value == "null" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if value is a boolean, then don't add quotes
|
// if value is a boolean, then don't add quotes
|
||||||
if _, err := strconv.ParseBool(value); err == nil {
|
if strings.ToLower(*value) == "true" || strings.ToLower(*value) == "false" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if value is a json object or array, then don't add quotes.
|
// if value is a json object or array, then don't add quotes.
|
||||||
if strings.HasPrefix(value, "{") || strings.HasPrefix(value, "[") {
|
if strings.HasPrefix(*value, "{") || strings.HasPrefix(*value, "[") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if value is a number, then don't add quotes
|
// if value is a number, then don't add quotes
|
||||||
if _, err := strconv.ParseFloat(value, 64); err == nil {
|
if _, err := strconv.ParseFloat(*value, 64); err == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -256,6 +256,64 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cm9 := &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-configmap",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: replace\n path: \"/value/bool\"\n value: \"\\\"true\\\"\"\n\n\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rules9 := &ResourceModifiers{
|
||||||
|
Version: "v1",
|
||||||
|
ResourceModifierRules: []ResourceModifierRule{
|
||||||
|
{
|
||||||
|
Conditions: Conditions{
|
||||||
|
GroupResource: "deployments.apps",
|
||||||
|
ResourceNameRegex: "^test-.*$",
|
||||||
|
Namespaces: []string{"bar", "foo"},
|
||||||
|
},
|
||||||
|
Patches: []JSONPatch{
|
||||||
|
{
|
||||||
|
Operation: "replace",
|
||||||
|
Path: "/value/bool",
|
||||||
|
Value: `"true"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cm10 := &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-configmap",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: replace\n path: \"/value/bool\"\n value: \"true\"\n\n\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rules10 := &ResourceModifiers{
|
||||||
|
Version: "v1",
|
||||||
|
ResourceModifierRules: []ResourceModifierRule{
|
||||||
|
{
|
||||||
|
Conditions: Conditions{
|
||||||
|
GroupResource: "deployments.apps",
|
||||||
|
ResourceNameRegex: "^test-.*$",
|
||||||
|
Namespaces: []string{"bar", "foo"},
|
||||||
|
},
|
||||||
|
Patches: []JSONPatch{
|
||||||
|
{
|
||||||
|
Operation: "replace",
|
||||||
|
Path: "/value/bool",
|
||||||
|
Value: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
cm *v1.ConfigMap
|
cm *v1.ConfigMap
|
||||||
|
@ -338,6 +396,22 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||||
want: rules8,
|
want: rules8,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bool value as string",
|
||||||
|
args: args{
|
||||||
|
cm: cm9,
|
||||||
|
},
|
||||||
|
want: rules9,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool value as bool",
|
||||||
|
args: args{
|
||||||
|
cm: cm10,
|
||||||
|
},
|
||||||
|
want: rules10,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -480,7 +554,24 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmTrue := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmFalse := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"test": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
type fields struct {
|
type fields struct {
|
||||||
Version string
|
Version string
|
||||||
ResourceModifierRules []ResourceModifierRule
|
ResourceModifierRules []ResourceModifierRule
|
||||||
|
@ -496,6 +587,33 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantObj *unstructured.Unstructured
|
wantObj *unstructured.Unstructured
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "configmap true false string",
|
||||||
|
fields: fields{
|
||||||
|
Version: "v1",
|
||||||
|
ResourceModifierRules: []ResourceModifierRule{
|
||||||
|
{
|
||||||
|
Conditions: Conditions{
|
||||||
|
GroupResource: "configmaps",
|
||||||
|
ResourceNameRegex: ".*",
|
||||||
|
},
|
||||||
|
Patches: []JSONPatch{
|
||||||
|
{
|
||||||
|
Operation: "replace",
|
||||||
|
Path: "/data/test",
|
||||||
|
Value: `"false"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
obj: cmTrue.DeepCopy(),
|
||||||
|
groupResource: "configmaps",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
wantObj: cmFalse.DeepCopy(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Regex throws error",
|
name: "Invalid Regex throws error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
|
|
|
@ -25,7 +25,7 @@ type DownloadRequestSpec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadTargetKind represents what type of file to download.
|
// 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
|
type DownloadTargetKind string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -41,6 +41,7 @@ const (
|
||||||
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
|
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
|
||||||
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
||||||
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
||||||
|
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DownloadTarget is the specification for what kind of file to download, and the name of the
|
// DownloadTarget is the specification for what kind of file to download, and the name of the
|
||||||
|
|
|
@ -71,6 +71,7 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
|
||||||
req := &Request{
|
req := &Request{
|
||||||
Backup: defaultBackup().Result(),
|
Backup: defaultBackup().Result(),
|
||||||
SkippedPVTracker: NewSkipPVTracker(),
|
SkippedPVTracker: NewSkipPVTracker(),
|
||||||
|
PVMap: map[string]PvcPvInfo{},
|
||||||
}
|
}
|
||||||
backupFile := bytes.NewBuffer([]byte{})
|
backupFile := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
@ -84,8 +85,8 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
|
||||||
builder.ForDeployment("zoo", "raz").Result(),
|
builder.ForDeployment("zoo", "raz").Result(),
|
||||||
),
|
),
|
||||||
test.PVs(
|
test.PVs(
|
||||||
builder.ForPersistentVolume("bar").Result(),
|
builder.ForPersistentVolume("bar").ClaimRef("foo", "pvc1").Result(),
|
||||||
builder.ForPersistentVolume("baz").Result(),
|
builder.ForPersistentVolume("baz").ClaimRef("bar", "pvc2").Result(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
for _, resource := range apiResources {
|
for _, resource := range apiResources {
|
||||||
|
|
|
@ -250,6 +250,10 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
|
||||||
namespace = metadata.GetNamespace()
|
namespace = metadata.GetNamespace()
|
||||||
|
|
||||||
if groupResource == kuberesource.PersistentVolumes {
|
if groupResource == kuberesource.PersistentVolumes {
|
||||||
|
if err := ib.addVolumeInfo(obj, log); err != nil {
|
||||||
|
backupErrs = append(backupErrs, err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := ib.takePVSnapshot(obj, log); err != nil {
|
if err := ib.takePVSnapshot(obj, log); err != nil {
|
||||||
backupErrs = append(backupErrs, err)
|
backupErrs = append(backupErrs, err)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// convert the input object to PV/PVC and get the PV name
|
||||||
func getPVName(obj runtime.Unstructured, groupResource schema.GroupResource) (string, error) {
|
func getPVName(obj runtime.Unstructured, groupResource schema.GroupResource) (string, error) {
|
||||||
if groupResource == kuberesource.PersistentVolumes {
|
if groupResource == kuberesource.PersistentVolumes {
|
||||||
|
|
|
@ -19,10 +19,12 @@ package backup
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
corev1api "k8s.io/api/core/v1"
|
corev1api "k8s.io/api/core/v1"
|
||||||
|
@ -237,3 +239,55 @@ func TestRandom(t *testing.T) {
|
||||||
err2 := runtime.DefaultUnstructuredConverter.FromUnstructured(o, pvc)
|
err2 := runtime.DefaultUnstructuredConverter.FromUnstructured(o, pvc)
|
||||||
t.Logf("err1: %v, err2: %v", err1, err2)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,14 @@ type SkippedPV struct {
|
||||||
Reasons []PVSkipReason `json:"reasons"`
|
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 {
|
type PVSkipReason struct {
|
||||||
Approach string `json:"approach"`
|
Approach string `json:"approach"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSummary(t *testing.T) {
|
func TestSummary(t *testing.T) {
|
||||||
|
@ -41,3 +42,14 @@ func TestSummary(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, tracker.Summary())
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
corev1api "k8s.io/api/core/v1"
|
||||||
|
|
||||||
"github.com/vmware-tanzu/velero/internal/hook"
|
"github.com/vmware-tanzu/velero/internal/hook"
|
||||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
|
@ -52,6 +54,16 @@ type Request struct {
|
||||||
itemOperationsList *[]*itemoperation.BackupOperation
|
itemOperationsList *[]*itemoperation.BackupOperation
|
||||||
ResPolicies *resourcepolicies.Policies
|
ResPolicies *resourcepolicies.Policies
|
||||||
SkippedPVTracker *skipPVTracker
|
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
|
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
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"
|
"github.com/sirupsen/logrus"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
@ -17,27 +16,25 @@ import (
|
||||||
|
|
||||||
// Common function to update the status of CSI snapshots
|
// Common function to update the status of CSI snapshots
|
||||||
// returns VolumeSnapshot, VolumeSnapshotContent, VolumeSnapshotClasses referenced
|
// 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) {
|
||||||
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
||||||
backupLog.Info("backup SnapshotMoveData is set to true, skip VolumeSnapshot resource persistence.")
|
backupLog.Info("backup SnapshotMoveData is set to true, skip VolumeSnapshot resource persistence.")
|
||||||
} else if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
} else if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||||
selector := label.NewSelectorForBackup(backup.Name)
|
selector := label.NewSelectorForBackup(backup.Name)
|
||||||
vscList := &snapshotv1api.VolumeSnapshotContentList{}
|
vscList := &snapshotv1api.VolumeSnapshotContentList{}
|
||||||
|
|
||||||
if volumeSnapshotLister != nil {
|
vsList := new(snapshotv1api.VolumeSnapshotList)
|
||||||
tmpVSs, err := volumeSnapshotLister.List(label.NewSelectorForBackup(backup.Name))
|
err := globalCRClient.List(context.TODO(), vsList, &kbclient.ListOptions{
|
||||||
if err != nil {
|
LabelSelector: label.NewSelectorForBackup(backup.Name),
|
||||||
backupLog.Error(err)
|
})
|
||||||
}
|
|
||||||
for _, vs := range tmpVSs {
|
|
||||||
volumeSnapshots = append(volumeSnapshots, *vs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
backupLog.Error(err)
|
backupLog.Error(err)
|
||||||
}
|
}
|
||||||
|
volumeSnapshots = append(volumeSnapshots, vsList.Items...)
|
||||||
|
|
||||||
|
if err := client.List(context.Background(), vscList, &kbclient.ListOptions{LabelSelector: selector}); err != nil {
|
||||||
|
backupLog.Error(err)
|
||||||
|
}
|
||||||
if len(vscList.Items) >= 0 {
|
if len(vscList.Items) >= 0 {
|
||||||
volumeSnapshotContents = vscList.Items
|
volumeSnapshotContents = vscList.Items
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
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"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,7 +69,21 @@ func (v *VolumeSnapshotBuilder) BoundVolumeSnapshotContentName(vscName string) *
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SourcePVC set the built VolumeSnapshot's spec.Source.PersistentVolumeClaimName.
|
||||||
func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
|
func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
|
||||||
v.object.Spec.Source.PersistentVolumeClaimName = &name
|
v.object.Spec.Source.PersistentVolumeClaimName = &name
|
||||||
return v
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ func (v *VolumeSnapshotContentBuilder) DeletionPolicy(policy snapshotv1api.Delet
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VolumeSnapshotRef sets the built VolumeSnapshotContent's spec.VolumeSnapshotRef value.
|
||||||
func (v *VolumeSnapshotContentBuilder) VolumeSnapshotRef(namespace, name string) *VolumeSnapshotContentBuilder {
|
func (v *VolumeSnapshotContentBuilder) VolumeSnapshotRef(namespace, name string) *VolumeSnapshotContentBuilder {
|
||||||
v.object.Spec.VolumeSnapshotRef = v1.ObjectReference{
|
v.object.Spec.VolumeSnapshotRef = v1.ObjectReference{
|
||||||
APIVersion: "snapshot.storage.k8s.io/v1",
|
APIVersion: "snapshot.storage.k8s.io/v1",
|
||||||
|
@ -68,3 +69,18 @@ func (v *VolumeSnapshotContentBuilder) VolumeSnapshotRef(namespace, name string)
|
||||||
}
|
}
|
||||||
return v
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
k8scheme "k8s.io/client-go/kubernetes/scheme"
|
k8scheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
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/pkg/errors"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -158,6 +159,9 @@ func (f *factory) KubebuilderClient() (kbclient.Client, error) {
|
||||||
if err := apiextv1.AddToScheme(scheme); err != nil {
|
if err := apiextv1.AddToScheme(scheme); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := snapshotv1api.AddToScheme(scheme); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{
|
kubebuilderClient, err := kbclient.New(clientConfig, kbclient.Options{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,21 +23,16 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
logrusr "github.com/bombsimon/logrusr/v3"
|
logrusr "github.com/bombsimon/logrusr/v3"
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||||
snapshotv1client "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned"
|
|
||||||
snapshotv1informers "github.com/kubernetes-csi/external-snapshotter/client/v4/informers/externalversions"
|
|
||||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1api "k8s.io/api/core/v1"
|
corev1api "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
@ -244,15 +239,17 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
namespace string
|
namespace string
|
||||||
metricsAddress string
|
metricsAddress string
|
||||||
kubeClientConfig *rest.Config
|
kubeClientConfig *rest.Config
|
||||||
kubeClient kubernetes.Interface
|
kubeClient kubernetes.Interface
|
||||||
discoveryClient discovery.DiscoveryInterface
|
discoveryClient discovery.DiscoveryInterface
|
||||||
discoveryHelper velerodiscovery.Helper
|
discoveryHelper velerodiscovery.Helper
|
||||||
dynamicClient dynamic.Interface
|
dynamicClient dynamic.Interface
|
||||||
csiSnapshotClient *snapshotv1client.Clientset
|
// controller-runtime client. the difference from the controller-manager's client
|
||||||
csiSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
// is that the the controller-manager's client is limited to list namespaced-scoped
|
||||||
|
// resources in the namespace where Velero is installed, or the cluster-scoped
|
||||||
|
// resources. The crClient doesn't have the limitation.
|
||||||
crClient ctrlclient.Client
|
crClient ctrlclient.Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelFunc context.CancelFunc
|
cancelFunc context.CancelFunc
|
||||||
|
@ -399,23 +396,6 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
|
||||||
featureVerifier: featureVerifier,
|
featureVerifier: featureVerifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup CSI snapshot client and lister
|
|
||||||
var csiSnapClient *snapshotv1client.Clientset
|
|
||||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
|
||||||
csiSnapClient, err = snapshotv1client.NewForConfig(clientConfig)
|
|
||||||
if err != nil {
|
|
||||||
cancelFunc()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.csiSnapshotClient = csiSnapClient
|
|
||||||
|
|
||||||
s.csiSnapshotLister, err = s.getCSIVolumeSnapshotListers()
|
|
||||||
if err != nil {
|
|
||||||
cancelFunc()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,40 +595,6 @@ func (s *server) initRepoManager() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) getCSIVolumeSnapshotListers() (vsLister snapshotv1listers.VolumeSnapshotLister, err error) {
|
|
||||||
_, err = s.discoveryClient.ServerResourcesForGroupVersion(snapshotv1api.SchemeGroupVersion.String())
|
|
||||||
switch {
|
|
||||||
case apierrors.IsNotFound(err):
|
|
||||||
// CSI is enabled, but the required CRDs aren't installed, so halt.
|
|
||||||
s.logger.Warnf("The '%s' feature flag was specified, but CSI API group [%s] was not found.", velerov1api.CSIFeatureFlag, snapshotv1api.SchemeGroupVersion.String())
|
|
||||||
err = nil
|
|
||||||
case err == nil:
|
|
||||||
wrapper := NewCSIInformerFactoryWrapper(s.csiSnapshotClient)
|
|
||||||
|
|
||||||
s.logger.Debug("Creating CSI listers")
|
|
||||||
// Access the wrapped factory directly here since we've already done the feature flag check above to know it's safe.
|
|
||||||
vsLister = wrapper.factory.Snapshot().V1().VolumeSnapshots().Lister()
|
|
||||||
|
|
||||||
// start the informers & and wait for the caches to sync
|
|
||||||
wrapper.Start(s.ctx.Done())
|
|
||||||
s.logger.Info("Waiting for informer caches to sync")
|
|
||||||
csiCacheSyncResults := wrapper.WaitForCacheSync(s.ctx.Done())
|
|
||||||
s.logger.Info("Done waiting for informer caches to sync")
|
|
||||||
|
|
||||||
for informer, synced := range csiCacheSyncResults {
|
|
||||||
if !synced {
|
|
||||||
err = errors.Errorf("cache was not synced for informer %v", informer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.logger.WithField("informer", informer).Info("Informer cache synced")
|
|
||||||
}
|
|
||||||
case err != nil:
|
|
||||||
s.logger.Errorf("fail to find snapshot v1 schema: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vsLister, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string) error {
|
func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string) error {
|
||||||
s.logger.Info("Starting controllers")
|
s.logger.Info("Starting controllers")
|
||||||
|
|
||||||
|
@ -775,10 +721,10 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||||
s.metrics,
|
s.metrics,
|
||||||
backupStoreGetter,
|
backupStoreGetter,
|
||||||
s.config.formatFlag.Parse(),
|
s.config.formatFlag.Parse(),
|
||||||
s.csiSnapshotLister,
|
|
||||||
s.credentialFileStore,
|
s.credentialFileStore,
|
||||||
s.config.maxConcurrentK8SConnections,
|
s.config.maxConcurrentK8SConnections,
|
||||||
s.config.defaultSnapshotMoveData,
|
s.config.defaultSnapshotMoveData,
|
||||||
|
s.crClient,
|
||||||
).SetupWithManager(s.mgr); err != nil {
|
).SetupWithManager(s.mgr); err != nil {
|
||||||
s.logger.Fatal(err, "unable to create controller", "controller", controller.Backup)
|
s.logger.Fatal(err, "unable to create controller", "controller", controller.Backup)
|
||||||
}
|
}
|
||||||
|
@ -837,7 +783,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||||
cmd.CheckError(err)
|
cmd.CheckError(err)
|
||||||
r := controller.NewBackupFinalizerReconciler(
|
r := controller.NewBackupFinalizerReconciler(
|
||||||
s.mgr.GetClient(),
|
s.mgr.GetClient(),
|
||||||
s.csiSnapshotLister,
|
s.crClient,
|
||||||
clock.RealClock{},
|
clock.RealClock{},
|
||||||
backupper,
|
backupper,
|
||||||
newPluginManager,
|
newPluginManager,
|
||||||
|
@ -1027,37 +973,6 @@ func (s *server) runProfiler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSIInformerFactoryWrapper is a proxy around the CSI SharedInformerFactory that checks the CSI feature flag before performing operations.
|
|
||||||
type CSIInformerFactoryWrapper struct {
|
|
||||||
factory snapshotv1informers.SharedInformerFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCSIInformerFactoryWrapper(c snapshotv1client.Interface) *CSIInformerFactoryWrapper {
|
|
||||||
// If no namespace is specified, all namespaces are watched.
|
|
||||||
// This is desirable for VolumeSnapshots, as we want to query for all VolumeSnapshots across all namespaces using this informer
|
|
||||||
w := &CSIInformerFactoryWrapper{}
|
|
||||||
|
|
||||||
if features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
|
||||||
w.factory = snapshotv1informers.NewSharedInformerFactoryWithOptions(c, 0)
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start proxies the Start call to the CSI SharedInformerFactory.
|
|
||||||
func (w *CSIInformerFactoryWrapper) Start(stopCh <-chan struct{}) {
|
|
||||||
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(velerov1api.CSIFeatureFlag) {
|
|
||||||
return w.factory.WaitForCacheSync(stopCh)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is a restarting during the reconciling of backups/restores/etc, these CRs may be stuck in progress status
|
// if there is a restarting during the reconciling of backups/restores/etc, these CRs may be stuck in progress status
|
||||||
// markInProgressCRsFailed tries to mark the in progress CRs as failed when starting the server to avoid the issue
|
// markInProgressCRsFailed tries to mark the in progress CRs as failed when starting the server to avoid the issue
|
||||||
func markInProgressCRsFailed(ctx context.Context, cfg *rest.Config, scheme *runtime.Scheme, namespace string, log logrus.FieldLogger) {
|
func markInProgressCRsFailed(ctx context.Context, cfg *rest.Config, scheme *runtime.Scheme, namespace string, log logrus.FieldLogger) {
|
||||||
|
|
|
@ -21,12 +21,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||||
snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned"
|
|
||||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
corev1api "k8s.io/api/core/v1"
|
corev1api "k8s.io/api/core/v1"
|
||||||
|
@ -43,14 +42,18 @@ import (
|
||||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||||
"github.com/vmware-tanzu/velero/internal/storage"
|
"github.com/vmware-tanzu/velero/internal/storage"
|
||||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
|
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||||
"github.com/vmware-tanzu/velero/pkg/features"
|
"github.com/vmware-tanzu/velero/pkg/features"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||||
"github.com/vmware-tanzu/velero/pkg/label"
|
"github.com/vmware-tanzu/velero/pkg/label"
|
||||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||||
"github.com/vmware-tanzu/velero/pkg/util/encode"
|
"github.com/vmware-tanzu/velero/pkg/util/encode"
|
||||||
|
@ -84,11 +87,10 @@ type backupReconciler struct {
|
||||||
metrics *metrics.ServerMetrics
|
metrics *metrics.ServerMetrics
|
||||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||||
formatFlag logging.Format
|
formatFlag logging.Format
|
||||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
|
||||||
volumeSnapshotClient snapshotterClientSet.Interface
|
|
||||||
credentialFileStore credentials.FileStore
|
credentialFileStore credentials.FileStore
|
||||||
maxConcurrentK8SConnections int
|
maxConcurrentK8SConnections int
|
||||||
defaultSnapshotMoveData bool
|
defaultSnapshotMoveData bool
|
||||||
|
globalCRClient kbclient.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBackupReconciler(
|
func NewBackupReconciler(
|
||||||
|
@ -110,10 +112,10 @@ func NewBackupReconciler(
|
||||||
metrics *metrics.ServerMetrics,
|
metrics *metrics.ServerMetrics,
|
||||||
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
||||||
formatFlag logging.Format,
|
formatFlag logging.Format,
|
||||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister,
|
|
||||||
credentialStore credentials.FileStore,
|
credentialStore credentials.FileStore,
|
||||||
maxConcurrentK8SConnections int,
|
maxConcurrentK8SConnections int,
|
||||||
defaultSnapshotMoveData bool,
|
defaultSnapshotMoveData bool,
|
||||||
|
globalCRClient kbclient.Client,
|
||||||
) *backupReconciler {
|
) *backupReconciler {
|
||||||
b := &backupReconciler{
|
b := &backupReconciler{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -135,10 +137,10 @@ func NewBackupReconciler(
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
backupStoreGetter: backupStoreGetter,
|
backupStoreGetter: backupStoreGetter,
|
||||||
formatFlag: formatFlag,
|
formatFlag: formatFlag,
|
||||||
volumeSnapshotLister: volumeSnapshotLister,
|
|
||||||
credentialFileStore: credentialStore,
|
credentialFileStore: credentialStore,
|
||||||
maxConcurrentK8SConnections: maxConcurrentK8SConnections,
|
maxConcurrentK8SConnections: maxConcurrentK8SConnections,
|
||||||
defaultSnapshotMoveData: defaultSnapshotMoveData,
|
defaultSnapshotMoveData: defaultSnapshotMoveData,
|
||||||
|
globalCRClient: globalCRClient,
|
||||||
}
|
}
|
||||||
b.updateTotalBackupMetric()
|
b.updateTotalBackupMetric()
|
||||||
return b
|
return b
|
||||||
|
@ -317,6 +319,7 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
|
||||||
request := &pkgbackup.Request{
|
request := &pkgbackup.Request{
|
||||||
Backup: backup.DeepCopy(), // don't modify items in the cache
|
Backup: backup.DeepCopy(), // don't modify items in the cache
|
||||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||||
|
PVMap: map[string]pkgbackup.PvcPvInfo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// set backup major version - deprecated, use Status.FormatVersion
|
// set backup major version - deprecated, use Status.FormatVersion
|
||||||
|
@ -665,7 +668,7 @@ func (b *backupReconciler) runBackup(backup *pkgbackup.Request) error {
|
||||||
backup.Status.VolumeSnapshotsCompleted++
|
backup.Status.VolumeSnapshotsCompleted++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses := pkgbackup.UpdateBackupCSISnapshotsStatus(b.kbClient, b.volumeSnapshotLister, backup.Backup, backupLog)
|
volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses := pkgbackup.UpdateBackupCSISnapshotsStatus(b.kbClient, b.globalCRClient, backup.Backup, backupLog)
|
||||||
|
|
||||||
// Iterate over backup item operations and update progress.
|
// Iterate over backup item operations and update progress.
|
||||||
// Any errors on operations at this point should be added to backup errors.
|
// Any errors on operations at this point should be added to backup errors.
|
||||||
|
@ -734,6 +737,8 @@ func (b *backupReconciler) runBackup(backup *pkgbackup.Request) error {
|
||||||
if logFile, err := backupLog.GetPersistFile(); err != nil {
|
if logFile, err := backupLog.GetPersistFile(); err != nil {
|
||||||
fatalErrs = append(fatalErrs, errors.Wrap(err, "error getting backup log file"))
|
fatalErrs = append(fatalErrs, errors.Wrap(err, "error getting backup log file"))
|
||||||
} else {
|
} else {
|
||||||
|
backup.VolumeInfos.VolumeInfos = generateVolumeInfo(backup, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, b.globalCRClient, backupLog)
|
||||||
|
|
||||||
if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, results); len(errs) > 0 {
|
if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, results); len(errs) > 0 {
|
||||||
fatalErrs = append(fatalErrs, errs...)
|
fatalErrs = append(fatalErrs, errs...)
|
||||||
}
|
}
|
||||||
|
@ -796,7 +801,6 @@ func persistBackup(backup *pkgbackup.Request,
|
||||||
) []error {
|
) []error {
|
||||||
persistErrs := []error{}
|
persistErrs := []error{}
|
||||||
backupJSON := new(bytes.Buffer)
|
backupJSON := new(bytes.Buffer)
|
||||||
volumeInfos := make([]volume.VolumeInfo, 0)
|
|
||||||
|
|
||||||
if err := encode.To(backup.Backup, "json", backupJSON); err != nil {
|
if err := encode.To(backup.Backup, "json", backupJSON); err != nil {
|
||||||
persistErrs = append(persistErrs, errors.Wrap(err, "error encoding backup"))
|
persistErrs = append(persistErrs, errors.Wrap(err, "error encoding backup"))
|
||||||
|
@ -843,7 +847,7 @@ func persistBackup(backup *pkgbackup.Request,
|
||||||
persistErrs = append(persistErrs, errs...)
|
persistErrs = append(persistErrs, errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeInfoJSON, errs := encode.ToJSONGzip(volumeInfos, "backup volumes information")
|
volumeInfoJSON, errs := encode.ToJSONGzip(backup.VolumeInfos, "backup volumes information")
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
persistErrs = append(persistErrs, errs...)
|
persistErrs = append(persistErrs, errs...)
|
||||||
}
|
}
|
||||||
|
@ -908,3 +912,328 @@ func oldAndNewFilterParametersUsedTogether(backupSpec velerov1api.BackupSpec) bo
|
||||||
|
|
||||||
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
|
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateVolumeInfo(backup *pkgbackup.Request, csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||||
|
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||||
|
crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
volumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
|
||||||
|
skippedVolumeInfos := generateVolumeInfoForSkippedPV(backup, logger)
|
||||||
|
volumeInfos = append(volumeInfos, skippedVolumeInfos...)
|
||||||
|
|
||||||
|
nativeSnapshotVolumeInfos := generateVolumeInfoForVeleroNativeSnapshot(backup, logger)
|
||||||
|
volumeInfos = append(volumeInfos, nativeSnapshotVolumeInfos...)
|
||||||
|
|
||||||
|
csiVolumeInfos := generateVolumeInfoForCSIVolumeSnapshot(backup, csiVolumeSnapshots, csiVolumeSnapshotContents, csiVolumesnapshotClasses, logger)
|
||||||
|
volumeInfos = append(volumeInfos, csiVolumeInfos...)
|
||||||
|
|
||||||
|
pvbVolumeInfos := generateVolumeInfoFromPVB(backup, crClient, logger)
|
||||||
|
volumeInfos = append(volumeInfos, pvbVolumeInfos...)
|
||||||
|
|
||||||
|
dataUploadVolumeInfos := generateVolumeInfoFromDataUpload(backup, crClient, logger)
|
||||||
|
volumeInfos = append(volumeInfos, dataUploadVolumeInfos...)
|
||||||
|
|
||||||
|
return volumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||||
|
func generateVolumeInfoForSkippedPV(backup *pkgbackup.Request, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
|
||||||
|
for _, skippedPV := range backup.SkippedPVTracker.Summary() {
|
||||||
|
if pvcPVInfo, ok := backup.PVMap[skippedPV.Name]; ok {
|
||||||
|
volumeInfo := volume.VolumeInfo{
|
||||||
|
PVCName: pvcPVInfo.PVCName,
|
||||||
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
PVName: skippedPV.Name,
|
||||||
|
SnapshotDataMoved: false,
|
||||||
|
Skipped: true,
|
||||||
|
SkippedReason: skippedPV.SerializeSkipReasons(),
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("Cannot find info for PV %s", skippedPV.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
||||||
|
func generateVolumeInfoForVeleroNativeSnapshot(backup *pkgbackup.Request, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
|
||||||
|
for _, nativeSnapshot := range backup.VolumeSnapshots {
|
||||||
|
var iops int64
|
||||||
|
if nativeSnapshot.Spec.VolumeIOPS != nil {
|
||||||
|
iops = *nativeSnapshot.Spec.VolumeIOPS
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvcPVInfo, ok := backup.PVMap[nativeSnapshot.Spec.PersistentVolumeName]; ok {
|
||||||
|
volumeInfo := volume.VolumeInfo{
|
||||||
|
BackupMethod: volume.NativeSnapshot,
|
||||||
|
PVCName: pvcPVInfo.PVCName,
|
||||||
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
PVName: pvcPVInfo.PV.Name,
|
||||||
|
SnapshotDataMoved: false,
|
||||||
|
Skipped: false,
|
||||||
|
NativeSnapshotInfo: volume.NativeSnapshotInfo{
|
||||||
|
SnapshotHandle: nativeSnapshot.Status.ProviderSnapshotID,
|
||||||
|
VolumeType: nativeSnapshot.Spec.VolumeType,
|
||||||
|
VolumeAZ: nativeSnapshot.Spec.VolumeAZ,
|
||||||
|
IOPS: strconv.FormatInt(iops, 10),
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("cannot find info for PV %s", nativeSnapshot.Spec.PersistentVolumeName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
|
||||||
|
func generateVolumeInfoForCSIVolumeSnapshot(backup *pkgbackup.Request, csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||||
|
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||||
|
logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
|
||||||
|
for _, volumeSnapshot := range csiVolumeSnapshots {
|
||||||
|
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||||
|
var volumeSnapshotContent *snapshotv1api.VolumeSnapshotContent
|
||||||
|
|
||||||
|
// This is protective logic. The passed-in VS should be all related
|
||||||
|
// to this backup.
|
||||||
|
if volumeSnapshot.Labels[velerov1api.BackupNameLabel] != backup.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumeSnapshot.Spec.VolumeSnapshotClassName == nil {
|
||||||
|
logger.Warnf("Cannot find VolumeSnapshotClass for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumeSnapshot.Status == nil || volumeSnapshot.Status.BoundVolumeSnapshotContentName == nil {
|
||||||
|
logger.Warnf("Cannot fine VolumeSnapshotContent for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumeSnapshot.Spec.Source.PersistentVolumeClaimName == nil {
|
||||||
|
logger.Warnf("VolumeSnapshot %s/%s doesn't have a source PVC", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for index := range csiVolumesnapshotClasses {
|
||||||
|
if *volumeSnapshot.Spec.VolumeSnapshotClassName == csiVolumesnapshotClasses[index].Name {
|
||||||
|
volumeSnapshotClass = &csiVolumesnapshotClasses[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index := range csiVolumeSnapshotContents {
|
||||||
|
if *volumeSnapshot.Status.BoundVolumeSnapshotContentName == csiVolumeSnapshotContents[index].Name {
|
||||||
|
volumeSnapshotContent = &csiVolumeSnapshotContents[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumeSnapshotClass == nil || volumeSnapshotContent == nil {
|
||||||
|
logger.Warnf("fail to get VolumeSnapshotContent or VolumeSnapshotClass for VolumeSnapshot: %s/%s",
|
||||||
|
volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var operation itemoperation.BackupOperation
|
||||||
|
for _, op := range *backup.GetItemOperationsList() {
|
||||||
|
if op.Spec.ResourceIdentifier.GroupResource.String() == kuberesource.VolumeSnapshots.String() &&
|
||||||
|
op.Spec.ResourceIdentifier.Name == volumeSnapshot.Name &&
|
||||||
|
op.Spec.ResourceIdentifier.Namespace == volumeSnapshot.Namespace {
|
||||||
|
operation = *op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var size int64
|
||||||
|
if volumeSnapshot.Status.RestoreSize != nil {
|
||||||
|
size = volumeSnapshot.Status.RestoreSize.Value()
|
||||||
|
}
|
||||||
|
snapshotHandle := ""
|
||||||
|
if volumeSnapshotContent.Status.SnapshotHandle != nil {
|
||||||
|
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
|
||||||
|
}
|
||||||
|
if pvcPVInfo, ok := backup.PVMap[volumeSnapshot.Namespace+"/"+*volumeSnapshot.Spec.Source.PersistentVolumeClaimName]; ok {
|
||||||
|
volumeInfo := volume.VolumeInfo{
|
||||||
|
BackupMethod: volume.CSISnapshot,
|
||||||
|
PVCName: pvcPVInfo.PVCName,
|
||||||
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
PVName: pvcPVInfo.PV.Name,
|
||||||
|
Skipped: false,
|
||||||
|
SnapshotDataMoved: false,
|
||||||
|
PreserveLocalSnapshot: true,
|
||||||
|
OperationID: operation.Spec.OperationID,
|
||||||
|
StartTimestamp: &volumeSnapshot.CreationTimestamp,
|
||||||
|
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||||
|
VSCName: *volumeSnapshot.Status.BoundVolumeSnapshotContentName,
|
||||||
|
Size: size,
|
||||||
|
Driver: volumeSnapshotClass.Driver,
|
||||||
|
SnapshotHandle: snapshotHandle,
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("cannot find info for PVC %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Spec.Source.PersistentVolumeClaimName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
|
||||||
|
func generateVolumeInfoFromPVB(backup *pkgbackup.Request, crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
|
||||||
|
for _, pvb := range backup.PodVolumeBackups {
|
||||||
|
volumeInfo := volume.VolumeInfo{
|
||||||
|
BackupMethod: volume.PodVolumeBackup,
|
||||||
|
SnapshotDataMoved: false,
|
||||||
|
Skipped: false,
|
||||||
|
StartTimestamp: pvb.Status.StartTimestamp,
|
||||||
|
PVBInfo: volume.PodVolumeBackupInfo{
|
||||||
|
SnapshotHandle: pvb.Status.SnapshotID,
|
||||||
|
Size: pvb.Status.Progress.TotalBytes,
|
||||||
|
UploaderType: pvb.Spec.UploaderType,
|
||||||
|
VolumeName: pvb.Spec.Volume,
|
||||||
|
PodName: pvb.Spec.Pod.Name,
|
||||||
|
PodNamespace: pvb.Spec.Pod.Namespace,
|
||||||
|
NodeName: pvb.Spec.Node,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := new(corev1api.Pod)
|
||||||
|
pvcName := ""
|
||||||
|
err := crClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: pvb.Spec.Pod.Namespace, Name: pvb.Spec.Pod.Name}, pod)
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Warn("Fail to get pod for PodVolumeBackup: ", pvb.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, volume := range pod.Spec.Volumes {
|
||||||
|
if volume.Name == pvb.Spec.Volume && volume.PersistentVolumeClaim != nil {
|
||||||
|
pvcName = volume.PersistentVolumeClaim.ClaimName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvcName != "" {
|
||||||
|
if pvcPVInfo, ok := backup.PVMap[pod.Namespace+"/"+pvcName]; ok {
|
||||||
|
volumeInfo.PVCName = pvcPVInfo.PVCName
|
||||||
|
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||||
|
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||||
|
volumeInfo.PVInfo = volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warnf("Cannot find info for PVC %s/%s", pod.Namespace, pvcName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateVolumeInfoFromDataUpload generate VolumeInfo for DataUpload.
|
||||||
|
func generateVolumeInfoFromDataUpload(backup *pkgbackup.Request, crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||||
|
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||||
|
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
||||||
|
if err := crClient.List(context.TODO(), vsClassList); err != nil {
|
||||||
|
logger.WithError(err).Errorf("cannot list VolumeSnapshotClass %s", err.Error())
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, operation := range *backup.GetItemOperationsList() {
|
||||||
|
if operation.Spec.ResourceIdentifier.GroupResource.String() == kuberesource.PersistentVolumeClaims.String() {
|
||||||
|
var duIdentifier velero.ResourceIdentifier
|
||||||
|
|
||||||
|
for _, identifier := range operation.Spec.PostOperationItems {
|
||||||
|
if identifier.GroupResource.String() == "datauploads.velero.io" {
|
||||||
|
duIdentifier = identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if duIdentifier.Empty() {
|
||||||
|
logger.Warnf("cannot find DataUpload for PVC %s/%s backup async operation",
|
||||||
|
operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dataUpload := new(velerov2alpha1.DataUpload)
|
||||||
|
err := crClient.Get(
|
||||||
|
context.TODO(),
|
||||||
|
kbclient.ObjectKey{
|
||||||
|
Namespace: duIdentifier.Namespace,
|
||||||
|
Name: duIdentifier.Name},
|
||||||
|
dataUpload,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("fail to get DataUpload for operation %s: %s", operation.Spec.OperationID, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
driverUsedByVSClass := ""
|
||||||
|
for index := range vsClassList.Items {
|
||||||
|
if vsClassList.Items[index].Name == dataUpload.Spec.CSISnapshot.SnapshotClass {
|
||||||
|
driverUsedByVSClass = vsClassList.Items[index].Driver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvcPVInfo, ok := backup.PVMap[operation.Spec.ResourceIdentifier.Namespace+"/"+operation.Spec.ResourceIdentifier.Name]; ok {
|
||||||
|
volumeInfo := volume.VolumeInfo{
|
||||||
|
BackupMethod: volume.CSISnapshot,
|
||||||
|
PVCName: pvcPVInfo.PVCName,
|
||||||
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
PVName: pvcPVInfo.PV.Name,
|
||||||
|
SnapshotDataMoved: true,
|
||||||
|
Skipped: false,
|
||||||
|
OperationID: operation.Spec.OperationID,
|
||||||
|
StartTimestamp: operation.Status.Created,
|
||||||
|
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||||
|
Driver: driverUsedByVSClass,
|
||||||
|
},
|
||||||
|
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||||
|
DataMover: dataUpload.Spec.DataMover,
|
||||||
|
UploaderType: "kopia",
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("Cannot find info for PVC %s/%s", operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpVolumeInfos
|
||||||
|
}
|
||||||
|
|
|
@ -29,15 +29,16 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||||
snapshotfake "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned/fake"
|
|
||||||
snapshotinformers "github.com/kubernetes-csi/external-snapshotter/client/v4/informers/externalversions"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1api "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/version"
|
"k8s.io/apimachinery/pkg/version"
|
||||||
"k8s.io/utils/clock"
|
"k8s.io/utils/clock"
|
||||||
|
@ -45,11 +46,14 @@ import (
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/backup"
|
||||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||||
|
|
||||||
fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
|
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||||
|
@ -61,6 +65,7 @@ import (
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||||
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
|
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
|
||||||
|
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||||
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
||||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||||
|
@ -1062,12 +1067,11 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "backup with snapshot data movement set to false when CSI feature is enabled",
|
name: "backup with snapshot data movement set to false when CSI feature is enabled",
|
||||||
backup: defaultBackup().SnapshotMoveData(false).Result(),
|
backup: defaultBackup().SnapshotMoveData(false).Result(),
|
||||||
//backup: defaultBackup().Result(),
|
|
||||||
backupLocation: defaultBackupLocation,
|
backupLocation: defaultBackupLocation,
|
||||||
defaultVolumesToFsBackup: false,
|
defaultVolumesToFsBackup: false,
|
||||||
expectedResult: &velerov1api.Backup{
|
expectedResult: &velerov1api.Backup{
|
||||||
|
@ -1103,7 +1107,7 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "backup with snapshot data movement not set when CSI feature is enabled",
|
name: "backup with snapshot data movement not set when CSI feature is enabled",
|
||||||
|
@ -1143,7 +1147,7 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "backup with snapshot data movement set to true and defaultSnapshotMoveData set to false",
|
name: "backup with snapshot data movement set to true and defaultSnapshotMoveData set to false",
|
||||||
|
@ -1184,7 +1188,7 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "backup with snapshot data movement set to false and defaultSnapshotMoveData set to true",
|
name: "backup with snapshot data movement set to false and defaultSnapshotMoveData set to true",
|
||||||
|
@ -1225,7 +1229,7 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "backup with snapshot data movement not set and defaultSnapshotMoveData set to true",
|
name: "backup with snapshot data movement not set and defaultSnapshotMoveData set to true",
|
||||||
|
@ -1266,35 +1270,43 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
CSIVolumeSnapshotsCompleted: 0,
|
CSIVolumeSnapshotsCompleted: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
volumeSnapshot: builder.ForVolumeSnapshot("velero", "testVS").VolumeSnapshotClass("testClass").Status().BoundVolumeSnapshotContentName("testVSC").RestoreSize("10G").SourcePVC("testPVC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).Result(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snapshotHandle := "testSnapshotID"
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
formatFlag := logging.FormatText
|
formatFlag := logging.FormatText
|
||||||
var (
|
var (
|
||||||
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||||
pluginManager = new(pluginmocks.Manager)
|
pluginManager = new(pluginmocks.Manager)
|
||||||
backupStore = new(persistencemocks.BackupStore)
|
backupStore = new(persistencemocks.BackupStore)
|
||||||
backupper = new(fakeBackupper)
|
backupper = new(fakeBackupper)
|
||||||
snapshotClient = snapshotfake.NewSimpleClientset()
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
sharedInformer = snapshotinformers.NewSharedInformerFactory(snapshotClient, 0)
|
|
||||||
snapshotLister = sharedInformer.Snapshot().V1().VolumeSnapshots().Lister()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var fakeClient kbclient.Client
|
var fakeClient kbclient.Client
|
||||||
// add the test's backup storage location if it's different than the default
|
// add the test's backup storage location if it's different than the default
|
||||||
if test.backupLocation != nil && test.backupLocation != defaultBackupLocation {
|
if test.backupLocation != nil && test.backupLocation != defaultBackupLocation {
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, test.backupLocation)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, test.backupLocation,
|
||||||
|
builder.ForVolumeSnapshotClass("testClass").Driver("testDriver").Result(),
|
||||||
|
builder.ForVolumeSnapshotContent("testVSC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).VolumeSnapshotClassName("testClass").Status(&snapshotv1api.VolumeSnapshotContentStatus{
|
||||||
|
SnapshotHandle: &snapshotHandle,
|
||||||
|
}).Result(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t,
|
||||||
|
builder.ForVolumeSnapshotClass("testClass").Driver("testDriver").Result(),
|
||||||
|
builder.ForVolumeSnapshotContent("testVSC").ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, "backup-1")).VolumeSnapshotClassName("testClass").Status(&snapshotv1api.VolumeSnapshotContentStatus{
|
||||||
|
SnapshotHandle: &snapshotHandle,
|
||||||
|
}).Result(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.volumeSnapshot != nil {
|
if test.volumeSnapshot != nil {
|
||||||
snapshotClient.SnapshotV1().VolumeSnapshots(test.volumeSnapshot.Namespace).Create(context.Background(), test.volumeSnapshot, metav1.CreateOptions{})
|
require.NoError(t, fakeGlobalClient.Create(context.TODO(), test.volumeSnapshot))
|
||||||
sharedInformer.Snapshot().V1().VolumeSnapshots().Informer().GetStore().Add(test.volumeSnapshot)
|
|
||||||
sharedInformer.WaitForCacheSync(make(chan struct{}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apiServer := velerotest.NewAPIServer(t)
|
apiServer := velerotest.NewAPIServer(t)
|
||||||
|
@ -1328,8 +1340,7 @@ func TestProcessBackupCompletions(t *testing.T) {
|
||||||
backupStoreGetter: NewFakeSingleObjectBackupStoreGetter(backupStore),
|
backupStoreGetter: NewFakeSingleObjectBackupStoreGetter(backupStore),
|
||||||
backupper: backupper,
|
backupper: backupper,
|
||||||
formatFlag: formatFlag,
|
formatFlag: formatFlag,
|
||||||
volumeSnapshotClient: snapshotClient,
|
globalCRClient: fakeGlobalClient,
|
||||||
volumeSnapshotLister: snapshotLister,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginManager.On("GetBackupItemActionsV2").Return(nil, nil)
|
pluginManager.On("GetBackupItemActionsV2").Return(nil, nil)
|
||||||
|
@ -1731,3 +1742,749 @@ func TestPatchResourceWorksWithStatus(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
skippedPVName string
|
||||||
|
pvMap map[string]backup.PvcPvInfo
|
||||||
|
expectedVolumeInfos []volume.VolumeInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Cannot find info for PV",
|
||||||
|
skippedPVName: "testPV",
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Normal Skipped PV info",
|
||||||
|
skippedPVName: "testPV",
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"velero/testPVC": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"testPV": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
Skipped: true,
|
||||||
|
SkippedReason: "CSI: skipped for PodVolumeBackup;",
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: "Delete",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
request := new(backup.Request)
|
||||||
|
request.SkippedPVTracker = backup.NewSkipPVTracker()
|
||||||
|
if tc.skippedPVName != "" {
|
||||||
|
request.SkippedPVTracker.Track(tc.skippedPVName, "CSI", "skipped for PodVolumeBackup")
|
||||||
|
}
|
||||||
|
if tc.pvMap != nil {
|
||||||
|
request.PVMap = tc.pvMap
|
||||||
|
}
|
||||||
|
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
|
||||||
|
volumeInfos := generateVolumeInfoForSkippedPV(request, logger)
|
||||||
|
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
|
resourceQuantity := resource.MustParse("100Gi")
|
||||||
|
now := metav1.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
volumeSnapshot snapshotv1api.VolumeSnapshot
|
||||||
|
volumeSnapshotContent snapshotv1api.VolumeSnapshotContent
|
||||||
|
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
|
||||||
|
pvMap map[string]backup.PvcPvInfo
|
||||||
|
operation *itemoperation.BackupOperation
|
||||||
|
expectedVolumeInfos []volume.VolumeInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "VS doesn't have VolumeSnapshotClass name",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VS doesn't have status",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||||
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VS doesn't have PVC",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||||
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
|
},
|
||||||
|
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||||
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cannot find VSC for VS",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||||
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
|
Source: snapshotv1api.VolumeSnapshotSource{
|
||||||
|
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||||
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cannot find VolumeInfo for PVC",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||||
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
|
Source: snapshotv1api.VolumeSnapshotSource{
|
||||||
|
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||||
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||||
|
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Normal VolumeSnapshot case",
|
||||||
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testVS",
|
||||||
|
Namespace: "velero",
|
||||||
|
CreationTimestamp: now,
|
||||||
|
},
|
||||||
|
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||||
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
|
Source: snapshotv1api.VolumeSnapshotSource{
|
||||||
|
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||||
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
|
RestoreSize: &resourceQuantity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||||
|
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"velero/testPVC": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
OperationID: "testID",
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "snapshot.storage.k8s.io",
|
||||||
|
Resource: "volumesnapshots",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testVS",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
BackupMethod: volume.CSISnapshot,
|
||||||
|
OperationID: "testID",
|
||||||
|
StartTimestamp: &now,
|
||||||
|
PreserveLocalSnapshot: true,
|
||||||
|
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||||
|
Driver: "pd.csi.storage.gke.io",
|
||||||
|
SnapshotHandle: "testSnapshotHandle",
|
||||||
|
Size: 107374182400,
|
||||||
|
VSCName: "testContent",
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: "Delete",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
request := new(backup.Request)
|
||||||
|
request.Backup = new(velerov1api.Backup)
|
||||||
|
if tc.pvMap != nil {
|
||||||
|
request.PVMap = tc.pvMap
|
||||||
|
}
|
||||||
|
operationList := request.GetItemOperationsList()
|
||||||
|
if tc.operation != nil {
|
||||||
|
*operationList = append(*operationList, tc.operation)
|
||||||
|
}
|
||||||
|
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
|
||||||
|
volumeInfos := generateVolumeInfoForCSIVolumeSnapshot(request, []snapshotv1api.VolumeSnapshot{tc.volumeSnapshot}, []snapshotv1api.VolumeSnapshotContent{tc.volumeSnapshotContent}, []snapshotv1api.VolumeSnapshotClass{tc.volumeSnapshotClass}, logger)
|
||||||
|
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
nativeSnapshot volume.Snapshot
|
||||||
|
pvMap map[string]backup.PvcPvInfo
|
||||||
|
expectedVolumeInfos []volume.VolumeInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Native snapshot's IPOS pointer is nil",
|
||||||
|
nativeSnapshot: volume.Snapshot{
|
||||||
|
Spec: volume.SnapshotSpec{
|
||||||
|
PersistentVolumeName: "testPV",
|
||||||
|
VolumeIOPS: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cannot find info for the PV",
|
||||||
|
nativeSnapshot: volume.Snapshot{
|
||||||
|
Spec: volume.SnapshotSpec{
|
||||||
|
PersistentVolumeName: "testPV",
|
||||||
|
VolumeIOPS: int64Ptr(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Normal native snapshot",
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"testPV": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nativeSnapshot: volume.Snapshot{
|
||||||
|
Spec: volume.SnapshotSpec{
|
||||||
|
PersistentVolumeName: "testPV",
|
||||||
|
VolumeIOPS: int64Ptr(100),
|
||||||
|
VolumeType: "ssd",
|
||||||
|
VolumeAZ: "us-central1-a",
|
||||||
|
},
|
||||||
|
Status: volume.SnapshotStatus{
|
||||||
|
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
BackupMethod: volume.NativeSnapshot,
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: "Delete",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NativeSnapshotInfo: volume.NativeSnapshotInfo{
|
||||||
|
SnapshotHandle: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||||
|
VolumeType: "ssd",
|
||||||
|
VolumeAZ: "us-central1-a",
|
||||||
|
IOPS: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
request := new(backup.Request)
|
||||||
|
request.VolumeSnapshots = append(request.VolumeSnapshots, &tc.nativeSnapshot)
|
||||||
|
if tc.pvMap != nil {
|
||||||
|
request.PVMap = tc.pvMap
|
||||||
|
}
|
||||||
|
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
|
||||||
|
volumeInfos := generateVolumeInfoForVeleroNativeSnapshot(request, logger)
|
||||||
|
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pvb *velerov1api.PodVolumeBackup
|
||||||
|
pod *corev1api.Pod
|
||||||
|
pvMap map[string]backup.PvcPvInfo
|
||||||
|
expectedVolumeInfos []volume.VolumeInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "cannot find PVB's pod, should fail",
|
||||||
|
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PVB doesn't have a related PVC",
|
||||||
|
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||||
|
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||||
|
Name: "test",
|
||||||
|
VolumeMounts: []corev1api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "testVolume",
|
||||||
|
MountPath: "/data",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Volumes(
|
||||||
|
&corev1api.Volume{
|
||||||
|
Name: "",
|
||||||
|
VolumeSource: corev1api.VolumeSource{
|
||||||
|
HostPath: &corev1api.HostPathVolumeSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Result(),
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "",
|
||||||
|
PVCNamespace: "",
|
||||||
|
PVName: "",
|
||||||
|
BackupMethod: volume.PodVolumeBackup,
|
||||||
|
PVBInfo: volume.PodVolumeBackupInfo{
|
||||||
|
PodName: "testPod",
|
||||||
|
PodNamespace: "velero",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Backup doesn't have information for PVC",
|
||||||
|
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||||
|
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||||
|
Name: "test",
|
||||||
|
VolumeMounts: []corev1api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "testVolume",
|
||||||
|
MountPath: "/data",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Volumes(
|
||||||
|
&corev1api.Volume{
|
||||||
|
Name: "",
|
||||||
|
VolumeSource: corev1api.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: "testPVC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Result(),
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PVB's volume has a PVC",
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"velero/testPVC": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||||
|
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||||
|
Name: "test",
|
||||||
|
VolumeMounts: []corev1api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "testVolume",
|
||||||
|
MountPath: "/data",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Volumes(
|
||||||
|
&corev1api.Volume{
|
||||||
|
Name: "",
|
||||||
|
VolumeSource: corev1api.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: "testPVC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).Result(),
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
BackupMethod: volume.PodVolumeBackup,
|
||||||
|
PVBInfo: volume.PodVolumeBackupInfo{
|
||||||
|
PodName: "testPod",
|
||||||
|
PodNamespace: "velero",
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
|
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
request := new(pkgbackup.Request)
|
||||||
|
request.PodVolumeBackups = append(request.PodVolumeBackups, tc.pvb)
|
||||||
|
if tc.pvMap != nil {
|
||||||
|
request.PVMap = tc.pvMap
|
||||||
|
}
|
||||||
|
if tc.pod != nil {
|
||||||
|
require.NoError(t, crClient.Create(context.TODO(), tc.pod))
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeInfos := generateVolumeInfoFromPVB(request, crClient, logger)
|
||||||
|
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
|
now := metav1.Now()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||||
|
dataUpload *velerov2alpha1.DataUpload
|
||||||
|
operation *itemoperation.BackupOperation
|
||||||
|
pvMap map[string]backup.PvcPvInfo
|
||||||
|
expectedVolumeInfos []volume.VolumeInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Operation is not for PVC",
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "configmaps",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Operation doesn't have DataUpload PostItemOperation",
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "persistentvolumeclaims",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testPVC",
|
||||||
|
},
|
||||||
|
PostOperationItems: []velero.ResourceIdentifier{
|
||||||
|
{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "configmaps",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DataUpload cannot be found for operation",
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
OperationID: "testOperation",
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "persistentvolumeclaims",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testPVC",
|
||||||
|
},
|
||||||
|
PostOperationItems: []velero.ResourceIdentifier{
|
||||||
|
{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "velero.io",
|
||||||
|
Resource: "datauploads",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testDU",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VolumeSnapshotClass cannot be found for operation",
|
||||||
|
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||||
|
VolumeSnapshot: "testVS",
|
||||||
|
}).SnapshotID("testSnapshotHandle").Result(),
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
OperationID: "testOperation",
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "persistentvolumeclaims",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testPVC",
|
||||||
|
},
|
||||||
|
PostOperationItems: []velero.ResourceIdentifier{
|
||||||
|
{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "velero.io",
|
||||||
|
Resource: "datauploads",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testDU",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"velero/testPVC": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
BackupMethod: volume.CSISnapshot,
|
||||||
|
SnapshotDataMoved: true,
|
||||||
|
OperationID: "testOperation",
|
||||||
|
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||||
|
DataMover: "velero",
|
||||||
|
UploaderType: "kopia",
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Normal DataUpload case",
|
||||||
|
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||||
|
VolumeSnapshot: "testVS",
|
||||||
|
SnapshotClass: "testClass",
|
||||||
|
}).SnapshotID("testSnapshotHandle").Result(),
|
||||||
|
volumeSnapshotClass: builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||||
|
operation: &itemoperation.BackupOperation{
|
||||||
|
Spec: itemoperation.BackupOperationSpec{
|
||||||
|
OperationID: "testOperation",
|
||||||
|
ResourceIdentifier: velero.ResourceIdentifier{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "",
|
||||||
|
Resource: "persistentvolumeclaims",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testPVC",
|
||||||
|
},
|
||||||
|
PostOperationItems: []velero.ResourceIdentifier{
|
||||||
|
{
|
||||||
|
GroupResource: schema.GroupResource{
|
||||||
|
Group: "velero.io",
|
||||||
|
Resource: "datauploads",
|
||||||
|
},
|
||||||
|
Namespace: "velero",
|
||||||
|
Name: "testDU",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: itemoperation.OperationStatus{
|
||||||
|
Created: &now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pvMap: map[string]backup.PvcPvInfo{
|
||||||
|
"velero/testPVC": {
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PV: corev1api.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testPV",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
Spec: corev1api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVolumeInfos: []volume.VolumeInfo{
|
||||||
|
{
|
||||||
|
PVCName: "testPVC",
|
||||||
|
PVCNamespace: "velero",
|
||||||
|
PVName: "testPV",
|
||||||
|
BackupMethod: volume.CSISnapshot,
|
||||||
|
SnapshotDataMoved: true,
|
||||||
|
OperationID: "testOperation",
|
||||||
|
StartTimestamp: &now,
|
||||||
|
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||||
|
Driver: "pd.csi.storage.gke.io",
|
||||||
|
},
|
||||||
|
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||||
|
DataMover: "velero",
|
||||||
|
UploaderType: "kopia",
|
||||||
|
},
|
||||||
|
PVInfo: volume.PVInfo{
|
||||||
|
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
request := new(backup.Request)
|
||||||
|
operationList := request.GetItemOperationsList()
|
||||||
|
if tc.operation != nil {
|
||||||
|
*operationList = append(*operationList, tc.operation)
|
||||||
|
}
|
||||||
|
if tc.pvMap != nil {
|
||||||
|
request.PVMap = tc.pvMap
|
||||||
|
}
|
||||||
|
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
|
||||||
|
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
|
if tc.dataUpload != nil {
|
||||||
|
crClient.Create(context.TODO(), tc.dataUpload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.volumeSnapshotClass != nil {
|
||||||
|
crClient.Create(context.TODO(), tc.volumeSnapshotClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeInfos := generateVolumeInfoFromDataUpload(request, crClient, logger)
|
||||||
|
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64Ptr(val int) *int64 {
|
||||||
|
i := int64(val)
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringPtr(str string) *string {
|
||||||
|
return &str
|
||||||
|
}
|
||||||
|
|
|
@ -29,8 +29,6 @@ import (
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
|
||||||
|
|
||||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||||
|
@ -42,21 +40,21 @@ import (
|
||||||
|
|
||||||
// backupFinalizerReconciler reconciles a Backup object
|
// backupFinalizerReconciler reconciles a Backup object
|
||||||
type backupFinalizerReconciler struct {
|
type backupFinalizerReconciler struct {
|
||||||
client kbclient.Client
|
client kbclient.Client
|
||||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
globalCRClient kbclient.Client
|
||||||
clock clocks.WithTickerAndDelayedExecution
|
clock clocks.WithTickerAndDelayedExecution
|
||||||
backupper pkgbackup.Backupper
|
backupper pkgbackup.Backupper
|
||||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||||
backupTracker BackupTracker
|
backupTracker BackupTracker
|
||||||
metrics *metrics.ServerMetrics
|
metrics *metrics.ServerMetrics
|
||||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||||
log logrus.FieldLogger
|
log logrus.FieldLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackupFinalizerReconciler initializes and returns backupFinalizerReconciler struct.
|
// NewBackupFinalizerReconciler initializes and returns backupFinalizerReconciler struct.
|
||||||
func NewBackupFinalizerReconciler(
|
func NewBackupFinalizerReconciler(
|
||||||
client kbclient.Client,
|
client kbclient.Client,
|
||||||
volumeSnapshotLister snapshotv1listers.VolumeSnapshotLister,
|
globalCRClient kbclient.Client,
|
||||||
clock clocks.WithTickerAndDelayedExecution,
|
clock clocks.WithTickerAndDelayedExecution,
|
||||||
backupper pkgbackup.Backupper,
|
backupper pkgbackup.Backupper,
|
||||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||||
|
@ -67,6 +65,7 @@ func NewBackupFinalizerReconciler(
|
||||||
) *backupFinalizerReconciler {
|
) *backupFinalizerReconciler {
|
||||||
return &backupFinalizerReconciler{
|
return &backupFinalizerReconciler{
|
||||||
client: client,
|
client: client,
|
||||||
|
globalCRClient: globalCRClient,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
backupper: backupper,
|
backupper: backupper,
|
||||||
newPluginManager: newPluginManager,
|
newPluginManager: newPluginManager,
|
||||||
|
@ -191,7 +190,7 @@ func (r *backupFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||||
backup.Status.CompletionTimestamp = &metav1.Time{Time: r.clock.Now()}
|
backup.Status.CompletionTimestamp = &metav1.Time{Time: r.clock.Now()}
|
||||||
recordBackupMetrics(log, backup, outBackupFile, r.metrics, true)
|
recordBackupMetrics(log, backup, outBackupFile, r.metrics, true)
|
||||||
|
|
||||||
pkgbackup.UpdateBackupCSISnapshotsStatus(r.client, r.volumeSnapshotLister, backup, log)
|
pkgbackup.UpdateBackupCSISnapshotsStatus(r.client, r.globalCRClient, backup, log)
|
||||||
// update backup metadata in object store
|
// update backup metadata in object store
|
||||||
backupJSON := new(bytes.Buffer)
|
backupJSON := new(bytes.Buffer)
|
||||||
if err := encode.To(backup, "json", backupJSON); err != nil {
|
if err := encode.To(backup, "json", backupJSON); err != nil {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
@ -44,14 +43,13 @@ import (
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||||
velerotestmocks "github.com/vmware-tanzu/velero/pkg/test/mocks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func mockBackupFinalizerReconciler(fakeClient kbclient.Client, fakeVolumeSnapshotLister snapshotv1listers.VolumeSnapshotLister, fakeClock *testclocks.FakeClock) (*backupFinalizerReconciler, *fakeBackupper) {
|
func mockBackupFinalizerReconciler(fakeClient kbclient.Client, fakeGlobalClient kbclient.Client, fakeClock *testclocks.FakeClock) (*backupFinalizerReconciler, *fakeBackupper) {
|
||||||
backupper := new(fakeBackupper)
|
backupper := new(fakeBackupper)
|
||||||
return NewBackupFinalizerReconciler(
|
return NewBackupFinalizerReconciler(
|
||||||
fakeClient,
|
fakeClient,
|
||||||
fakeVolumeSnapshotLister,
|
fakeGlobalClient,
|
||||||
fakeClock,
|
fakeClock,
|
||||||
backupper,
|
backupper,
|
||||||
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
|
||||||
|
@ -164,9 +162,9 @@ func TestBackupFinalizerReconcile(t *testing.T) {
|
||||||
|
|
||||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, initObjs...)
|
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, initObjs...)
|
||||||
|
|
||||||
fakeVolumeSnapshotLister := velerotestmocks.NewVolumeSnapshotLister(t)
|
fakeGlobalClient := velerotest.NewFakeControllerRuntimeClient(t, initObjs...)
|
||||||
|
|
||||||
reconciler, backupper := mockBackupFinalizerReconciler(fakeClient, fakeVolumeSnapshotLister, fakeClock)
|
reconciler, backupper := mockBackupFinalizerReconciler(fakeClient, fakeGlobalClient, fakeClock)
|
||||||
pluginManager.On("CleanupClients").Return(nil)
|
pluginManager.On("CleanupClients").Return(nil)
|
||||||
backupStore.On("GetBackupItemOperations", test.backup.Name).Return(test.backupOperations, nil)
|
backupStore.On("GetBackupItemOperations", test.backup.Name).Return(test.backupOperations, nil)
|
||||||
backupStore.On("GetBackupContents", mock.Anything).Return(io.NopCloser(bytes.NewReader([]byte("hello world"))), nil)
|
backupStore.On("GetBackupContents", mock.Anything).Return(io.NopCloser(bytes.NewReader([]byte("hello world"))), nil)
|
||||||
|
|
|
@ -608,6 +608,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
|
||||||
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getCSIVolumeSnapshotContentsKey(target.Name), DownloadURLTTL)
|
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getCSIVolumeSnapshotContentsKey(target.Name), DownloadURLTTL)
|
||||||
case velerov1api.DownloadTargetKindBackupResults:
|
case velerov1api.DownloadTargetKindBackupResults:
|
||||||
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL)
|
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL)
|
||||||
|
case velerov1api.DownloadTargetKindBackupVolumeInfos:
|
||||||
|
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeInfoKey(target.Name), DownloadURLTTL)
|
||||||
default:
|
default:
|
||||||
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
|
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
|
||||||
}
|
}
|
||||||
|
|
|
@ -768,6 +768,13 @@ func TestGetDownloadURL(t *testing.T) {
|
||||||
velerov1api.DownloadTargetKindRestoreResourceList: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-resource-list.json.gz",
|
velerov1api.DownloadTargetKindRestoreResourceList: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-resource-list.json.gz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
targetName: "my-backup",
|
||||||
|
expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{
|
||||||
|
velerov1api.DownloadTargetKindBackupVolumeInfos: "backups/my-backup/my-backup-volumeinfos.json.gz",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
|
@ -51,6 +51,8 @@ type VolumeInfo struct {
|
||||||
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
||||||
|
|
||||||
// Whether the local snapshot is preserved after snapshot is moved.
|
// Whether the local snapshot is preserved after snapshot is moved.
|
||||||
|
// The local snapshot may be a result of CSI snapshot backup(no data movement)
|
||||||
|
// or a CSI snapshot data movement plus preserve local snapshot.
|
||||||
PreserveLocalSnapshot bool `json:"preserveLocalSnapshot"`
|
PreserveLocalSnapshot bool `json:"preserveLocalSnapshot"`
|
||||||
|
|
||||||
// Whether the Volume is skipped in this backup.
|
// Whether the Volume is skipped in this backup.
|
||||||
|
@ -69,6 +71,7 @@ type VolumeInfo struct {
|
||||||
SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
||||||
NativeSnapshotInfo NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
NativeSnapshotInfo NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
||||||
PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
|
PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
|
||||||
|
PVInfo PVInfo `json:"pvInfo,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||||
|
@ -76,7 +79,7 @@ type CSISnapshotInfo struct {
|
||||||
// It's the storage provider's snapshot ID for CSI.
|
// It's the storage provider's snapshot ID for CSI.
|
||||||
SnapshotHandle string `json:"snapshotHandle"`
|
SnapshotHandle string `json:"snapshotHandle"`
|
||||||
|
|
||||||
// The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
|
// The snapshot corresponding volume size.
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
|
|
||||||
// The name of the CSI driver.
|
// The name of the CSI driver.
|
||||||
|
@ -91,7 +94,7 @@ type SnapshotDataMovementInfo struct {
|
||||||
// The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
|
// The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
|
||||||
DataMover string `json:"dataMover"`
|
DataMover string `json:"dataMover"`
|
||||||
|
|
||||||
// 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.
|
// The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`.
|
||||||
UploaderType string `json:"uploaderType"`
|
UploaderType string `json:"uploaderType"`
|
||||||
|
|
||||||
// The name or ID of the snapshot associated object(SAO).
|
// The name or ID of the snapshot associated object(SAO).
|
||||||
|
@ -111,9 +114,6 @@ type NativeSnapshotInfo struct {
|
||||||
// It's the storage provider's snapshot ID for the Velero-native snapshot.
|
// It's the storage provider's snapshot ID for the Velero-native snapshot.
|
||||||
SnapshotHandle string `json:"snapshotHandle"`
|
SnapshotHandle string `json:"snapshotHandle"`
|
||||||
|
|
||||||
// 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 `json:"size"`
|
|
||||||
|
|
||||||
// The cloud provider snapshot volume type.
|
// The cloud provider snapshot volume type.
|
||||||
VolumeType string `json:"volumeType"`
|
VolumeType string `json:"volumeType"`
|
||||||
|
|
||||||
|
@ -129,19 +129,32 @@ type PodVolumeBackupInfo struct {
|
||||||
// It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
// It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
||||||
SnapshotHandle string `json:"snapshotHandle"`
|
SnapshotHandle string `json:"snapshotHandle"`
|
||||||
|
|
||||||
// The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot.
|
// The snapshot corresponding volume size.
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
|
|
||||||
// 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.
|
// The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
|
||||||
UploaderType string `json:"uploaderType"`
|
UploaderType string `json:"uploaderType"`
|
||||||
|
|
||||||
// The PVC's corresponding volume name used by Pod
|
// The PVC's corresponding volume name used by Pod
|
||||||
// https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48
|
// https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48
|
||||||
VolumeName string `json:"volumeName"`
|
VolumeName string `json:"volumeName"`
|
||||||
|
|
||||||
// The Pod name mounting this PVC. The format should be <namespace-name>/<pod-name>.
|
// The Pod name mounting this PVC.
|
||||||
PodName string `json:"podName"`
|
PodName string `json:"podName"`
|
||||||
|
|
||||||
|
// The Pod namespace
|
||||||
|
PodNamespace string `json:"podNamespace"`
|
||||||
|
|
||||||
// The PVB-taken k8s node's name.
|
// The PVB-taken k8s node's name.
|
||||||
NodeName string `json:"nodeName"`
|
NodeName string `json:"nodeName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PVInfo is used to store some PV information modified after creation.
|
||||||
|
// Those information are lost after PV recreation.
|
||||||
|
type PVInfo struct {
|
||||||
|
// ReclaimPolicy of PV. It could be different from the referenced StorageClass.
|
||||||
|
ReclaimPolicy string `json:"reclaimPolicy"`
|
||||||
|
|
||||||
|
// The PV's labels should be kept after recreation.
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue