From 1f27f0e3311e7f7f616e025c9ee841a102c39101 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 5 Mar 2026 20:48:35 -0800 Subject: [PATCH] Sort DeviceIDs in GetPodResourceMap for deterministic ordering When a namespace uses a primary User-Defined Network (UDN) with a device-plugin resource (e.g. SR-IOV), OVN-Kubernetes uses the last device in the list for the primary interface while Multus assigns earlier devices to cluster-default/secondary interfaces. The kubelet and checkpoint paths build the list from map iteration, so order was non-deterministic and the "last" device could differ between callers. Sorting ensures both Multus and OVN-K8s see the same order so the last device is consistently the one reserved for the primary UDN. Signed-off-by: Yun Zhou --- pkg/checkpoint/checkpoint.go | 1 + pkg/checkpoint/checkpoint_test.go | 9 +++++---- pkg/kubeletclient/kubeletclient.go | 1 + pkg/types/types.go | 11 +++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/checkpoint/checkpoint.go b/pkg/checkpoint/checkpoint.go index 2fc0de5a3..d68822b4a 100644 --- a/pkg/checkpoint/checkpoint.go +++ b/pkg/checkpoint/checkpoint.go @@ -108,5 +108,6 @@ func (cp *checkpoint) GetPodResourceMap(pod *v1.Pod) (map[string]*types.Resource } } } + types.SortDeviceIDs(resourceMap) return resourceMap, nil } diff --git a/pkg/checkpoint/checkpoint_test.go b/pkg/checkpoint/checkpoint_test.go index 42f89cae7..7e3301a5f 100644 --- a/pkg/checkpoint/checkpoint_test.go +++ b/pkg/checkpoint/checkpoint_test.go @@ -132,12 +132,13 @@ var _ = Describe("Kubelet checkpoint data read operations", func() { Expect(len(resourceInfo.DeviceIDs)).To(BeEquivalentTo(2)) }) - It("should have \"0000:03:02.3\" in deviceIDs[0]", func() { - Expect(resourceInfo.DeviceIDs[0]).To(BeEquivalentTo("0000:03:02.3")) + // DeviceIDs are sorted for deterministic order across callers + It("should have \"0000:03:02.0\" in deviceIDs[0] (sorted order)", func() { + Expect(resourceInfo.DeviceIDs[0]).To(BeEquivalentTo("0000:03:02.0")) }) - It("should have \"0000:03:02.0\" in deviceIDs[1]", func() { - Expect(resourceInfo.DeviceIDs[1]).To(BeEquivalentTo("0000:03:02.0")) + It("should have \"0000:03:02.3\" in deviceIDs[1] (sorted order)", func() { + Expect(resourceInfo.DeviceIDs[1]).To(BeEquivalentTo("0000:03:02.3")) }) }) diff --git a/pkg/kubeletclient/kubeletclient.go b/pkg/kubeletclient/kubeletclient.go index 630945e4c..0b7bc5d2a 100644 --- a/pkg/kubeletclient/kubeletclient.go +++ b/pkg/kubeletclient/kubeletclient.go @@ -143,6 +143,7 @@ func (rc *kubeletClient) GetPodResourceMap(pod *v1.Pod) (map[string]*types.Resou } } } + types.SortDeviceIDs(resourceMap) return resourceMap, nil } diff --git a/pkg/types/types.go b/pkg/types/types.go index 04df96ee8..a306ea55d 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -17,6 +17,7 @@ package types import ( "net" + "sort" "github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/pkg/types" @@ -178,6 +179,16 @@ type ResourceInfo struct { DeviceIDs []string } +// SortDeviceIDs sorts DeviceIDs in each ResourceInfo in place so that device +// order is deterministic across GetPodResourceMap callers (e.g. Multus and OVN-Kubernetes). +func SortDeviceIDs(resourceMap map[string]*ResourceInfo) { + for _, rInfo := range resourceMap { + if rInfo.DeviceIDs != nil { + sort.Strings(rInfo.DeviceIDs) + } + } +} + // ResourceClient provides a kubelet Pod resource handle type ResourceClient interface { // GetPodResourceMap returns an instance of a map of Pod ResourceInfo given a (Pod name, namespace) tuple