diff --git a/changelogs/unreleased/6368-Lyndon-Li b/changelogs/unreleased/6368-Lyndon-Li new file mode 100644 index 000000000..dfb815699 --- /dev/null +++ b/changelogs/unreleased/6368-Lyndon-Li @@ -0,0 +1 @@ +Add UT for pkg/util \ No newline at end of file diff --git a/pkg/test/test_logger.go b/pkg/test/test_logger.go index 05fc942cd..d8095a79d 100644 --- a/pkg/test/test_logger.go +++ b/pkg/test/test_logger.go @@ -34,3 +34,19 @@ func NewLoggerWithLevel(level logrus.Level) logrus.FieldLogger { logger.Level = level return logrus.NewEntry(logger) } + +type singleLogRecorder struct { + buffer *string +} + +func (s *singleLogRecorder) Write(p []byte) (n int, err error) { + *s.buffer = string(p[:]) + return len(p), nil +} + +func NewSingleLogger(buffer *string) logrus.FieldLogger { + logger := logrus.New() + logger.Out = &singleLogRecorder{buffer: buffer} + logger.Level = logrus.TraceLevel + return logrus.NewEntry(logger) +} diff --git a/pkg/util/csi/volume_snapshot.go b/pkg/util/csi/volume_snapshot.go index c6d665e74..b4585e3ed 100644 --- a/pkg/util/csi/volume_snapshot.go +++ b/pkg/util/csi/volume_snapshot.go @@ -112,15 +112,12 @@ func RetainVSC(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interfa // DeleteVolumeSnapshotContentIfAny deletes a VSC by name if it exists, and log an error when the deletion fails func DeleteVolumeSnapshotContentIfAny(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface, vscName string, log logrus.FieldLogger) { - vsc, err := snapshotClient.VolumeSnapshotContents().Get(ctx, vscName, metav1.GetOptions{}) + err := snapshotClient.VolumeSnapshotContents().Delete(ctx, vscName, metav1.DeleteOptions{}) if err != nil { - if !apierrors.IsNotFound(err) { - log.WithError(err).Warnf("Abort deleting VSC, it doesn't exist %s", vscName) - } - } else { - err = snapshotClient.VolumeSnapshotContents().Delete(ctx, vsc.Name, metav1.DeleteOptions{}) - if err != nil { - log.WithError(err).Warnf("Failed to delete volume snapshot content %s", vsc.Name) + if apierrors.IsNotFound(err) { + log.WithError(err).Debugf("Abort deleting VSC, it doesn't exist %s", vscName) + } else { + log.WithError(err).Errorf("Failed to delete volume snapshot content %s", vscName) } } } diff --git a/pkg/util/csi/volume_snapshot_test.go b/pkg/util/csi/volume_snapshot_test.go index 9c34f746a..37f202ef9 100644 --- a/pkg/util/csi/volume_snapshot_test.go +++ b/pkg/util/csi/volume_snapshot_test.go @@ -31,6 +31,8 @@ import ( clientTesting "k8s.io/client-go/testing" "github.com/vmware-tanzu/velero/pkg/util/boolptr" + + velerotest "github.com/vmware-tanzu/velero/pkg/test" ) type reactor struct { @@ -282,6 +284,12 @@ func TestEnsureDeleteVS(t *testing.T) { }, err: "error to assure VolumeSnapshot is deleted, fake-vs: error to get VolumeSnapshot fake-vs: fake-get-error", }, + { + name: "success", + vsName: "fake-vs", + namespace: "fake-ns", + clientObj: []runtime.Object{vsObj}, + }, } for _, test := range tests { @@ -336,6 +344,11 @@ func TestEnsureDeleteVSC(t *testing.T) { }, err: "error to assure VolumeSnapshotContent is deleted, fake-vsc: error to get VolumeSnapshotContent fake-vsc: fake-get-error", }, + { + name: "success", + vscName: "fake-vsc", + clientObj: []runtime.Object{vscObj}, + }, } for _, test := range tests { @@ -355,3 +368,222 @@ func TestEnsureDeleteVSC(t *testing.T) { }) } } + +func TestDeleteVolumeSnapshotContentIfAny(t *testing.T) { + tests := []struct { + name string + clientObj []runtime.Object + reactors []reactor + vscName string + logMessage string + logLevel string + logError string + }{ + { + name: "vsc not exist", + vscName: "fake-vsc", + logMessage: "Abort deleting VSC, it doesn't exist fake-vsc", + logLevel: "level=debug", + }, + { + name: "deleete fail", + vscName: "fake-vsc", + reactors: []reactor{ + { + verb: "delete", + resource: "volumesnapshotcontents", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-delete-error") + }, + }, + }, + logMessage: "Failed to delete volume snapshot content fake-vsc", + logLevel: "level=error", + logError: "error=fake-delete-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeSnapshotClient := snapshotFake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeSnapshotClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + logMessage := "" + + DeleteVolumeSnapshotContentIfAny(context.Background(), fakeSnapshotClient.SnapshotV1(), test.vscName, velerotest.NewSingleLogger(&logMessage)) + + if len(test.logMessage) > 0 { + assert.Contains(t, logMessage, test.logMessage) + } + + if len(test.logLevel) > 0 { + assert.Contains(t, logMessage, test.logLevel) + } + + if len(test.logError) > 0 { + assert.Contains(t, logMessage, test.logError) + } + }) + } +} + +func TestDeleteVolumeSnapshotIfAny(t *testing.T) { + tests := []struct { + name string + clientObj []runtime.Object + reactors []reactor + vsName string + vsNamespace string + logMessage string + logLevel string + logError string + }{ + { + name: "vs not exist", + vsName: "fake-vs", + vsNamespace: "fake-ns", + logMessage: "Abort deleting volume snapshot, it doesn't exist fake-ns/fake-vs", + logLevel: "level=debug", + }, + { + name: "delete fail", + vsName: "fake-vs", + vsNamespace: "fake-ns", + reactors: []reactor{ + { + verb: "delete", + resource: "volumesnapshots", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-delete-error") + }, + }, + }, + logMessage: "Failed to delete volume snapshot fake-ns/fake-vs", + logLevel: "level=error", + logError: "error=fake-delete-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeSnapshotClient := snapshotFake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeSnapshotClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + logMessage := "" + + DeleteVolumeSnapshotIfAny(context.Background(), fakeSnapshotClient.SnapshotV1(), test.vsName, test.vsNamespace, velerotest.NewSingleLogger(&logMessage)) + + if len(test.logMessage) > 0 { + assert.Contains(t, logMessage, test.logMessage) + } + + if len(test.logLevel) > 0 { + assert.Contains(t, logMessage, test.logLevel) + } + + if len(test.logError) > 0 { + assert.Contains(t, logMessage, test.logError) + } + }) + } +} + +func TestRetainVSC(t *testing.T) { + vscObj := &snapshotv1api.VolumeSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vsc", + }, + } + + tests := []struct { + name string + clientObj []runtime.Object + reactors []reactor + vsc *snapshotv1api.VolumeSnapshotContent + updated *snapshotv1api.VolumeSnapshotContent + err string + }{ + { + name: "already retained", + vsc: &snapshotv1api.VolumeSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vsc", + }, + Spec: snapshotv1api.VolumeSnapshotContentSpec{ + DeletionPolicy: snapshotv1api.VolumeSnapshotContentRetain, + }, + }, + }, + { + name: "path vsc fail", + vsc: &snapshotv1api.VolumeSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vsc", + }, + Spec: snapshotv1api.VolumeSnapshotContentSpec{ + DeletionPolicy: snapshotv1api.VolumeSnapshotContentDelete, + }, + }, + reactors: []reactor{ + { + verb: "patch", + resource: "volumesnapshotcontents", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-patch-error") + }, + }, + }, + err: "error patching VSC: fake-patch-error", + }, + { + name: "success", + vsc: &snapshotv1api.VolumeSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vsc", + }, + Spec: snapshotv1api.VolumeSnapshotContentSpec{ + DeletionPolicy: snapshotv1api.VolumeSnapshotContentDelete, + }, + }, + clientObj: []runtime.Object{vscObj}, + updated: &snapshotv1api.VolumeSnapshotContent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-vsc", + }, + Spec: snapshotv1api.VolumeSnapshotContentSpec{ + DeletionPolicy: snapshotv1api.VolumeSnapshotContentRetain, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeSnapshotClient := snapshotFake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeSnapshotClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + returned, err := RetainVSC(context.Background(), fakeSnapshotClient.SnapshotV1(), test.vsc) + + if len(test.err) == 0 { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, test.err) + } + + if test.updated != nil { + assert.Equal(t, *test.updated, *returned) + } else { + assert.Nil(t, returned) + } + }) + } +} diff --git a/pkg/util/kube/pod_test.go b/pkg/util/kube/pod_test.go index 8131b02c7..6f39c0b23 100644 --- a/pkg/util/kube/pod_test.go +++ b/pkg/util/kube/pod_test.go @@ -30,6 +30,8 @@ import ( "k8s.io/client-go/kubernetes/fake" clientTesting "k8s.io/client-go/testing" + + velerotest "github.com/vmware-tanzu/velero/pkg/test" ) func TestEnsureDeletePod(t *testing.T) { @@ -91,3 +93,253 @@ func TestEnsureDeletePod(t *testing.T) { }) } } + +func TestIsPodRunning(t *testing.T) { + tests := []struct { + name string + pod *corev1api.Pod + err string + }{ + { + name: "pod is nil", + err: "invalid input pod", + }, + { + name: "pod is not scheduled", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Status: corev1api.PodStatus{ + Phase: "fake-phase", + }, + }, + err: "pod is not scheduled, name=fake-pod, namespace=fake-ns, phase=fake-phase", + }, + { + name: "pod is not running", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: "fake-phase", + }, + }, + err: "pod is not in the expected status, name=fake-pod, namespace=fake-ns, phase=fake-phase: pod is not running", + }, + { + name: "pod is being deleted", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: corev1api.PodRunning, + }, + }, + err: "pod is being terminated, name=fake-pod, namespace=fake-ns, phase=Running", + }, + { + name: "success", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: corev1api.PodRunning, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := IsPodRunning(test.pod) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestIsPodScheduled(t *testing.T) { + tests := []struct { + name string + pod *corev1api.Pod + err string + }{ + { + name: "pod is nil", + err: "invalid input pod", + }, + { + name: "pod is not scheduled", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Status: corev1api.PodStatus{ + Phase: "fake-phase", + }, + }, + err: "pod is not scheduled, name=fake-pod, namespace=fake-ns, phase=fake-phase", + }, + { + name: "pod is not running or pending", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: "fake-phase", + }, + }, + err: "pod is not in the expected status, name=fake-pod, namespace=fake-ns, phase=fake-phase: pod is not running or pending", + }, + { + name: "pod is being deleted", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: corev1api.PodRunning, + }, + }, + err: "pod is being terminated, name=fake-pod, namespace=fake-ns, phase=Running", + }, + { + name: "success on running", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: corev1api.PodRunning, + }, + }, + }, + { + name: "success on pending", + pod: &corev1api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pod", + }, + Spec: corev1api.PodSpec{ + NodeName: "fake-node", + }, + Status: corev1api.PodStatus{ + Phase: corev1api.PodPending, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := IsPodScheduled(test.pod) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDeletePodIfAny(t *testing.T) { + tests := []struct { + name string + podName string + podNamespace string + kubeClientObj []runtime.Object + kubeReactors []reactor + logMessage string + logLevel string + logError string + }{ + { + name: "get fail", + podName: "fake-pod", + podNamespace: "fake-namespace", + logMessage: "Abort deleting pod, it doesn't exist fake-namespace/fake-pod", + logLevel: "level=debug", + }, + { + name: "delete fail", + podName: "fake-pod", + podNamespace: "fake-namespace", + kubeReactors: []reactor{ + { + verb: "delete", + resource: "pods", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-delete-error") + }, + }, + }, + logMessage: "Failed to delete pod fake-namespace/fake-pod", + logLevel: "level=error", + logError: "error=fake-delete-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...) + + for _, reactor := range test.kubeReactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + logMessage := "" + DeletePodIfAny(context.Background(), kubeClient.CoreV1(), test.podName, test.podNamespace, velerotest.NewSingleLogger(&logMessage)) + + if len(test.logMessage) > 0 { + assert.Contains(t, logMessage, test.logMessage) + } + + if len(test.logLevel) > 0 { + assert.Contains(t, logMessage, test.logLevel) + } + + if len(test.logError) > 0 { + assert.Contains(t, logMessage, test.logError) + } + }) + } +} diff --git a/pkg/util/kube/pvc_pv_test.go b/pkg/util/kube/pvc_pv_test.go index d5e8736d2..d31c5d57d 100644 --- a/pkg/util/kube/pvc_pv_test.go +++ b/pkg/util/kube/pvc_pv_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -31,6 +32,8 @@ import ( storagev1api "k8s.io/api/storage/v1" clientTesting "k8s.io/client-go/testing" + + velerotest "github.com/vmware-tanzu/velero/pkg/test" ) type reactor struct { @@ -284,3 +287,550 @@ func TestWaitPVCConsumed(t *testing.T) { }) } } + +func TestDeletePVCIfAny(t *testing.T) { + tests := []struct { + name string + pvcName string + pvcNamespace string + kubeClientObj []runtime.Object + kubeReactors []reactor + logMessage string + logLevel string + logError string + }{ + { + name: "get fail", + pvcName: "fake-pvc", + pvcNamespace: "fake-namespace", + logMessage: "Abort deleting PVC, it doesn't exist, fake-namespace/fake-pvc", + logLevel: "level=debug", + }, + { + name: "delete fail", + pvcName: "fake-pvc", + pvcNamespace: "fake-namespace", + kubeReactors: []reactor{ + { + verb: "delete", + resource: "persistentvolumeclaims", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-delete-error") + }, + }, + }, + logMessage: "Failed to delete pvc fake-namespace/fake-pvc", + logLevel: "level=error", + logError: "error=fake-delete-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...) + + for _, reactor := range test.kubeReactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + logMessage := "" + DeletePVCIfAny(context.Background(), kubeClient.CoreV1(), test.pvcName, test.pvcNamespace, velerotest.NewSingleLogger(&logMessage)) + + if len(test.logMessage) > 0 { + assert.Contains(t, logMessage, test.logMessage) + } + + if len(test.logLevel) > 0 { + assert.Contains(t, logMessage, test.logLevel) + } + + if len(test.logError) > 0 { + assert.Contains(t, logMessage, test.logError) + } + }) + } +} + +func TestDeletePVIfAny(t *testing.T) { + tests := []struct { + name string + pvName string + kubeClientObj []runtime.Object + kubeReactors []reactor + logMessage string + logLevel string + logError string + }{ + { + name: "get fail", + pvName: "fake-pv", + logMessage: "Abort deleting PV, it doesn't exist, fake-pv", + logLevel: "level=debug", + }, + { + name: "delete fail", + pvName: "fake-pv", + kubeReactors: []reactor{ + { + verb: "delete", + resource: "persistentvolumes", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-delete-error") + }, + }, + }, + logMessage: "Failed to delete PV fake-pv", + logLevel: "level=error", + logError: "error=fake-delete-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...) + + for _, reactor := range test.kubeReactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + logMessage := "" + DeletePVIfAny(context.Background(), kubeClient.CoreV1(), test.pvName, velerotest.NewSingleLogger(&logMessage)) + + if len(test.logMessage) > 0 { + assert.Contains(t, logMessage, test.logMessage) + } + + if len(test.logLevel) > 0 { + assert.Contains(t, logMessage, test.logLevel) + } + + if len(test.logError) > 0 { + assert.Contains(t, logMessage, test.logError) + } + }) + } +} + +func TestEnsureDeletePVC(t *testing.T) { + pvcObject := &corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pvc", + }, + } + + tests := []struct { + name string + clientObj []runtime.Object + pvcName string + namespace string + reactors []reactor + err string + }{ + { + name: "delete fail", + pvcName: "fake-pvc", + namespace: "fake-ns", + err: "error to delete pvc fake-pvc: persistentvolumeclaims \"fake-pvc\" not found", + }, + { + name: "wait fail", + pvcName: "fake-pvc", + namespace: "fake-ns", + clientObj: []runtime.Object{pvcObject}, + reactors: []reactor{ + { + verb: "get", + resource: "persistentvolumeclaims", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-get-error") + }, + }, + }, + err: "error to retrieve pvc info for fake-pvc: error to get pvc fake-pvc: fake-get-error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + err := EnsureDeletePVC(context.Background(), kubeClient.CoreV1(), test.pvcName, test.namespace, time.Millisecond) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestRebindPVC(t *testing.T) { + pvcObject := &corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pvc", + Annotations: map[string]string{ + KubeAnnBindCompleted: "true", + KubeAnnBoundByController: "true", + }, + }, + } + + tests := []struct { + name string + clientObj []runtime.Object + pvc *corev1api.PersistentVolumeClaim + pv string + reactors []reactor + result *corev1api.PersistentVolumeClaim + err string + }{ + { + name: "path fail", + pvc: pvcObject, + pv: "fake-pv", + clientObj: []runtime.Object{pvcObject}, + reactors: []reactor{ + { + verb: "patch", + resource: "persistentvolumeclaims", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-patch-error") + }, + }, + }, + err: "error patching PVC: fake-patch-error", + }, + { + name: "succeed", + pvc: pvcObject, + pv: "fake-pv", + clientObj: []runtime.Object{pvcObject}, + result: &corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "fake-ns", + Name: "fake-pvc", + }, + Spec: corev1api.PersistentVolumeClaimSpec{ + VolumeName: "fake-pv", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + result, err := RebindPVC(context.Background(), kubeClient.CoreV1(), test.pvc, test.pv) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.result, result) + }) + } +} + +func TestResetPVBinding(t *testing.T) { + pvObject := &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + Annotations: map[string]string{ + KubeAnnBoundByController: "true", + }, + }, + Spec: corev1api.PersistentVolumeSpec{ + ClaimRef: &corev1api.ObjectReference{ + Kind: "fake-kind", + Namespace: "fake-ns", + Name: "fake-pvc", + }, + }, + } + + tests := []struct { + name string + clientObj []runtime.Object + pv *corev1api.PersistentVolume + labels map[string]string + reactors []reactor + result *corev1api.PersistentVolume + err string + }{ + { + name: "path fail", + pv: pvObject, + clientObj: []runtime.Object{pvObject}, + reactors: []reactor{ + { + verb: "patch", + resource: "persistentvolumes", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-patch-error") + }, + }, + }, + err: "error patching PV: fake-patch-error", + }, + { + name: "succeed", + pv: pvObject, + labels: map[string]string{ + "fake-label-1": "fake-value-1", + "fake-label-2": "fake-value-2", + }, + clientObj: []runtime.Object{pvObject}, + result: &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + Labels: map[string]string{ + "fake-label-1": "fake-value-1", + "fake-label-2": "fake-value-2", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + result, err := ResetPVBinding(context.Background(), kubeClient.CoreV1(), test.pv, test.labels) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.result, result) + }) + } +} + +func TestSetPVReclaimPolicy(t *testing.T) { + pvObject := &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimRetain, + }, + } + + tests := []struct { + name string + clientObj []runtime.Object + pv *corev1api.PersistentVolume + policy corev1api.PersistentVolumeReclaimPolicy + reactors []reactor + result *corev1api.PersistentVolume + err string + }{ + { + name: "policy not changed", + pv: pvObject, + policy: corev1api.PersistentVolumeReclaimRetain, + }, + { + name: "path fail", + pv: pvObject, + policy: corev1api.PersistentVolumeReclaimDelete, + clientObj: []runtime.Object{pvObject}, + reactors: []reactor{ + { + verb: "patch", + resource: "persistentvolumes", + reactorFunc: func(action clientTesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake-patch-error") + }, + }, + }, + err: "error patching PV: fake-patch-error", + }, + { + name: "succeed", + pv: pvObject, + policy: corev1api.PersistentVolumeReclaimDelete, + clientObj: []runtime.Object{pvObject}, + result: &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.clientObj...) + + for _, reactor := range test.reactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + result, err := SetPVReclaimPolicy(context.Background(), kubeClient.CoreV1(), test.pv, test.policy) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.result, result) + }) + } +} + +func TestWaitPVBound(t *testing.T) { + tests := []struct { + name string + pvName string + pvcName string + pvcNamespace string + kubeClientObj []runtime.Object + kubeReactors []reactor + expectedPV *corev1api.PersistentVolume + err string + }{ + { + name: "get pv error", + pvName: "fake-pv", + err: "error to wait for bound of PV: failed to get pv fake-pv: persistentvolumes \"fake-pv\" not found", + }, + { + name: "pvc claimRef miss", + pvName: "fake-pv", + kubeClientObj: []runtime.Object{ + &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + }, + }, + err: "error to wait for bound of PV: timed out waiting for the condition", + }, + { + name: "pvc claimRef pvc name mismatch", + pvName: "fake-pv", + pvcName: "fake-pvc", + kubeClientObj: []runtime.Object{ + &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + ClaimRef: &corev1api.ObjectReference{ + Kind: "fake-kind", + }, + }, + }, + }, + err: "error to wait for bound of PV: timed out waiting for the condition", + }, + { + name: "pvc claimRef pvc namespace mismatch", + pvName: "fake-pv", + pvcName: "fake-pvc", + pvcNamespace: "fake-ns", + kubeClientObj: []runtime.Object{ + &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + ClaimRef: &corev1api.ObjectReference{ + Kind: "fake-kind", + Name: "fake-pvc", + }, + }, + }, + }, + err: "error to wait for bound of PV: timed out waiting for the condition", + }, + { + name: "success", + pvName: "fake-pv", + pvcName: "fake-pvc", + pvcNamespace: "fake-ns", + kubeClientObj: []runtime.Object{ + &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + ClaimRef: &corev1api.ObjectReference{ + Kind: "fake-kind", + Name: "fake-pvc", + Namespace: "fake-ns", + }, + }, + }, + }, + expectedPV: &corev1api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-pv", + }, + Spec: corev1api.PersistentVolumeSpec{ + ClaimRef: &corev1api.ObjectReference{ + Kind: "fake-kind", + Name: "fake-pvc", + Namespace: "fake-ns", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...) + + for _, reactor := range test.kubeReactors { + fakeKubeClient.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorFunc) + } + + var kubeClient kubernetes.Interface = fakeKubeClient + + pv, err := WaitPVBound(context.Background(), kubeClient.CoreV1(), test.pvName, test.pvcName, test.pvcNamespace, time.Millisecond) + + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, test.expectedPV, pv) + }) + } +} diff --git a/pkg/util/logging/kopia_log_test.go b/pkg/util/logging/kopia_log_test.go index 1180988b8..848638c6d 100644 --- a/pkg/util/logging/kopia_log_test.go +++ b/pkg/util/logging/kopia_log_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" @@ -57,6 +58,12 @@ func TestEnabled(t *testing.T) { zapLevel: zapcore.InfoLevel, expected: true, }, + { + name: "check warn again warn", + level: logrus.WarnLevel, + zapLevel: zapcore.WarnLevel, + expected: true, + }, { name: "check info again error", level: logrus.ErrorLevel, @@ -69,12 +76,24 @@ func TestEnabled(t *testing.T) { zapLevel: zapcore.ErrorLevel, expected: true, }, + { + name: "check dppanic again panic", + level: logrus.PanicLevel, + zapLevel: zapcore.DPanicLevel, + expected: true, + }, { name: "check panic again error", level: logrus.ErrorLevel, zapLevel: zapcore.PanicLevel, expected: true, }, + { + name: "check fatal again fatal", + level: logrus.FatalLevel, + zapLevel: zapcore.FatalLevel, + expected: true, + }, } for _, tc := range testCases { @@ -89,7 +108,7 @@ func TestEnabled(t *testing.T) { } } -func TestWrite(t *testing.T) { +func TestLogrusFieldsForWrite(t *testing.T) { testCases := []struct { name string module string @@ -151,6 +170,65 @@ func TestWrite(t *testing.T) { "logger name": "logger-name-01", }, }, + { + name: "info with function name", + module: "module-05", + zapEntry: zapcore.Entry{ + Level: zapcore.InfoLevel, + Caller: zapcore.EntryCaller{ + Function: "function-name-01", + }, + }, + zapFields: nil, + expected: logrus.Fields{ + "logModule": "kopia/module-05", + "function": "function-name-01", + }, + }, + { + name: "info with undefined path", + module: "module-06", + zapEntry: zapcore.Entry{ + Level: zapcore.InfoLevel, + Caller: zapcore.EntryCaller{ + Defined: false, + }, + }, + zapFields: nil, + expected: logrus.Fields{ + "logModule": "kopia/module-06", + }, + }, + { + name: "info with defined path", + module: "module-06", + zapEntry: zapcore.Entry{ + Level: zapcore.InfoLevel, + Caller: zapcore.EntryCaller{ + Defined: true, + File: "file-name-01", + Line: 100, + }, + }, + zapFields: nil, + expected: logrus.Fields{ + "logModule": "kopia/module-06", + "path": "file-name-01:100", + }, + }, + { + name: "info with stack", + module: "module-07", + zapEntry: zapcore.Entry{ + Level: zapcore.InfoLevel, + Stack: "fake-stack", + }, + zapFields: nil, + expected: logrus.Fields{ + "logModule": "kopia/module-07", + "stack": "fake-stack", + }, + }, } for _, tc := range testCases { @@ -165,3 +243,107 @@ func TestWrite(t *testing.T) { }) } } + +func TestWrite(t *testing.T) { + testCases := []struct { + name string + ent zapcore.Entry + logMessage string + logLevel string + shouldPanic bool + }{ + { + name: "write debug", + ent: zapcore.Entry{ + Level: zapcore.DebugLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=debug", + }, + { + name: "write info", + ent: zapcore.Entry{ + Level: zapcore.InfoLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=info", + }, + { + name: "write warn", + ent: zapcore.Entry{ + Level: zapcore.WarnLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=warn", + }, + { + name: "write error", + ent: zapcore.Entry{ + Level: zapcore.ErrorLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=warn", + }, + { + name: "write DPanic", + ent: zapcore.Entry{ + Level: zapcore.DPanicLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=panic", + shouldPanic: true, + }, + { + name: "write panic", + ent: zapcore.Entry{ + Level: zapcore.PanicLevel, + Message: "fake-message", + }, + logMessage: "fake-message", + logLevel: "level=panic", + shouldPanic: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + logMessage := "" + + log := kopiaLog{ + logger: test.NewSingleLogger(&logMessage), + } + + if tc.shouldPanic { + defer func() { + r := recover() + assert.NotNil(t, r) + + if len(tc.logMessage) > 0 { + assert.Contains(t, logMessage, tc.logMessage) + } + + if len(tc.logLevel) > 0 { + assert.Contains(t, logMessage, tc.logLevel) + } + }() + } + + err := log.Write(tc.ent, nil) + + assert.NoError(t, err) + + if len(tc.logMessage) > 0 { + assert.Contains(t, logMessage, tc.logMessage) + } + + if len(tc.logLevel) > 0 { + assert.Contains(t, logMessage, tc.logLevel) + } + }) + } +}