mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
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.
This commit is contained in:
parent
f9e7b15c00
commit
9492a2ca9b
12
test/integration/dra/OWNERS
Normal file
12
test/integration/dra/OWNERS
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- johnbelamaric
|
||||||
|
- klueska
|
||||||
|
- pohly
|
||||||
|
reviewers:
|
||||||
|
- pohly
|
||||||
|
- bart0sh
|
||||||
|
labels:
|
||||||
|
- sig/node
|
||||||
|
- wg/device-management
|
222
test/integration/dra/dra_test.go
Normal file
222
test/integration/dra/dra_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
resourcealphaapi "k8s.io/api/resource/v1alpha3"
|
||||||
|
resourceapi "k8s.io/api/resource/v1beta1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
st "k8s.io/kubernetes/pkg/scheduler/testing"
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
"k8s.io/kubernetes/test/utils/ktesting"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// For more test data see pkg/scheduler/framework/plugin/dynamicresources/dynamicresources_test.go.
|
||||||
|
|
||||||
|
podName = "my-pod"
|
||||||
|
namespace = "default"
|
||||||
|
resourceName = "my-resource"
|
||||||
|
className = "my-resource-class"
|
||||||
|
claimName = podName + "-" + resourceName
|
||||||
|
podWithClaimName = st.MakePod().Name(podName).Namespace(namespace).
|
||||||
|
Container("my-container").
|
||||||
|
PodResourceClaims(v1.PodResourceClaim{Name: resourceName, ResourceClaimName: &claimName}).
|
||||||
|
Obj()
|
||||||
|
claim = st.MakeResourceClaim().
|
||||||
|
Name(claimName).
|
||||||
|
Namespace(namespace).
|
||||||
|
Request(className).
|
||||||
|
Obj()
|
||||||
|
)
|
||||||
|
|
||||||
|
// createTestNamespace creates a namespace with a name that is derived from the
|
||||||
|
// current test name:
|
||||||
|
// - Non-alpha-numeric characters replaced by hyphen.
|
||||||
|
// - Truncated in the middle to make it short enough for GenerateName.
|
||||||
|
// - Hyphen plus random suffix added by the apiserver.
|
||||||
|
func createTestNamespace(tCtx ktesting.TContext) string {
|
||||||
|
tCtx.Helper()
|
||||||
|
name := regexp.MustCompile(`[^[:alnum:]_-]`).ReplaceAllString(tCtx.Name(), "-")
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
if len(name) > 63 {
|
||||||
|
name = name[:30] + "--" + name[len(name)-30:]
|
||||||
|
}
|
||||||
|
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: name + "-"}}
|
||||||
|
ns, err := tCtx.Client().CoreV1().Namespaces().Create(tCtx, ns, metav1.CreateOptions{})
|
||||||
|
tCtx.ExpectNoError(err, "create test namespace")
|
||||||
|
tCtx.CleanupCtx(func(tCtx ktesting.TContext) {
|
||||||
|
tCtx.ExpectNoError(tCtx.Client().CoreV1().Namespaces().Delete(tCtx, ns.Name, metav1.DeleteOptions{}), "delete test namespace")
|
||||||
|
})
|
||||||
|
return ns.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDRA(t *testing.T) {
|
||||||
|
// Each sub-test brings up the API server in a certain
|
||||||
|
// configuration. These sub-tests must run sequentially because they
|
||||||
|
// change the global DefaultFeatureGate. For each configuration,
|
||||||
|
// multiple tests can run in parallel as long as they are careful
|
||||||
|
// about what they create.
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
apis map[schema.GroupVersion]bool
|
||||||
|
features map[featuregate.Feature]bool
|
||||||
|
f func(tCtx ktesting.TContext)
|
||||||
|
}{
|
||||||
|
"default": {
|
||||||
|
f: func(tCtx ktesting.TContext) {
|
||||||
|
tCtx.Run("Pod", func(tCtx ktesting.TContext) { testPod(tCtx, false) })
|
||||||
|
tCtx.Run("APIDisabled", testAPIDisabled)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"core": {
|
||||||
|
apis: map[schema.GroupVersion]bool{
|
||||||
|
resourceapi.SchemeGroupVersion: true,
|
||||||
|
},
|
||||||
|
features: map[featuregate.Feature]bool{features.DynamicResourceAllocation: true},
|
||||||
|
f: func(tCtx ktesting.TContext) {
|
||||||
|
tCtx.Run("AdminAccess", func(tCtx ktesting.TContext) { testAdminAccess(tCtx, false) })
|
||||||
|
tCtx.Run("Pod", func(tCtx ktesting.TContext) { testPod(tCtx, true) })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all": {
|
||||||
|
apis: map[schema.GroupVersion]bool{
|
||||||
|
resourceapi.SchemeGroupVersion: true,
|
||||||
|
resourcealphaapi.SchemeGroupVersion: true,
|
||||||
|
},
|
||||||
|
features: map[featuregate.Feature]bool{
|
||||||
|
features.DynamicResourceAllocation: true,
|
||||||
|
// Additional DRA feature gates go here,
|
||||||
|
// in alphabetical order,
|
||||||
|
// as needed by tests for them.
|
||||||
|
features.DRAAdminAccess: true,
|
||||||
|
},
|
||||||
|
f: func(tCtx ktesting.TContext) {
|
||||||
|
tCtx.Run("AdminAccess", func(tCtx ktesting.TContext) { testAdminAccess(tCtx, true) })
|
||||||
|
tCtx.Run("Convert", testConvert)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
tCtx := ktesting.Init(t)
|
||||||
|
var entries []string
|
||||||
|
for key, value := range tc.features {
|
||||||
|
entries = append(entries, fmt.Sprintf("%s=%t", key, value))
|
||||||
|
}
|
||||||
|
for key, value := range tc.apis {
|
||||||
|
entries = append(entries, fmt.Sprintf("%s=%t", key, value))
|
||||||
|
}
|
||||||
|
sort.Strings(entries)
|
||||||
|
t.Logf("Config: %s", strings.Join(entries, ","))
|
||||||
|
|
||||||
|
for key, value := range tc.features {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
etcdOptions := framework.SharedEtcd()
|
||||||
|
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
|
||||||
|
apiServerFlags := framework.DefaultTestServerFlags()
|
||||||
|
// Default kube-apiserver behavior, must be requested explicitly for test server.
|
||||||
|
runtimeConfigs := []string{"api/alpha=false", "api/beta=false"}
|
||||||
|
for key, value := range tc.apis {
|
||||||
|
runtimeConfigs = append(runtimeConfigs, fmt.Sprintf("%s=%t", key, value))
|
||||||
|
}
|
||||||
|
apiServerFlags = append(apiServerFlags, "--runtime-config="+strings.Join(runtimeConfigs, ","))
|
||||||
|
server := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions, apiServerFlags, etcdOptions)
|
||||||
|
tCtx.Cleanup(server.TearDownFn)
|
||||||
|
|
||||||
|
tCtx = ktesting.WithRESTConfig(tCtx, server.ClientConfig)
|
||||||
|
tc.f(tCtx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPod creates a pod with a resource claim reference and then checks
|
||||||
|
// whether that field is or isn't getting dropped.
|
||||||
|
func testPod(tCtx ktesting.TContext, draEnabled bool) {
|
||||||
|
tCtx.Parallel()
|
||||||
|
namespace := createTestNamespace(tCtx)
|
||||||
|
podWithClaimName := podWithClaimName.DeepCopy()
|
||||||
|
podWithClaimName.Namespace = namespace
|
||||||
|
pod, err := tCtx.Client().CoreV1().Pods(namespace).Create(tCtx, podWithClaimName, metav1.CreateOptions{})
|
||||||
|
tCtx.ExpectNoError(err, "create pod")
|
||||||
|
if draEnabled {
|
||||||
|
assert.NotEmpty(tCtx, pod.Spec.ResourceClaims, "should store resource claims in pod spec")
|
||||||
|
} else {
|
||||||
|
assert.Empty(tCtx, pod.Spec.ResourceClaims, "should drop resource claims from pod spec")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAPIDisabled checks that the resource.k8s.io API is disabled.
|
||||||
|
func testAPIDisabled(tCtx ktesting.TContext) {
|
||||||
|
tCtx.Parallel()
|
||||||
|
_, err := tCtx.Client().ResourceV1beta1().ResourceClaims(claim.Namespace).Create(tCtx, claim, metav1.CreateOptions{})
|
||||||
|
if !apierrors.IsNotFound(err) {
|
||||||
|
tCtx.Fatalf("expected 'resource not found' error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testConvert creates a claim using a one API version and reads it with another.
|
||||||
|
func testConvert(tCtx ktesting.TContext) {
|
||||||
|
tCtx.Parallel()
|
||||||
|
namespace := createTestNamespace(tCtx)
|
||||||
|
claim := claim.DeepCopy()
|
||||||
|
claim.Namespace = namespace
|
||||||
|
claim, err := tCtx.Client().ResourceV1beta1().ResourceClaims(namespace).Create(tCtx, claim, metav1.CreateOptions{})
|
||||||
|
tCtx.ExpectNoError(err, "create claim")
|
||||||
|
claimAlpha, err := tCtx.Client().ResourceV1alpha3().ResourceClaims(namespace).Get(tCtx, claim.Name, metav1.GetOptions{})
|
||||||
|
tCtx.ExpectNoError(err, "get claim")
|
||||||
|
// We could check more fields, but there are unit tests which cover this better.
|
||||||
|
assert.Equal(tCtx, claim.Name, claimAlpha.Name, "claim name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAdminAccess creates a claim with AdminAccess and then checks
|
||||||
|
// whether that field is or isn't getting dropped.
|
||||||
|
func testAdminAccess(tCtx ktesting.TContext, adminAccessEnabled bool) {
|
||||||
|
tCtx.Parallel()
|
||||||
|
namespace := createTestNamespace(tCtx)
|
||||||
|
claim := claim.DeepCopy()
|
||||||
|
claim.Namespace = namespace
|
||||||
|
claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
|
||||||
|
claim, err := tCtx.Client().ResourceV1beta1().ResourceClaims(namespace).Create(tCtx, claim, metav1.CreateOptions{})
|
||||||
|
tCtx.ExpectNoError(err, "create claim")
|
||||||
|
if adminAccessEnabled {
|
||||||
|
if !ptr.Deref(claim.Spec.Devices.Requests[0].AdminAccess, false) {
|
||||||
|
tCtx.Fatal("should store AdminAccess in ResourceClaim")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if claim.Spec.Devices.Requests[0].AdminAccess != nil {
|
||||||
|
tCtx.Fatal("should drop AdminAccess in ResourceClaim")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
test/integration/dra/main_test.go
Normal file
27
test/integration/dra/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
framework.EtcdMain(m.Run)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user