diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index b762e52dc..2ec525048 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -286,7 +286,8 @@ func (e *genericRestoreExposer) Expose(ctx context.Context, ownerObject corev1ap selectedNode = "" - restorePV, err := kube.WaitPVCBound(ctx, e.kubeClient.CoreV1(), e.kubeClient.CoreV1(), restorePVC.Name, restorePVC.Namespace, param.ExposeTimeout) + var restorePV *corev1api.PersistentVolume + restorePV, err = kube.WaitPVCBound(ctx, e.kubeClient.CoreV1(), e.kubeClient.CoreV1(), restorePVC.Name, restorePVC.Namespace, param.ExposeTimeout) if err != nil { return errors.Wrap(err, "error waiting for restore PVC bound") } @@ -309,7 +310,8 @@ func (e *genericRestoreExposer) Expose(ctx context.Context, ownerObject corev1ap return errors.Wrapf(err, "error to get volume snapshot %s/%s", param.CSI.Snapshot.VolumeSnapshotNamespace, param.CSI.Snapshot.VolumeSnapshot) } - vsc, err := csi.GetVSCForVS(ctx, vs, e.ctrlClient) + var vsc *snapshotv1api.VolumeSnapshotContent + vsc, err = csi.GetVSCForVS(ctx, vs, e.ctrlClient) if err != nil { return errors.Wrapf(err, "error to get volume snapshot content for volume snapshot %s/%s", vs.Namespace, vs.Name) } diff --git a/pkg/exposer/generic_restore_test.go b/pkg/exposer/generic_restore_test.go index 06ec45f68..abeb17f40 100644 --- a/pkg/exposer/generic_restore_test.go +++ b/pkg/exposer/generic_restore_test.go @@ -188,20 +188,22 @@ func TestRestoreExpose(t *testing.T) { } tests := []struct { - name string - kubeClientObj []runtime.Object - ownerRestore *velerov1.Restore - targetPVCName string - targetNamespace string - targetPVName string - kubeReactors []reactor - cacheVolume *CacheConfigs - dataMover string - expectBackupPod bool - expectBackupPVC bool - expectCachePVC bool - expectBackupPV bool - err string + name string + kubeClientObj []runtime.Object + ownerRestore *velerov1.Restore + targetPVCName string + targetNamespace string + targetPVName string + kubeReactors []reactor + cacheVolume *CacheConfigs + dataMover string + expectBackupPod bool + expectBackupPVC bool + expectCachePVC bool + expectBackupPV bool + expectedNodeSelector map[string]string + expectedNodeAffinity *corev1api.NodeAffinity + err string }{ { name: "wait target pvc consumed fail", @@ -339,8 +341,29 @@ func TestRestoreExpose(t *testing.T) { }, }, }, - expectBackupPod: true, - expectBackupPVC: true, + expectBackupPod: true, + expectBackupPVC: true, + expectedNodeSelector: map[string]string{}, + expectedNodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "topology.kubernetes.io/zone", + Operator: corev1api.NodeSelectorOpIn, + Values: []string{"zone-1"}, + }, + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, }, { name: "create temporary PV fail", @@ -559,9 +582,16 @@ func TestRestoreExpose(t *testing.T) { require.NoError(t, err) } - _, err = exposer.kubeClient.CoreV1().Pods(ownerObject.Namespace).Get(t.Context(), ownerObject.Name, metav1.GetOptions{}) + pod, err := exposer.kubeClient.CoreV1().Pods(ownerObject.Namespace).Get(t.Context(), ownerObject.Name, metav1.GetOptions{}) if test.expectBackupPod { require.NoError(t, err) + if test.expectedNodeSelector != nil { + assert.Equal(t, test.expectedNodeSelector, pod.Spec.NodeSelector) + } + if test.expectedNodeAffinity != nil { + require.NotNil(t, pod.Spec.Affinity) + assert.Equal(t, test.expectedNodeAffinity, pod.Spec.Affinity.NodeAffinity) + } } else { require.True(t, apierrors.IsNotFound(err), "expected IsNotFound, got %v", err) } @@ -1608,6 +1638,115 @@ end diagnose restore exposer`, } } +func TestValidateSelectedNode(t *testing.T) { + tests := []struct { + name string + node string + dataMover string + kubeClientObj []runtime.Object + expected bool + }{ + { + name: "empty node", + node: "", + expected: true, + }, + { + name: "node os is linux", + node: "fake-node", + kubeClientObj: []runtime.Object{ + &corev1api.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-node", + Labels: map[string]string{ + corev1api.LabelOSStable: kube.NodeOSLinux, + }, + }, + }, + }, + expected: true, + }, + { + name: "node os is windows", + node: "fake-node", + kubeClientObj: []runtime.Object{ + &corev1api.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-node", + Labels: map[string]string{ + corev1api.LabelOSStable: kube.NodeOSWindows, + }, + }, + }, + }, + expected: true, + }, + { + name: "node without os label", + node: "fake-node", + kubeClientObj: []runtime.Object{ + &corev1api.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-node", + }, + }, + }, + expected: false, + }, + { + name: "node not found", + node: "fake-node", + expected: false, + }, + { + name: "block data mover with linux node", + node: "fake-node", + dataMover: datamover.DataMoverTypeVeleroBlock, + kubeClientObj: []runtime.Object{ + &corev1api.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-node", + Labels: map[string]string{ + corev1api.LabelOSStable: kube.NodeOSLinux, + }, + }, + }, + }, + expected: true, + }, + { + name: "block data mover with windows node", + node: "fake-node", + dataMover: datamover.DataMoverTypeVeleroBlock, + kubeClientObj: []runtime.Object{ + &corev1api.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fake-node", + Labels: map[string]string{ + corev1api.LabelOSStable: kube.NodeOSWindows, + }, + }, + }, + }, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeKubeClient := fake.NewSimpleClientset(test.kubeClientObj...) + + exposer := genericRestoreExposer{ + kubeClient: fakeKubeClient, + log: velerotest.NewLogger(), + } + + actual := exposer.validateSelectedNode(t.Context(), test.node, test.dataMover, exposer.log) + assert.Equal(t, test.expected, actual) + }) + } +} + func TestCreateRestorePod(t *testing.T) { scName := "storage-class-01" @@ -1744,28 +1883,28 @@ func TestCreateRestorePod(t *testing.T) { log: velerotest.NewLogger(), } - pod, err := exposer.createRestorePod( - t.Context(), - corev1api.ObjectReference{ - Namespace: velerov1.DefaultNamespace, - Name: "data-download", - }, - targetPVCObj, - time.Second*3, - nil, - nil, - nil, - test.selectedNode, - corev1api.ResourceRequirements{}, - test.nodeOS, - test.affinity, - "", // priority class name - nil, - "", // volumeSnapshotNamespace - "", // volumeID - nil, - nil, // volumeTopology - ) + pod, err := exposer.createRestorePod( + t.Context(), + corev1api.ObjectReference{ + Namespace: velerov1.DefaultNamespace, + Name: "data-download", + }, + targetPVCObj, + time.Second*3, + nil, + nil, + nil, + test.selectedNode, + corev1api.ResourceRequirements{}, + test.nodeOS, + test.affinity, + "", // priority class name + nil, + "", // volumeSnapshotNamespace + "", // volumeID + nil, + nil, // volumeTopology + ) require.NoError(t, err) if test.expectedPod != nil {