From 41fa7748441fe9b70edbd9cd8c4139129507d527 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 9 Feb 2026 18:50:59 +0800 Subject: [PATCH] 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, }