From 9d9f4ddd88a4c303c0917b9df5d2bb8a55c27070 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 10 Mar 2026 18:36:58 +0100 Subject: [PATCH] E2E framework: introduce WithKubeletMinVersion This simplifies specifying the minimum required kubelet (a bit shorter overall, harder to introduce typos because the string is shorter) and enables usage of `--sem-ver-filter`. Jobs that filter by label or text continue to work as before. As a proof-of-concept, the DRA tests are labeled using the new helper and are tested with canary jobs which use --sem-ver-filter. The normal jobs which rely on the label still work. --- test/e2e/dra/dra.go | 20 ++++++++++---------- test/e2e/framework/ginkgowrapper.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/test/e2e/dra/dra.go b/test/e2e/dra/dra.go index 5c4eae5d51e..909195216ce 100644 --- a/test/e2e/dra/dra.go +++ b/test/e2e/dra/dra.go @@ -432,7 +432,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { // https://github.com/kubernetes/kubernetes/issues/131513 was fixed in master for 1.34 and not backported, // so this test only passes for kubelet >= 1.34. - f.It("blocks new pod after force-delete", f.WithLabel("KubeletMinVersion:1.34"), func(ctx context.Context) { + f.It("blocks new pod after force-delete", f.WithKubeletMinVersion("1.34"), func(ctx context.Context) { // The problem with a force-deleted pod is that kubelet // is not necessarily done yet with tearing down the // pod at the time when the pod and its claim are @@ -701,7 +701,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { }) // Seamless upgrade support was added in Kubernetes 1.33. - f.It("rolling update", f.WithLabel("KubeletMinVersion:1.33"), func(ctx context.Context) { + f.It("rolling update", f.WithKubeletMinVersion("1.33"), func(ctx context.Context) { tCtx := f.TContext(ctx) nodes := drautils.NewNodesNow(tCtx, 1, 1) @@ -742,7 +742,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { }) // Seamless upgrade support was added in Kubernetes 1.33. - f.It("failed update", f.WithLabel("KubeletMinVersion:1.33"), func(ctx context.Context) { + f.It("failed update", f.WithKubeletMinVersion("1.33"), func(ctx context.Context) { tCtx := f.TContext(ctx) nodes := drautils.NewNodesNow(tCtx, 1, 1) @@ -785,7 +785,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { }) // Seamless upgrade support was added in Kubernetes 1.33. - f.It("sequential update with pods replacing each other", f.WithLabel("KubeletMinVersion:1.33"), framework.WithSlow(), func(ctx context.Context) { + f.It("sequential update with pods replacing each other", f.WithKubeletMinVersion("1.33"), framework.WithSlow(), func(ctx context.Context) { tCtx := f.TContext(ctx) nodes := drautils.NewNodesNow(tCtx, 1, 1) @@ -1110,7 +1110,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { // https://github.com/kubernetes/kubernetes/issues/135901 was fixed in master for Kubernetes 1.35 and not backported // so this test only passes for kubelet >= 1.35. - f.It("requests an already allocated and a new claim for a pod", f.WithLabel("KubeletMinVersion:1.35"), func(ctx context.Context) { + f.It("requests an already allocated and a new claim for a pod", f.WithKubeletMinVersion("1.35"), func(ctx context.Context) { // This test covers a situation when a pod references a mix of already-prepared and new claims. tCtx := f.TContext(ctx) @@ -1961,7 +1961,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { )) b := drautils.NewBuilder(f, driver) - f.It("must allow multiple allocations and consume capacity", f.WithLabel("KubeletMinVersion:1.34"), func(ctx context.Context) { + f.It("must allow multiple allocations and consume capacity", f.WithKubeletMinVersion("1.34"), func(ctx context.Context) { tCtx := f.TContext(ctx) // The first pod will use 4Gi of the device. claim := b.ExternalClaim() @@ -2237,7 +2237,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { }).WithTimeout(time.Minute).Should(gomega.BeTrueBecause("extended resource claim should be automatically deleted when pod %s", cleanupMessage)) } - framework.Context(f.WithFeatureGate(features.DRAExtendedResource), feature.DynamicResourceAllocation, f.WithLabel("KubeletMinVersion:1.36"), func() { + framework.Context(f.WithFeatureGate(features.DRAExtendedResource), feature.DynamicResourceAllocation, f.WithKubeletMinVersion("1.36"), func() { nodes := drautils.NewNodes(f, 1, 1) driver := drautils.NewDriver(f, nodes, drautils.NetworkResources(10, false)) b := drautils.NewBuilder(f, driver) @@ -2674,7 +2674,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { }) }) - framework.Context(f.WithFeatureGate(features.DRAExtendedResource), feature.DynamicResourceAllocation, f.WithLabel("KubeletMinVersion:1.36"), func() { + framework.Context(f.WithFeatureGate(features.DRAExtendedResource), feature.DynamicResourceAllocation, f.WithKubeletMinVersion("1.36"), func() { nodes := drautils.NewNodes(f, 2, 2) nodes.NumReservedNodes = 1 driver := drautils.NewDriver(f, nodes, drautils.NetworkResources(2, false)) @@ -3125,7 +3125,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { // If the v1beta1 gRPC API is disabled, then // kubelet from 1.34 is required because that is // when v1 was introduced. - args = append(args, f.WithLabel("KubeletMinVersion:1.34")) + args = append(args, f.WithKubeletMinVersion("1.34")) } framework.Context(args...) } @@ -3142,7 +3142,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() { driver := drautils.NewDriver(f, nodes, drautils.NetworkResources(10, false)) b := drautils.NewBuilder(f, driver) - f.It("should report device health with custom messages", f.WithLabel("KubeletMinVersion:1.36"), framework.WithFeatureGate(features.ResourceHealthStatusMessage), func(ctx context.Context) { + f.It("should report device health with custom messages", f.WithKubeletMinVersion("1.36"), framework.WithFeatureGate(features.ResourceHealthStatusMessage), func(ctx context.Context) { claimName := "health-test-claim" claim := b.ExternalClaim() claim.Name = claimName diff --git a/test/e2e/framework/ginkgowrapper.go b/test/e2e/framework/ginkgowrapper.go index fb3e1cbd2d1..944be9b7075 100644 --- a/test/e2e/framework/ginkgowrapper.go +++ b/test/e2e/framework/ginkgowrapper.go @@ -292,6 +292,9 @@ func expandGinkgoArgs(offset ginkgo.Offset, text string, args []any) (string, [] ginkgoArgs = append(ginkgoArgs, ginkgo.Label("BetaOffByDefault")) } } + if arg.parts[0] == "KubeletMinVersion" { + ginkgoArgs = append(ginkgoArgs, ginkgo.ComponentSemVerConstraint("kubelet", ">="+arg.parts[1])) + } switch fullLabel { case "Serial": ginkgoArgs = append(ginkgoArgs, ginkgo.Serial) @@ -665,6 +668,32 @@ func withFlaky() interface{} { return newLabel("Flaky") } +// WithKubeletMinVersion specifies that a certain test or group tests needs +// a kubelet version >= the given version string. Specifying the minimum +// version as `.` is sufficient. The patch version may be +// added, but is not required. +// +// This adds +// - a `[KubeletMinVersion:]` tag in the text, +// - a `KubeletMinVersion:` label, +// - and a Ginkgo semver constraint for the "kubelet" component. +// +// The easiest way to filter tests is via `ginkgo +// --sem-ver-filter="kubelet=1.35"` which filters out tests that need a newer +// kubelet. +func WithKubeletMinVersion(version string) interface{} { + return withKubeletMinVersion(version) +} + +// WithKubeletMinVersion is a shorthand for the corresponding package function. +func (f *Framework) WithKubeletMinVersion(version string) interface{} { + return withKubeletMinVersion(version) +} + +func withKubeletMinVersion(version string) interface{} { + return newLabel("KubeletMinVersion", version) +} + type label struct { // parts get concatenated with ":" to build the full label. parts []string