From 5dce711c112042c7bd6587795e290706c28e2683 Mon Sep 17 00:00:00 2001 From: Sameh Mohamed Date: Tue, 1 Sep 2026 15:36:34 +0000 Subject: [PATCH] test(otel/multi_efa): assert per-device EFA pod correlation (claimed vs unclaimed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TestMultiEFAClaimedVsUnclaimedCorrelation to the multi-EFA suite. On the 2-EFA node, efaburn (replicas: 1) claims one device and leaves the other unclaimed, so the test asserts the claimed device correlates to exactly one pod and the remaining unclaimed device carries no pod attributes. It also flags a single device correlated to multiple pods. This closes a coverage gap: the existing multi_efa tests check device count, ENIs, ports, and that at least one series is correlated, but none detect the EFA multi-device correlation collapse — where the resource-level pod promotion (without a groupbyattrs/efa split) attributes every EFA device on the node, including unclaimed ones, to a single pod. Verified failing on the current chart and passing with the groupbyattrs/efa fix. No topology change is required: the multi-EFA node already exposes two EFA devices with efaburn claiming one, which yields the claimed/unclaimed split. --- test/otel/multi_efa/helpers_test.go | 20 ++++++++ test/otel/multi_efa/multi_efa_test.go | 67 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/test/otel/multi_efa/helpers_test.go b/test/otel/multi_efa/helpers_test.go index 5734e94d0..51b36fb16 100644 --- a/test/otel/multi_efa/helpers_test.go +++ b/test/otel/multi_efa/helpers_test.go @@ -31,6 +31,26 @@ func filterByNodeLabel(results []otelmetrics.MetricResult, labelKey, labelValue return out } +// deviceKeys returns the sorted device names of a device->pods map (for messages). +func deviceKeys(m map[string]map[string]struct{}) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + sort.Strings(out) + return out +} + +// setKeys returns the sorted keys of a string set (for messages). +func setKeys(m map[string]struct{}) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + sort.Strings(out) + return out +} + // uniqueAnyValues returns the sorted unique non-empty values of an attribute // found in either resource or datapoint scope across all results. func uniqueAnyValues(results []otelmetrics.MetricResult, attr string) []string { diff --git a/test/otel/multi_efa/multi_efa_test.go b/test/otel/multi_efa/multi_efa_test.go index 0b0b5a343..757916359 100644 --- a/test/otel/multi_efa/multi_efa_test.go +++ b/test/otel/multi_efa/multi_efa_test.go @@ -149,3 +149,70 @@ func TestMultiEFACorrelatedPodLabels(t *testing.T) { } t.Fatal("No efa_rx_bytes result correlated to efaburn pod") } + +// expectedClaimedEFACount is how many of the node's EFA devices are claimed by a +// pod. efaburn (replicas: 1) requests 1 EFA, so exactly one device is claimed and +// the remaining device(s) must stay unclaimed. +const expectedClaimedEFACount = 1 + +// TestMultiEFAClaimedVsUnclaimedCorrelation validates per-device pod correlation +// on a multi-EFA node: the device claimed by efaburn is correlated to that pod, +// and every remaining (unclaimed) device carries NO pod attributes. +// +// Regression guard for the EFA multi-device correlation collapse. Without the +// groupbyattrs/efa split before the resource-level promote, ALL of the node's EFA +// devices — including unclaimed ones — are attributed to a single pod. This test +// fails in that case because (a) more than expectedClaimedEFACount devices carry a +// pod, and (b) no device is left unclaimed. It also catches a single device being +// attributed to multiple pods. +func TestMultiEFAClaimedVsUnclaimedCorrelation(t *testing.T) { + t.Parallel() + results, err := queryCache.Get(context.Background(), "efa_rx_bytes") + require.NoError(t, err, "querying efa_rx_bytes") + multi := filterByNodeLabel(results, multiEfaSmNodeLabel, "true") + require.NotEmpty(t, multi, + "No efa_rx_bytes results from multi-EFA node (label %s)", multiEfaSmNodeLabel) + + // For each device, collect the distinct pods it is attributed to (empty = unclaimed). + devicePods := make(map[string]map[string]struct{}) + for _, r := range multi { + dev := getAnyValue(r, "aws.efa.device") + if dev == "" { + continue + } + if devicePods[dev] == nil { + devicePods[dev] = make(map[string]struct{}) + } + if pod := r.Labels.Resource["k8s.pod.name"]; pod != "" { + devicePods[dev][pod] = struct{}{} + } + } + require.Len(t, devicePods, expectedMultiEFACount, + "expected %d EFA devices on the node, got %d: %v", + expectedMultiEFACount, len(devicePods), deviceKeys(devicePods)) + + var claimed, unclaimed []string + for dev, pods := range devicePods { + switch len(pods) { + case 0: + unclaimed = append(unclaimed, dev) + case 1: + claimed = append(claimed, dev) + default: + // A single device correlated to multiple pods is itself a collapse symptom. + t.Errorf("EFA device %s correlated to multiple pods %v", dev, setKeys(pods)) + } + } + + // Claimed side: exactly the number of EFAs efaburn requested map to a pod. + require.Len(t, claimed, expectedClaimedEFACount, + "expected %d correlated (claimed) EFA device(s), got %d: %v "+ + "(collapse over-correlates unclaimed devices onto a pod)", + expectedClaimedEFACount, len(claimed), claimed) + + // Unclaimed side: the remaining devices must carry no pod — this is the + // coverage that distinguishes correct correlation from the collapse. + require.Len(t, unclaimed, expectedMultiEFACount-expectedClaimedEFACount, + "expected %d unclaimed EFA device(s) with no pod, got %d: %v", + expectedMultiEFACount-expectedClaimedEFACount, len(unclaimed), unclaimed) +}