From 57892169a90b73aa28d12e0408ab6ca01173d2dd Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 26 Jan 2026 17:56:23 +0800 Subject: [PATCH 1/5] support customized host os Signed-off-by: Lyndon-Li --- pkg/exposer/csi_snapshot.go | 3 ++- pkg/install/daemonset.go | 21 ++++++++++++++++----- pkg/util/kube/node.go | 36 +++++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index 637cd7c07..c2017d12d 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -330,7 +330,8 @@ func (e *csiSnapshotExposer) GetExposed(ctx context.Context, ownerObject corev1a curLog.WithField("pod", pod.Name).Infof("Backup volume is found in pod at index %v", i) var nodeOS *string - if os, found := pod.Spec.NodeSelector[kube.NodeOSLabel]; found { + if pod.Spec.OS != nil { + os := string(pod.Spec.OS.Name) nodeOS = &os } diff --git a/pkg/install/daemonset.go b/pkg/install/daemonset.go index ee63f3736..9c5433cb0 100644 --- a/pkg/install/daemonset.go +++ b/pkg/install/daemonset.go @@ -256,11 +256,22 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1api.DaemonSet }, } } else { - daemonSet.Spec.Template.Spec.NodeSelector = map[string]string{ - "kubernetes.io/os": "linux", - } - daemonSet.Spec.Template.Spec.OS = &corev1api.PodOS{ - Name: "linux", + daemonSet.Spec.Template.Spec.Affinity = &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpNotIn, + }, + }, + }, + }, + }, + }, } } diff --git a/pkg/util/kube/node.go b/pkg/util/kube/node.go index da68183a5..d92a6566d 100644 --- a/pkg/util/kube/node.go +++ b/pkg/util/kube/node.go @@ -17,7 +17,6 @@ package kube import ( "context" - "fmt" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -34,6 +33,11 @@ const ( NodeOSLabel = "kubernetes.io/os" ) +var nodeOSMap map[string]string = map[string]string{ + "linux": NodeOSLinux, + "windows": NodeOSWindows, +} + func IsLinuxNode(ctx context.Context, nodeName string, client client.Client) error { node := &corev1api.Node{} if err := client.Get(ctx, types.NamespacedName{Name: nodeName}, node); err != nil { @@ -41,12 +45,11 @@ func IsLinuxNode(ctx context.Context, nodeName string, client client.Client) err } os, found := node.Labels[NodeOSLabel] - if !found { return errors.Errorf("no os type label for node %s", nodeName) } - if os != NodeOSLinux { + if getRealOS(os) != NodeOSLinux { return errors.Errorf("os type %s for node %s is not linux", os, nodeName) } @@ -72,7 +75,7 @@ func withOSNode(ctx context.Context, client client.Client, osType string, log lo for _, node := range nodeList.Items { os, found := node.Labels[NodeOSLabel] - if os == osType { + if getRealOS(os) == osType { return true } @@ -98,7 +101,7 @@ func GetNodeOS(ctx context.Context, nodeName string, nodeClient corev1client.Cor return "", nil } - return node.Labels[NodeOSLabel], nil + return getRealOS(node.Labels[NodeOSLabel]), nil } func HasNodeWithOS(ctx context.Context, os string, nodeClient corev1client.CoreV1Interface) error { @@ -106,14 +109,29 @@ func HasNodeWithOS(ctx context.Context, os string, nodeClient corev1client.CoreV return errors.New("invalid node OS") } - nodes, err := nodeClient.Nodes().List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", NodeOSLabel, os)}) + nodes, err := nodeClient.Nodes().List(ctx, metav1.ListOptions{}) if err != nil { return errors.Wrapf(err, "error listing nodes with OS %s", os) } - if len(nodes.Items) == 0 { - return errors.Errorf("node with OS %s doesn't exist", os) + for _, node := range nodes.Items { + osLabel, found := node.Labels[NodeOSLabel] + if !found { + continue + } + + if getRealOS(osLabel) == os { + return nil + } } - return nil + return errors.Errorf("node with OS %s doesn't exist", os) +} + +func getRealOS(osLabel string) string { + if os, found := nodeOSMap[osLabel]; !found { + return NodeOSLinux + } else { + return os + } } From 01bd153968fb9c30aaf258410e2c030daa0b1578 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 27 Jan 2026 14:49:55 +0800 Subject: [PATCH 2/5] support customized host os - use affinity for host os selection Signed-off-by: Lyndon-Li --- pkg/exposer/csi_snapshot.go | 18 ++++++++++++++++-- pkg/exposer/generic_restore.go | 29 ++++++++++++++++++++--------- pkg/exposer/pod_volume.go | 22 ++++++++++++++++++++-- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index c2017d12d..2d777c668 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -655,6 +655,10 @@ func (e *csiSnapshotExposer) createBackupPod( args = append(args, podInfo.logFormatArgs...) args = append(args, podInfo.logLevelArgs...) + if affinity == nil { + affinity = &kube.LoadAffinity{} + } + var securityCtx *corev1api.PodSecurityContext nodeSelector := map[string]string{} podOS := corev1api.PodOS{} @@ -666,9 +670,14 @@ func (e *csiSnapshotExposer) createBackupPod( }, } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows podOS.Name = kube.NodeOSWindows + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpIn, + }) + toleration = append(toleration, []corev1api.Toleration{ { Key: "os", @@ -695,8 +704,13 @@ func (e *csiSnapshotExposer) createBackupPod( } } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux podOS.Name = kube.NodeOSLinux + + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpNotIn, + }) } var podAffinity *corev1api.Affinity diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index f7cf24d8d..dd9e1e16b 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -493,13 +493,9 @@ func (e *genericRestoreExposer) createRestorePod( containerName := string(ownerObject.UID) volumeName := string(ownerObject.UID) - var podAffinity *corev1api.Affinity - if selectedNode == "" { - e.log.Infof("No selected node for restore pod. Try to get affinity from the node-agent config.") - - if affinity != nil { - podAffinity = kube.ToSystemAffinity(affinity, nil) - } + if selectedNode != "" { + affinity = &kube.LoadAffinity{} + e.log.Infof("Selected node for restore pod. Ignore affinity from the node-agent config.") } podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace, nodeOS) @@ -576,9 +572,14 @@ func (e *genericRestoreExposer) createRestorePod( }, } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows podOS.Name = kube.NodeOSWindows + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpIn, + }) + toleration = append(toleration, []corev1api.Toleration{ { Key: "os", @@ -599,8 +600,18 @@ func (e *genericRestoreExposer) createRestorePod( RunAsUser: &userID, } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux podOS.Name = kube.NodeOSLinux + + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpNotIn, + }) + } + + var podAffinity *corev1api.Affinity + if affinity != nil { + podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) } pod := &corev1api.Pod{ diff --git a/pkg/exposer/pod_volume.go b/pkg/exposer/pod_volume.go index 246d04e28..10ab14859 100644 --- a/pkg/exposer/pod_volume.go +++ b/pkg/exposer/pod_volume.go @@ -434,6 +434,8 @@ func (e *podVolumeExposer) createHostingPod( args = append(args, podInfo.logFormatArgs...) args = append(args, podInfo.logLevelArgs...) + affinity := &kube.LoadAffinity{} + var securityCtx *corev1api.PodSecurityContext var containerSecurityCtx *corev1api.SecurityContext nodeSelector := map[string]string{} @@ -446,9 +448,14 @@ func (e *podVolumeExposer) createHostingPod( }, } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSWindows podOS.Name = kube.NodeOSWindows + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpIn, + }) + toleration = append(toleration, []corev1api.Toleration{ { Key: "os", @@ -472,8 +479,18 @@ func (e *podVolumeExposer) createHostingPod( Privileged: &privileged, } - nodeSelector[kube.NodeOSLabel] = kube.NodeOSLinux podOS.Name = kube.NodeOSLinux + + affinity.NodeSelector.MatchExpressions = append(affinity.NodeSelector.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: kube.NodeOSLabel, + Values: []string{kube.NodeOSWindows}, + Operator: metav1.LabelSelectorOpNotIn, + }) + } + + var podAffinity *corev1api.Affinity + if affinity != nil { + podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) } pod := &corev1api.Pod{ @@ -495,6 +512,7 @@ func (e *podVolumeExposer) createHostingPod( Spec: corev1api.PodSpec{ NodeSelector: nodeSelector, OS: &podOS, + Affinity: podAffinity, Containers: []corev1api.Container{ { Name: containerName, From 733b2eb6f56234e888a8f9cd066c157db7e19a62 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 27 Jan 2026 14:56:14 +0800 Subject: [PATCH 3/5] support customized host os Signed-off-by: Lyndon-Li --- pkg/exposer/generic_restore.go | 6 +++++- pkg/install/daemonset.go | 22 +++++++++++++++++++--- pkg/install/deployment.go | 20 +++++++++++++++++--- pkg/util/kube/node.go | 4 ++-- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index dd9e1e16b..e634517ff 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -494,10 +494,14 @@ func (e *genericRestoreExposer) createRestorePod( volumeName := string(ownerObject.UID) if selectedNode != "" { - affinity = &kube.LoadAffinity{} + affinity = nil e.log.Infof("Selected node for restore pod. Ignore affinity from the node-agent config.") } + if affinity == nil { + affinity = &kube.LoadAffinity{} + } + podInfo, err := getInheritedPodInfo(ctx, e.kubeClient, ownerObject.Namespace, nodeOS) if err != nil { return nil, errors.Wrap(err, "error to get inherited pod info from node-agent") diff --git a/pkg/install/daemonset.go b/pkg/install/daemonset.go index 9c5433cb0..771114e82 100644 --- a/pkg/install/daemonset.go +++ b/pkg/install/daemonset.go @@ -235,12 +235,28 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1api.DaemonSet if c.forWindows { daemonSet.Spec.Template.Spec.SecurityContext = nil daemonSet.Spec.Template.Spec.Containers[0].SecurityContext = nil - daemonSet.Spec.Template.Spec.NodeSelector = map[string]string{ - "kubernetes.io/os": "windows", - } daemonSet.Spec.Template.Spec.OS = &corev1api.PodOS{ Name: "windows", } + + daemonSet.Spec.Template.Spec.Affinity = &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpIn, + }, + }, + }, + }, + }, + }, + } + daemonSet.Spec.Template.Spec.Tolerations = []corev1api.Toleration{ { Key: "os", diff --git a/pkg/install/deployment.go b/pkg/install/deployment.go index d1010d294..04ea40e04 100644 --- a/pkg/install/deployment.go +++ b/pkg/install/deployment.go @@ -364,12 +364,26 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1api.Deployme Spec: corev1api.PodSpec{ RestartPolicy: corev1api.RestartPolicyAlways, ServiceAccountName: c.serviceAccountName, - NodeSelector: map[string]string{ - "kubernetes.io/os": "linux", - }, OS: &corev1api.PodOS{ Name: "linux", }, + Affinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpNotIn, + }, + }, + }, + }, + }, + }, + }, Containers: []corev1api.Container{ { Name: "velero", diff --git a/pkg/util/kube/node.go b/pkg/util/kube/node.go index d92a6566d..96bbd54ce 100644 --- a/pkg/util/kube/node.go +++ b/pkg/util/kube/node.go @@ -33,7 +33,7 @@ const ( NodeOSLabel = "kubernetes.io/os" ) -var nodeOSMap map[string]string = map[string]string{ +var realNodeOSMap map[string]string = map[string]string{ "linux": NodeOSLinux, "windows": NodeOSWindows, } @@ -129,7 +129,7 @@ func HasNodeWithOS(ctx context.Context, os string, nodeClient corev1client.CoreV } func getRealOS(osLabel string) string { - if os, found := nodeOSMap[osLabel]; !found { + if os, found := realNodeOSMap[osLabel]; !found { return NodeOSLinux } else { return os From eac69375c9b95bf5b3ff71d0a58b08ca5e3f1830 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 9 Feb 2026 18:50:59 +0800 Subject: [PATCH 4/5] support custom os Signed-off-by: Lyndon-Li --- changelogs/unreleased/9533-Lyndon-Li‎‎ | 1 + pkg/exposer/csi_snapshot.go | 5 +- pkg/exposer/csi_snapshot_test.go | 173 ++++++++++++++++++++++++++- pkg/exposer/generic_restore.go | 5 +- pkg/exposer/pod_volume.go | 5 +- pkg/install/daemonset_test.go | 37 +++++- pkg/install/deployment_test.go | 19 ++- pkg/util/kube/node.go | 2 +- 8 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/9533-Lyndon-Li‎‎ diff --git a/changelogs/unreleased/9533-Lyndon-Li‎‎ b/changelogs/unreleased/9533-Lyndon-Li‎‎ new file mode 100644 index 000000000..acd2b37cb --- /dev/null +++ b/changelogs/unreleased/9533-Lyndon-Li‎‎ @@ -0,0 +1 @@ +Fix issue #9496, support customized host os \ No newline at end of file diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index 2d777c668..247b37bd0 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -713,7 +713,6 @@ func (e *csiSnapshotExposer) createBackupPod( }) } - var podAffinity *corev1api.Affinity if len(intoleratableNodes) > 0 { if affinity == nil { affinity = &kube.LoadAffinity{} @@ -726,9 +725,7 @@ func (e *csiSnapshotExposer) createBackupPod( }) } - if affinity != nil { - podAffinity = kube.ToSystemAffinity(affinity, volumeTopology) - } + podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/exposer/csi_snapshot_test.go b/pkg/exposer/csi_snapshot_test.go index 04cf2b8b6..e1a9860eb 100644 --- a/pkg/exposer/csi_snapshot_test.go +++ b/pkg/exposer/csi_snapshot_test.go @@ -467,6 +467,23 @@ func TestExpose(t *testing.T) { daemonSet, scObj, }, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "success-with-labels", @@ -488,6 +505,23 @@ func TestExpose(t *testing.T) { daemonSet, scObj, }, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "restore size from exposeParam", @@ -511,6 +545,23 @@ func TestExpose(t *testing.T) { scObj, }, expectedVolumeSize: resource.NewQuantity(567890, ""), + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "backupPod mounts read only backupPVC", @@ -539,6 +590,23 @@ func TestExpose(t *testing.T) { scObj, }, expectedReadOnlyPVC: true, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "backupPod mounts read only backupPVC and storageClass specified in backupPVC config", @@ -568,6 +636,23 @@ func TestExpose(t *testing.T) { }, expectedReadOnlyPVC: true, expectedBackupPVCStorageClass: "fake-sc-read-only", + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "backupPod mounts backupPVC with storageClass specified in backupPVC config", @@ -595,6 +680,23 @@ func TestExpose(t *testing.T) { scObj, }, expectedBackupPVCStorageClass: "fake-sc-read-only", + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "Affinity per StorageClass", @@ -641,6 +743,11 @@ func TestExpose(t *testing.T) { Operator: corev1api.NodeSelectorOpIn, Values: []string{"Linux"}, }, + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, }, }, }, @@ -699,6 +806,11 @@ func TestExpose(t *testing.T) { Operator: corev1api.NodeSelectorOpIn, Values: []string{"amd64"}, }, + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, }, }, }, @@ -733,7 +845,23 @@ func TestExpose(t *testing.T) { scObj, }, expectedBackupPVCStorageClass: "fake-sc-read-only", - expectedAffinity: nil, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, }, { name: "IntolerateSourceNode, get source node fail", @@ -770,7 +898,23 @@ func TestExpose(t *testing.T) { }, }, }, - expectedAffinity: nil, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, expectedPVCAnnotation: nil, }, { @@ -799,7 +943,23 @@ func TestExpose(t *testing.T) { daemonSet, scObj, }, - expectedAffinity: nil, + expectedAffinity: &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, + }, + }, + }, + }, + }, + }, expectedPVCAnnotation: map[string]string{util.VSphereCNSFastCloneAnno: "true"}, }, { @@ -836,6 +996,11 @@ func TestExpose(t *testing.T) { NodeSelectorTerms: []corev1api.NodeSelectorTerm{ { MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, { Key: "kubernetes.io/hostname", Operator: corev1api.NodeSelectorOpNotIn, @@ -929,6 +1094,8 @@ func TestExpose(t *testing.T) { if test.expectedAffinity != nil { assert.Equal(t, test.expectedAffinity, backupPod.Spec.Affinity) + } else { + assert.Nil(t, backupPod.Spec.Affinity) } if test.expectedPVCAnnotation != nil { diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index e634517ff..830313c26 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -613,10 +613,7 @@ func (e *genericRestoreExposer) createRestorePod( }) } - var podAffinity *corev1api.Affinity - if affinity != nil { - podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) - } + podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/exposer/pod_volume.go b/pkg/exposer/pod_volume.go index 10ab14859..53f055f7f 100644 --- a/pkg/exposer/pod_volume.go +++ b/pkg/exposer/pod_volume.go @@ -488,10 +488,7 @@ func (e *podVolumeExposer) createHostingPod( }) } - var podAffinity *corev1api.Affinity - if affinity != nil { - podAffinity = kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) - } + podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/install/daemonset_test.go b/pkg/install/daemonset_test.go index 139d3dcd0..0f4de11bd 100644 --- a/pkg/install/daemonset_test.go +++ b/pkg/install/daemonset_test.go @@ -34,8 +34,23 @@ func TestDaemonSet(t *testing.T) { assert.Equal(t, "velero", ds.ObjectMeta.Namespace) assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["name"]) assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["role"]) - assert.Equal(t, "linux", ds.Spec.Template.Spec.NodeSelector["kubernetes.io/os"]) - assert.Equal(t, "linux", string(ds.Spec.Template.Spec.OS.Name)) + assert.Equal(t, &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpNotIn, + }, + }, + }, + }, + }, + }, + }, ds.Spec.Template.Spec.Affinity) assert.Equal(t, corev1api.PodSecurityContext{RunAsUser: &userID}, *ds.Spec.Template.Spec.SecurityContext) assert.Equal(t, corev1api.SecurityContext{Privileged: &boolFalse}, *ds.Spec.Template.Spec.Containers[0].SecurityContext) assert.Len(t, ds.Spec.Template.Spec.Volumes, 3) @@ -80,8 +95,24 @@ func TestDaemonSet(t *testing.T) { assert.Equal(t, "velero", ds.ObjectMeta.Namespace) assert.Equal(t, "node-agent-windows", ds.Spec.Template.ObjectMeta.Labels["name"]) assert.Equal(t, "node-agent", ds.Spec.Template.ObjectMeta.Labels["role"]) - assert.Equal(t, "windows", ds.Spec.Template.Spec.NodeSelector["kubernetes.io/os"]) assert.Equal(t, "windows", string(ds.Spec.Template.Spec.OS.Name)) + assert.Equal(t, &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpIn, + }, + }, + }, + }, + }, + }, + }, ds.Spec.Template.Spec.Affinity) assert.Equal(t, (*corev1api.PodSecurityContext)(nil), ds.Spec.Template.Spec.SecurityContext) assert.Equal(t, (*corev1api.SecurityContext)(nil), ds.Spec.Template.Spec.Containers[0].SecurityContext) } diff --git a/pkg/install/deployment_test.go b/pkg/install/deployment_test.go index b8aeaa9dd..53b696f72 100644 --- a/pkg/install/deployment_test.go +++ b/pkg/install/deployment_test.go @@ -100,8 +100,23 @@ func TestDeployment(t *testing.T) { assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2) assert.Equal(t, "--repo-maintenance-job-configmap=test-repo-maintenance-config", deploy.Spec.Template.Spec.Containers[0].Args[1]) - assert.Equal(t, "linux", deploy.Spec.Template.Spec.NodeSelector["kubernetes.io/os"]) - assert.Equal(t, "linux", string(deploy.Spec.Template.Spec.OS.Name)) + assert.Equal(t, &corev1api.Affinity{ + NodeAffinity: &corev1api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1api.NodeSelector{ + NodeSelectorTerms: []corev1api.NodeSelectorTerm{ + { + MatchExpressions: []corev1api.NodeSelectorRequirement{ + { + Key: "kubernetes.io/os", + Values: []string{"windows"}, + Operator: corev1api.NodeSelectorOpNotIn, + }, + }, + }, + }, + }, + }, + }, deploy.Spec.Template.Spec.Affinity) } func TestDeploymentWithPriorityClassName(t *testing.T) { diff --git a/pkg/util/kube/node.go b/pkg/util/kube/node.go index 96bbd54ce..ba6853624 100644 --- a/pkg/util/kube/node.go +++ b/pkg/util/kube/node.go @@ -33,7 +33,7 @@ const ( NodeOSLabel = "kubernetes.io/os" ) -var realNodeOSMap map[string]string = map[string]string{ +var realNodeOSMap = map[string]string{ "linux": NodeOSLinux, "windows": NodeOSWindows, } From 2c0fddc498edf2899cac5c37f2a291ff6b13fff8 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Wed, 11 Mar 2026 17:58:48 +0800 Subject: [PATCH 5/5] support custom os Signed-off-by: Lyndon-Li --- changelogs/unreleased/{9533-Lyndon-Li‎‎ => 9606-Lyndon-Li‎‎} | 0 pkg/exposer/csi_snapshot.go | 2 +- pkg/exposer/generic_restore.go | 2 +- pkg/exposer/pod_volume.go | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename changelogs/unreleased/{9533-Lyndon-Li‎‎ => 9606-Lyndon-Li‎‎} (100%) diff --git a/changelogs/unreleased/9533-Lyndon-Li‎‎ b/changelogs/unreleased/9606-Lyndon-Li‎‎ similarity index 100% rename from changelogs/unreleased/9533-Lyndon-Li‎‎ rename to changelogs/unreleased/9606-Lyndon-Li‎‎ diff --git a/pkg/exposer/csi_snapshot.go b/pkg/exposer/csi_snapshot.go index 247b37bd0..079a4b527 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -725,7 +725,7 @@ func (e *csiSnapshotExposer) createBackupPod( }) } - podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) + podAffinity := kube.ToSystemAffinity(affinity, volumeTopology) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/exposer/generic_restore.go b/pkg/exposer/generic_restore.go index 830313c26..186815d77 100644 --- a/pkg/exposer/generic_restore.go +++ b/pkg/exposer/generic_restore.go @@ -613,7 +613,7 @@ func (e *genericRestoreExposer) createRestorePod( }) } - podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) + podAffinity := kube.ToSystemAffinity(affinity, nil) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/exposer/pod_volume.go b/pkg/exposer/pod_volume.go index 53f055f7f..aeb6f1903 100644 --- a/pkg/exposer/pod_volume.go +++ b/pkg/exposer/pod_volume.go @@ -488,7 +488,7 @@ func (e *podVolumeExposer) createHostingPod( }) } - podAffinity := kube.ToSystemAffinity([]*kube.LoadAffinity{affinity}) + podAffinity := kube.ToSystemAffinity(affinity, nil) pod := &corev1api.Pod{ ObjectMeta: metav1.ObjectMeta{