Skip to content

Commit 9492a2c

Browse files
committed
DRA: add dedicated integration tests
DRA had integration tests as part of test/integration/scheduler_perf (for the scheduler plugin) and some others scattered in different places (e.g. test/integration/resourceclaim for device status). The new test/integration/dra is meant to become the common location for all DRA-related integration tests. This makes it simpler to share common setup code.
1 parent f9e7b15 commit 9492a2c

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed

test/integration/dra/OWNERS

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# See the OWNERS docs at https://go.k8s.io/owners
2+
3+
approvers:
4+
- johnbelamaric
5+
- klueska
6+
- pohly
7+
reviewers:
8+
- pohly
9+
- bart0sh
10+
labels:
11+
- sig/node
12+
- wg/device-management

test/integration/dra/dra_test.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package dra
18+
19+
import (
20+
"fmt"
21+
"regexp"
22+
"sort"
23+
"strings"
24+
"testing"
25+
26+
"github.com/stretchr/testify/assert"
27+
28+
v1 "k8s.io/api/core/v1"
29+
resourcealphaapi "k8s.io/api/resource/v1alpha3"
30+
resourceapi "k8s.io/api/resource/v1beta1"
31+
apierrors "k8s.io/apimachinery/pkg/api/errors"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33+
"k8s.io/apimachinery/pkg/runtime/schema"
34+
utilfeature "k8s.io/apiserver/pkg/util/feature"
35+
"k8s.io/component-base/featuregate"
36+
featuregatetesting "k8s.io/component-base/featuregate/testing"
37+
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
38+
"k8s.io/kubernetes/pkg/features"
39+
st "k8s.io/kubernetes/pkg/scheduler/testing"
40+
"k8s.io/kubernetes/test/integration/framework"
41+
"k8s.io/kubernetes/test/utils/ktesting"
42+
"k8s.io/utils/ptr"
43+
)
44+
45+
var (
46+
// For more test data see pkg/scheduler/framework/plugin/dynamicresources/dynamicresources_test.go.
47+
48+
podName = "my-pod"
49+
namespace = "default"
50+
resourceName = "my-resource"
51+
className = "my-resource-class"
52+
claimName = podName + "-" + resourceName
53+
podWithClaimName = st.MakePod().Name(podName).Namespace(namespace).
54+
Container("my-container").
55+
PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}).
56+
Obj()
57+
claim = st.MakeResourceClaim().
58+
Name(claimName).
59+
Namespace(namespace).
60+
Request(className).
61+
Obj()
62+
)
63+
64+
// createTestNamespace creates a namespace with a name that is derived from the
65+
// current test name:
66+
// - Non-alpha-numeric characters replaced by hyphen.
67+
// - Truncated in the middle to make it short enough for GenerateName.
68+
// - Hyphen plus random suffix added by the apiserver.
69+
func createTestNamespace(tCtx ktesting.TContext) string {
70+
tCtx.Helper()
71+
name := regexp.MustCompile(`[^[:alnum:]_-]`).ReplaceAllString(tCtx.Name(), "-")
72+
name = strings.ToLower(name)
73+
if len(name) > 63 {
74+
name = name[:30] + "--" + name[len(name)-30:]
75+
}
76+
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: name + "-"}}
77+
ns, err := tCtx.Client().CoreV1().Namespaces().Create(tCtx, ns, metav1.CreateOptions{})
78+
tCtx.ExpectNoError(err, "create test namespace")
79+
tCtx.CleanupCtx(func(tCtx ktesting.TContext) {
80+
tCtx.ExpectNoError(tCtx.Client().CoreV1().Namespaces().Delete(tCtx, ns.Name, metav1.DeleteOptions{}), "delete test namespace")
81+
})
82+
return ns.Name
83+
}
84+
85+
func TestDRA(t *testing.T) {
86+
// Each sub-test brings up the API server in a certain
87+
// configuration. These sub-tests must run sequentially because they
88+
// change the global DefaultFeatureGate. For each configuration,
89+
// multiple tests can run in parallel as long as they are careful
90+
// about what they create.
91+
for name, tc := range map[string]struct {
92+
apis map[schema.GroupVersion]bool
93+
features map[featuregate.Feature]bool
94+
f func(tCtx ktesting.TContext)
95+
}{
96+
"default": {
97+
f: func(tCtx ktesting.TContext) {
98+
tCtx.Run("Pod", func(tCtx ktesting.TContext) { testPod(tCtx, false) })
99+
tCtx.Run("APIDisabled", testAPIDisabled)
100+
},
101+
},
102+
"core": {
103+
apis: map[schema.GroupVersion]bool{
104+
resourceapi.SchemeGroupVersion: true,
105+
},
106+
features: map[featuregate.Feature]bool{features.DynamicResourceAllocation: true},
107+
f: func(tCtx ktesting.TContext) {
108+
tCtx.Run("AdminAccess", func(tCtx ktesting.TContext) { testAdminAccess(tCtx, false) })
109+
tCtx.Run("Pod", func(tCtx ktesting.TContext) { testPod(tCtx, true) })
110+
},
111+
},
112+
"all": {
113+
apis: map[schema.GroupVersion]bool{
114+
resourceapi.SchemeGroupVersion: true,
115+
resourcealphaapi.SchemeGroupVersion: true,
116+
},
117+
features: map[featuregate.Feature]bool{
118+
features.DynamicResourceAllocation: true,
119+
// Additional DRA feature gates go here,
120+
// in alphabetical order,
121+
// as needed by tests for them.
122+
features.DRAAdminAccess: true,
123+
},
124+
f: func(tCtx ktesting.TContext) {
125+
tCtx.Run("AdminAccess", func(tCtx ktesting.TContext) { testAdminAccess(tCtx, true) })
126+
tCtx.Run("Convert", testConvert)
127+
},
128+
},
129+
} {
130+
t.Run(name, func(t *testing.T) {
131+
tCtx := ktesting.Init(t)
132+
var entries []string
133+
for key, value := range tc.features {
134+
entries = append(entries, fmt.Sprintf("%s=%t", key, value))
135+
}
136+
for key, value := range tc.apis {
137+
entries = append(entries, fmt.Sprintf("%s=%t", key, value))
138+
}
139+
sort.Strings(entries)
140+
t.Logf("Config: %s", strings.Join(entries, ","))
141+
142+
for key, value := range tc.features {
143+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, key, value)
144+
}
145+
146+
etcdOptions := framework.SharedEtcd()
147+
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
148+
apiServerFlags := framework.DefaultTestServerFlags()
149+
// Default kube-apiserver behavior, must be requested explicitly for test server.
150+
runtimeConfigs := []string{"api/alpha=false", "api/beta=false"}
151+
for key, value := range tc.apis {
152+
runtimeConfigs = append(runtimeConfigs, fmt.Sprintf("%s=%t", key, value))
153+
}
154+
apiServerFlags = append(apiServerFlags, "--runtime-config="+strings.Join(runtimeConfigs, ","))
155+
server := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions, apiServerFlags, etcdOptions)
156+
tCtx.Cleanup(server.TearDownFn)
157+
158+
tCtx = ktesting.WithRESTConfig(tCtx, server.ClientConfig)
159+
tc.f(tCtx)
160+
})
161+
}
162+
}
163+
164+
// testPod creates a pod with a resource claim reference and then checks
165+
// whether that field is or isn't getting dropped.
166+
func testPod(tCtx ktesting.TContext, draEnabled bool) {
167+
tCtx.Parallel()
168+
namespace := createTestNamespace(tCtx)
169+
podWithClaimName := podWithClaimName.DeepCopy()
170+
podWithClaimName.Namespace = namespace
171+
pod, err := tCtx.Client().CoreV1().Pods(namespace).Create(tCtx, podWithClaimName, metav1.CreateOptions{})
172+
tCtx.ExpectNoError(err, "create pod")
173+
if draEnabled {
174+
assert.NotEmpty(tCtx, pod.Spec.ResourceClaims, "should store resource claims in pod spec")
175+
} else {
176+
assert.Empty(tCtx, pod.Spec.ResourceClaims, "should drop resource claims from pod spec")
177+
}
178+
}
179+
180+
// testAPIDisabled checks that the resource.k8s.io API is disabled.
181+
func testAPIDisabled(tCtx ktesting.TContext) {
182+
tCtx.Parallel()
183+
_, err := tCtx.Client().ResourceV1beta1().ResourceClaims(claim.Namespace).Create(tCtx, claim, metav1.CreateOptions{})
184+
if !apierrors.IsNotFound(err) {
185+
tCtx.Fatalf("expected 'resource not found' error, got %v", err)
186+
}
187+
}
188+
189+
// testConvert creates a claim using a one API version and reads it with another.
190+
func testConvert(tCtx ktesting.TContext) {
191+
tCtx.Parallel()
192+
namespace := createTestNamespace(tCtx)
193+
claim := claim.DeepCopy()
194+
claim.Namespace = namespace
195+
claim, err := tCtx.Client().ResourceV1beta1().ResourceClaims(namespace).Create(tCtx, claim, metav1.CreateOptions{})
196+
tCtx.ExpectNoError(err, "create claim")
197+
claimAlpha, err := tCtx.Client().ResourceV1alpha3().ResourceClaims(namespace).Get(tCtx, claim.Name, metav1.GetOptions{})
198+
tCtx.ExpectNoError(err, "get claim")
199+
// We could check more fields, but there are unit tests which cover this better.
200+
assert.Equal(tCtx, claim.Name, claimAlpha.Name, "claim name")
201+
}
202+
203+
// testAdminAccess creates a claim with AdminAccess and then checks
204+
// whether that field is or isn't getting dropped.
205+
func testAdminAccess(tCtx ktesting.TContext, adminAccessEnabled bool) {
206+
tCtx.Parallel()
207+
namespace := createTestNamespace(tCtx)
208+
claim := claim.DeepCopy()
209+
claim.Namespace = namespace
210+
claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
211+
claim, err := tCtx.Client().ResourceV1beta1().ResourceClaims(namespace).Create(tCtx, claim, metav1.CreateOptions{})
212+
tCtx.ExpectNoError(err, "create claim")
213+
if adminAccessEnabled {
214+
if !ptr.Deref(claim.Spec.Devices.Requests[0].AdminAccess, false) {
215+
tCtx.Fatal("should store AdminAccess in ResourceClaim")
216+
}
217+
} else {
218+
if claim.Spec.Devices.Requests[0].AdminAccess != nil {
219+
tCtx.Fatal("should drop AdminAccess in ResourceClaim")
220+
}
221+
}
222+
}

test/integration/dra/main_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package dra
18+
19+
import (
20+
"testing"
21+
22+
"k8s.io/kubernetes/test/integration/framework"
23+
)
24+
25+
func TestMain(m *testing.M) {
26+
framework.EtcdMain(m.Run)
27+
}

0 commit comments

Comments
 (0)