mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-27 18:34:18 +00:00
Merge pull request #7437 from Lyndon-Li/issue-fix-7036
Issue 7036: node selection for data mover backup
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/nodeagent"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -67,6 +68,9 @@ type CSISnapshotExposeParam struct {
|
||||
|
||||
// VolumeSize specifies the size of the source volume
|
||||
VolumeSize resource.Quantity
|
||||
|
||||
// Affinity specifies the node affinity of the backup pod
|
||||
Affinity *nodeagent.LoadAffinity
|
||||
}
|
||||
|
||||
// CSISnapshotExposeWaitParam define the input param for WaitExposed of CSI snapshots
|
||||
@@ -189,12 +193,12 @@ func (e *csiSnapshotExposer) Expose(ctx context.Context, ownerObject corev1.Obje
|
||||
}
|
||||
}()
|
||||
|
||||
backupPod, err := e.createBackupPod(ctx, ownerObject, backupPVC, csiExposeParam.HostingPodLabels)
|
||||
backupPod, err := e.createBackupPod(ctx, ownerObject, backupPVC, csiExposeParam.HostingPodLabels, csiExposeParam.Affinity)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error to create backup pod")
|
||||
}
|
||||
|
||||
curLog.WithField("pod name", backupPod.Name).Info("Backup pod is created")
|
||||
curLog.WithField("pod name", backupPod.Name).WithField("affinity", csiExposeParam.Affinity).Info("Backup pod is created")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -255,6 +259,30 @@ func (e *csiSnapshotExposer) GetExposed(ctx context.Context, ownerObject corev1.
|
||||
return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, VolumeName: volumeName}}, nil
|
||||
}
|
||||
|
||||
func (e *csiSnapshotExposer) PeekExposed(ctx context.Context, ownerObject corev1.ObjectReference) error {
|
||||
backupPodName := ownerObject.Name
|
||||
|
||||
curLog := e.log.WithFields(logrus.Fields{
|
||||
"owner": ownerObject.Name,
|
||||
})
|
||||
|
||||
pod, err := e.kubeClient.CoreV1().Pods(ownerObject.Namespace).Get(ctx, backupPodName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
curLog.WithError(err).Warnf("error to peek backup pod %s", backupPodName)
|
||||
return nil
|
||||
}
|
||||
|
||||
if podFailed, message := kube.IsPodUnrecoverable(pod, curLog); podFailed {
|
||||
return errors.New(message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1.ObjectReference, vsName string, sourceNamespace string) {
|
||||
backupPodName := ownerObject.Name
|
||||
backupPVCName := ownerObject.Name
|
||||
@@ -382,7 +410,8 @@ func (e *csiSnapshotExposer) createBackupPVC(ctx context.Context, ownerObject co
|
||||
return created, err
|
||||
}
|
||||
|
||||
func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject corev1.ObjectReference, backupPVC *corev1.PersistentVolumeClaim, label map[string]string) (*corev1.Pod, error) {
|
||||
func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject corev1.ObjectReference, backupPVC *corev1.PersistentVolumeClaim,
|
||||
label map[string]string, affinity *nodeagent.LoadAffinity) (*corev1.Pod, error) {
|
||||
podName := ownerObject.Name
|
||||
|
||||
volumeName := string(ownerObject.UID)
|
||||
@@ -430,6 +459,7 @@ func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject co
|
||||
},
|
||||
},
|
||||
},
|
||||
Affinity: toSystemAffinity(affinity),
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
@@ -455,3 +485,42 @@ func (e *csiSnapshotExposer) createBackupPod(ctx context.Context, ownerObject co
|
||||
|
||||
return e.kubeClient.CoreV1().Pods(ownerObject.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func toSystemAffinity(loadAffinity *nodeagent.LoadAffinity) *corev1.Affinity {
|
||||
if loadAffinity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
requirements := []corev1.NodeSelectorRequirement{}
|
||||
for k, v := range loadAffinity.NodeSelector.MatchLabels {
|
||||
requirements = append(requirements, corev1.NodeSelectorRequirement{
|
||||
Key: k,
|
||||
Values: []string{v},
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
})
|
||||
}
|
||||
|
||||
for _, exp := range loadAffinity.NodeSelector.MatchExpressions {
|
||||
requirements = append(requirements, corev1.NodeSelectorRequirement{
|
||||
Key: exp.Key,
|
||||
Values: exp.Values,
|
||||
Operator: corev1.NodeSelectorOperator(exp.Operator),
|
||||
})
|
||||
}
|
||||
|
||||
if len(requirements) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &corev1.Affinity{
|
||||
NodeAffinity: &corev1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
|
||||
NodeSelectorTerms: []corev1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: requirements,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package exposer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -35,6 +36,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/nodeagent"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
|
||||
@@ -620,3 +622,211 @@ func TestGetExpose(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeekExpose(t *testing.T) {
|
||||
backup := &velerov1.Backup{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: velerov1.SchemeGroupVersion.String(),
|
||||
Kind: "Backup",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: velerov1.DefaultNamespace,
|
||||
Name: "fake-backup",
|
||||
UID: "fake-uid",
|
||||
},
|
||||
}
|
||||
|
||||
backupPodUrecoverable := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: backup.Namespace,
|
||||
Name: backup.Name,
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
Phase: corev1.PodPending,
|
||||
Conditions: []corev1.PodCondition{
|
||||
{
|
||||
Type: corev1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "unrecoverable",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
backupPod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: backup.Namespace,
|
||||
Name: backup.Name,
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
corev1.AddToScheme(scheme)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeClientObj []runtime.Object
|
||||
ownerBackup *velerov1.Backup
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "backup pod is not found",
|
||||
ownerBackup: backup,
|
||||
},
|
||||
{
|
||||
name: "pod is unrecoverable",
|
||||
ownerBackup: backup,
|
||||
kubeClientObj: []runtime.Object{
|
||||
backupPodUrecoverable,
|
||||
},
|
||||
err: "Pod is unschedulable: unrecoverable",
|
||||
},
|
||||
{
|
||||
name: "succeed",
|
||||
ownerBackup: backup,
|
||||
kubeClientObj: []runtime.Object{
|
||||
backupPod,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...)
|
||||
|
||||
exposer := csiSnapshotExposer{
|
||||
kubeClient: fakeKubeClient,
|
||||
log: velerotest.NewLogger(),
|
||||
}
|
||||
|
||||
var ownerObject corev1.ObjectReference
|
||||
if test.ownerBackup != nil {
|
||||
ownerObject = corev1.ObjectReference{
|
||||
Kind: test.ownerBackup.Kind,
|
||||
Namespace: test.ownerBackup.Namespace,
|
||||
Name: test.ownerBackup.Name,
|
||||
UID: test.ownerBackup.UID,
|
||||
APIVersion: test.ownerBackup.APIVersion,
|
||||
}
|
||||
}
|
||||
|
||||
err := exposer.PeekExposed(context.Background(), ownerObject)
|
||||
if test.err == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.EqualError(t, err, test.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSystemAffinity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
loadAffinity *nodeagent.LoadAffinity
|
||||
expected *corev1.Affinity
|
||||
}{
|
||||
{
|
||||
name: "loadAffinity is nil",
|
||||
},
|
||||
{
|
||||
name: "loadAffinity is empty",
|
||||
loadAffinity: &nodeagent.LoadAffinity{},
|
||||
},
|
||||
{
|
||||
name: "with match label",
|
||||
loadAffinity: &nodeagent.LoadAffinity{
|
||||
NodeSelector: metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"key-1": "value-1",
|
||||
"key-2": "value-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &corev1.Affinity{
|
||||
NodeAffinity: &corev1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
|
||||
NodeSelectorTerms: []corev1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "key-1",
|
||||
Values: []string{"value-1"},
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
},
|
||||
{
|
||||
Key: "key-2",
|
||||
Values: []string{"value-2"},
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with match expression",
|
||||
loadAffinity: &nodeagent.LoadAffinity{
|
||||
NodeSelector: metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"key-1": "value-1",
|
||||
"key-2": "value-2",
|
||||
},
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "key-3",
|
||||
Values: []string{"value-3-1", "value-3-2"},
|
||||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
},
|
||||
{
|
||||
Key: "key-4",
|
||||
Values: []string{"value-4-1", "value-4-2", "value-4-3"},
|
||||
Operator: metav1.LabelSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: &corev1.Affinity{
|
||||
NodeAffinity: &corev1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
|
||||
NodeSelectorTerms: []corev1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []corev1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "key-1",
|
||||
Values: []string{"value-1"},
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
},
|
||||
{
|
||||
Key: "key-2",
|
||||
Values: []string{"value-2"},
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
},
|
||||
{
|
||||
Key: "key-3",
|
||||
Values: []string{"value-3-1", "value-3-2"},
|
||||
Operator: corev1.NodeSelectorOpNotIn,
|
||||
},
|
||||
{
|
||||
Key: "key-4",
|
||||
Values: []string{"value-4-1", "value-4-2", "value-4-3"},
|
||||
Operator: corev1.NodeSelectorOpDoesNotExist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
affinity := toSystemAffinity(test.loadAffinity)
|
||||
assert.Equal(t, true, reflect.DeepEqual(affinity, test.expected))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ type GenericRestoreExposer interface {
|
||||
// Otherwise, it returns nil as the expose result without an error.
|
||||
GetExposed(context.Context, corev1.ObjectReference, client.Client, string, time.Duration) (*ExposeResult, error)
|
||||
|
||||
// PeekExposed tests the status of the expose.
|
||||
// If the expose is incomplete but not recoverable, it returns an error.
|
||||
// Otherwise, it returns nil immediately.
|
||||
PeekExposed(context.Context, corev1.ObjectReference) error
|
||||
|
||||
// RebindVolume unexposes the restored PV and rebind it to the target PVC
|
||||
RebindVolume(context.Context, corev1.ObjectReference, string, string, time.Duration) error
|
||||
|
||||
@@ -160,6 +165,30 @@ func (e *genericRestoreExposer) GetExposed(ctx context.Context, ownerObject core
|
||||
return &ExposeResult{ByPod: ExposeByPod{HostingPod: pod, VolumeName: volumeName}}, nil
|
||||
}
|
||||
|
||||
func (e *genericRestoreExposer) PeekExposed(ctx context.Context, ownerObject corev1.ObjectReference) error {
|
||||
restorePodName := ownerObject.Name
|
||||
|
||||
curLog := e.log.WithFields(logrus.Fields{
|
||||
"owner": ownerObject.Name,
|
||||
})
|
||||
|
||||
pod, err := e.kubeClient.CoreV1().Pods(ownerObject.Namespace).Get(ctx, restorePodName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
curLog.WithError(err).Warnf("error to peek restore pod %s", restorePodName)
|
||||
return nil
|
||||
}
|
||||
|
||||
if podFailed, message := kube.IsPodUnrecoverable(pod, curLog); podFailed {
|
||||
return errors.New(message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *genericRestoreExposer) CleanUp(ctx context.Context, ownerObject corev1.ObjectReference) {
|
||||
restorePodName := ownerObject.Name
|
||||
restorePVCName := ownerObject.Name
|
||||
|
||||
@@ -409,3 +409,97 @@ func TestRebindVolume(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestorePeekExpose(t *testing.T) {
|
||||
restore := &velerov1.Restore{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: velerov1.SchemeGroupVersion.String(),
|
||||
Kind: "Restore",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: velerov1.DefaultNamespace,
|
||||
Name: "fake-restore",
|
||||
UID: "fake-uid",
|
||||
},
|
||||
}
|
||||
|
||||
restorePodUrecoverable := &corev1api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: restore.Namespace,
|
||||
Name: restore.Name,
|
||||
},
|
||||
Status: corev1api.PodStatus{
|
||||
Phase: corev1api.PodPending,
|
||||
Conditions: []corev1api.PodCondition{
|
||||
{
|
||||
Type: corev1api.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "unrecoverable",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
restorePod := &corev1api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: restore.Namespace,
|
||||
Name: restore.Name,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeClientObj []runtime.Object
|
||||
ownerRestore *velerov1.Restore
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "restore pod is not found",
|
||||
ownerRestore: restore,
|
||||
},
|
||||
{
|
||||
name: "pod is unrecoverable",
|
||||
ownerRestore: restore,
|
||||
kubeClientObj: []runtime.Object{
|
||||
restorePodUrecoverable,
|
||||
},
|
||||
err: "Pod is unschedulable: unrecoverable",
|
||||
},
|
||||
{
|
||||
name: "succeed",
|
||||
ownerRestore: restore,
|
||||
kubeClientObj: []runtime.Object{
|
||||
restorePod,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...)
|
||||
|
||||
exposer := genericRestoreExposer{
|
||||
kubeClient: fakeKubeClient,
|
||||
log: velerotest.NewLogger(),
|
||||
}
|
||||
|
||||
var ownerObject corev1api.ObjectReference
|
||||
if test.ownerRestore != nil {
|
||||
ownerObject = corev1api.ObjectReference{
|
||||
Kind: test.ownerRestore.Kind,
|
||||
Namespace: test.ownerRestore.Namespace,
|
||||
Name: test.ownerRestore.Name,
|
||||
UID: test.ownerRestore.UID,
|
||||
APIVersion: test.ownerRestore.APIVersion,
|
||||
}
|
||||
}
|
||||
|
||||
err := exposer.PeekExposed(context.Background(), ownerObject)
|
||||
if test.err == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.EqualError(t, err, test.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.20.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.39.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -30,6 +30,10 @@ func (_m *GenericRestoreExposer) CleanUp(_a0 context.Context, _a1 v1.ObjectRefer
|
||||
func (_m *GenericRestoreExposer) Expose(_a0 context.Context, _a1 v1.ObjectReference, _a2 string, _a3 string, _a4 map[string]string, _a5 time.Duration) error {
|
||||
ret := _m.Called(_a0, _a1, _a2, _a3, _a4, _a5)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Expose")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, v1.ObjectReference, string, string, map[string]string, time.Duration) error); ok {
|
||||
r0 = rf(_a0, _a1, _a2, _a3, _a4, _a5)
|
||||
@@ -44,6 +48,10 @@ func (_m *GenericRestoreExposer) Expose(_a0 context.Context, _a1 v1.ObjectRefere
|
||||
func (_m *GenericRestoreExposer) GetExposed(_a0 context.Context, _a1 v1.ObjectReference, _a2 client.Client, _a3 string, _a4 time.Duration) (*exposer.ExposeResult, error) {
|
||||
ret := _m.Called(_a0, _a1, _a2, _a3, _a4)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetExposed")
|
||||
}
|
||||
|
||||
var r0 *exposer.ExposeResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, v1.ObjectReference, client.Client, string, time.Duration) (*exposer.ExposeResult, error)); ok {
|
||||
@@ -66,10 +74,32 @@ func (_m *GenericRestoreExposer) GetExposed(_a0 context.Context, _a1 v1.ObjectRe
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// PeekExposed provides a mock function with given fields: _a0, _a1
|
||||
func (_m *GenericRestoreExposer) PeekExposed(_a0 context.Context, _a1 v1.ObjectReference) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PeekExposed")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, v1.ObjectReference) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RebindVolume provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4
|
||||
func (_m *GenericRestoreExposer) RebindVolume(_a0 context.Context, _a1 v1.ObjectReference, _a2 string, _a3 string, _a4 time.Duration) error {
|
||||
ret := _m.Called(_a0, _a1, _a2, _a3, _a4)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RebindVolume")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, v1.ObjectReference, string, string, time.Duration) error); ok {
|
||||
r0 = rf(_a0, _a1, _a2, _a3, _a4)
|
||||
@@ -80,13 +110,12 @@ func (_m *GenericRestoreExposer) RebindVolume(_a0 context.Context, _a1 v1.Object
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewGenericRestoreExposer interface {
|
||||
// NewGenericRestoreExposer creates a new instance of GenericRestoreExposer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewGenericRestoreExposer(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewGenericRestoreExposer creates a new instance of GenericRestoreExposer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewGenericRestoreExposer(t mockConstructorTestingTNewGenericRestoreExposer) *GenericRestoreExposer {
|
||||
}) *GenericRestoreExposer {
|
||||
mock := &GenericRestoreExposer{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@ type SnapshotExposer interface {
|
||||
// Otherwise, it returns nil as the expose result without an error.
|
||||
GetExposed(context.Context, corev1.ObjectReference, time.Duration, interface{}) (*ExposeResult, error)
|
||||
|
||||
// PeekExposed tests the status of the expose.
|
||||
// If the expose is incomplete but not recoverable, it returns an error.
|
||||
// Otherwise, it returns nil immediately.
|
||||
PeekExposed(context.Context, corev1.ObjectReference) error
|
||||
|
||||
// CleanUp cleans up any objects generated during the snapshot expose
|
||||
CleanUp(context.Context, corev1.ObjectReference, string, string)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user