From 8f9beb04f080dde7d04b04576504ecf70920d275 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 26 Jan 2026 17:56:23 +0800 Subject: [PATCH 1/6] 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 5acb229d2..7521a155a 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -320,7 +320,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 598c8c528bcda995de1fd213f14f0f93a1603539 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 27 Jan 2026 14:49:55 +0800 Subject: [PATCH 2/6] 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 7521a155a..175a31da9 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -644,6 +644,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{} @@ -655,9 +659,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", @@ -684,8 +693,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 c10370072..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([]*kube.LoadAffinity{affinity}) - } + 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 18c32ed29c2d6f8fd17a891de4dee831d4254150 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 27 Jan 2026 14:56:14 +0800 Subject: [PATCH 3/6] 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 41fa7748441fe9b70edbd9cd8c4139129507d527 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 9 Feb 2026 18:50:59 +0800 Subject: [PATCH 4/6] 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 175a31da9..242d1376f 100644 --- a/pkg/exposer/csi_snapshot.go +++ b/pkg/exposer/csi_snapshot.go @@ -702,7 +702,6 @@ func (e *csiSnapshotExposer) createBackupPod( }) } - var podAffinity *corev1api.Affinity if len(intoleratableNodes) > 0 { if affinity == nil { affinity = &kube.LoadAffinity{} @@ -715,9 +714,7 @@ func (e *csiSnapshotExposer) createBackupPod( }) } - 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/csi_snapshot_test.go b/pkg/exposer/csi_snapshot_test.go index b4dd92c3f..17fea5d6d 100644 --- a/pkg/exposer/csi_snapshot_test.go +++ b/pkg/exposer/csi_snapshot_test.go @@ -403,6 +403,23 @@ func TestExpose(t *testing.T) { kubeClientObj: []runtime.Object{ daemonSet, }, + 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", @@ -421,6 +438,23 @@ func TestExpose(t *testing.T) { kubeClientObj: []runtime.Object{ daemonSet, }, + 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", @@ -441,6 +475,23 @@ func TestExpose(t *testing.T) { daemonSet, }, 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", @@ -467,6 +518,23 @@ func TestExpose(t *testing.T) { daemonSet, }, 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", @@ -494,6 +562,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", @@ -519,6 +604,23 @@ func TestExpose(t *testing.T) { daemonSet, }, 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", @@ -563,6 +665,11 @@ func TestExpose(t *testing.T) { Operator: corev1api.NodeSelectorOpIn, Values: []string{"Linux"}, }, + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, }, }, }, @@ -619,6 +726,11 @@ func TestExpose(t *testing.T) { Operator: corev1api.NodeSelectorOpIn, Values: []string{"amd64"}, }, + { + Key: "kubernetes.io/os", + Operator: corev1api.NodeSelectorOpNotIn, + Values: []string{"windows"}, + }, }, }, }, @@ -651,7 +763,23 @@ func TestExpose(t *testing.T) { daemonSet, }, 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", @@ -687,7 +815,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, }, { @@ -715,7 +859,23 @@ func TestExpose(t *testing.T) { kubeClientObj: []runtime.Object{ daemonSet, }, - 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"}, }, { @@ -751,6 +911,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, @@ -844,6 +1009,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 2a696a443172e26b8c33cd4f659d234538bf0445 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Fri, 13 Feb 2026 17:34:36 +0800 Subject: [PATCH 5/6] update doc link for 1.18 Signed-off-by: Lyndon-Li --- ...{upgrade-to-1.17.md => upgrade-to-1.18.md} | 37 ++++++++----------- site/data/docs/main-toc.yml | 4 +- site/data/docs/v1-18-toc.yml | 4 +- 3 files changed, 20 insertions(+), 25 deletions(-) rename site/content/docs/main/{upgrade-to-1.17.md => upgrade-to-1.18.md} (70%) diff --git a/site/content/docs/main/upgrade-to-1.17.md b/site/content/docs/main/upgrade-to-1.18.md similarity index 70% rename from site/content/docs/main/upgrade-to-1.17.md rename to site/content/docs/main/upgrade-to-1.18.md index f6738d55c..0bf839fe3 100644 --- a/site/content/docs/main/upgrade-to-1.17.md +++ b/site/content/docs/main/upgrade-to-1.18.md @@ -1,13 +1,13 @@ --- -title: "Upgrading to Velero 1.17" +title: "Upgrading to Velero 1.18" layout: docs --- ## Prerequisites -- Velero [v1.16.x][9] installed. +- Velero [v1.17.x][9] installed. -If you're not yet running at least Velero v1.16, see the following: +If you're not yet running at least Velero v1.17, see the following: - [Upgrading to v1.8][1] - [Upgrading to v1.9][2] @@ -18,13 +18,14 @@ If you're not yet running at least Velero v1.16, see the following: - [Upgrading to v1.14][7] - [Upgrading to v1.15][8] - [Upgrading to v1.16][9] +- [Upgrading to v1.17][10] Before upgrading, check the [Velero compatibility matrix](https://github.com/vmware-tanzu/velero#velero-compatibility-matrix) to make sure your version of Kubernetes is supported by the new version of Velero. ## Instructions -### Upgrade from v1.16 -1. Install the Velero v1.17 command-line interface (CLI) by following the [instructions here][0]. +### Upgrade from v1.17 +1. Install the Velero v1.18 command-line interface (CLI) by following the [instructions here][0]. Verify that you've properly installed it by running: @@ -36,7 +37,7 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw ```bash Client: - Version: v1.17.0 + Version: v1.18.0 Git commit: ``` @@ -46,28 +47,21 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw velero install --crds-only --dry-run -o yaml | kubectl apply -f - ``` -3. (optional) Update the `uploader-type` to `kopia` if you are using `restic`: - ```bash - kubectl get deploy -n velero -ojson \ - | sed "s/\"--uploader-type=restic\"/\"--uploader-type=kopia\"/g" \ - | kubectl apply -f - - ``` - -4. Update the container image used by the Velero deployment, plugin and (optionally) the node agent daemon set: +3. Update the container image used by the Velero deployment, plugin and (optionally) the node agent daemon set: ```bash # set the container and image of the init container for plugin accordingly, # if you are using other plugin kubectl set image deployment/velero \ - velero=velero/velero:v1.17.0 \ - velero-plugin-for-aws=velero/velero-plugin-for-aws:v1.13.0 \ + velero=velero/velero:v1.18.0 \ + velero-plugin-for-aws=velero/velero-plugin-for-aws:v1.14.0 \ --namespace velero # optional, if using the node agent daemonset kubectl set image daemonset/node-agent \ - node-agent=velero/velero:v1.17.0 \ + node-agent=velero/velero:v1.18.0 \ --namespace velero ``` -5. Confirm that the deployment is up and running with the correct version by running: +4. Confirm that the deployment is up and running with the correct version by running: ```bash velero version @@ -77,11 +71,11 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw ```bash Client: - Version: v1.17.0 + Version: v1.18.0 Git commit: Server: - Version: v1.17.0 + Version: v1.18.0 ``` [0]: basic-install.md#install-the-cli @@ -93,4 +87,5 @@ Before upgrading, check the [Velero compatibility matrix](https://github.com/vmw [6]: https://velero.io/docs/v1.13/upgrade-to-1.13 [7]: https://velero.io/docs/v1.14/upgrade-to-1.14 [8]: https://velero.io/docs/v1.15/upgrade-to-1.15 -[9]: https://velero.io/docs/v1.16/upgrade-to-1.16 \ No newline at end of file +[9]: https://velero.io/docs/v1.16/upgrade-to-1.16 +[10]: https://velero.io/docs/v1.17/upgrade-to-1.17 \ No newline at end of file diff --git a/site/data/docs/main-toc.yml b/site/data/docs/main-toc.yml index dacec0651..271705a1b 100644 --- a/site/data/docs/main-toc.yml +++ b/site/data/docs/main-toc.yml @@ -13,8 +13,8 @@ toc: url: /basic-install - page: Customize Installation url: /customize-installation - - page: Upgrade to 1.17 - url: /upgrade-to-1.17 + - page: Upgrade to 1.18 + url: /upgrade-to-1.18 - page: Supported providers url: /supported-providers - page: Evaluation install diff --git a/site/data/docs/v1-18-toc.yml b/site/data/docs/v1-18-toc.yml index dacec0651..271705a1b 100644 --- a/site/data/docs/v1-18-toc.yml +++ b/site/data/docs/v1-18-toc.yml @@ -13,8 +13,8 @@ toc: url: /basic-install - page: Customize Installation url: /customize-installation - - page: Upgrade to 1.17 - url: /upgrade-to-1.17 + - page: Upgrade to 1.18 + url: /upgrade-to-1.18 - page: Supported providers url: /supported-providers - page: Evaluation install From ba5e7681ff60641cd6427619181bf7991bb43c7e Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Thu, 19 Feb 2026 14:28:15 -0500 Subject: [PATCH 6/6] rename malformed changelog file name (#9552) Signed-off-by: Tiger Kaovilai --- changelogs/unreleased/{9532-Lyndon-Li‎ => 9532-Lyndon-Li} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelogs/unreleased/{9532-Lyndon-Li‎ => 9532-Lyndon-Li} (100%) diff --git a/changelogs/unreleased/9532-Lyndon-Li‎ b/changelogs/unreleased/9532-Lyndon-Li similarity index 100% rename from changelogs/unreleased/9532-Lyndon-Li‎ rename to changelogs/unreleased/9532-Lyndon-Li