DRA API: v1 registration + tests

This commit is contained in:
Patrick Ohly
2025-07-03 15:17:35 +02:00
parent 4e592f6c14
commit cff91579e8
11 changed files with 107 additions and 10 deletions

View File

@@ -50,6 +50,7 @@ var apiVersionPriorities = merge(controlplaneapiserver.DefaultGenericAPIServiceP
{Group: "node.k8s.io", Version: "v1"}: {Group: 16300, Version: 15},
{Group: "node.k8s.io", Version: "v1alpha1"}: {Group: 16300, Version: 1},
{Group: "node.k8s.io", Version: "v1beta1"}: {Group: 16300, Version: 9},
{Group: "resource.k8s.io", Version: "v1"}: {Group: 16200, Version: 21},
{Group: "resource.k8s.io", Version: "v1beta2"}: {Group: 16200, Version: 15},
{Group: "resource.k8s.io", Version: "v1beta1"}: {Group: 16200, Version: 9},
{Group: "resource.k8s.io", Version: "v1alpha3"}: {Group: 16200, Version: 1},

View File

@@ -157,6 +157,12 @@ func TestDefaulting(t *testing.T) {
{Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaimTemplateList"}: {},
{Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceSlice"}: {},
{Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceSliceList"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceClaim"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceClaimList"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceClaimTemplate"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceClaimTemplateList"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceSlice"}: {},
{Group: "resource.k8s.io", Version: "v1", Kind: "ResourceSliceList"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicy"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyList"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyBinding"}: {},

View File

@@ -23,6 +23,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/resource"
v1 "k8s.io/kubernetes/pkg/apis/resource/v1"
"k8s.io/kubernetes/pkg/apis/resource/v1alpha3"
"k8s.io/kubernetes/pkg/apis/resource/v1beta1"
"k8s.io/kubernetes/pkg/apis/resource/v1beta2"
@@ -38,7 +39,8 @@ func Install(scheme *runtime.Scheme) {
utilruntime.Must(v1alpha3.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(v1beta2.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
// TODO(https://github.com/kubernetes/kubernetes/issues/129889) We should
// change the serialization version to v1beta2 for 1.34.
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1alpha3.SchemeGroupVersion))
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1.SchemeGroupVersion, v1alpha3.SchemeGroupVersion))
}

View File

@@ -49,6 +49,7 @@ import (
nodev1 "k8s.io/api/node/v1"
policyapiv1 "k8s.io/api/policy/v1"
rbacv1 "k8s.io/api/rbac/v1"
resourcev1 "k8s.io/api/resource/v1"
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
resourcev1beta2 "k8s.io/api/resource/v1beta2"
@@ -459,6 +460,7 @@ var (
nodev1.SchemeGroupVersion,
policyapiv1.SchemeGroupVersion,
rbacv1.SchemeGroupVersion,
resourcev1.SchemeGroupVersion,
storageapiv1.SchemeGroupVersion,
schedulingapiv1.SchemeGroupVersion,
flowcontrolv1.SchemeGroupVersion,

View File

@@ -30,7 +30,7 @@ import (
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes/typed/core/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/dynamic-resource-allocation/structured"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/resource"
@@ -74,6 +74,9 @@ func (*resourceclaimStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpa
"resource.k8s.io/v1beta2": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"resource.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
@@ -151,6 +154,9 @@ func (*resourceclaimStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*f
"resource.k8s.io/v1beta2": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"resource.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields

View File

@@ -17,6 +17,7 @@ limitations under the License.
package rest
import (
resourcev1 "k8s.io/api/resource/v1"
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
resourcev1beta2 "k8s.io/api/resource/v1beta2"
@@ -47,6 +48,12 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
// TODO refactor the plumbing to provide the information in the APIGroupInfo
if storageMap, err := p.v1Storage(apiResourceConfigSource, restOptionsGetter, p.NamespaceClient); err != nil {
return genericapiserver.APIGroupInfo{}, err
} else if len(storageMap) > 0 {
apiGroupInfo.VersionedResourcesStorageMap[resourcev1.SchemeGroupVersion.Version] = storageMap
}
if storageMap, err := p.v1alpha3Storage(apiResourceConfigSource, restOptionsGetter, p.NamespaceClient); err != nil {
return genericapiserver.APIGroupInfo{}, err
} else if len(storageMap) > 0 {
@@ -68,6 +75,45 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
return apiGroupInfo, nil
}
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}
if resource := "deviceclasses"; apiResourceConfigSource.ResourceEnabled(resourcev1.SchemeGroupVersion.WithResource(resource)) {
deviceclassStorage, err := deviceclassstore.NewREST(restOptionsGetter)
if err != nil {
return nil, err
}
storage[resource] = deviceclassStorage
}
if resource := "resourceclaims"; apiResourceConfigSource.ResourceEnabled(resourcev1.SchemeGroupVersion.WithResource(resource)) {
resourceClaimStorage, resourceClaimStatusStorage, err := resourceclaimstore.NewREST(restOptionsGetter, nsClient)
if err != nil {
return nil, err
}
storage[resource] = resourceClaimStorage
storage[resource+"/status"] = resourceClaimStatusStorage
}
if resource := "resourceclaimtemplates"; apiResourceConfigSource.ResourceEnabled(resourcev1.SchemeGroupVersion.WithResource(resource)) {
resourceClaimTemplateStorage, err := resourceclaimtemplatestore.NewREST(restOptionsGetter, nsClient)
if err != nil {
return nil, err
}
storage[resource] = resourceClaimTemplateStorage
}
if resource := "resourceslices"; apiResourceConfigSource.ResourceEnabled(resourcev1.SchemeGroupVersion.WithResource(resource)) {
resourceSliceStorage, err := resourceslicestore.NewREST(restOptionsGetter)
if err != nil {
return nil, err
}
storage[resource] = resourceSliceStorage
}
return storage, nil
}
func (p RESTStorageProvider) v1alpha3Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}

View File

@@ -36,7 +36,7 @@ const (
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceSlice represents one or more resources in a pool of similar resources,
// managed by a common driver. A pool may span more than one ResourceSlice, and exactly how many
@@ -500,7 +500,7 @@ const (
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceSliceList is a collection of ResourceSlices.
type ResourceSliceList struct {
@@ -515,7 +515,7 @@ type ResourceSliceList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceClaim describes a request for access to resources in the cluster,
// for use by workloads. For example, if a workload needs an accelerator device
@@ -1314,7 +1314,7 @@ const (
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceClaimList is a collection of claims.
type ResourceClaimList struct {
@@ -1330,7 +1330,7 @@ type ResourceClaimList struct {
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// DeviceClass is a vendor- or admin-provided resource that contains
// device configuration and selectors. It can be referenced in
@@ -1387,7 +1387,7 @@ type DeviceClassConfiguration struct {
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// DeviceClassList is a collection of classes.
type DeviceClassList struct {
@@ -1402,7 +1402,7 @@ type DeviceClassList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceClaimTemplate is used to produce ResourceClaim objects.
//
@@ -1437,7 +1437,7 @@ type ResourceClaimTemplateSpec struct {
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// ResourceClaimTemplateList is a collection of claim templates.
type ResourceClaimTemplateList struct {

View File

@@ -68,6 +68,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
resourcev1 "k8s.io/api/resource/v1"
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
resourcev1beta2 "k8s.io/api/resource/v1beta2"
@@ -138,6 +139,7 @@ var groups = []runtime.SchemeBuilder{
resourcev1alpha3.SchemeBuilder,
resourcev1beta1.SchemeBuilder,
resourcev1beta2.SchemeBuilder,
resourcev1.SchemeBuilder,
schedulingv1alpha1.SchemeBuilder,
schedulingv1beta1.SchemeBuilder,
schedulingv1.SchemeBuilder,

View File

@@ -61,6 +61,7 @@ var resetFieldsStatusData = map[schema.GroupVersionResource]string{
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 25}}`,
gvr("resource.k8s.io", "v1beta1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
gvr("resource.k8s.io", "v1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
// standard for []metav1.Condition
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
@@ -157,6 +158,9 @@ var resetFieldsSpecData = map[schema.GroupVersionResource]string{
gvr("resource.k8s.io", "v1beta2", "deviceclasses"): `{"metadata": {"labels":{"a":"c"}}}`,
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "other-class"}}]}}}`, // spec is immutable, but that doesn't matter for the test.
gvr("resource.k8s.io", "v1beta2", "resourceclaimtemplates"): `{"spec": {"spec": {"resourceClassName": "class2name"}}}`,
gvr("resource.k8s.io", "v1", "deviceclasses"): `{"metadata": {"labels":{"a":"c"}}}`,
gvr("resource.k8s.io", "v1", "resourceclaims"): `{"spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "other-class"}}]}}}`, // spec is immutable, but that doesn't matter for the test.
gvr("resource.k8s.io", "v1", "resourceclaimtemplates"): `{"spec": {"spec": {"resourceClassName": "class2name"}}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{}`,
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"paramKind": {"apiVersion": "apps/v1", "kind": "Deployment"}}}`,
gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"paramKind": {"apiVersion": "apps/v1", "kind": "Deployment"}}}`,

View File

@@ -60,6 +60,7 @@ var statusData = map[schema.GroupVersionResource]string{
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("resource.k8s.io", "v1beta1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
gvr("resource.k8s.io", "v1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
// standard for []metav1.Condition
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,

View File

@@ -651,6 +651,33 @@ func GetEtcdStorageDataForNamespaceServedAt(namespace string, v string, isEmulat
},
// --
// k8s.io/kubernetes/pkg/apis/resource/v1
gvr("resource.k8s.io", "v1", "deviceclasses"): {
Stub: `{"metadata": {"name": "class4name"}}`,
ExpectedEtcdPath: "/registry/deviceclasses/class4name",
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "DeviceClass"),
IntroducedVersion: "1.34",
},
gvr("resource.k8s.io", "v1", "resourceclaims"): {
Stub: `{"metadata": {"name": "claim4name"}, "spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}}]}}}`,
ExpectedEtcdPath: "/registry/resourceclaims/" + namespace + "/claim4name",
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceClaim"),
IntroducedVersion: "1.34",
},
gvr("resource.k8s.io", "v1", "resourceclaimtemplates"): {
Stub: `{"metadata": {"name": "claimtemplate4name"}, "spec": {"spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}}]}}}}`,
ExpectedEtcdPath: "/registry/resourceclaimtemplates/" + namespace + "/claimtemplate4name",
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceClaimTemplate"),
IntroducedVersion: "1.34",
},
gvr("resource.k8s.io", "v1", "resourceslices"): {
Stub: `{"metadata": {"name": "node4slice"}, "spec": {"nodeName": "worker1", "driver": "dra.example.com", "pool": {"name": "worker1", "resourceSliceCount": 1}}}`,
ExpectedEtcdPath: "/registry/resourceslices/node4slice",
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceSlice"),
IntroducedVersion: "1.34",
},
// --
// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
Stub: `{"metadata":{"name":"sv1.test"},"spec":{}}`,