diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list
index 470618222c3..f9614e6f156 100644
--- a/api/api-rules/violation_exceptions.list
+++ b/api/api-rules/violation_exceptions.list
@@ -63,6 +63,11 @@ API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,IntV
API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,StringValue
API rule violation: names_match,k8s.io/api/resource/v1beta1,DeviceAttribute,VersionValue
API rule violation: names_match,k8s.io/api/resource/v1beta1,NetworkDeviceData,IPs
+API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,BoolValue
+API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,IntValue
+API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,StringValue
+API rule violation: names_match,k8s.io/api/resource/v1beta2,DeviceAttribute,VersionValue
+API rule violation: names_match,k8s.io/api/resource/v1beta2,NetworkDeviceData,IPs
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema
API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource
diff --git a/cmd/kube-apiserver/app/aggregator.go b/cmd/kube-apiserver/app/aggregator.go
index 74923724ee2..243093d771e 100644
--- a/cmd/kube-apiserver/app/aggregator.go
+++ b/cmd/kube-apiserver/app/aggregator.go
@@ -51,6 +51,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: "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},
// Append a new group to the end of the list if unsure.
diff --git a/hack/lib/init.sh b/hack/lib/init.sh
index a818dc1c30d..a8e515bbeba 100755
--- a/hack/lib/init.sh
+++ b/hack/lib/init.sh
@@ -94,6 +94,7 @@ coordination.k8s.io/v1beta1 \
coordination.k8s.io/v1 \
discovery.k8s.io/v1 \
discovery.k8s.io/v1beta1 \
+resource.k8s.io/v1beta2 \
resource.k8s.io/v1beta1 \
resource.k8s.io/v1alpha3 \
extensions/v1beta1 \
diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go
index 3ab7111d015..2e04d1dcda1 100644
--- a/pkg/api/testing/defaulting_test.go
+++ b/pkg/api/testing/defaulting_test.go
@@ -149,6 +149,12 @@ func TestDefaulting(t *testing.T) {
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceClaimTemplateList"}: {},
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceSlice"}: {},
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceSliceList"}: {},
+ {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaim"}: {},
+ {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaimList"}: {},
+ {Group: "resource.k8s.io", Version: "v1beta2", Kind: "ResourceClaimTemplate"}: {},
+ {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: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicy"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyList"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyBinding"}: {},
diff --git a/pkg/apis/resource/fuzzer/fuzzer.go b/pkg/apis/resource/fuzzer/fuzzer.go
index 34f12e9847d..e07c71373e1 100644
--- a/pkg/apis/resource/fuzzer/fuzzer.go
+++ b/pkg/apis/resource/fuzzer/fuzzer.go
@@ -32,7 +32,7 @@ import (
// leads to errors during roundtrip tests.
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
- func(r *resource.DeviceRequest, c randfill.Continue) {
+ func(r *resource.ExactDeviceRequest, c randfill.Continue) {
c.FillNoCustom(r) // fuzz self without calling this function again
if r.AllocationMode == "" {
@@ -44,6 +44,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
},
func(r *resource.DeviceSubRequest, c randfill.Continue) {
c.FillNoCustom(r) // fuzz self without calling this function again
+
if r.AllocationMode == "" {
r.AllocationMode = []resource.DeviceAllocationMode{
resource.DeviceAllocationModeAll,
@@ -94,5 +95,16 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
// might be valid JSON which changes during re-encoding.
r.Data = &runtime.RawExtension{Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`)}
},
+ func(r *resource.ResourceSliceSpec, c randfill.Continue) {
+ c.FillNoCustom(r)
+ // Setting AllNodes to false is not allowed. It must be
+ // either true or nil.
+ if r.AllNodes != nil && !*r.AllNodes {
+ r.AllNodes = nil
+ }
+ if r.NodeName != nil && *r.NodeName == "" {
+ r.NodeName = nil
+ }
+ },
}
}
diff --git a/pkg/apis/resource/install/install.go b/pkg/apis/resource/install/install.go
index e473b8f5486..5e5f7104a36 100644
--- a/pkg/apis/resource/install/install.go
+++ b/pkg/apis/resource/install/install.go
@@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/apis/resource"
"k8s.io/kubernetes/pkg/apis/resource/v1alpha3"
"k8s.io/kubernetes/pkg/apis/resource/v1beta1"
+ "k8s.io/kubernetes/pkg/apis/resource/v1beta2"
)
func init() {
@@ -36,5 +37,8 @@ func Install(scheme *runtime.Scheme) {
utilruntime.Must(resource.AddToScheme(scheme))
utilruntime.Must(v1alpha3.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
- utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion, v1alpha3.SchemeGroupVersion))
+ utilruntime.Must(v1beta2.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))
}
diff --git a/pkg/apis/resource/types.go b/pkg/apis/resource/types.go
index dcc9503f49c..ca5b4999f29 100644
--- a/pkg/apis/resource/types.go
+++ b/pkg/apis/resource/types.go
@@ -111,7 +111,7 @@ type ResourceSliceSpec struct {
//
// +optional
// +oneOf=NodeSelection
- NodeName string
+ NodeName *string
// NodeSelector defines which nodes have access to the resources in the pool,
// when that pool is not limited to a single node.
@@ -130,7 +130,7 @@ type ResourceSliceSpec struct {
//
// +optional
// +oneOf=NodeSelection
- AllNodes bool
+ AllNodes *bool
// Devices lists some or all of the devices in this pool.
//
@@ -157,7 +157,7 @@ type ResourceSliceSpec struct {
//
// The names of the SharedCounters must be unique in the ResourceSlice.
//
- // The maximum number of SharedCounters is 32.
+ // The maximum number of counters in all sets is 32.
//
// +optional
// +listType=atomic
@@ -183,7 +183,7 @@ type CounterSet struct {
// Counters defines the set of counters for this CounterSet
// The name of each counter must be unique in that set and must be a DNS label.
//
- // The maximum number of counters is 32.
+ // The maximum number of counters in all sets is 32.
//
// +required
Counters map[string]Counter
@@ -234,28 +234,10 @@ const ResourceSliceMaxSharedCapacity = 128
const ResourceSliceMaxDevices = 128
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
-// Defines the max number of SharedCounters that can be specified
-// in a ResourceSlice. This is used to validate the fields:
-// * spec.sharedCounters
+// Defines the max number of shared counters that can be specified
+// in a ResourceSlice. The number is summed up across all sets.
const ResourceSliceMaxSharedCounters = 32
-// Defines the max number of Counters from which a device
-// can consume. This is used to validate the fields:
-// * spec.devices[].consumesCounter
-const ResourceSliceMaxDeviceCounterConsumptions = 32
-
-// Defines the max number of counters
-// that can be specified for sharedCounters in a ResourceSlice.
-// This is used to validate the fields:
-// * spec.sharedCounters[].counters
-const ResourceSliceMaxSharedCountersCounters = 32
-
-// Defines the max number of counters
-// that can be specified for consumesCounter in a ResourceSlice.
-// This is used to validate the fields:
-// * spec.devices[].consumesCounter[].counters
-const ResourceSliceMaxDeviceCounterConsumptionCounters = 32
-
// Device represents one individual hardware instance that can be selected based
// on its attributes. Besides the name, exactly one field must be set.
type Device struct {
@@ -265,15 +247,6 @@ type Device struct {
// +required
Name string
- // Basic defines one device instance.
- //
- // +optional
- // +oneOf=deviceType
- Basic *BasicDevice
-}
-
-// BasicDevice defines one device instance.
-type BasicDevice struct {
// Attributes defines the set of attributes for this device.
// The name of each attribute must be unique in that set.
//
@@ -290,20 +263,21 @@ type BasicDevice struct {
// +optional
Capacity map[QualifiedName]DeviceCapacity
- // ConsumesCounter defines a list of references to sharedCounters
+ // ConsumesCounters defines a list of references to sharedCounters
// and the set of counters that the device will
// consume from those counter sets.
//
// There can only be a single entry per counterSet.
//
- // The maximum number of device counter consumption entries
- // is 32. This is the same as the maximum number of shared counters
- // allowed in a ResourceSlice.
+ // The total number of device counter consumption entries
+ // must be <= 32. In addition, the total number in the
+ // entire ResourceSlice must be <= 1024 (for example,
+ // 64 devices with 16 counters each).
//
// +optional
// +listType=atomic
// +featureGate=DRAPartitionableDevices
- ConsumesCounter []DeviceCounterConsumption
+ ConsumesCounters []DeviceCounterConsumption
// NodeName identifies the node where the device is available.
//
@@ -317,6 +291,8 @@ type BasicDevice struct {
// NodeSelector defines the nodes where the device is available.
//
+ // Must use exactly one term.
+ //
// Must only be set if Spec.PerDeviceNodeSelection is set to true.
// At most one of NodeName, NodeSelector and AllNodes can be set.
//
@@ -337,7 +313,7 @@ type BasicDevice struct {
// If specified, these are the driver-defined taints.
//
- // The maximum number of taints is 8.
+ // The maximum number of taints is 4.
//
// This is an alpha field and requires enabling the DRADeviceTaints
// feature gate.
@@ -351,17 +327,18 @@ type BasicDevice struct {
// DeviceCounterConsumption defines a set of counters that
// a device will consume from a CounterSet.
type DeviceCounterConsumption struct {
- // SharedCounter defines the shared counter from which the
+ // CounterSet is the name of the set from which the
// counters defined will be consumed.
//
// +required
- SharedCounter string
+ CounterSet string
- // Counters defines the Counter that will be consumed by
- // the device.
+ // Counters defines the counters that will be consumed by the device.
//
- //
- // The maximum number of Counters is 32.
+ // The maximum number counters in a device is 32.
+ // In addition, the maximum number of all counters
+ // in all devices is 1024 (for example, 64 devices with
+ // 16 counters each).
//
// +required
Counters map[string]Counter
@@ -389,6 +366,14 @@ type Counter struct {
// Limit for the sum of the number of entries in both attributes and capacity.
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
+// Limit for the total number of counters in each device.
+const ResourceSliceMaxCountersPerDevice = 32
+
+// Limit for the total number of counters defined in devices in
+// a ResourceSlice. We want to allow up to 64 devices to specify
+// up to 16 counters, so the limit for the ResourceSlice will be 1024.
+const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
+
// QualifiedName is the name of a device attribute or capacity.
//
// Attributes and capacities are defined either by the owner of the specific
@@ -452,7 +437,7 @@ type DeviceAttribute struct {
const DeviceAttributeMaxValueLength = 64
// DeviceTaintsMaxLength is the maximum number of taints per device.
-const DeviceTaintsMaxLength = 8
+const DeviceTaintsMaxLength = 4
// The device this taint is attached to has the "effect" on
// any claim which does not tolerate the taint and, through the claim,
@@ -608,25 +593,60 @@ const (
// DeviceRequest is a request for devices required for a claim.
// This is typically a request for a single resource like a device, but can
-// also ask for several identical devices.
+// also ask for several identical devices. With FirstAvailable it is also
+// possible to provide a prioritized list of requests.
type DeviceRequest struct {
// Name can be used to reference this request in a pod.spec.containers[].resources.claims
// entry and in a constraint of the claim.
//
- // Must be a DNS label and unique among all DeviceRequests in a
- // ResourceClaim.
+ // References using the name in the DeviceRequest will uniquely
+ // identify a request when the Exactly field is set. When the
+ // FirstAvailable field is set, a reference to the name of the
+ // DeviceRequest will match whatever subrequest is chosen by the
+ // scheduler.
+ //
+ // Must be a DNS label.
//
// +required
Name string
+ // Exactly specifies the details for a single request that must
+ // be met exactly for the request to be satisfied.
+ //
+ // One of Exactly or FirstAvailable must be set.
+ //
+ // +optional
+ // +oneOf=deviceRequestType
+ Exactly *ExactDeviceRequest
+
+ // FirstAvailable contains subrequests, of which exactly one will be
+ // selected by the scheduler. It tries to
+ // satisfy them in the order in which they are listed here. So if
+ // there are two entries in the list, the scheduler will only check
+ // the second one if it determines that the first one can not be used.
+ //
+ // DRA does not yet implement scoring, so the scheduler will
+ // select the first set of devices that satisfies all the
+ // requests in the claim. And if the requirements can
+ // be satisfied on more than one node, other scheduling features
+ // will determine which node is chosen. This means that the set of
+ // devices allocated to a claim might not be the optimal set
+ // available to the cluster. Scoring will be implemented later.
+ //
+ // +optional
+ // +oneOf=deviceRequestType
+ // +listType=atomic
+ // +featureGate=DRAPrioritizedList
+ FirstAvailable []DeviceSubRequest
+}
+
+// ExactDeviceRequest is a request for one or more identical devices.
+type ExactDeviceRequest struct {
// DeviceClassName references a specific DeviceClass, which can define
// additional configuration and selectors to be inherited by this
// request.
//
- // A class is required if no subrequests are specified in the
- // firstAvailable list and no class can be set if subrequests
- // are specified in the firstAvailable list.
- // Which classes are available depends on the cluster.
+ // A DeviceClassName is required.
//
// Administrators may use this to restrict which devices may get
// requested by only installing classes with selectors for permitted
@@ -634,8 +654,7 @@ type DeviceRequest struct {
// then administrators can create an empty DeviceClass for users
// to reference.
//
- // +optional
- // +oneOf=deviceRequestType
+ // +required
DeviceClassName string
// Selectors define criteria which must be satisfied by a specific
@@ -643,9 +662,6 @@ type DeviceRequest struct {
// request. All selectors must be satisfied for a device to be
// considered.
//
- // This field can only be set when deviceClassName is set and no subrequests
- // are specified in the firstAvailable list.
- //
// +optional
// +listType=atomic
Selectors []DeviceSelector
@@ -666,9 +682,6 @@ type DeviceRequest struct {
// the mode is ExactCount and count is not specified, the default count is
// one. Any other requests must specify this field.
//
- // This field can only be set when deviceClassName is set and no subrequests
- // are specified in the firstAvailable list.
- //
// More modes may get added in the future. Clients must refuse to handle
// requests with unknown modes.
//
@@ -678,9 +691,6 @@ type DeviceRequest struct {
// Count is used only when the count mode is "ExactCount". Must be greater than zero.
// If AllocationMode is ExactCount and this field is not specified, the default is one.
//
- // This field can only be set when deviceClassName is set and no subrequests
- // are specified in the firstAvailable list.
- //
// +optional
// +oneOf=AllocationMode
Count int64
@@ -691,9 +701,6 @@ type DeviceRequest struct {
// all ordinary claims to the device with respect to access modes and
// any resource allocations.
//
- // This field can only be set when deviceClassName is set and no subrequests
- // are specified in the firstAvailable list.
- //
// This is an alpha field and requires enabling the DRAAdminAccess
// feature gate. Admin access is disabled if this field is unset or
// set to false, otherwise it is enabled.
@@ -702,28 +709,6 @@ type DeviceRequest struct {
// +featureGate=DRAAdminAccess
AdminAccess *bool
- // FirstAvailable contains subrequests, of which exactly one will be
- // satisfied by the scheduler to satisfy this request. It tries to
- // satisfy them in the order in which they are listed here. So if
- // there are two entries in the list, the scheduler will only check
- // the second one if it determines that the first one cannot be used.
- //
- // This field may only be set in the entries of DeviceClaim.Requests.
- //
- // DRA does not yet implement scoring, so the scheduler will
- // select the first set of devices that satisfies all the
- // requests in the claim. And if the requirements can
- // be satisfied on more than one node, other scheduling features
- // will determine which node is chosen. This means that the set of
- // devices allocated to a claim might not be the optimal set
- // available to the cluster. Scoring will be implemented later.
- //
- // +optional
- // +oneOf=deviceRequestType
- // +listType=atomic
- // +featureGate=DRAPrioritizedList
- FirstAvailable []DeviceSubRequest
-
// If specified, the request's tolerations.
//
// Tolerations for NoSchedule are required to allocate a
@@ -739,9 +724,6 @@ type DeviceRequest struct {
//
// The maximum number of tolerations is 16.
//
- // This field can only be set when deviceClassName is set and no subrequests
- // are specified in the firstAvailable list.
- //
// This is an alpha field and requires enabling the DRADeviceTaints
// feature gate.
//
@@ -756,10 +738,9 @@ type DeviceRequest struct {
// is typically a request for a single resource like a device, but can
// also ask for several identical devices.
//
-// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess
-// or FirstAvailable fields, as those can only be set on the top-level request.
-// AdminAccess is not supported for requests with a prioritized list, and
-// recursive FirstAvailable fields are not supported.
+// DeviceSubRequest is similar to ExactDeviceRequest, but doesn't expose the
+// AdminAccess field as that one is only supported when requesting a
+// specific device.
type DeviceSubRequest struct {
// Name can be used to reference this subrequest in the list of constraints
// or the list of configurations for the claim. References must use the
@@ -805,7 +786,7 @@ type DeviceSubRequest struct {
// Allocation will fail if some devices are already allocated,
// unless adminAccess is requested.
//
- // If AlloctionMode is not specified, the default mode is ExactCount. If
+ // If AllocationMode is not specified, the default mode is ExactCount. If
// the mode is ExactCount and count is not specified, the default count is
// one. Any other subrequests must specify this field.
//
diff --git a/pkg/apis/resource/v1alpha3/conversion.go b/pkg/apis/resource/v1alpha3/conversion.go
index 8c37ef0c514..5ffde01cc1b 100644
--- a/pkg/apis/resource/v1alpha3/conversion.go
+++ b/pkg/apis/resource/v1alpha3/conversion.go
@@ -18,10 +18,14 @@ package v1alpha3
import (
"fmt"
+ unsafe "unsafe"
+ corev1 "k8s.io/api/core/v1"
+ resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
"k8s.io/apimachinery/pkg/api/resource"
conversion "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
+ core "k8s.io/kubernetes/pkg/apis/core"
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
)
@@ -50,3 +54,252 @@ func Convert_resource_Quantity_To_resource_DeviceCapacity(in *resource.Quantity,
out.Value = *in
return nil
}
+
+func Convert_v1alpha3_DeviceRequest_To_resource_DeviceRequest(in *resourcev1alpha3.DeviceRequest, out *resourceapi.DeviceRequest, s conversion.Scope) error {
+ if err := autoConvert_v1alpha3_DeviceRequest_To_resource_DeviceRequest(in, out, s); err != nil {
+ return err
+ }
+ // If any fields on the main request is set, we create a ExactDeviceRequest
+ // and set the Exactly field. It might be invalid but that will be caught in validation.
+ if hasAnyMainRequestFieldsSet(in) {
+ var exactDeviceRequest resourceapi.ExactDeviceRequest
+ exactDeviceRequest.DeviceClassName = in.DeviceClassName
+ if in.Selectors != nil {
+ selectors := make([]resourceapi.DeviceSelector, 0, len(in.Selectors))
+ for i := range in.Selectors {
+ var selector resourceapi.DeviceSelector
+ err := Convert_v1alpha3_DeviceSelector_To_resource_DeviceSelector(&in.Selectors[i], &selector, s)
+ if err != nil {
+ return err
+ }
+ selectors = append(selectors, selector)
+ }
+ exactDeviceRequest.Selectors = selectors
+ }
+ exactDeviceRequest.AllocationMode = resourceapi.DeviceAllocationMode(in.AllocationMode)
+ exactDeviceRequest.Count = in.Count
+ exactDeviceRequest.AdminAccess = in.AdminAccess
+ var tolerations []resourceapi.DeviceToleration
+ for _, e := range in.Tolerations {
+ var toleration resourceapi.DeviceToleration
+ if err := Convert_v1alpha3_DeviceToleration_To_resource_DeviceToleration(&e, &toleration, s); err != nil {
+ return err
+ }
+ tolerations = append(tolerations, toleration)
+ }
+ exactDeviceRequest.Tolerations = tolerations
+ out.Exactly = &exactDeviceRequest
+ }
+ return nil
+}
+
+func hasAnyMainRequestFieldsSet(deviceRequest *resourcev1alpha3.DeviceRequest) bool {
+ return deviceRequest.DeviceClassName != "" ||
+ deviceRequest.Selectors != nil ||
+ deviceRequest.AllocationMode != "" ||
+ deviceRequest.Count != 0 ||
+ deviceRequest.AdminAccess != nil ||
+ deviceRequest.Tolerations != nil
+}
+
+func Convert_resource_DeviceRequest_To_v1alpha3_DeviceRequest(in *resourceapi.DeviceRequest, out *resourcev1alpha3.DeviceRequest, s conversion.Scope) error {
+ if err := autoConvert_resource_DeviceRequest_To_v1alpha3_DeviceRequest(in, out, s); err != nil {
+ return err
+ }
+ if in.Exactly != nil {
+ out.DeviceClassName = in.Exactly.DeviceClassName
+ if in.Exactly.Selectors != nil {
+ selectors := make([]resourcev1alpha3.DeviceSelector, 0, len(in.Exactly.Selectors))
+ for i := range in.Exactly.Selectors {
+ var selector resourcev1alpha3.DeviceSelector
+ err := Convert_resource_DeviceSelector_To_v1alpha3_DeviceSelector(&in.Exactly.Selectors[i], &selector, s)
+ if err != nil {
+ return err
+ }
+ selectors = append(selectors, selector)
+ }
+ out.Selectors = selectors
+ }
+ out.AllocationMode = resourcev1alpha3.DeviceAllocationMode(in.Exactly.AllocationMode)
+ out.Count = in.Exactly.Count
+ out.AdminAccess = in.Exactly.AdminAccess
+ var tolerations []resourcev1alpha3.DeviceToleration
+ for _, e := range in.Exactly.Tolerations {
+ var toleration resourcev1alpha3.DeviceToleration
+ if err := Convert_resource_DeviceToleration_To_v1alpha3_DeviceToleration(&e, &toleration, s); err != nil {
+ return err
+ }
+ tolerations = append(tolerations, toleration)
+ }
+ out.Tolerations = tolerations
+ }
+ return nil
+}
+
+func Convert_v1alpha3_ResourceSliceSpec_To_resource_ResourceSliceSpec(in *resourcev1alpha3.ResourceSliceSpec, out *resourceapi.ResourceSliceSpec, s conversion.Scope) error {
+ if err := autoConvert_v1alpha3_ResourceSliceSpec_To_resource_ResourceSliceSpec(in, out, s); err != nil {
+ return err
+ }
+ if in.NodeName == "" {
+ out.NodeName = nil
+ } else {
+ out.NodeName = &in.NodeName
+ }
+ if !in.AllNodes {
+ out.AllNodes = nil
+ } else {
+ out.AllNodes = &in.AllNodes
+ }
+ return nil
+}
+
+func Convert_resource_ResourceSliceSpec_To_v1alpha3_ResourceSliceSpec(in *resourceapi.ResourceSliceSpec, out *resourcev1alpha3.ResourceSliceSpec, s conversion.Scope) error {
+ if err := autoConvert_resource_ResourceSliceSpec_To_v1alpha3_ResourceSliceSpec(in, out, s); err != nil {
+ return err
+ }
+ if in.NodeName == nil {
+ out.NodeName = ""
+ } else {
+ out.NodeName = *in.NodeName
+ }
+ if in.AllNodes == nil {
+ out.AllNodes = false
+ } else {
+ out.AllNodes = *in.AllNodes
+ }
+ return nil
+}
+
+func Convert_v1alpha3_Device_To_resource_Device(in *resourcev1alpha3.Device, out *resourceapi.Device, s conversion.Scope) error {
+ if err := autoConvert_v1alpha3_Device_To_resource_Device(in, out, s); err != nil {
+ return err
+ }
+ if in.Basic != nil {
+ basic := in.Basic
+ if len(basic.Attributes) > 0 {
+ attributes := make(map[resourceapi.QualifiedName]resourceapi.DeviceAttribute)
+ if err := convert_v1alpha3_Attributes_To_resource_Attributes(basic.Attributes, attributes, s); err != nil {
+ return err
+ }
+ out.Attributes = attributes
+ }
+
+ if len(basic.Capacity) > 0 {
+ capacity := make(map[resourceapi.QualifiedName]resourceapi.DeviceCapacity)
+ if err := convert_v1alpha3_Capacity_To_resource_Capacity(basic.Capacity, capacity, s); err != nil {
+ return err
+ }
+ out.Capacity = capacity
+ }
+ var consumesCounters []resourceapi.DeviceCounterConsumption
+ for _, e := range basic.ConsumesCounters {
+ var deviceCounterConsumption resourceapi.DeviceCounterConsumption
+ if err := Convert_v1alpha3_DeviceCounterConsumption_To_resource_DeviceCounterConsumption(&e, &deviceCounterConsumption, s); err != nil {
+ return err
+ }
+ consumesCounters = append(consumesCounters, deviceCounterConsumption)
+ }
+ out.ConsumesCounters = consumesCounters
+ out.NodeName = basic.NodeName
+ out.NodeSelector = (*core.NodeSelector)(unsafe.Pointer(basic.NodeSelector))
+ out.AllNodes = basic.AllNodes
+ var taints []resourceapi.DeviceTaint
+ for _, e := range basic.Taints {
+ var taint resourceapi.DeviceTaint
+ if err := Convert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(&e, &taint, s); err != nil {
+ return err
+ }
+ taints = append(taints, taint)
+ }
+ out.Taints = taints
+ }
+ return nil
+}
+
+func Convert_resource_Device_To_v1alpha3_Device(in *resourceapi.Device, out *resourcev1alpha3.Device, s conversion.Scope) error {
+ if err := autoConvert_resource_Device_To_v1alpha3_Device(in, out, s); err != nil {
+ return err
+ }
+ out.Basic = &resourcev1alpha3.BasicDevice{}
+ if len(in.Attributes) > 0 {
+ attributes := make(map[resourcev1alpha3.QualifiedName]resourcev1alpha3.DeviceAttribute)
+ if err := convert_resource_Attributes_To_v1alpha3_Attributes(in.Attributes, attributes, s); err != nil {
+ return err
+ }
+ out.Basic.Attributes = attributes
+ }
+
+ if len(in.Capacity) > 0 {
+ capacity := make(map[resourcev1alpha3.QualifiedName]resource.Quantity)
+ if err := convert_resource_Capacity_To_v1alpha3_Capacity(in.Capacity, capacity, s); err != nil {
+ return err
+ }
+ out.Basic.Capacity = capacity
+ }
+ var consumesCounters []resourcev1alpha3.DeviceCounterConsumption
+ for _, e := range in.ConsumesCounters {
+ var deviceCounterConsumption resourcev1alpha3.DeviceCounterConsumption
+ if err := Convert_resource_DeviceCounterConsumption_To_v1alpha3_DeviceCounterConsumption(&e, &deviceCounterConsumption, s); err != nil {
+ return err
+ }
+ consumesCounters = append(consumesCounters, deviceCounterConsumption)
+ }
+ out.Basic.ConsumesCounters = consumesCounters
+ out.Basic.NodeName = in.NodeName
+ out.Basic.NodeSelector = (*corev1.NodeSelector)(unsafe.Pointer(in.NodeSelector))
+ out.Basic.AllNodes = in.AllNodes
+ var taints []resourcev1alpha3.DeviceTaint
+ for _, e := range in.Taints {
+ var taint resourcev1alpha3.DeviceTaint
+ if err := Convert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(&e, &taint, s); err != nil {
+ return err
+ }
+ taints = append(taints, taint)
+ }
+ out.Basic.Taints = taints
+ return nil
+}
+
+func convert_resource_Attributes_To_v1alpha3_Attributes(in map[resourceapi.QualifiedName]resourceapi.DeviceAttribute, out map[resourcev1alpha3.QualifiedName]resourcev1alpha3.DeviceAttribute, s conversion.Scope) error {
+ for k, v := range in {
+ var a resourcev1alpha3.DeviceAttribute
+ if err := Convert_resource_DeviceAttribute_To_v1alpha3_DeviceAttribute(&v, &a, s); err != nil {
+ return err
+ }
+ out[resourcev1alpha3.QualifiedName(k)] = a
+ }
+ return nil
+}
+
+func convert_resource_Capacity_To_v1alpha3_Capacity(in map[resourceapi.QualifiedName]resourceapi.DeviceCapacity, out map[resourcev1alpha3.QualifiedName]resource.Quantity, s conversion.Scope) error {
+ for k, v := range in {
+ var c resource.Quantity
+ if err := Convert_resource_DeviceCapacity_To_resource_Quantity(&v, &c, s); err != nil {
+ return err
+ }
+ out[resourcev1alpha3.QualifiedName(k)] = c
+ }
+ return nil
+}
+
+func convert_v1alpha3_Attributes_To_resource_Attributes(in map[resourcev1alpha3.QualifiedName]resourcev1alpha3.DeviceAttribute, out map[resourceapi.QualifiedName]resourceapi.DeviceAttribute, s conversion.Scope) error {
+ for k, v := range in {
+ var a resourceapi.DeviceAttribute
+ if err := Convert_v1alpha3_DeviceAttribute_To_resource_DeviceAttribute(&v, &a, s); err != nil {
+ return err
+ }
+ out[resourceapi.QualifiedName(k)] = a
+ }
+ return nil
+}
+
+func convert_v1alpha3_Capacity_To_resource_Capacity(in map[resourcev1alpha3.QualifiedName]resource.Quantity, out map[resourceapi.QualifiedName]resourceapi.DeviceCapacity, s conversion.Scope) error {
+ for k, v := range in {
+ var c resourceapi.DeviceCapacity
+ if err := Convert_resource_Quantity_To_resource_DeviceCapacity(&v, &c, s); err != nil {
+ return err
+ }
+ out[resourceapi.QualifiedName(k)] = c
+ }
+ return nil
+}
diff --git a/pkg/apis/resource/v1beta1/conversion.go b/pkg/apis/resource/v1beta1/conversion.go
index 045d6a9cfd7..c1ac24ed0df 100644
--- a/pkg/apis/resource/v1beta1/conversion.go
+++ b/pkg/apis/resource/v1beta1/conversion.go
@@ -18,16 +18,21 @@ package v1beta1
import (
"fmt"
+ unsafe "unsafe"
- resourceapi "k8s.io/api/resource/v1beta1"
+ corev1 "k8s.io/api/core/v1"
+ resourcev1beta1 "k8s.io/api/resource/v1beta1"
+ conversion "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
+ core "k8s.io/kubernetes/pkg/apis/core"
+ "k8s.io/kubernetes/pkg/apis/resource"
)
func addConversionFuncs(scheme *runtime.Scheme) error {
if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("ResourceSlice"),
func(label, value string) (string, string, error) {
switch label {
- case "metadata.name", resourceapi.ResourceSliceSelectorNodeName, resourceapi.ResourceSliceSelectorDriver:
+ case "metadata.name", resourcev1beta1.ResourceSliceSelectorNodeName, resourcev1beta1.ResourceSliceSelectorDriver:
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported for %s: %s", SchemeGroupVersion.WithKind("ResourceSlice"), label)
@@ -38,3 +43,251 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
return nil
}
+
+func Convert_v1beta1_DeviceRequest_To_resource_DeviceRequest(in *resourcev1beta1.DeviceRequest, out *resource.DeviceRequest, s conversion.Scope) error {
+ if err := autoConvert_v1beta1_DeviceRequest_To_resource_DeviceRequest(in, out, s); err != nil {
+ return err
+ }
+ // If any fields on the main request is set, we create a ExactDeviceRequest
+ // and set the Exactly field. It might be invalid but that will be caught in validation.
+ if hasAnyMainRequestFieldsSet(in) {
+ var exactDeviceRequest resource.ExactDeviceRequest
+ exactDeviceRequest.DeviceClassName = in.DeviceClassName
+ if in.Selectors != nil {
+ selectors := make([]resource.DeviceSelector, 0, len(in.Selectors))
+ for i := range in.Selectors {
+ var selector resource.DeviceSelector
+ err := Convert_v1beta1_DeviceSelector_To_resource_DeviceSelector(&in.Selectors[i], &selector, s)
+ if err != nil {
+ return err
+ }
+ selectors = append(selectors, selector)
+ }
+ exactDeviceRequest.Selectors = selectors
+ }
+ exactDeviceRequest.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode)
+ exactDeviceRequest.Count = in.Count
+ exactDeviceRequest.AdminAccess = in.AdminAccess
+ var tolerations []resource.DeviceToleration
+ for _, e := range in.Tolerations {
+ var toleration resource.DeviceToleration
+ if err := Convert_v1beta1_DeviceToleration_To_resource_DeviceToleration(&e, &toleration, s); err != nil {
+ return err
+ }
+ tolerations = append(tolerations, toleration)
+ }
+ exactDeviceRequest.Tolerations = tolerations
+ out.Exactly = &exactDeviceRequest
+ }
+ return nil
+}
+
+func hasAnyMainRequestFieldsSet(deviceRequest *resourcev1beta1.DeviceRequest) bool {
+ return deviceRequest.DeviceClassName != "" ||
+ deviceRequest.Selectors != nil ||
+ deviceRequest.AllocationMode != "" ||
+ deviceRequest.Count != 0 ||
+ deviceRequest.AdminAccess != nil ||
+ deviceRequest.Tolerations != nil
+}
+
+func Convert_resource_DeviceRequest_To_v1beta1_DeviceRequest(in *resource.DeviceRequest, out *resourcev1beta1.DeviceRequest, s conversion.Scope) error {
+ if err := autoConvert_resource_DeviceRequest_To_v1beta1_DeviceRequest(in, out, s); err != nil {
+ return err
+ }
+ if in.Exactly != nil {
+ out.DeviceClassName = in.Exactly.DeviceClassName
+ if in.Exactly.Selectors != nil {
+ selectors := make([]resourcev1beta1.DeviceSelector, 0, len(in.Exactly.Selectors))
+ for i := range in.Exactly.Selectors {
+ var selector resourcev1beta1.DeviceSelector
+ err := Convert_resource_DeviceSelector_To_v1beta1_DeviceSelector(&in.Exactly.Selectors[i], &selector, s)
+ if err != nil {
+ return err
+ }
+ selectors = append(selectors, selector)
+ }
+ out.Selectors = selectors
+ }
+ out.AllocationMode = resourcev1beta1.DeviceAllocationMode(in.Exactly.AllocationMode)
+ out.Count = in.Exactly.Count
+ out.AdminAccess = in.Exactly.AdminAccess
+ var tolerations []resourcev1beta1.DeviceToleration
+ for _, e := range in.Exactly.Tolerations {
+ var toleration resourcev1beta1.DeviceToleration
+ if err := Convert_resource_DeviceToleration_To_v1beta1_DeviceToleration(&e, &toleration, s); err != nil {
+ return err
+ }
+ tolerations = append(tolerations, toleration)
+ }
+ out.Tolerations = tolerations
+ }
+ return nil
+}
+
+func Convert_v1beta1_ResourceSliceSpec_To_resource_ResourceSliceSpec(in *resourcev1beta1.ResourceSliceSpec, out *resource.ResourceSliceSpec, s conversion.Scope) error {
+ if err := autoConvert_v1beta1_ResourceSliceSpec_To_resource_ResourceSliceSpec(in, out, s); err != nil {
+ return err
+ }
+ if in.NodeName == "" {
+ out.NodeName = nil
+ } else {
+ out.NodeName = &in.NodeName
+ }
+ if !in.AllNodes {
+ out.AllNodes = nil
+ } else {
+ out.AllNodes = &in.AllNodes
+ }
+ return nil
+}
+
+func Convert_resource_ResourceSliceSpec_To_v1beta1_ResourceSliceSpec(in *resource.ResourceSliceSpec, out *resourcev1beta1.ResourceSliceSpec, s conversion.Scope) error {
+ if err := autoConvert_resource_ResourceSliceSpec_To_v1beta1_ResourceSliceSpec(in, out, s); err != nil {
+ return err
+ }
+ if in.NodeName == nil {
+ out.NodeName = ""
+ } else {
+ out.NodeName = *in.NodeName
+ }
+ if in.AllNodes == nil {
+ out.AllNodes = false
+ } else {
+ out.AllNodes = *in.AllNodes
+ }
+ return nil
+}
+
+func Convert_v1beta1_Device_To_resource_Device(in *resourcev1beta1.Device, out *resource.Device, s conversion.Scope) error {
+ if err := autoConvert_v1beta1_Device_To_resource_Device(in, out, s); err != nil {
+ return err
+ }
+ if in.Basic != nil {
+ basic := in.Basic
+ if len(basic.Attributes) > 0 {
+ attributes := make(map[resource.QualifiedName]resource.DeviceAttribute)
+ if err := convert_v1beta1_Attributes_To_resource_Attributes(basic.Attributes, attributes, s); err != nil {
+ return err
+ }
+ out.Attributes = attributes
+ }
+ if len(basic.Capacity) > 0 {
+ capacity := make(map[resource.QualifiedName]resource.DeviceCapacity)
+ if err := convert_v1beta1_Capacity_To_resource_Capacity(basic.Capacity, capacity, s); err != nil {
+ return err
+ }
+ out.Capacity = capacity
+ }
+ var consumesCounters []resource.DeviceCounterConsumption
+ for _, e := range basic.ConsumesCounters {
+ var deviceCounterConsumption resource.DeviceCounterConsumption
+ if err := Convert_v1beta1_DeviceCounterConsumption_To_resource_DeviceCounterConsumption(&e, &deviceCounterConsumption, s); err != nil {
+ return err
+ }
+ consumesCounters = append(consumesCounters, deviceCounterConsumption)
+ }
+ out.ConsumesCounters = consumesCounters
+ out.NodeName = basic.NodeName
+ out.NodeSelector = (*core.NodeSelector)(unsafe.Pointer(basic.NodeSelector))
+ out.AllNodes = basic.AllNodes
+ var taints []resource.DeviceTaint
+ for _, e := range basic.Taints {
+ var taint resource.DeviceTaint
+ if err := Convert_v1beta1_DeviceTaint_To_resource_DeviceTaint(&e, &taint, s); err != nil {
+ return err
+ }
+ taints = append(taints, taint)
+ }
+ out.Taints = taints
+ }
+ return nil
+}
+
+func Convert_resource_Device_To_v1beta1_Device(in *resource.Device, out *resourcev1beta1.Device, s conversion.Scope) error {
+ if err := autoConvert_resource_Device_To_v1beta1_Device(in, out, s); err != nil {
+ return err
+ }
+ out.Basic = &resourcev1beta1.BasicDevice{}
+ if len(in.Attributes) > 0 {
+ attributes := make(map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute)
+ if err := convert_resource_Attributes_To_v1beta1_Attributes(in.Attributes, attributes, s); err != nil {
+ return err
+ }
+ out.Basic.Attributes = attributes
+ }
+
+ if len(in.Capacity) > 0 {
+ capacity := make(map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceCapacity)
+ if err := convert_resource_Capacity_To_v1beta1_Capacity(in.Capacity, capacity, s); err != nil {
+ return err
+ }
+ out.Basic.Capacity = capacity
+ }
+ var consumesCounters []resourcev1beta1.DeviceCounterConsumption
+ for _, e := range in.ConsumesCounters {
+ var deviceCounterConsumption resourcev1beta1.DeviceCounterConsumption
+ if err := Convert_resource_DeviceCounterConsumption_To_v1beta1_DeviceCounterConsumption(&e, &deviceCounterConsumption, s); err != nil {
+ return err
+ }
+ consumesCounters = append(consumesCounters, deviceCounterConsumption)
+ }
+ out.Basic.ConsumesCounters = consumesCounters
+ out.Basic.NodeName = in.NodeName
+ out.Basic.NodeSelector = (*corev1.NodeSelector)(unsafe.Pointer(in.NodeSelector))
+ out.Basic.AllNodes = in.AllNodes
+ var taints []resourcev1beta1.DeviceTaint
+ for _, e := range in.Taints {
+ var taint resourcev1beta1.DeviceTaint
+ if err := Convert_resource_DeviceTaint_To_v1beta1_DeviceTaint(&e, &taint, s); err != nil {
+ return err
+ }
+ taints = append(taints, taint)
+ }
+ out.Basic.Taints = taints
+ return nil
+}
+
+func convert_resource_Attributes_To_v1beta1_Attributes(in map[resource.QualifiedName]resource.DeviceAttribute, out map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute, s conversion.Scope) error {
+ for k, v := range in {
+ var a resourcev1beta1.DeviceAttribute
+ if err := Convert_resource_DeviceAttribute_To_v1beta1_DeviceAttribute(&v, &a, s); err != nil {
+ return err
+ }
+ out[resourcev1beta1.QualifiedName(k)] = a
+ }
+ return nil
+}
+
+func convert_resource_Capacity_To_v1beta1_Capacity(in map[resource.QualifiedName]resource.DeviceCapacity, out map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceCapacity, s conversion.Scope) error {
+ for k, v := range in {
+ var c resourcev1beta1.DeviceCapacity
+ if err := Convert_resource_DeviceCapacity_To_v1beta1_DeviceCapacity(&v, &c, s); err != nil {
+ return err
+ }
+ out[resourcev1beta1.QualifiedName(k)] = c
+ }
+ return nil
+}
+
+func convert_v1beta1_Attributes_To_resource_Attributes(in map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute, out map[resource.QualifiedName]resource.DeviceAttribute, s conversion.Scope) error {
+ for k, v := range in {
+ var a resource.DeviceAttribute
+ if err := Convert_v1beta1_DeviceAttribute_To_resource_DeviceAttribute(&v, &a, s); err != nil {
+ return err
+ }
+ out[resource.QualifiedName(k)] = a
+ }
+ return nil
+}
+
+func convert_v1beta1_Capacity_To_resource_Capacity(in map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceCapacity, out map[resource.QualifiedName]resource.DeviceCapacity, s conversion.Scope) error {
+ for k, v := range in {
+ var c resource.DeviceCapacity
+ if err := Convert_v1beta1_DeviceCapacity_To_resource_DeviceCapacity(&v, &c, s); err != nil {
+ return err
+ }
+ out[resource.QualifiedName(k)] = c
+ }
+ return nil
+}
diff --git a/pkg/apis/resource/v1beta1/conversion_test.go b/pkg/apis/resource/v1beta1/conversion_test.go
new file mode 100644
index 00000000000..276c928cedc
--- /dev/null
+++ b/pkg/apis/resource/v1beta1/conversion_test.go
@@ -0,0 +1,333 @@
+/*
+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 v1beta1
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ resourcev1beta1 "k8s.io/api/resource/v1beta1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/kubernetes/pkg/apis/resource"
+)
+
+func TestConversion(t *testing.T) {
+ testcases := []struct {
+ name string
+ in runtime.Object
+ out runtime.Object
+ expectOut runtime.Object
+ expectErr string
+ }{
+ {
+ name: "v1beta1 to internal without alternatives",
+ in: &resourcev1beta1.ResourceClaim{
+ Spec: resourcev1beta1.ResourceClaimSpec{
+ Devices: resourcev1beta1.DeviceClaim{
+ Requests: []resourcev1beta1.DeviceRequest{
+ {
+ Name: "foo",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ },
+ },
+ },
+ },
+ out: &resource.ResourceClaim{},
+ expectOut: &resource.ResourceClaim{
+ Spec: resource.ResourceClaimSpec{
+ Devices: resource.DeviceClaim{
+ Requests: []resource.DeviceRequest{
+ {
+ Name: "foo",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "internal to v1beta1 without alternatives",
+ in: &resource.ResourceClaim{
+ Spec: resource.ResourceClaimSpec{
+ Devices: resource.DeviceClaim{
+ Requests: []resource.DeviceRequest{
+ {
+ Name: "foo",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ },
+ },
+ },
+ },
+ },
+ out: &resourcev1beta1.ResourceClaim{},
+ expectOut: &resourcev1beta1.ResourceClaim{
+ Spec: resourcev1beta1.ResourceClaimSpec{
+ Devices: resourcev1beta1.DeviceClaim{
+ Requests: []resourcev1beta1.DeviceRequest{
+ {
+ Name: "foo",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ },
+ },
+ },
+ },
+ },
+
+ {
+ name: "v1beta1 to internal with alternatives",
+ in: &resourcev1beta1.ResourceClaim{
+ Spec: resourcev1beta1.ResourceClaimSpec{
+ Devices: resourcev1beta1.DeviceClaim{
+ Requests: []resourcev1beta1.DeviceRequest{
+ {
+ Name: "foo",
+ FirstAvailable: []resourcev1beta1.DeviceSubRequest{
+ {
+ Name: "sub-1",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ {
+ Name: "sub-2",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ out: &resource.ResourceClaim{},
+ expectOut: &resource.ResourceClaim{
+ Spec: resource.ResourceClaimSpec{
+ Devices: resource.DeviceClaim{
+ Requests: []resource.DeviceRequest{
+ {
+ Name: "foo",
+ FirstAvailable: []resource.DeviceSubRequest{
+ {
+ Name: "sub-1",
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ {
+ Name: "sub-2",
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "v1beta1 to internal with alternatives",
+ in: &resource.ResourceClaim{
+ Spec: resource.ResourceClaimSpec{
+ Devices: resource.DeviceClaim{
+ Requests: []resource.DeviceRequest{
+ {
+ Name: "foo",
+ FirstAvailable: []resource.DeviceSubRequest{
+ {
+ Name: "sub-1",
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ {
+ Name: "sub-2",
+ DeviceClassName: "class-a",
+ Selectors: []resource.DeviceSelector{
+ {
+ CEL: &resource.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ out: &resourcev1beta1.ResourceClaim{},
+ expectOut: &resourcev1beta1.ResourceClaim{
+ Spec: resourcev1beta1.ResourceClaimSpec{
+ Devices: resourcev1beta1.DeviceClaim{
+ Requests: []resourcev1beta1.DeviceRequest{
+ {
+ Name: "foo",
+ FirstAvailable: []resourcev1beta1.DeviceSubRequest{
+ {
+ Name: "sub-1",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 2,
+ },
+ {
+ Name: "sub-2",
+ DeviceClassName: "class-a",
+ Selectors: []resourcev1beta1.DeviceSelector{
+ {
+ CEL: &resourcev1beta1.CELDeviceSelector{
+ Expression: `device.attributes["driver-a"].exists`,
+ },
+ },
+ },
+ AllocationMode: resourcev1beta1.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ scheme := runtime.NewScheme()
+ if err := resource.AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+
+ for i := range testcases {
+ name := testcases[i].name
+ tc := testcases[i]
+ t.Run(name, func(t *testing.T) {
+ err := scheme.Convert(tc.in, tc.out, nil)
+ if err != nil {
+ if len(tc.expectErr) == 0 {
+ t.Fatalf("unexpected error %v", err)
+ }
+ if !strings.Contains(err.Error(), tc.expectErr) {
+ t.Fatalf("expected error %s, got %v", tc.expectErr, err)
+ }
+ return
+ }
+ if len(tc.expectErr) > 0 {
+ t.Fatalf("expected error %s, got none", tc.expectErr)
+ }
+ if !reflect.DeepEqual(tc.out, tc.expectOut) {
+ t.Fatalf("unexpected result:\n %s", cmp.Diff(tc.expectOut, tc.out))
+ }
+ })
+ }
+
+}
diff --git a/pkg/apis/resource/v1beta2/conversion.go b/pkg/apis/resource/v1beta2/conversion.go
new file mode 100644
index 00000000000..64d285b9cf7
--- /dev/null
+++ b/pkg/apis/resource/v1beta2/conversion.go
@@ -0,0 +1,40 @@
+/*
+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 v1beta2
+
+import (
+ "fmt"
+
+ resourceapi "k8s.io/api/resource/v1beta2"
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+func addConversionFuncs(scheme *runtime.Scheme) error {
+ if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("ResourceSlice"),
+ func(label, value string) (string, string, error) {
+ switch label {
+ case "metadata.name", resourceapi.ResourceSliceSelectorNodeName, resourceapi.ResourceSliceSelectorDriver:
+ return label, value, nil
+ default:
+ return "", "", fmt.Errorf("field label not supported for %s: %s", SchemeGroupVersion.WithKind("ResourceSlice"), label)
+ }
+ }); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/apis/resource/v1beta2/defaults.go b/pkg/apis/resource/v1beta2/defaults.go
new file mode 100644
index 00000000000..4fb093567ae
--- /dev/null
+++ b/pkg/apis/resource/v1beta2/defaults.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2022 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 v1beta2
+
+import (
+ "time"
+
+ resourceapi "k8s.io/api/resource/v1beta2"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+func addDefaultingFuncs(scheme *runtime.Scheme) error {
+ return RegisterDefaults(scheme)
+}
+
+func SetDefaults_ExactDeviceRequest(obj *resourceapi.ExactDeviceRequest) {
+ if obj.AllocationMode == "" {
+ obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount
+ }
+
+ if obj.AllocationMode == resourceapi.DeviceAllocationModeExactCount && obj.Count == 0 {
+ obj.Count = 1
+ }
+}
+
+func SetDefaults_DeviceSubRequest(obj *resourceapi.DeviceSubRequest) {
+ if obj.AllocationMode == "" {
+ obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount
+ }
+
+ if obj.AllocationMode == resourceapi.DeviceAllocationModeExactCount && obj.Count == 0 {
+ obj.Count = 1
+ }
+}
+
+func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) {
+ if obj.TimeAdded == nil {
+ obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)}
+ }
+}
diff --git a/pkg/apis/resource/v1beta2/defaults_test.go b/pkg/apis/resource/v1beta2/defaults_test.go
new file mode 100644
index 00000000000..3e32a9e2792
--- /dev/null
+++ b/pkg/apis/resource/v1beta2/defaults_test.go
@@ -0,0 +1,179 @@
+/*
+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 v1beta2_test
+
+import (
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+
+ v1beta2 "k8s.io/api/resource/v1beta2"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/utils/ptr"
+
+ // ensure types are installed
+ "k8s.io/kubernetes/pkg/api/legacyscheme"
+ _ "k8s.io/kubernetes/pkg/apis/resource/install"
+)
+
+func TestSetDefaultAllocationMode(t *testing.T) {
+ claim := &v1beta2.ResourceClaim{
+ Spec: v1beta2.ResourceClaimSpec{
+ Devices: v1beta2.DeviceClaim{
+ Requests: []v1beta2.DeviceRequest{
+ {
+ Exactly: &v1beta2.ExactDeviceRequest{},
+ },
+ },
+ },
+ },
+ }
+
+ // fields should be defaulted
+ defaultMode := v1beta2.DeviceAllocationModeExactCount
+ defaultCount := int64(1)
+ output := roundTrip(t, runtime.Object(claim)).(*v1beta2.ResourceClaim)
+ assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].Exactly.AllocationMode)
+ assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].Exactly.Count)
+
+ // field should not change
+ nonDefaultMode := v1beta2.DeviceAllocationModeExactCount
+ nonDefaultCount := int64(10)
+ claim = &v1beta2.ResourceClaim{
+ Spec: v1beta2.ResourceClaimSpec{
+ Devices: v1beta2.DeviceClaim{
+ Requests: []v1beta2.DeviceRequest{{
+ Exactly: &v1beta2.ExactDeviceRequest{
+ AllocationMode: nonDefaultMode,
+ Count: nonDefaultCount,
+ },
+ }},
+ },
+ },
+ }
+ output = roundTrip(t, runtime.Object(claim)).(*v1beta2.ResourceClaim)
+ assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].Exactly.AllocationMode)
+ assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].Exactly.Count)
+}
+
+func TestSetDefaultAllocationModeWithSubRequests(t *testing.T) {
+ claim := &v1beta2.ResourceClaim{
+ Spec: v1beta2.ResourceClaimSpec{
+ Devices: v1beta2.DeviceClaim{
+ Requests: []v1beta2.DeviceRequest{
+ {
+ Name: "req-1",
+ FirstAvailable: []v1beta2.DeviceSubRequest{
+ {
+ Name: "subReq-1",
+ },
+ {
+ Name: "subReq-2",
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ defaultMode := v1beta2.DeviceAllocationModeExactCount
+ defaultCount := int64(1)
+ output := roundTrip(t, runtime.Object(claim)).(*v1beta2.ResourceClaim)
+ // the exactly field is not set.
+ assert.Nil(t, output.Spec.Devices.Requests[0].Exactly)
+ // fields on the subRequests should be defaulted.
+ assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode)
+ assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count)
+ assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode)
+ assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count)
+
+ // field should not change
+ nonDefaultMode := v1beta2.DeviceAllocationModeExactCount
+ nonDefaultCount := int64(10)
+ claim = &v1beta2.ResourceClaim{
+ Spec: v1beta2.ResourceClaimSpec{
+ Devices: v1beta2.DeviceClaim{
+ Requests: []v1beta2.DeviceRequest{{
+ Name: "req-1",
+ FirstAvailable: []v1beta2.DeviceSubRequest{
+ {
+ Name: "subReq-1",
+ AllocationMode: nonDefaultMode,
+ Count: nonDefaultCount,
+ },
+ {
+ Name: "subReq-2",
+ AllocationMode: nonDefaultMode,
+ Count: nonDefaultCount,
+ },
+ },
+ }},
+ },
+ },
+ }
+ output = roundTrip(t, runtime.Object(claim)).(*v1beta2.ResourceClaim)
+ assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode)
+ assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count)
+ assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode)
+ assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count)
+}
+
+func TestSetDefaultDeviceTaint(t *testing.T) {
+ slice := &v1beta2.ResourceSlice{
+ Spec: v1beta2.ResourceSliceSpec{
+ Devices: []v1beta2.Device{{
+ Name: "device-0",
+ Taints: []v1beta2.DeviceTaint{{}},
+ }},
+ },
+ }
+
+ // fields should be defaulted
+ output := roundTrip(t, slice).(*v1beta2.ResourceSlice)
+ assert.WithinDuration(t, time.Now(), ptr.Deref(output.Spec.Devices[0].Taints[0].TimeAdded, metav1.Time{}).Time, time.Minute /* allow for some processing delay */, "time added default")
+
+ // field should not change
+ timeAdded, _ := time.ParseInLocation(time.RFC3339, "2006-01-02T15:04:05Z", time.UTC)
+ slice.Spec.Devices[0].Taints[0].TimeAdded = &metav1.Time{Time: timeAdded}
+ output = roundTrip(t, slice).(*v1beta2.ResourceSlice)
+ assert.WithinDuration(t, timeAdded, ptr.Deref(output.Spec.Devices[0].Taints[0].TimeAdded, metav1.Time{}).Time, 0 /* semantically the same, different time zone allowed */, "time added fixed")
+}
+
+func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
+ codec := legacyscheme.Codecs.LegacyCodec(v1beta2.SchemeGroupVersion)
+ data, err := runtime.Encode(codec, obj)
+ if err != nil {
+ t.Errorf("%v\n %#v", err, obj)
+ return nil
+ }
+ obj2, err := runtime.Decode(codec, data)
+ if err != nil {
+ t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj)
+ return nil
+ }
+ obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object)
+ err = legacyscheme.Scheme.Convert(obj2, obj3, nil)
+ if err != nil {
+ t.Errorf("%v\nSource: %#v", err, obj2)
+ return nil
+ }
+ return obj3
+}
diff --git a/pkg/apis/resource/v1beta2/doc.go b/pkg/apis/resource/v1beta2/doc.go
new file mode 100644
index 00000000000..beea4f266b9
--- /dev/null
+++ b/pkg/apis/resource/v1beta2/doc.go
@@ -0,0 +1,23 @@
+/*
+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.
+*/
+
+// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/resource
+// +k8s:conversion-gen-external-types=k8s.io/api/resource/v1beta2
+// +k8s:defaulter-gen=TypeMeta
+// +k8s:defaulter-gen-input=k8s.io/api/resource/v1beta2
+
+// Package v1beta2 is the v1beta2 version of the resource API.
+package v1beta2
diff --git a/pkg/apis/resource/v1beta2/register.go b/pkg/apis/resource/v1beta2/register.go
new file mode 100644
index 00000000000..b7c2803b2c6
--- /dev/null
+++ b/pkg/apis/resource/v1beta2/register.go
@@ -0,0 +1,46 @@
+/*
+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 v1beta2
+
+import (
+ "k8s.io/api/resource/v1beta2"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+var (
+ localSchemeBuilder = &v1beta2.SchemeBuilder
+ AddToScheme = localSchemeBuilder.AddToScheme
+)
+
+func init() {
+ // We only register manually written functions here. The registration of the
+ // generated functions takes place in the generated files. The separation
+ // makes the code compile even when the generated files are missing.
+ localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
+}
+
+// TODO: remove these global variables
+// GroupName is the group name use in this package
+const GroupName = "resource.k8s.io"
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta2"}
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+ return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go
index 4d10005a523..2f10ecd5f98 100644
--- a/pkg/apis/resource/validation/validation.go
+++ b/pkg/apis/resource/validation/validation.go
@@ -173,27 +173,9 @@ func gatherAllocatedDevices(allocationResult *resource.DeviceAllocationResult) s
func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path, stored bool) field.ErrorList {
allErrs := validateRequestName(request.Name, fldPath.Child("name"))
- if request.DeviceClassName == "" && len(request.FirstAvailable) == 0 {
- allErrs = append(allErrs, field.Required(fldPath, "exactly one of `deviceClassName` or `firstAvailable` must be specified"))
- } else if request.DeviceClassName != "" && len(request.FirstAvailable) > 0 {
- allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified"))
- } else if request.DeviceClassName != "" {
- allErrs = append(allErrs, validateDeviceClass(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
- allErrs = append(allErrs, validateSelectorSlice(request.Selectors, fldPath.Child("selectors"), stored)...)
- allErrs = append(allErrs, validateDeviceAllocationMode(request.AllocationMode, request.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
- } else if len(request.FirstAvailable) > 0 {
- if request.Selectors != nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("selectors"), request.Selectors, "must not be specified when firstAvailable is set"))
- }
- if request.AllocationMode != "" {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("allocationMode"), request.AllocationMode, "must not be specified when firstAvailable is set"))
- }
- if request.Count != 0 {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must not be specified when firstAvailable is set"))
- }
- if request.AdminAccess != nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("adminAccess"), request.AdminAccess, "must not be specified when firstAvailable is set"))
- }
+ numDeviceRequestType := 0
+ if len(request.FirstAvailable) > 0 {
+ numDeviceRequestType++
allErrs = append(allErrs, validateSet(request.FirstAvailable, resource.FirstAvailableDeviceRequestMaxSize,
func(subRequest resource.DeviceSubRequest, fldPath *field.Path) field.ErrorList {
return validateDeviceSubRequest(subRequest, fldPath, stored)
@@ -203,10 +185,19 @@ func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path,
},
fldPath.Child("firstAvailable"))...)
}
- for i, toleration := range request.Tolerations {
- allErrs = append(allErrs, validateDeviceToleration(toleration, fldPath.Child("tolerations").Index(i))...)
+
+ if request.Exactly != nil {
+ numDeviceRequestType++
+ allErrs = append(allErrs, validateExactDeviceRequest(*request.Exactly, fldPath.Child("exactly"), stored)...)
}
+ switch numDeviceRequestType {
+ case 0:
+ allErrs = append(allErrs, field.Required(fldPath, "exactly one of `exactly` or `firstAvailable` is required"))
+ case 1:
+ default:
+ allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one of `exactly` or `firstAvailable` is required, but multiple fields are set"))
+ }
return allErrs
}
@@ -221,6 +212,17 @@ func validateDeviceSubRequest(subRequest resource.DeviceSubRequest, fldPath *fie
return allErrs
}
+func validateExactDeviceRequest(request resource.ExactDeviceRequest, fldPath *field.Path, stored bool) field.ErrorList {
+ var allErrs field.ErrorList
+ allErrs = append(allErrs, validateDeviceClass(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
+ allErrs = append(allErrs, validateSelectorSlice(request.Selectors, fldPath.Child("selectors"), stored)...)
+ allErrs = append(allErrs, validateDeviceAllocationMode(request.AllocationMode, request.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
+ for i, toleration := range request.Tolerations {
+ allErrs = append(allErrs, validateDeviceToleration(toleration, fldPath.Child("tolerations").Index(i))...)
+ }
+ return allErrs
+}
+
func validateDeviceAllocationMode(deviceAllocationMode resource.DeviceAllocationMode, count int64, allocModeFldPath, countFldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
switch deviceAllocationMode {
@@ -587,9 +589,14 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat
}
setFields := make([]string, 0, 4)
- if spec.NodeName != "" {
- setFields = append(setFields, "`nodeName`")
- allErrs = append(allErrs, validateNodeName(spec.NodeName, fldPath.Child("nodeName"))...)
+ if spec.NodeName != nil {
+ if *spec.NodeName != "" {
+ setFields = append(setFields, "`nodeName`")
+ allErrs = append(allErrs, validateNodeName(*spec.NodeName, fldPath.Child("nodeName"))...)
+ } else {
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *spec.NodeName,
+ "must be either unset or set to a non-empty string"))
+ }
}
if spec.NodeSelector != nil {
setFields = append(setFields, "`nodeSelector`")
@@ -600,9 +607,16 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat
allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeSelector", "nodeSelectorTerms"), spec.NodeSelector.NodeSelectorTerms, "must have exactly one node selector term"))
}
}
- if spec.AllNodes {
- setFields = append(setFields, "`allNodes`")
+
+ if spec.AllNodes != nil {
+ if *spec.AllNodes {
+ setFields = append(setFields, "`allNodes`")
+ } else {
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("allNodes"), *spec.AllNodes,
+ "must be either unset or set to true"))
+ }
}
+
if spec.PerDeviceNodeSelection != nil {
if *spec.PerDeviceNodeSelection {
setFields = append(setFields, "`perDeviceNodeSelection`")
@@ -610,8 +624,8 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat
allErrs = append(allErrs, field.Invalid(fldPath.Child("perDeviceNodeSelection"), *spec.PerDeviceNodeSelection,
"must be either unset or set to true"))
}
-
}
+
switch len(setFields) {
case 0:
allErrs = append(allErrs, field.Required(fldPath, "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required"))
@@ -630,7 +644,26 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat
return device.Name, "name"
}, fldPath.Child("devices"))...)
- allErrs = append(allErrs, validateSet(spec.SharedCounters, resource.ResourceSliceMaxSharedCounters,
+ // Size limit for total number of counters in devices enforced here.
+ numDeviceCounters := 0
+ for _, device := range spec.Devices {
+ for _, c := range device.ConsumesCounters {
+ numDeviceCounters += len(c.Counters)
+ }
+ }
+ if numDeviceCounters > resource.ResourceSliceMaxDeviceCountersPerSlice {
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("devices"), numDeviceCounters, fmt.Sprintf("the total number of counters in devices must not exceed %d", resource.ResourceSliceMaxDeviceCountersPerSlice)))
+ }
+
+ // Size limit for all shared counters enforced here.
+ numCounters := 0
+ for _, set := range spec.SharedCounters {
+ numCounters += len(set.Counters)
+ }
+ if numCounters > resource.ResourceSliceMaxSharedCounters {
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("sharedCounters"), numCounters, fmt.Sprintf("the total number of shared counters must not exceed %d", resource.ResourceSliceMaxSharedCounters)))
+ }
+ allErrs = append(allErrs, validateSet(spec.SharedCounters, -1,
validateCounterSet,
func(counterSet resource.CounterSet) (string, string) {
return counterSet.Name, "name"
@@ -649,7 +682,8 @@ func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) fie
if len(counterSet.Counters) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("counters"), ""))
} else {
- allErrs = append(allErrs, validateMap(counterSet.Counters, resource.ResourceSliceMaxSharedCountersCounters, attributeAndCapacityMaxKeyLength,
+ // The size limit is enforced for across all sets by the caller.
+ allErrs = append(allErrs, validateMap(counterSet.Counters, -1, validation.DNS1123LabelMaxLength,
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
}
@@ -658,12 +692,12 @@ func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) fie
func gatherSharedCounterCounterNames(sharedCounters []resource.CounterSet) map[string]sets.Set[string] {
sharedCounterToCounterMap := make(map[string]sets.Set[string])
- for _, sharedCounter := range sharedCounters {
+ for _, counterSet := range sharedCounters {
counterNames := sets.New[string]()
- for counterName := range sharedCounter.Counters {
+ for counterName := range counterSet.Counters {
counterNames.Insert(counterName)
}
- sharedCounterToCounterMap[sharedCounter.Name] = counterNames
+ sharedCounterToCounterMap[counterSet.Name] = counterNames
}
return sharedCounterToCounterMap
}
@@ -683,44 +717,42 @@ func validateResourcePool(pool resource.ResourcePool, fldPath *field.Path) field
func validateDevice(device resource.Device, fldPath *field.Path, sharedCounterToCounterNames map[string]sets.Set[string], perDeviceNodeSelection *bool) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, validateDeviceName(device.Name, fldPath.Child("name"))...)
- if device.Basic == nil {
- allErrs = append(allErrs, field.Required(fldPath.Child("basic"), ""))
- } else {
- allErrs = append(allErrs, validateBasicDevice(*device.Basic, fldPath.Child("basic"), sharedCounterToCounterNames, perDeviceNodeSelection)...)
- }
- return allErrs
-}
-
-func validateBasicDevice(device resource.BasicDevice, fldPath *field.Path, sharedCounterToCounterNames map[string]sets.Set[string], perDeviceNodeSelection *bool) field.ErrorList {
- var allErrs field.ErrorList
// Warn about exceeding the maximum length only once. If any individual
// field is too large, then so is the combination.
+ attributeAndCapacityLength := len(device.Attributes) + len(device.Capacity)
+ if attributeAndCapacityLength > resource.ResourceSliceMaxAttributesAndCapacitiesPerDevice {
+ allErrs = append(allErrs, field.Invalid(fldPath, attributeAndCapacityLength, fmt.Sprintf("the total number of attributes and capacities must not exceed %d", resource.ResourceSliceMaxAttributesAndCapacitiesPerDevice)))
+ }
+
allErrs = append(allErrs, validateMap(device.Attributes, -1, attributeAndCapacityMaxKeyLength, validateQualifiedName, validateDeviceAttribute, fldPath.Child("attributes"))...)
allErrs = append(allErrs, validateMap(device.Capacity, -1, attributeAndCapacityMaxKeyLength, validateQualifiedName, validateDeviceCapacity, fldPath.Child("capacity"))...)
- if combinedLen, max := len(device.Attributes)+len(device.Capacity), resource.ResourceSliceMaxAttributesAndCapacitiesPerDevice; combinedLen > max {
- allErrs = append(allErrs, field.Invalid(fldPath, combinedLen, fmt.Sprintf("the total number of attributes and capacities must not exceed %d", max)))
- }
- for i, taint := range device.Taints {
- allErrs = append(allErrs, validateDeviceTaint(taint, fldPath.Child("taints").Index(i))...)
- }
+ allErrs = append(allErrs, validateSlice(device.Taints, resource.DeviceTaintsMaxLength, validateDeviceTaint, fldPath.Child("taints"))...)
- allErrs = append(allErrs, validateSet(device.ConsumesCounter, resource.ResourceSliceMaxDeviceCounterConsumptions,
+ allErrs = append(allErrs, validateSet(device.ConsumesCounters, -1,
validateDeviceCounterConsumption,
func(deviceCapacityConsumption resource.DeviceCounterConsumption) (string, string) {
- return deviceCapacityConsumption.SharedCounter, "sharedCounter"
- }, fldPath.Child("consumesCounter"))...)
+ return deviceCapacityConsumption.CounterSet, "counterSet"
+ }, fldPath.Child("consumesCounters"))...)
- for i, deviceCounterConsumption := range device.ConsumesCounter {
- if capacityNames, exists := sharedCounterToCounterNames[deviceCounterConsumption.SharedCounter]; exists {
+ var countersLength int
+ for _, set := range device.ConsumesCounters {
+ countersLength += len(set.Counters)
+ }
+ if countersLength > resource.ResourceSliceMaxCountersPerDevice {
+ allErrs = append(allErrs, field.Invalid(fldPath, countersLength, fmt.Sprintf("the total number of counters must not exceed %d", resource.ResourceSliceMaxCountersPerDevice)))
+ }
+
+ for i, deviceCounterConsumption := range device.ConsumesCounters {
+ if capacityNames, exists := sharedCounterToCounterNames[deviceCounterConsumption.CounterSet]; exists {
for capacityName := range deviceCounterConsumption.Counters {
if !capacityNames.Has(string(capacityName)) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounter").Index(i).Child("counters"),
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounters").Index(i).Child("counters"),
capacityName, "must reference a counter defined in the ResourceSlice sharedCounters"))
}
}
} else {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounter").Index(i).Child("sharedCounter"),
- deviceCounterConsumption.SharedCounter, "must reference a counterSet defined in the ResourceSlice sharedCounters"))
+ allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounters").Index(i).Child("counterSet"),
+ deviceCounterConsumption.CounterSet, "must reference a counterSet defined in the ResourceSlice sharedCounters"))
}
}
@@ -756,20 +788,20 @@ func validateBasicDevice(device resource.BasicDevice, fldPath *field.Path, share
} else if (perDeviceNodeSelection == nil || !*perDeviceNodeSelection) && (device.NodeName != nil || device.NodeSelector != nil || device.AllNodes != nil) {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "`nodeName`, `nodeSelector` and `allNodes` can only be set if `perDeviceNodeSelection` is set to true in the ResourceSlice spec"))
}
-
return allErrs
}
func validateDeviceCounterConsumption(deviceCounterConsumption resource.DeviceCounterConsumption, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
- if len(deviceCounterConsumption.SharedCounter) == 0 {
- allErrs = append(allErrs, field.Required(fldPath.Child("sharedCounter"), ""))
+ if len(deviceCounterConsumption.CounterSet) == 0 {
+ allErrs = append(allErrs, field.Required(fldPath.Child("counterSet"), ""))
}
- if deviceCounterConsumption.Counters == nil {
+ if len(deviceCounterConsumption.Counters) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("counters"), ""))
} else {
- allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, resource.ResourceSliceMaxDeviceCounterConsumptionCounters, attributeAndCapacityMaxKeyLength,
+ // The size limit is enforced for the entire device.
+ allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, -1, validation.DNS1123LabelMaxLength,
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
}
return allErrs
diff --git a/pkg/apis/resource/validation/validation_resourceclaim_test.go b/pkg/apis/resource/validation/validation_resourceclaim_test.go
index 69745829b08..53a6b0db589 100644
--- a/pkg/apis/resource/validation/validation_resourceclaim_test.go
+++ b/pkg/apis/resource/validation/validation_resourceclaim_test.go
@@ -59,10 +59,12 @@ var (
validClaimSpec = resource.ResourceClaimSpec{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{{
- Name: goodName,
- DeviceClassName: goodName,
- AllocationMode: resource.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: goodName,
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: goodName,
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
}},
},
}
@@ -238,18 +240,18 @@ func TestValidateClaim(t *testing.T) {
}(),
},
"bad-classname": {
- wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
+ wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests[0].DeviceClassName = badName
+ claim.Spec.Devices.Requests[0].Exactly.DeviceClassName = badName
return claim
}(),
},
- "missing-classname-and-firstavailable": {
- wantFailures: field.ErrorList{field.Required(field.NewPath("spec", "devices", "requests").Index(0), "exactly one of `deviceClassName` or `firstAvailable` must be specified")},
+ "missing-classname": {
+ wantFailures: field.ErrorList{field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "deviceClassName"), "")},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests[0].DeviceClassName = ""
+ claim.Spec.Devices.Requests[0].Exactly.DeviceClassName = ""
return claim
}(),
},
@@ -382,9 +384,9 @@ func TestValidateClaim(t *testing.T) {
},
"allocation-mode": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices", "requests").Index(2).Child("count"), int64(-1), "must be greater than zero"),
- field.NotSupported(field.NewPath("spec", "devices", "requests").Index(3).Child("allocationMode"), resource.DeviceAllocationMode("other"), []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}),
- field.Invalid(field.NewPath("spec", "devices", "requests").Index(4).Child("count"), int64(2), "must not be specified when allocationMode is 'All'"),
+ field.Invalid(field.NewPath("spec", "devices", "requests").Index(2).Child("exactly", "count"), int64(-1), "must be greater than zero"),
+ field.NotSupported(field.NewPath("spec", "devices", "requests").Index(3).Child("exactly", "allocationMode"), resource.DeviceAllocationMode("other"), []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}),
+ field.Invalid(field.NewPath("spec", "devices", "requests").Index(4).Child("exactly", "count"), int64(2), "must not be specified when allocationMode is 'All'"),
field.Duplicate(field.NewPath("spec", "devices", "requests").Index(5).Child("name"), "foo"),
},
claim: func() *resource.ResourceClaim {
@@ -392,30 +394,30 @@ func TestValidateClaim(t *testing.T) {
goodReq := &claim.Spec.Devices.Requests[0]
goodReq.Name = "foo"
- goodReq.AllocationMode = resource.DeviceAllocationModeExactCount
- goodReq.Count = 1
+ goodReq.Exactly.AllocationMode = resource.DeviceAllocationModeExactCount
+ goodReq.Exactly.Count = 1
req := goodReq.DeepCopy()
req.Name += "2"
- req.AllocationMode = resource.DeviceAllocationModeAll
- req.Count = 0
+ req.Exactly.AllocationMode = resource.DeviceAllocationModeAll
+ req.Exactly.Count = 0
claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *req)
req = goodReq.DeepCopy()
req.Name += "3"
- req.AllocationMode = resource.DeviceAllocationModeExactCount
- req.Count = -1
+ req.Exactly.AllocationMode = resource.DeviceAllocationModeExactCount
+ req.Exactly.Count = -1
claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *req)
req = goodReq.DeepCopy()
req.Name += "4"
- req.AllocationMode = resource.DeviceAllocationMode("other")
+ req.Exactly.AllocationMode = resource.DeviceAllocationMode("other")
claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *req)
req = goodReq.DeepCopy()
req.Name += "5"
- req.AllocationMode = resource.DeviceAllocationModeAll
- req.Count = 2
+ req.Exactly.AllocationMode = resource.DeviceAllocationModeAll
+ req.Exactly.Count = 2
claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *req)
req = goodReq.DeepCopy()
@@ -503,13 +505,13 @@ func TestValidateClaim(t *testing.T) {
},
"CEL-compile-errors": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices", "requests").Index(1).Child("selectors").Index(1).Child("cel", "expression"), `device.attributes[true].someBoolean`, "compilation failed: ERROR: :1:18: found no matching overload for '_[_]' applied to '(map(string, map(string, any)), bool)'\n | device.attributes[true].someBoolean\n | .................^"),
+ field.Invalid(field.NewPath("spec", "devices", "requests").Index(1).Child("exactly", "selectors").Index(1).Child("cel", "expression"), `device.attributes[true].someBoolean`, "compilation failed: ERROR: :1:18: found no matching overload for '_[_]' applied to '(map(string, map(string, any)), bool)'\n | device.attributes[true].someBoolean\n | .................^"),
},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, claim.Spec.Devices.Requests[0])
+ claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *claim.Spec.Devices.Requests[0].DeepCopy())
claim.Spec.Devices.Requests[1].Name += "-2"
- claim.Spec.Devices.Requests[1].Selectors = []resource.DeviceSelector{
+ claim.Spec.Devices.Requests[1].Exactly.Selectors = []resource.DeviceSelector{
{
// Good selector.
CEL: &resource.CELDeviceSelector{
@@ -528,14 +530,14 @@ func TestValidateClaim(t *testing.T) {
},
"CEL-length": {
wantFailures: field.ErrorList{
- field.TooLong(field.NewPath("spec", "devices", "requests").Index(1).Child("selectors").Index(1).Child("cel", "expression"), "" /*unused*/, resource.CELSelectorExpressionMaxLength),
+ field.TooLong(field.NewPath("spec", "devices", "requests").Index(1).Child("exactly", "selectors").Index(1).Child("cel", "expression"), "" /*unused*/, resource.CELSelectorExpressionMaxLength),
},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, claim.Spec.Devices.Requests[0])
+ claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *claim.Spec.Devices.Requests[0].DeepCopy())
claim.Spec.Devices.Requests[1].Name += "-2"
expression := `device.driver == ""`
- claim.Spec.Devices.Requests[1].Selectors = []resource.DeviceSelector{
+ claim.Spec.Devices.Requests[1].Exactly.Selectors = []resource.DeviceSelector{
{
// Good selector.
CEL: &resource.CELDeviceSelector{
@@ -554,11 +556,11 @@ func TestValidateClaim(t *testing.T) {
},
"CEL-cost": {
wantFailures: field.ErrorList{
- field.Forbidden(field.NewPath("spec", "devices", "requests").Index(0).Child("selectors").Index(0).Child("cel", "expression"), "too complex, exceeds cost limit"),
+ field.Forbidden(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "selectors").Index(0).Child("cel", "expression"), "too complex, exceeds cost limit"),
},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests[0].Selectors = []resource.DeviceSelector{
+ claim.Spec.Devices.Requests[0].Exactly.Selectors = []resource.DeviceSelector{
{
CEL: &resource.CELDeviceSelector{
// From https://github.com/kubernetes/kubernetes/blob/50fc400f178d2078d0ca46aee955ee26375fc437/test/integration/apiserver/cel/validatingadmissionpolicy_test.go#L2150.
@@ -576,17 +578,20 @@ func TestValidateClaim(t *testing.T) {
return claim
}(),
},
- "prioritized-list-field-on-parent": {
+ "prioritized-list-both-first-available-and-exactly-set": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices", "requests").Index(0), nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified"),
+ field.Invalid(field.NewPath("spec", "devices", "requests").Index(0), nil, "exactly one of `exactly` or `firstAvailable` is required, but multiple fields are set"),
},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
- claim.Spec.Devices.Requests[0].DeviceClassName = goodName
- claim.Spec.Devices.Requests[0].Selectors = validSelector
- claim.Spec.Devices.Requests[0].AllocationMode = resource.DeviceAllocationModeAll
- claim.Spec.Devices.Requests[0].Count = 2
- claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
+
+ claim.Spec.Devices.Requests[0].Exactly = &resource.ExactDeviceRequest{
+ DeviceClassName: goodName,
+ Selectors: validSelector,
+ AllocationMode: resource.DeviceAllocationModeExactCount,
+ Count: 2,
+ AdminAccess: ptr.To(true),
+ }
return claim
}(),
},
@@ -620,9 +625,7 @@ func TestValidateClaim(t *testing.T) {
},
claim: func() *resource.ResourceClaim {
claim := testClaim(goodName, goodNS, validClaimSpec)
- claim.Spec.Devices.Requests[0].DeviceClassName = ""
- claim.Spec.Devices.Requests[0].AllocationMode = ""
- claim.Spec.Devices.Requests[0].Count = 0
+ claim.Spec.Devices.Requests[0].Exactly = nil
var subRequests []resource.DeviceSubRequest
for i := 0; i <= 8; i++ {
subRequests = append(subRequests, resource.DeviceSubRequest{
@@ -742,7 +745,7 @@ func TestValidateClaim(t *testing.T) {
allErrs = append(allErrs,
field.Required(fldPath.Index(0).Child("operator"), ""),
)
- fldPath = field.NewPath("spec", "devices", "requests").Index(1).Child("tolerations")
+ fldPath = field.NewPath("spec", "devices", "requests").Index(1).Child("exactly", "tolerations")
allErrs = append(allErrs,
field.Required(fldPath.Index(3).Child("operator"), ""),
@@ -765,7 +768,7 @@ func TestValidateClaim(t *testing.T) {
}
claim.Spec.Devices.Requests = append(claim.Spec.Devices.Requests, *validClaimSpec.Devices.Requests[0].DeepCopy())
claim.Spec.Devices.Requests[1].Name += "-other"
- claim.Spec.Devices.Requests[1].Tolerations = []resource.DeviceToleration{
+ claim.Spec.Devices.Requests[1].Exactly.Tolerations = []resource.DeviceToleration{
{
// Minimal valid toleration: match all taints.
Operator: resource.DeviceTolerationOpExists,
@@ -822,12 +825,12 @@ func TestValidateClaimUpdate(t *testing.T) {
"invalid-update": {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), func() resource.ResourceClaimSpec {
spec := validClaim.Spec.DeepCopy()
- spec.Devices.Requests[0].DeviceClassName += "2"
+ spec.Devices.Requests[0].Exactly.DeviceClassName += "2"
return *spec
}(), "field is immutable")},
oldClaim: validClaim,
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
- claim.Spec.Devices.Requests[0].DeviceClassName += "2"
+ claim.Spec.Devices.Requests[0].Exactly.DeviceClassName += "2"
return claim
},
},
diff --git a/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go b/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go
index 2e24d766bbc..07db2676bee 100644
--- a/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go
+++ b/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go
@@ -178,10 +178,10 @@ func TestValidateClaimTemplate(t *testing.T) {
}(),
},
"bad-classname": {
- wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0).Child("deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
+ wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0).Child("exactly", "deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
template: func() *resource.ResourceClaimTemplate {
template := testClaimTemplate(goodName, goodNS, validClaimSpec)
- template.Spec.Spec.Devices.Requests[0].DeviceClassName = badName
+ template.Spec.Spec.Devices.Requests[0].Exactly.DeviceClassName = badName
return template
}(),
},
@@ -189,11 +189,16 @@ func TestValidateClaimTemplate(t *testing.T) {
wantFailures: nil,
template: testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable),
},
- "proritized-list-class-name-on-parent": {
- wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0), nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified")},
+ "prioritized-list-both-first-available-and-exactly-set": {
+ wantFailures: field.ErrorList{
+ field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0), nil, "exactly one of `exactly` or `firstAvailable` is required, but multiple fields are set"),
+ },
template: func() *resource.ResourceClaimTemplate {
template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable)
- template.Spec.Spec.Devices.Requests[0].DeviceClassName = goodName
+ template.Spec.Spec.Devices.Requests[0].Exactly = &resource.ExactDeviceRequest{
+ DeviceClassName: goodName,
+ AllocationMode: resource.DeviceAllocationModeAll,
+ }
return template
}(),
},
@@ -230,12 +235,12 @@ func TestValidateClaimTemplateUpdate(t *testing.T) {
"invalid-update-class": {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), func() resource.ResourceClaimTemplateSpec {
spec := validClaimTemplate.Spec.DeepCopy()
- spec.Spec.Devices.Requests[0].DeviceClassName += "2"
+ spec.Spec.Devices.Requests[0].Exactly.DeviceClassName += "2"
return *spec
}(), "field is immutable")},
oldClaimTemplate: validClaimTemplate,
update: func(template *resource.ResourceClaimTemplate) *resource.ResourceClaimTemplate {
- template.Spec.Spec.Devices.Requests[0].DeviceClassName += "2"
+ template.Spec.Spec.Devices.Requests[0].Exactly.DeviceClassName += "2"
return template
},
},
diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go
index 6a487874f88..99f55d1814d 100644
--- a/pkg/apis/resource/validation/validation_resourceslice_test.go
+++ b/pkg/apis/resource/validation/validation_resourceslice_test.go
@@ -27,6 +27,8 @@ import (
"k8s.io/kubernetes/pkg/apis/core"
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
"k8s.io/utils/ptr"
+
+ _ "k8s.io/kubernetes/pkg/apis/resource/install"
)
func testAttributes() map[resourceapi.QualifiedName]resourceapi.DeviceAttribute {
@@ -56,7 +58,7 @@ func testResourceSlice(name, nodeName, driverName string, numDevices int) *resou
Name: name,
},
Spec: resourceapi.ResourceSliceSpec{
- NodeName: nodeName,
+ NodeName: &nodeName,
Driver: driverName,
Pool: resourceapi.ResourcePool{
Name: nodeName,
@@ -66,11 +68,9 @@ func testResourceSlice(name, nodeName, driverName string, numDevices int) *resou
}
for i := 0; i < numDevices; i++ {
device := resourceapi.Device{
- Name: fmt.Sprintf("device-%d", i),
- Basic: &resourceapi.BasicDevice{
- Attributes: testAttributes(),
- Capacity: testCapacity(),
- },
+ Name: fmt.Sprintf("device-%d", i),
+ Attributes: testAttributes(),
+ Capacity: testCapacity(),
}
slice.Spec.Devices = append(slice.Spec.Devices, device)
}
@@ -273,7 +273,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = ""
+ slice.Spec.NodeName = nil
slice.Spec.NodeSelector = &core.NodeSelector{}
return slice
}(),
@@ -282,7 +282,7 @@ func TestValidateResourceSlice(t *testing.T) {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), "{`nodeName`, `nodeSelector`}", "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required, but multiple fields are set")},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = "worker"
+ slice.Spec.NodeName = ptr.To("worker")
slice.Spec.NodeSelector = &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{{MatchFields: []core.NodeSelectorRequirement{{Key: "metadata.name", Operator: core.NodeSelectorOpIn, Values: []string{"worker"}}}}},
}
@@ -293,8 +293,8 @@ func TestValidateResourceSlice(t *testing.T) {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), "{`nodeName`, `allNodes`}", "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required, but multiple fields are set")},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = "worker"
- slice.Spec.AllNodes = true
+ slice.Spec.NodeName = ptr.To("worker")
+ slice.Spec.AllNodes = ptr.To(true)
return slice
}(),
},
@@ -302,7 +302,7 @@ func TestValidateResourceSlice(t *testing.T) {
wantFailures: field.ErrorList{field.Required(field.NewPath("spec"), "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required")},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = ""
+ slice.Spec.NodeName = nil
return slice
}(),
},
@@ -313,36 +313,34 @@ func TestValidateResourceSlice(t *testing.T) {
"bad-devices": {
wantFailures: field.ErrorList{
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
- field.Required(field.NewPath("spec", "devices").Index(2).Child("basic"), ""),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 3)
slice.Spec.Devices[1].Name = badName
- slice.Spec.Devices[2].Basic = nil
return slice
}(),
},
"bad-attribute": {
wantFailures: field.ErrorList{
- field.TypeInvalid(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
- field.Required(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(badName), "exactly one value must be specified"),
- field.Invalid(field.NewPath("spec", "devices").Index(2).Child("basic", "attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified"),
- field.Invalid(field.NewPath("spec", "devices").Index(3).Child("basic", "attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"),
- field.TooLongMaxLength(field.NewPath("spec", "devices").Index(3).Child("basic", "attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength),
- field.TooLongMaxLength(field.NewPath("spec", "devices").Index(4).Child("basic", "attributes").Key(goodName).Child("string"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength),
+ field.TypeInvalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
+ field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "exactly one value must be specified"),
+ field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified"),
+ field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"),
+ field.TooLongMaxLength(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength),
+ field.TooLongMaxLength(field.NewPath("spec", "devices").Index(4).Child("attributes").Key(goodName).Child("string"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 5)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(badName): {},
}
- slice.Spec.Devices[2].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[2].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(goodName): {StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")},
}
- slice.Spec.Devices[3].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[3].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(goodName): {VersionValue: ptr.To(strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1))},
}
- slice.Spec.Devices[4].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[4].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(goodName): {StringValue: ptr.To(strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1))},
}
return slice
@@ -351,7 +349,7 @@ func TestValidateResourceSlice(t *testing.T) {
"good-attribute-names": {
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(strings.Repeat("x", resourceapi.DeviceMaxIDLength)): {StringValue: ptr.To("y")},
resourceapi.QualifiedName(strings.Repeat("x", resourceapi.DeviceMaxDomainLength) + "/" + strings.Repeat("y", resourceapi.DeviceMaxIDLength)): {StringValue: ptr.To("z")},
}
@@ -360,12 +358,12 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-c-identifier": {
wantFailures: field.ErrorList{
- field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
- field.TypeInvalid(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
+ field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
+ field.TypeInvalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)): {StringValue: ptr.To("y")},
}
return slice
@@ -373,12 +371,12 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-domain": {
wantFailures: field.ErrorList{
- field.TooLong(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
- field.Invalid(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
+ field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
+ field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1) + "/y"): {StringValue: ptr.To("z")},
}
return slice
@@ -386,12 +384,12 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-key-too-long": {
wantFailures: field.ErrorList{
- field.TooLong(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
- field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
+ field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
+ field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName(strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1) + "/" + strings.Repeat("y", resourceapi.DeviceMaxIDLength+1)): {StringValue: ptr.To("z")},
}
return slice
@@ -399,38 +397,39 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-empty-domain-and-c-identifier": {
wantFailures: field.ErrorList{
- field.Required(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key("/"), "the domain must not be empty"),
- field.Required(field.NewPath("spec", "devices").Index(1).Child("basic", "attributes").Key("/"), "the name must not be empty"),
+ field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "the domain must not be empty"),
+ field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "the name must not be empty"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
resourceapi.QualifiedName("/"): {StringValue: ptr.To("z")},
}
return slice
}(),
},
- "combined-attributes-and-capacity-length": {
+ "combined-attributes-capacity-length": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices").Index(2).Child("basic"), resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice+1, fmt.Sprintf("the total number of attributes and capacities must not exceed %d", resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice)),
+ field.Invalid(field.NewPath("spec", "devices").Index(3), resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice+1, fmt.Sprintf("the total number of attributes and capacities must not exceed %d", resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice)),
},
slice: func() *resourceapi.ResourceSlice {
- slice := testResourceSlice(goodName, goodName, goodName, 3)
- slice.Spec.Devices[0].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}
- slice.Spec.Devices[0].Basic.Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{}
+ slice := testResourceSlice(goodName, goodName, goodName, 5)
+ slice.Spec.Devices[0].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}
+ slice.Spec.Devices[0].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{}
for i := 0; i < resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice; i++ {
- slice.Spec.Devices[0].Basic.Attributes[resourceapi.QualifiedName(fmt.Sprintf("attr_%d", i))] = resourceapi.DeviceAttribute{StringValue: ptr.To("x")}
+ slice.Spec.Devices[0].Attributes[resourceapi.QualifiedName(fmt.Sprintf("attr_%d", i))] = resourceapi.DeviceAttribute{StringValue: ptr.To("x")}
}
- slice.Spec.Devices[1].Basic.Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}
- slice.Spec.Devices[1].Basic.Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{}
+ slice.Spec.Devices[1].Attributes = map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}
+ slice.Spec.Devices[1].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{}
quantity := resource.MustParse("1Gi")
capacity := resourceapi.DeviceCapacity{Value: quantity}
for i := 0; i < resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice; i++ {
- slice.Spec.Devices[1].Basic.Capacity[resourceapi.QualifiedName(fmt.Sprintf("cap_%d", i))] = capacity
+ slice.Spec.Devices[1].Capacity[resourceapi.QualifiedName(fmt.Sprintf("cap_%d", i))] = capacity
}
+
// Too large together by one.
- slice.Spec.Devices[2].Basic.Attributes = slice.Spec.Devices[0].Basic.Attributes
- slice.Spec.Devices[2].Basic.Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{
+ slice.Spec.Devices[3].Attributes = slice.Spec.Devices[0].Attributes
+ slice.Spec.Devices[3].Capacity = map[resourceapi.QualifiedName]resourceapi.DeviceCapacity{
"cap": capacity,
}
return slice
@@ -440,7 +439,7 @@ func TestValidateResourceSlice(t *testing.T) {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "nodeSelector", "nodeSelectorTerms").Index(0).Child("matchExpressions").Index(0).Child("values").Index(0), "-1", "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')")},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 3)
- slice.Spec.NodeName = ""
+ slice.Spec.NodeName = nil
slice.Spec.NodeSelector = &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{{
MatchExpressions: []core.NodeSelectorRequirement{{
@@ -455,7 +454,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
"taints": {
wantFailures: func() field.ErrorList {
- fldPath := field.NewPath("spec", "devices").Index(0).Child("basic", "taints")
+ fldPath := field.NewPath("spec", "devices").Index(0).Child("taints")
return field.ErrorList{
field.Invalid(fldPath.Index(2).Child("key"), "", "name part must be non-empty"),
field.Invalid(fldPath.Index(2).Child("key"), "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"),
@@ -468,7 +467,7 @@ func TestValidateResourceSlice(t *testing.T) {
}(),
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 3)
- slice.Spec.Devices[0].Basic.Taints = []resourceapi.DeviceTaint{
+ slice.Spec.Devices[0].Taints = []resourceapi.DeviceTaint{
{
// Minimal valid taint.
Key: "example.com/taint",
@@ -494,14 +493,29 @@ func TestValidateResourceSlice(t *testing.T) {
return slice
}(),
},
- "bad-PerDeviceNodeSelection": {
+ "too-many-taints": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec"), "{`nodeName`, `perDeviceNodeSelection`}", "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required, but multiple fields are set"),
- field.Required(field.NewPath("spec", "devices").Index(0).Child("basic"), "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
+ field.TooMany(field.NewPath("spec", "devices").Index(0).Child("taints"), resourceapi.DeviceTaintsMaxLength+1, resourceapi.DeviceTaintsMaxLength),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = "worker"
+ for i := 0; i < resourceapi.DeviceTaintsMaxLength+1; i++ {
+ slice.Spec.Devices[0].Taints = append(slice.Spec.Devices[0].Taints, resourceapi.DeviceTaint{
+ Key: "example.com/taint",
+ Effect: resourceapi.DeviceTaintEffectNoExecute,
+ })
+ }
+ return slice
+ }(),
+ },
+ "bad-PerDeviceNodeSelection": {
+ wantFailures: field.ErrorList{
+ field.Invalid(field.NewPath("spec"), "{`nodeName`, `perDeviceNodeSelection`}", "exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required, but multiple fields are set"),
+ field.Required(field.NewPath("spec", "devices").Index(0), "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
+ },
+ slice: func() *resourceapi.ResourceSlice {
+ slice := testResourceSlice(goodName, goodName, driverName, 1)
+ slice.Spec.NodeName = ptr.To("worker")
slice.Spec.PerDeviceNodeSelection = func() *bool {
r := true
return &r
@@ -515,7 +529,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.NodeName = "worker"
+ slice.Spec.NodeName = ptr.To("worker")
slice.Spec.PerDeviceNodeSelection = func() *bool {
r := false
return &r
@@ -523,11 +537,39 @@ func TestValidateResourceSlice(t *testing.T) {
return slice
}(),
},
+ "invalid-false-AllNodes": {
+ wantFailures: field.ErrorList{
+ field.Invalid(field.NewPath("spec", "allNodes"), false, "must be either unset or set to true"),
+ },
+ slice: func() *resourceapi.ResourceSlice {
+ slice := testResourceSlice(goodName, goodName, driverName, 1)
+ slice.Spec.NodeName = ptr.To("worker")
+ slice.Spec.AllNodes = func() *bool {
+ r := false
+ return &r
+ }()
+ return slice
+ }(),
+ },
+ "invalid-empty-NodeName": {
+ wantFailures: field.ErrorList{
+ field.Invalid(field.NewPath("spec", "nodeName"), "", "must be either unset or set to a non-empty string"),
+ },
+ slice: func() *resourceapi.ResourceSlice {
+ slice := testResourceSlice(goodName, goodName, driverName, 1)
+ slice.Spec.NodeName = ptr.To("")
+ slice.Spec.AllNodes = func() *bool {
+ r := true
+ return &r
+ }()
+ return slice
+ }(),
+ },
"invalid-node-selector-in-basicdevice": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic", "nodeName"), "", "must not be empty"),
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic", "allNodes"), false, "must be either unset or set to true"),
- field.Required(field.NewPath("spec", "devices").Index(0).Child("basic"), "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
+ field.Invalid(field.NewPath("spec", "devices").Index(0).Child("nodeName"), "", "must not be empty"),
+ field.Invalid(field.NewPath("spec", "devices").Index(0).Child("allNodes"), false, "must be either unset or set to true"),
+ field.Required(field.NewPath("spec", "devices").Index(0), "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
@@ -535,12 +577,12 @@ func TestValidateResourceSlice(t *testing.T) {
r := true
return &r
}()
- slice.Spec.NodeName = ""
- slice.Spec.Devices[0].Basic.NodeName = func() *string {
+ slice.Spec.NodeName = nil
+ slice.Spec.Devices[0].NodeName = func() *string {
r := ""
return &r
}()
- slice.Spec.Devices[0].Basic.AllNodes = func() *bool {
+ slice.Spec.Devices[0].AllNodes = func() *bool {
r := false
return &r
}()
@@ -549,7 +591,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-node-selector-in-basicdevice": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic"), "{`nodeName`, `allNodes`}", "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
+ field.Invalid(field.NewPath("spec", "devices").Index(0), "{`nodeName`, `allNodes`}", "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required when `perDeviceNodeSelection` is set to true in the ResourceSlice spec"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
@@ -557,12 +599,12 @@ func TestValidateResourceSlice(t *testing.T) {
r := true
return &r
}()
- slice.Spec.NodeName = ""
- slice.Spec.Devices[0].Basic.NodeName = func() *string {
+ slice.Spec.NodeName = nil
+ slice.Spec.Devices[0].NodeName = func() *string {
r := "worker"
return &r
}()
- slice.Spec.Devices[0].Basic.AllNodes = func() *bool {
+ slice.Spec.Devices[0].AllNodes = func() *bool {
r := true
return &r
}()
@@ -636,7 +678,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
"too-large-shared-counters": {
wantFailures: field.ErrorList{
- field.TooMany(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxSharedCounters+1, resourceapi.ResourceSliceMaxSharedCounters),
+ field.Invalid(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxSharedCounters+1, fmt.Sprintf("the total number of shared counters must not exceed %d", resourceapi.ResourceSliceMaxSharedCounters)),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
@@ -644,15 +686,15 @@ func TestValidateResourceSlice(t *testing.T) {
return slice
}(),
},
- "missing-sharedcounter-consumes-counter": {
+ "missing-counterset-consumes-counter": {
wantFailures: field.ErrorList{
- field.Required(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter").Index(0).Child("sharedCounter"), ""),
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter").Index(0).Child("sharedCounter"), "", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
+ field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""),
+ field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
slice.Spec.SharedCounters = createSharedCounters(1)
- slice.Spec.Devices[0].Basic.ConsumesCounter = []resourceapi.DeviceCounterConsumption{
+ slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
{
Counters: testCounters(),
},
@@ -662,14 +704,14 @@ func TestValidateResourceSlice(t *testing.T) {
},
"missing-counter-consumes-counter": {
wantFailures: field.ErrorList{
- field.Required(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter").Index(0).Child("counters"), ""),
+ field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), ""),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
slice.Spec.SharedCounters = createSharedCounters(1)
- slice.Spec.Devices[0].Basic.ConsumesCounter = []resourceapi.DeviceCounterConsumption{
+ slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
{
- SharedCounter: "sharedcounters-0",
+ CounterSet: "counterset-0",
},
}
return slice
@@ -677,17 +719,17 @@ func TestValidateResourceSlice(t *testing.T) {
},
"wrong-counterref-consumes-counter": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter").Index(0).Child("counters"), "fake", "must reference a counter defined in the ResourceSlice sharedCounters"),
+ field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "fake", "must reference a counter defined in the ResourceSlice sharedCounters"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
slice.Spec.SharedCounters = createSharedCounters(1)
- slice.Spec.Devices[0].Basic.ConsumesCounter = []resourceapi.DeviceCounterConsumption{
+ slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
{
Counters: map[string]resourceapi.Counter{
"fake": {Value: resource.MustParse("1Gi")},
},
- SharedCounter: "sharedcounters-0",
+ CounterSet: "counterset-0",
},
}
return slice
@@ -695,15 +737,15 @@ func TestValidateResourceSlice(t *testing.T) {
},
"wrong-sharedcounterref-consumes-counter": {
wantFailures: field.ErrorList{
- field.Invalid(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter").Index(0).Child("sharedCounter"), "fake", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
+ field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "fake", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
slice.Spec.SharedCounters = createSharedCounters(1)
- slice.Spec.Devices[0].Basic.ConsumesCounter = []resourceapi.DeviceCounterConsumption{
+ slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
{
- Counters: testCounters(),
- SharedCounter: "fake",
+ Counters: testCounters(),
+ CounterSet: "fake",
},
}
return slice
@@ -711,13 +753,29 @@ func TestValidateResourceSlice(t *testing.T) {
},
"too-large-consumes-counter": {
wantFailures: field.ErrorList{
- field.TooMany(field.NewPath("spec", "devices").Index(0).Child("basic", "consumesCounter"), resourceapi.ResourceSliceMaxDeviceCounterConsumptions+1, resourceapi.ResourceSliceMaxSharedCounters),
- field.TooMany(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxDeviceCounterConsumptions+1, resourceapi.ResourceSliceMaxSharedCounters),
+ field.Invalid(field.NewPath("spec", "devices").Index(0), resourceapi.ResourceSliceMaxCountersPerDevice+1, fmt.Sprintf("the total number of counters must not exceed %d", resourceapi.ResourceSliceMaxCountersPerDevice)),
+ field.Invalid(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxSharedCounters+1, fmt.Sprintf("the total number of shared counters must not exceed %d", resourceapi.ResourceSliceMaxSharedCounters)),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)
- slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxDeviceCounterConsumptions + 1)
- slice.Spec.Devices[0].Basic.ConsumesCounter = createConsumesCounter(resourceapi.ResourceSliceMaxDeviceCounterConsumptions + 1)
+ slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxSharedCounters + 1)
+ slice.Spec.Devices[0].Attributes = nil
+ slice.Spec.Devices[0].Capacity = nil
+ slice.Spec.Devices[0].ConsumesCounters = createConsumesCounters(resourceapi.ResourceSliceMaxCountersPerDevice + 1)
+ return slice
+ }(),
+ },
+ "too-many-device-counters-in-slice": {
+ wantFailures: field.ErrorList{
+ field.Invalid(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDeviceCountersPerSlice+1, fmt.Sprintf("the total number of counters in devices must not exceed %d", resourceapi.ResourceSliceMaxDeviceCountersPerSlice)),
+ },
+ slice: func() *resourceapi.ResourceSlice {
+ slice := testResourceSlice(goodName, goodName, driverName, 65)
+ slice.Spec.SharedCounters = createSharedCounters(16)
+ for i := 0; i < 64; i++ {
+ slice.Spec.Devices[i].ConsumesCounters = createConsumesCounters(16)
+ }
+ slice.Spec.Devices[64].ConsumesCounters = createConsumesCounters(1)
return slice
}(),
},
@@ -753,10 +811,10 @@ func TestValidateResourceSliceUpdate(t *testing.T) {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), name+"-update", "field is immutable")},
},
"invalid-update-nodename": {
- wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "nodeName"), name+"-updated", "field is immutable")},
+ wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "nodeName"), ptr.To(name+"-updated"), "field is immutable")},
oldResourceSlice: validResourceSlice,
update: func(slice *resourceapi.ResourceSlice) *resourceapi.ResourceSlice {
- slice.Spec.NodeName += "-updated"
+ slice.Spec.NodeName = ptr.To(*slice.Spec.NodeName + "-updated")
return slice
},
},
@@ -780,7 +838,7 @@ func TestValidateResourceSliceUpdate(t *testing.T) {
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "nodeSelector", "nodeSelectorTerms").Index(0).Child("matchExpressions").Index(0).Child("values").Index(0), "-1", "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')")},
oldResourceSlice: func() *resourceapi.ResourceSlice {
slice := validResourceSlice.DeepCopy()
- slice.Spec.NodeName = ""
+ slice.Spec.NodeName = nil
slice.Spec.NodeSelector = &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{{
MatchExpressions: []core.NodeSelectorRequirement{{
@@ -820,19 +878,19 @@ func createSharedCounters(count int) []resourceapi.CounterSet {
sharedCounters := make([]resourceapi.CounterSet, count)
for i := 0; i < count; i++ {
sharedCounters[i] = resourceapi.CounterSet{
- Name: fmt.Sprintf("sharedcounters-%d", i),
+ Name: fmt.Sprintf("counterset-%d", i),
Counters: testCounters(),
}
}
return sharedCounters
}
-func createConsumesCounter(count int) []resourceapi.DeviceCounterConsumption {
+func createConsumesCounters(count int) []resourceapi.DeviceCounterConsumption {
consumeCapacity := make([]resourceapi.DeviceCounterConsumption, count)
for i := 0; i < count; i++ {
consumeCapacity[i] = resourceapi.DeviceCounterConsumption{
- SharedCounter: fmt.Sprintf("sharedcounters-%d", i),
- Counters: testCounters(),
+ CounterSet: fmt.Sprintf("counterset-%d", i),
+ Counters: testCounters(),
}
}
return consumeCapacity
diff --git a/pkg/controlplane/instance.go b/pkg/controlplane/instance.go
index dbfb35d7334..83a8cf61db4 100644
--- a/pkg/controlplane/instance.go
+++ b/pkg/controlplane/instance.go
@@ -52,6 +52,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
+ resourcev1beta2 "k8s.io/api/resource/v1beta2"
schedulingapiv1 "k8s.io/api/scheduling/v1"
storageapiv1 "k8s.io/api/storage/v1"
storageapiv1alpha1 "k8s.io/api/storage/v1alpha1"
@@ -464,6 +465,7 @@ var (
flowcontrolv1beta3.SchemeGroupVersion,
networkingapiv1beta1.SchemeGroupVersion,
resourcev1beta1.SchemeGroupVersion,
+ resourcev1beta2.SchemeGroupVersion,
}
// alphaAPIGroupVersionsDisabledByDefault holds the alpha APIs we have. They are always disabled by default.
diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go
index 71ffb3acfa5..7e77db0757d 100644
--- a/pkg/printers/internalversion/printers.go
+++ b/pkg/printers/internalversion/printers.go
@@ -40,7 +40,7 @@ import (
flowcontrolv1 "k8s.io/api/flowcontrol/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
- resourceapi "k8s.io/api/resource/v1beta1"
+ resourceapi "k8s.io/api/resource/v1beta2"
schedulingv1 "k8s.io/api/scheduling/v1"
storagev1 "k8s.io/api/storage/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
@@ -3171,7 +3171,11 @@ func printResourceSlice(obj *resource.ResourceSlice, options printers.GenerateOp
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
- row.Cells = append(row.Cells, obj.Name, obj.Spec.NodeName, obj.Spec.Driver, obj.Spec.Pool.Name, translateTimestampSince(obj.CreationTimestamp))
+ nodeName := ""
+ if obj.Spec.NodeName != nil {
+ nodeName = *obj.Spec.NodeName
+ }
+ row.Cells = append(row.Cells, obj.Name, nodeName, obj.Spec.Driver, obj.Spec.Pool.Name, translateTimestampSince(obj.CreationTimestamp))
return []metav1.TableRow{row}, nil
}
diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go
index e5e71ae3fca..99c92ecd3b6 100644
--- a/pkg/printers/internalversion/printers_test.go
+++ b/pkg/printers/internalversion/printers_test.go
@@ -6313,10 +6313,12 @@ func TestPrintResourceClaim(t *testing.T) {
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{
{
- Name: "deviceRequest",
- DeviceClassName: "deviceClass",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClass",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
},
},
},
@@ -6337,10 +6339,12 @@ func TestPrintResourceClaim(t *testing.T) {
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{
{
- Name: "deviceRequest",
- DeviceClassName: "deviceClass",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClass",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
},
},
},
@@ -6360,10 +6364,12 @@ func TestPrintResourceClaim(t *testing.T) {
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{
{
- Name: "deviceRequest",
- DeviceClassName: "deviceClass",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClass",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
},
},
},
@@ -6394,10 +6400,12 @@ func TestPrintResourceClaim(t *testing.T) {
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{
{
- Name: "deviceRequest",
- DeviceClassName: "deviceClass",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClass",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
},
},
},
@@ -6438,10 +6446,12 @@ func TestPrintResourceClaimTemplate(t *testing.T) {
Spec: resourceapis.ResourceClaimSpec{
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{{
- Name: "test-deviceRequest",
- DeviceClassName: "deviceClassName",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "test-deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClassName",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
}},
},
},
@@ -6461,10 +6471,12 @@ func TestPrintResourceClaimTemplate(t *testing.T) {
Spec: resourceapis.ResourceClaimSpec{
Devices: resourceapis.DeviceClaim{
Requests: []resourceapis.DeviceRequest{{
- Name: "test-deviceRequest",
- DeviceClassName: "deviceClassName",
- AllocationMode: resourceapis.DeviceAllocationModeExactCount,
- Count: 1,
+ Name: "test-deviceRequest",
+ Exactly: &resourceapis.ExactDeviceRequest{
+ DeviceClassName: "deviceClassName",
+ AllocationMode: resourceapis.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
}},
},
},
@@ -6502,7 +6514,7 @@ func TestPrintResourceSlice(t *testing.T) {
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
},
Spec: resourceapis.ResourceSliceSpec{
- NodeName: "nodeName",
+ NodeName: ptr.To("nodeName"),
Driver: "driverName",
Pool: resourceapis.ResourcePool{
Name: "poolName",
@@ -6520,7 +6532,7 @@ func TestPrintResourceSlice(t *testing.T) {
CreationTimestamp: metav1.Time{},
},
Spec: resourceapis.ResourceSliceSpec{
- NodeName: "nodeName",
+ NodeName: ptr.To("nodeName"),
Driver: "driverName",
Pool: resourceapis.ResourcePool{
Name: "poolName",
diff --git a/pkg/quota/v1/evaluator/core/resource_claims_test.go b/pkg/quota/v1/evaluator/core/resource_claims_test.go
index 2f137e49edc..a1fc47eeaa7 100644
--- a/pkg/quota/v1/evaluator/core/resource_claims_test.go
+++ b/pkg/quota/v1/evaluator/core/resource_claims_test.go
@@ -40,7 +40,20 @@ func testResourceClaim(name string, namespace string, spec api.ResourceClaimSpec
func TestResourceClaimEvaluatorUsage(t *testing.T) {
classGpu := "gpu"
classTpu := "tpu"
- validClaim := testResourceClaim("foo", "ns", api.ResourceClaimSpec{Devices: api.DeviceClaim{Requests: []api.DeviceRequest{{Name: "req-0", DeviceClassName: classGpu, AllocationMode: api.DeviceAllocationModeExactCount, Count: 1}}}})
+ validClaim := testResourceClaim("foo", "ns", api.ResourceClaimSpec{
+ Devices: api.DeviceClaim{
+ Requests: []api.DeviceRequest{
+ {
+ Name: "req-0",
+ Exactly: &api.ExactDeviceRequest{
+ DeviceClassName: classGpu,
+ AllocationMode: api.DeviceAllocationModeExactCount,
+ Count: 1,
+ },
+ },
+ },
+ },
+ })
validClaimWithPrioritizedList := testResourceClaim("foo", "ns", api.ResourceClaimSpec{
Devices: api.DeviceClaim{
Requests: []api.DeviceRequest{
@@ -88,7 +101,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) {
"count": {
claim: func() *api.ResourceClaim {
claim := validClaim.DeepCopy()
- claim.Spec.Devices.Requests[0].Count = 5
+ claim.Spec.Devices.Requests[0].Exactly.Count = 5
return claim
}(),
usage: corev1.ResourceList{
@@ -99,7 +112,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) {
"all": {
claim: func() *api.ResourceClaim {
claim := validClaim.DeepCopy()
- claim.Spec.Devices.Requests[0].AllocationMode = api.DeviceAllocationModeAll
+ claim.Spec.Devices.Requests[0].Exactly.AllocationMode = api.DeviceAllocationModeAll
return claim
}(),
usage: corev1.ResourceList{
@@ -110,7 +123,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) {
"unknown-count-mode": {
claim: func() *api.ResourceClaim {
claim := validClaim.DeepCopy()
- claim.Spec.Devices.Requests[0].AllocationMode = "future-mode"
+ claim.Spec.Devices.Requests[0].Exactly.AllocationMode = "future-mode"
return claim
}(),
usage: corev1.ResourceList{
@@ -122,7 +135,7 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) {
claim: func() *api.ResourceClaim {
claim := validClaim.DeepCopy()
// Admins are *not* exempt from quota.
- claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
+ claim.Spec.Devices.Requests[0].Exactly.AdminAccess = ptr.To(true)
return claim
}(),
usage: corev1.ResourceList{
diff --git a/pkg/registry/resource/resourceclaim/strategy.go b/pkg/registry/resource/resourceclaim/strategy.go
index 6aff3bec488..8576ae4be21 100644
--- a/pkg/registry/resource/resourceclaim/strategy.go
+++ b/pkg/registry/resource/resourceclaim/strategy.go
@@ -71,6 +71,9 @@ func (*resourceclaimStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpa
"resource.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
+ "resource.k8s.io/v1beta2": fieldpath.NewSet(
+ fieldpath.MakePathOrDie("status"),
+ ),
}
return fields
@@ -145,6 +148,9 @@ func (*resourceclaimStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*f
"resource.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
+ "resource.k8s.io/v1beta2": fieldpath.NewSet(
+ fieldpath.MakePathOrDie("spec"),
+ ),
}
return fields
@@ -253,7 +259,9 @@ func dropDisabledDRAAdminAccessFields(newClaim, oldClaim *resource.ResourceClaim
}
for i := range newClaim.Spec.Devices.Requests {
- newClaim.Spec.Devices.Requests[i].AdminAccess = nil
+ if newClaim.Spec.Devices.Requests[i].Exactly != nil {
+ newClaim.Spec.Devices.Requests[i].Exactly.AdminAccess = nil
+ }
}
if newClaim.Status.Allocation == nil {
@@ -270,7 +278,7 @@ func draAdminAccessFeatureInUse(claim *resource.ResourceClaim) bool {
}
for _, request := range claim.Spec.Devices.Requests {
- if request.AdminAccess != nil {
+ if request.Exactly != nil && request.Exactly.AdminAccess != nil {
return true
}
}
diff --git a/pkg/registry/resource/resourceclaim/strategy_test.go b/pkg/registry/resource/resourceclaim/strategy_test.go
index cefd0716190..ae2c972f129 100644
--- a/pkg/registry/resource/resourceclaim/strategy_test.go
+++ b/pkg/registry/resource/resourceclaim/strategy_test.go
@@ -42,9 +42,11 @@ var obj = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ },
},
},
},
@@ -60,9 +62,11 @@ var objWithStatus = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ },
},
},
},
@@ -92,10 +96,12 @@ var objWithAdminAccess = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
- AdminAccess: ptr.To(true),
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -111,9 +117,11 @@ var objInNonAdminNamespace = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ },
},
},
},
@@ -129,10 +137,12 @@ var objWithAdminAccessInNonAdminNamespace = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
- AdminAccess: ptr.To(true),
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -148,9 +158,11 @@ var objStatusInNonAdminNamespace = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ },
},
},
},
@@ -179,9 +191,12 @@ var objWithAdminAccessStatusInNonAdminNamespace = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -236,10 +251,12 @@ var objWithAdminAccessStatus = &resource.ResourceClaim{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
- AdminAccess: ptr.To(true),
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -277,7 +294,7 @@ var ns2 = &corev1.Namespace{
var adminAccessError = "Forbidden: admin access to devices requires the `resource.k8s.io/admin-access: true` label"
var fieldImmutableError = "field is immutable"
var metadataError = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters"
-var deviceRequestError = "exactly one of `deviceClassName` or `firstAvailable` must be specified"
+var deviceRequestError = "exactly one of `exactly` or `firstAvailable` is required"
const (
testRequest = "test-request"
@@ -727,14 +744,14 @@ func TestStatusStrategyUpdate(t *testing.T) {
"keep-fields-admin-access-because-of-status": {
oldObj: func() *resource.ResourceClaim {
oldObj := objWithAdminAccessStatus.DeepCopy()
- oldObj.Spec.Devices.Requests[0].AdminAccess = ptr.To(false)
+ oldObj.Spec.Devices.Requests[0].Exactly.AdminAccess = ptr.To(false)
return oldObj
}(),
newObj: objWithAdminAccessStatus,
adminAccess: false,
expectObj: func() *resource.ResourceClaim {
oldObj := objWithAdminAccessStatus.DeepCopy()
- oldObj.Spec.Devices.Requests[0].AdminAccess = ptr.To(false)
+ oldObj.Spec.Devices.Requests[0].Exactly.AdminAccess = ptr.To(false)
return oldObj
}(),
verify: func(t *testing.T, as []testclient.Action) {
diff --git a/pkg/registry/resource/resourceclaimtemplate/strategy.go b/pkg/registry/resource/resourceclaimtemplate/strategy.go
index 547d3489c23..57692b463c1 100644
--- a/pkg/registry/resource/resourceclaimtemplate/strategy.go
+++ b/pkg/registry/resource/resourceclaimtemplate/strategy.go
@@ -155,7 +155,9 @@ func dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate *resour
}
for i := range newClaimTemplate.Spec.Spec.Devices.Requests {
- newClaimTemplate.Spec.Spec.Devices.Requests[i].AdminAccess = nil
+ if newClaimTemplate.Spec.Spec.Devices.Requests[i].Exactly != nil {
+ newClaimTemplate.Spec.Spec.Devices.Requests[i].Exactly.AdminAccess = nil
+ }
}
}
@@ -165,7 +167,7 @@ func draAdminAccessFeatureInUse(claimTemplate *resource.ResourceClaimTemplate) b
}
for _, request := range claimTemplate.Spec.Spec.Devices.Requests {
- if request.AdminAccess != nil {
+ if request.Exactly != nil && request.Exactly.AdminAccess != nil {
return true
}
}
diff --git a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go
index 81e260a144d..8048ab30529 100644
--- a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go
+++ b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go
@@ -43,9 +43,11 @@ var obj = &resource.ResourceClaimTemplate{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ },
},
},
},
@@ -63,10 +65,12 @@ var objWithAdminAccess = &resource.ResourceClaimTemplate{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
- AdminAccess: ptr.To(true),
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -84,10 +88,12 @@ var objWithAdminAccessInNonAdminNamespace = &resource.ResourceClaimTemplate{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
- Name: "req-0",
- DeviceClassName: "class",
- AllocationMode: resource.DeviceAllocationModeAll,
- AdminAccess: ptr.To(true),
+ Name: "req-0",
+ Exactly: &resource.ExactDeviceRequest{
+ DeviceClassName: "class",
+ AllocationMode: resource.DeviceAllocationModeAll,
+ AdminAccess: ptr.To(true),
+ },
},
},
},
@@ -136,7 +142,7 @@ var ns2 = &corev1.Namespace{
var adminAccessError = "Forbidden: admin access to devices requires the `resource.k8s.io/admin-access: true` label on the containing namespace"
var fieldImmutableError = "field is immutable"
var metadataError = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters"
-var deviceRequestError = "exactly one of `deviceClassName` or `firstAvailable` must be specified"
+var deviceRequestError = "exactly one of `exactly` or `firstAvailable` is required"
func TestClaimTemplateStrategy(t *testing.T) {
fakeClient := fake.NewSimpleClientset()
@@ -339,7 +345,7 @@ func TestClaimTemplateStrategyUpdate(t *testing.T) {
resourceClaimTemplate := obj.DeepCopy()
newClaimTemplate := resourceClaimTemplate.DeepCopy()
newClaimTemplate.ResourceVersion = "4"
- newClaimTemplate.Spec.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
+ newClaimTemplate.Spec.Spec.Devices.Requests[0].Exactly.AdminAccess = ptr.To(true)
strategy.PrepareForUpdate(ctx, newClaimTemplate, resourceClaimTemplate)
errs := strategy.ValidateUpdate(ctx, newClaimTemplate, resourceClaimTemplate)
diff --git a/pkg/registry/resource/resourceslice/storage/storage_test.go b/pkg/registry/resource/resourceslice/storage/storage_test.go
index 793a836303b..84a5aa53ab3 100644
--- a/pkg/registry/resource/resourceslice/storage/storage_test.go
+++ b/pkg/registry/resource/resourceslice/storage/storage_test.go
@@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/apis/resource"
_ "k8s.io/kubernetes/pkg/apis/resource/install"
"k8s.io/kubernetes/pkg/registry/registrytest"
+ "k8s.io/utils/ptr"
)
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
@@ -52,7 +53,7 @@ func validNewResourceSlice(name string) *resource.ResourceSlice {
Name: name,
},
Spec: resource.ResourceSliceSpec{
- NodeName: name,
+ NodeName: ptr.To(name),
Driver: "cdi.example.com",
Pool: resource.ResourcePool{
Name: "worker-1",
diff --git a/pkg/registry/resource/resourceslice/strategy.go b/pkg/registry/resource/resourceslice/strategy.go
index c6f0a8c2f36..d89d1184cf6 100644
--- a/pkg/registry/resource/resourceslice/strategy.go
+++ b/pkg/registry/resource/resourceslice/strategy.go
@@ -102,7 +102,12 @@ var TriggerFunc = map[string]storage.IndexerFunc{
}
func nodeNameTriggerFunc(obj runtime.Object) string {
- return obj.(*resource.ResourceSlice).Spec.NodeName
+ rs := obj.(*resource.ResourceSlice)
+ if rs.Spec.NodeName == nil {
+ return ""
+ } else {
+ return *rs.Spec.NodeName
+ }
}
// Indexers returns the indexers for ResourceSlice.
@@ -117,7 +122,10 @@ func nodeNameIndexFunc(obj interface{}) ([]string, error) {
if !ok {
return nil, fmt.Errorf("not a ResourceSlice")
}
- return []string{slice.Spec.NodeName}, nil
+ if slice.Spec.NodeName == nil {
+ return []string{""}, nil
+ }
+ return []string{*slice.Spec.NodeName}, nil
}
// GetAttrs returns labels and fields of a given object for filtering purposes.
@@ -147,7 +155,11 @@ func toSelectableFields(slice *resource.ResourceSlice) fields.Set {
// field here or the number of object-meta related fields changes, this should
// be adjusted.
fields := make(fields.Set, 3)
- fields[resource.ResourceSliceSelectorNodeName] = slice.Spec.NodeName
+ if slice.Spec.NodeName == nil {
+ fields[resource.ResourceSliceSelectorNodeName] = ""
+ } else {
+ fields[resource.ResourceSliceSelectorNodeName] = *slice.Spec.NodeName
+ }
fields[resource.ResourceSliceSelectorDriver] = slice.Spec.Driver
// Adds one field.
@@ -165,10 +177,8 @@ func dropDisabledDRADeviceTaintsFields(newSlice, oldSlice *resource.ResourceSlic
return
}
- for _, device := range newSlice.Spec.Devices {
- if device.Basic != nil {
- device.Basic.Taints = nil
- }
+ for i := range newSlice.Spec.Devices {
+ newSlice.Spec.Devices[i].Taints = nil
}
}
@@ -178,7 +188,7 @@ func draDeviceTaintsFeatureInUse(slice *resource.ResourceSlice) bool {
}
for _, device := range slice.Spec.Devices {
- if device.Basic != nil && len(device.Basic.Taints) > 0 {
+ if len(device.Taints) > 0 {
return true
}
}
@@ -192,14 +202,11 @@ func dropDisabledDRAPartitionableDevicesFields(newSlice, oldSlice *resource.Reso
newSlice.Spec.SharedCounters = nil
newSlice.Spec.PerDeviceNodeSelection = nil
- for _, device := range newSlice.Spec.Devices {
- if device.Basic != nil {
- device.Basic.ConsumesCounter = nil
- device.Basic.NodeName = nil
- device.Basic.NodeSelector = nil
- device.Basic.AllNodes = nil
- }
-
+ for i := range newSlice.Spec.Devices {
+ newSlice.Spec.Devices[i].ConsumesCounters = nil
+ newSlice.Spec.Devices[i].NodeName = nil
+ newSlice.Spec.Devices[i].NodeSelector = nil
+ newSlice.Spec.Devices[i].AllNodes = nil
}
}
@@ -214,15 +221,12 @@ func draPartitionableDevicesFeatureInUse(slice *resource.ResourceSlice) bool {
}
for _, device := range spec.Devices {
- if device.Basic != nil {
- if len(device.Basic.ConsumesCounter) > 0 {
- return true
- }
- if device.Basic.NodeName != nil || device.Basic.NodeSelector != nil || device.Basic.AllNodes != nil {
- return true
- }
+ if len(device.ConsumesCounters) > 0 {
+ return true
+ }
+ if device.NodeName != nil || device.NodeSelector != nil || device.AllNodes != nil {
+ return true
}
-
}
return false
}
diff --git a/pkg/registry/resource/resourceslice/strategy_test.go b/pkg/registry/resource/resourceslice/strategy_test.go
index e84bb54dbc1..39522081b6c 100644
--- a/pkg/registry/resource/resourceslice/strategy_test.go
+++ b/pkg/registry/resource/resourceslice/strategy_test.go
@@ -28,6 +28,7 @@ import (
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/resource"
"k8s.io/kubernetes/pkg/features"
+ "k8s.io/utils/ptr"
)
var slice = &resource.ResourceSlice{
@@ -35,22 +36,21 @@ var slice = &resource.ResourceSlice{
Name: "valid-resource-slice",
},
Spec: resource.ResourceSliceSpec{
- NodeName: "valid-node-name",
+ NodeName: ptr.To("valid-node-name"),
Driver: "testdriver.example.com",
Pool: resource.ResourcePool{
Name: "valid-pool-name",
ResourceSliceCount: 1,
},
Devices: []resource.Device{{
- Name: "device-0",
- Basic: &resource.BasicDevice{},
+ Name: "device-0",
}},
},
}
var sliceWithDeviceTaints = func() *resource.ResourceSlice {
slice := slice.DeepCopy()
- slice.Spec.Devices[0].Basic.Taints = []resource.DeviceTaint{{
+ slice.Spec.Devices[0].Taints = []resource.DeviceTaint{{
Key: "example.com/tainted",
Effect: resource.DeviceTaintEffectNoSchedule,
}}
@@ -85,33 +85,31 @@ var sliceWithPartitionableDevices = &resource.ResourceSlice{
Devices: []resource.Device{
{
Name: "device",
- Basic: &resource.BasicDevice{
- ConsumesCounter: []resource.DeviceCounterConsumption{
- {
- SharedCounter: "pool-1",
- Counters: map[string]resource.Counter{
- "memory": {
- Value: k8sresource.MustParse("40Gi"),
- },
+ ConsumesCounters: []resource.DeviceCounterConsumption{
+ {
+ CounterSet: "pool-1",
+ Counters: map[string]resource.Counter{
+ "memory": {
+ Value: k8sresource.MustParse("40Gi"),
},
},
},
- NodeName: func() *string {
- r := "valid-node-name"
- return &r
- }(),
- Attributes: map[resource.QualifiedName]resource.DeviceAttribute{
- resource.QualifiedName("version"): {
- StringValue: func() *string {
- v := "v1"
- return &v
- }(),
- },
+ },
+ NodeName: func() *string {
+ r := "valid-node-name"
+ return &r
+ }(),
+ Attributes: map[resource.QualifiedName]resource.DeviceAttribute{
+ resource.QualifiedName("version"): {
+ StringValue: func() *string {
+ v := "v1"
+ return &v
+ }(),
},
- Capacity: map[resource.QualifiedName]resource.DeviceCapacity{
- resource.QualifiedName("memory"): {
- Value: k8sresource.MustParse("40Gi"),
- },
+ },
+ Capacity: map[resource.QualifiedName]resource.DeviceCapacity{
+ resource.QualifiedName("memory"): {
+ Value: k8sresource.MustParse("40Gi"),
},
},
},
@@ -178,7 +176,7 @@ func TestResourceSliceStrategyCreate(t *testing.T) {
r := false
return &r
}()
- obj.Spec.NodeName = "valid-node-name"
+ obj.Spec.NodeName = ptr.To("valid-node-name")
return obj
}(),
partitionableDevices: false,
@@ -187,11 +185,11 @@ func TestResourceSliceStrategyCreate(t *testing.T) {
obj.ObjectMeta.Generation = 1
obj.Spec.SharedCounters = nil
obj.Spec.PerDeviceNodeSelection = nil
- obj.Spec.NodeName = "valid-node-name"
- obj.Spec.Devices[0].Basic.NodeName = nil
- obj.Spec.Devices[0].Basic.NodeSelector = nil
- obj.Spec.Devices[0].Basic.AllNodes = nil
- obj.Spec.Devices[0].Basic.ConsumesCounter = nil
+ obj.Spec.NodeName = ptr.To("valid-node-name")
+ obj.Spec.Devices[0].NodeName = nil
+ obj.Spec.Devices[0].NodeSelector = nil
+ obj.Spec.Devices[0].AllNodes = nil
+ obj.Spec.Devices[0].ConsumesCounters = nil
return obj
}(),
},
@@ -337,7 +335,7 @@ func TestResourceSliceStrategyUpdate(t *testing.T) {
r := false
return &r
}()
- obj.Spec.NodeName = "valid-node-name"
+ obj.Spec.NodeName = ptr.To("valid-node-name")
return obj
}(),
partitionableDevices: false,
@@ -347,9 +345,9 @@ func TestResourceSliceStrategyUpdate(t *testing.T) {
obj.Generation = 1
obj.Spec.SharedCounters = nil
obj.Spec.PerDeviceNodeSelection = nil
- obj.Spec.NodeName = "valid-node-name"
- obj.Spec.Devices[0].Basic.ConsumesCounter = nil
- obj.Spec.Devices[0].Basic.NodeName = nil
+ obj.Spec.NodeName = ptr.To("valid-node-name")
+ obj.Spec.Devices[0].ConsumesCounters = nil
+ obj.Spec.Devices[0].NodeName = nil
return obj
}(),
},
@@ -368,9 +366,9 @@ func TestResourceSliceStrategyUpdate(t *testing.T) {
newObj: func() *resource.ResourceSlice {
obj := sliceWithPartitionableDevices.DeepCopy()
obj.ResourceVersion = "4"
- obj.Spec.NodeName = "valid-node-name"
+ obj.Spec.NodeName = ptr.To("valid-node-name")
obj.Spec.PerDeviceNodeSelection = nil
- obj.Spec.Devices[0].Basic.NodeName = nil
+ obj.Spec.Devices[0].NodeName = nil
return obj
}(),
partitionableDevices: true,
@@ -378,9 +376,9 @@ func TestResourceSliceStrategyUpdate(t *testing.T) {
obj := sliceWithPartitionableDevices.DeepCopy()
obj.ResourceVersion = "4"
obj.Generation = 1
- obj.Spec.NodeName = "valid-node-name"
+ obj.Spec.NodeName = ptr.To("valid-node-name")
obj.Spec.PerDeviceNodeSelection = nil
- obj.Spec.Devices[0].Basic.NodeName = nil
+ obj.Spec.Devices[0].NodeName = nil
return obj
}(),
},
diff --git a/pkg/registry/resource/rest/storage_resource.go b/pkg/registry/resource/rest/storage_resource.go
index 1dff5ad9f6d..8a855e24937 100644
--- a/pkg/registry/resource/rest/storage_resource.go
+++ b/pkg/registry/resource/rest/storage_resource.go
@@ -19,11 +19,12 @@ package rest
import (
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
+ resourcev1beta2 "k8s.io/api/resource/v1beta2"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
- "k8s.io/client-go/kubernetes/typed/core/v1"
+ v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/resource"
deviceclassstore "k8s.io/kubernetes/pkg/registry/resource/deviceclass/storage"
@@ -58,6 +59,12 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
apiGroupInfo.VersionedResourcesStorageMap[resourcev1beta1.SchemeGroupVersion.Version] = storageMap
}
+ if storageMap, err := p.v1beta2Storage(apiResourceConfigSource, restOptionsGetter, p.NamespaceClient); err != nil {
+ return genericapiserver.APIGroupInfo{}, err
+ } else if len(storageMap) > 0 {
+ apiGroupInfo.VersionedResourcesStorageMap[resourcev1beta2.SchemeGroupVersion.Version] = storageMap
+ }
+
return apiGroupInfo, nil
}
@@ -147,6 +154,45 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
return storage, nil
}
+func (p RESTStorageProvider) v1beta2Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (map[string]rest.Storage, error) {
+ storage := map[string]rest.Storage{}
+
+ if resource := "deviceclasses"; apiResourceConfigSource.ResourceEnabled(resourcev1beta2.SchemeGroupVersion.WithResource(resource)) {
+ deviceclassStorage, err := deviceclassstore.NewREST(restOptionsGetter)
+ if err != nil {
+ return nil, err
+ }
+ storage[resource] = deviceclassStorage
+ }
+
+ if resource := "resourceclaims"; apiResourceConfigSource.ResourceEnabled(resourcev1beta2.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(resourcev1beta2.SchemeGroupVersion.WithResource(resource)) {
+ resourceClaimTemplateStorage, err := resourceclaimtemplatestore.NewREST(restOptionsGetter, nsClient)
+ if err != nil {
+ return nil, err
+ }
+ storage[resource] = resourceClaimTemplateStorage
+ }
+
+ if resource := "resourceslices"; apiResourceConfigSource.ResourceEnabled(resourcev1beta2.SchemeGroupVersion.WithResource(resource)) {
+ resourceSliceStorage, err := resourceslicestore.NewREST(restOptionsGetter)
+ if err != nil {
+ return nil, err
+ }
+ storage[resource] = resourceSliceStorage
+ }
+
+ return storage, nil
+}
+
func (p RESTStorageProvider) GroupName() string {
return resource.GroupName
}
diff --git a/pkg/registry/resource/utils.go b/pkg/registry/resource/utils.go
index 688f45e3930..fe848efb557 100644
--- a/pkg/registry/resource/utils.go
+++ b/pkg/registry/resource/utils.go
@@ -36,7 +36,12 @@ func AuthorizedForAdmin(ctx context.Context, deviceRequests []resource.DeviceReq
// no need to check old request since spec is immutable
for i := range deviceRequests {
- value := deviceRequests[i].AdminAccess
+ // AdminAccess can not be set on subrequests, so it can
+ // only be used when the Exactly field is set.
+ if deviceRequests[i].Exactly == nil {
+ continue
+ }
+ value := deviceRequests[i].Exactly.AdminAccess
if value != nil && *value {
adminRequested = true
adminAccessPath = field.NewPath("spec", "devices", "requests").Index(i).Child("adminAccess")
diff --git a/plugin/pkg/admission/noderestriction/admission.go b/plugin/pkg/admission/noderestriction/admission.go
index 45f790913e6..9bfcbb2ea2c 100644
--- a/plugin/pkg/admission/noderestriction/admission.go
+++ b/plugin/pkg/admission/noderestriction/admission.go
@@ -856,7 +856,7 @@ func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) err
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
}
- if slice.Spec.NodeName != nodeName {
+ if slice.Spec.NodeName == nil || *slice.Spec.NodeName != nodeName {
return admission.NewForbidden(a, errors.New("can only create ResourceSlice with the same NodeName as the requesting node"))
}
case admission.Delete:
@@ -865,7 +865,7 @@ func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) err
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
}
- if slice.Spec.NodeName != nodeName {
+ if slice.Spec.NodeName == nil || *slice.Spec.NodeName != nodeName {
return admission.NewForbidden(a, errors.New("can only delete ResourceSlice with the same NodeName as the requesting node"))
}
}
diff --git a/plugin/pkg/admission/noderestriction/admission_test.go b/plugin/pkg/admission/noderestriction/admission_test.go
index d3b3da0b2c5..16c84fba777 100644
--- a/plugin/pkg/admission/noderestriction/admission_test.go
+++ b/plugin/pkg/admission/noderestriction/admission_test.go
@@ -2154,7 +2154,7 @@ func TestAdmitResourceSlice(t *testing.T) {
Name: "something",
},
Spec: resourceapi.ResourceSliceSpec{
- NodeName: nodename,
+ NodeName: pointer.String(nodename),
},
}
sliceOtherNode := &resourceapi.ResourceSlice{
@@ -2162,7 +2162,7 @@ func TestAdmitResourceSlice(t *testing.T) {
Name: "something",
},
Spec: resourceapi.ResourceSliceSpec{
- NodeName: nodename + "-other",
+ NodeName: pointer.String(nodename + "-other"),
},
}
sliceNoNode := &resourceapi.ResourceSlice{
@@ -2170,7 +2170,7 @@ func TestAdmitResourceSlice(t *testing.T) {
Name: "something",
},
Spec: resourceapi.ResourceSliceSpec{
- NodeName: "",
+ NodeName: nil,
},
}
diff --git a/staging/src/k8s.io/api/resource/v1alpha3/types.go b/staging/src/k8s.io/api/resource/v1alpha3/types.go
index 2fc99f43f8f..29a4b0343f1 100644
--- a/staging/src/k8s.io/api/resource/v1alpha3/types.go
+++ b/staging/src/k8s.io/api/resource/v1alpha3/types.go
@@ -178,13 +178,13 @@ type ResourceSliceSpec struct {
// the portion of counters it uses will no longer be available for use
// by other devices.
type CounterSet struct {
- // Name defines the name of the counter set.
- // It must be a DNS label.
+ // CounterSet is the name of the set from which the
+ // counters defined will be consumed.
//
// +required
Name string `json:"name" protobuf:"bytes,1,name=name"`
- // Counters defines the set of counters for this CounterSet
+ // Counters defines the counters that will be consumed by the device.
// The name of each counter must be unique in that set and must be a DNS label.
//
// To ensure this uniqueness, capacities defined by the vendor
@@ -284,20 +284,21 @@ type BasicDevice struct {
// +optional
Capacity map[QualifiedName]resource.Quantity `json:"capacity,omitempty" protobuf:"bytes,2,rep,name=capacity"`
- // ConsumesCounter defines a list of references to sharedCounters
+ // ConsumesCounters defines a list of references to sharedCounters
// and the set of counters that the device will
// consume from those counter sets.
//
// There can only be a single entry per counterSet.
//
- // The maximum number of device counter consumption entries
- // is 32. This is the same as the maximum number of shared counters
- // allowed in a ResourceSlice.
+ // The total number of device counter consumption entries
+ // must be <= 32. In addition, the total number in the
+ // entire ResourceSlice must be <= 1024 (for example,
+ // 64 devices with 16 counters each).
//
// +optional
// +listType=atomic
// +featureGate=DRAPartitionableDevices
- ConsumesCounter []DeviceCounterConsumption `json:"consumesCounter,omitempty" protobuf:"bytes,3,rep,name=consumesCounter"`
+ ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,3,rep,name=consumesCounters"`
// NodeName identifies the node where the device is available.
//
@@ -331,7 +332,7 @@ type BasicDevice struct {
// If specified, these are the driver-defined taints.
//
- // The maximum number of taints is 8.
+ // The maximum number of taints is 4.
//
// This is an alpha field and requires enabling the DRADeviceTaints
// feature gate.
@@ -345,17 +346,19 @@ type BasicDevice struct {
// DeviceCounterConsumption defines a set of counters that
// a device will consume from a CounterSet.
type DeviceCounterConsumption struct {
- // SharedCounter defines the shared counter from which the
+ // CounterSet defines the set from which the
// counters defined will be consumed.
//
// +required
- SharedCounter string `json:"sharedCounter" protobuf:"bytes,1,opt,name=sharedCounter"`
+ CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"`
// Counters defines the Counter that will be consumed by
// the device.
//
- //
- // The maximum number of Counters is 32.
+ // The maximum number counters in a device is 32.
+ // In addition, the maximum number of all counters
+ // in all devices is 1024 (for example, 64 devices with
+ // 16 counters each).
//
// +required
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
@@ -364,6 +367,14 @@ type DeviceCounterConsumption struct {
// Limit for the sum of the number of entries in both attributes and capacity.
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
+// Limit for the total number of counters in each device.
+const ResourceSliceMaxCountersPerDevice = 32
+
+// Limit for the total number of counters defined in devices in
+// a ResourceSlice. We want to allow up to 64 devices to specify
+// up to 16 counters, so the limit for the ResourceSlice will be 1024.
+const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
+
// QualifiedName is the name of a device attribute or capacity.
//
// Attributes and capacities are defined either by the owner of the specific
@@ -427,7 +438,7 @@ type DeviceAttribute struct {
const DeviceAttributeMaxValueLength = 64
// DeviceTaintsMaxLength is the maximum number of taints per device.
-const DeviceTaintsMaxLength = 8
+const DeviceTaintsMaxLength = 4
// The device this taint is attached to has the "effect" on
// any claim which does not tolerate the taint and, through the claim,
@@ -784,7 +795,7 @@ type DeviceSubRequest struct {
// Allocation will fail if some devices are already allocated,
// unless adminAccess is requested.
//
- // If AlloctionMode is not specified, the default mode is ExactCount. If
+ // If AllocationMode is not specified, the default mode is ExactCount. If
// the mode is ExactCount and count is not specified, the default count is
// one. Any other requests must specify this field.
//
diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go
index 41903dc510b..3c64b426d94 100644
--- a/staging/src/k8s.io/api/resource/v1beta1/types.go
+++ b/staging/src/k8s.io/api/resource/v1beta1/types.go
@@ -279,20 +279,21 @@ type BasicDevice struct {
// +optional
Capacity map[QualifiedName]DeviceCapacity `json:"capacity,omitempty" protobuf:"bytes,2,rep,name=capacity"`
- // ConsumesCounter defines a list of references to sharedCounters
+ // ConsumesCounters defines a list of references to sharedCounters
// and the set of counters that the device will
// consume from those counter sets.
//
// There can only be a single entry per counterSet.
//
- // The maximum number of device counter consumption entries
- // is 32. This is the same as the maximum number of shared counters
- // allowed in a ResourceSlice.
+ // The total number of device counter consumption entries
+ // must be <= 32. In addition, the total number in the
+ // entire ResourceSlice must be <= 1024 (for example,
+ // 64 devices with 16 counters each).
//
// +optional
// +listType=atomic
// +featureGate=DRAPartitionableDevices
- ConsumesCounter []DeviceCounterConsumption `json:"consumesCounter,omitempty" protobuf:"bytes,3,rep,name=consumesCounter"`
+ ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,3,rep,name=consumesCounters"`
// NodeName identifies the node where the device is available.
//
@@ -306,6 +307,8 @@ type BasicDevice struct {
// NodeSelector defines the nodes where the device is available.
//
+ // Must use exactly one term.
+ //
// Must only be set if Spec.PerDeviceNodeSelection is set to true.
// At most one of NodeName, NodeSelector and AllNodes can be set.
//
@@ -325,7 +328,7 @@ type BasicDevice struct {
// If specified, these are the driver-defined taints.
//
- // The maximum number of taints is 8.
+ // The maximum number of taints is 4.
//
// This is an alpha field and requires enabling the DRADeviceTaints
// feature gate.
@@ -339,17 +342,18 @@ type BasicDevice struct {
// DeviceCounterConsumption defines a set of counters that
// a device will consume from a CounterSet.
type DeviceCounterConsumption struct {
- // SharedCounter defines the shared counter from which the
+ // CounterSet is the name of the set from which the
// counters defined will be consumed.
//
// +required
- SharedCounter string `json:"sharedCounter" protobuf:"bytes,1,opt,name=sharedCounter"`
+ CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"`
- // Counters defines the Counter that will be consumed by
- // the device.
+ // Counters defines the counters that will be consumed by the device.
//
- //
- // The maximum number of Counters is 32.
+ // The maximum number counters in a device is 32.
+ // In addition, the maximum number of all counters
+ // in all devices is 1024 (for example, 64 devices with
+ // 16 counters each).
//
// +required
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
@@ -369,6 +373,14 @@ type DeviceCapacity struct {
// Limit for the sum of the number of entries in both attributes and capacity.
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
+// Limit for the total number of counters in each device.
+const ResourceSliceMaxCountersPerDevice = 32
+
+// Limit for the total number of counters defined in devices in
+// a ResourceSlice. We want to allow up to 64 devices to specify
+// up to 16 counters, so the limit for the ResourceSlice will be 1024.
+const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
+
// QualifiedName is the name of a device attribute or capacity.
//
// Attributes and capacities are defined either by the owner of the specific
@@ -432,7 +444,7 @@ type DeviceAttribute struct {
const DeviceAttributeMaxValueLength = 64
// DeviceTaintsMaxLength is the maximum number of taints per device.
-const DeviceTaintsMaxLength = 8
+const DeviceTaintsMaxLength = 4
// The device this taint is attached to has the "effect" on
// any claim which does not tolerate the taint and, through the claim,
@@ -790,7 +802,7 @@ type DeviceSubRequest struct {
// Allocation will fail if some devices are already allocated,
// unless adminAccess is requested.
//
- // If AlloctionMode is not specified, the default mode is ExactCount. If
+ // If AllocationMode is not specified, the default mode is ExactCount. If
// the mode is ExactCount and count is not specified, the default count is
// one. Any other subrequests must specify this field.
//
diff --git a/staging/src/k8s.io/api/resource/v1beta2/devicetaint.go b/staging/src/k8s.io/api/resource/v1beta2/devicetaint.go
new file mode 100644
index 00000000000..c9367600225
--- /dev/null
+++ b/staging/src/k8s.io/api/resource/v1beta2/devicetaint.go
@@ -0,0 +1,35 @@
+/*
+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 v1beta2
+
+import "fmt"
+
+var _ fmt.Stringer = DeviceTaint{}
+
+// String converts to a string in the format '=:', '=:', ':', or ''.
+func (t DeviceTaint) String() string {
+ if len(t.Effect) == 0 {
+ if len(t.Value) == 0 {
+ return fmt.Sprintf("%v", t.Key)
+ }
+ return fmt.Sprintf("%v=%v:", t.Key, t.Value)
+ }
+ if len(t.Value) == 0 {
+ return fmt.Sprintf("%v:%v", t.Key, t.Effect)
+ }
+ return fmt.Sprintf("%v=%v:%v", t.Key, t.Value, t.Effect)
+}
diff --git a/staging/src/k8s.io/api/resource/v1beta2/doc.go b/staging/src/k8s.io/api/resource/v1beta2/doc.go
new file mode 100644
index 00000000000..365113ae4e1
--- /dev/null
+++ b/staging/src/k8s.io/api/resource/v1beta2/doc.go
@@ -0,0 +1,24 @@
+/*
+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.
+*/
+
+// +k8s:openapi-gen=true
+// +k8s:deepcopy-gen=package
+// +k8s:protobuf-gen=package
+// +k8s:prerelease-lifecycle-gen=true
+// +groupName=resource.k8s.io
+
+// Package v1beta2 is the v1beta2 version of the resource API.
+package v1beta2
diff --git a/staging/src/k8s.io/api/resource/v1beta2/register.go b/staging/src/k8s.io/api/resource/v1beta2/register.go
new file mode 100644
index 00000000000..5e676a05497
--- /dev/null
+++ b/staging/src/k8s.io/api/resource/v1beta2/register.go
@@ -0,0 +1,60 @@
+/*
+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 v1beta2
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// GroupName is the group name use in this package
+const GroupName = "resource.k8s.io"
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta2"}
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+ return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+var (
+ // We only register manually written functions here. The registration of the
+ // generated functions takes place in the generated files. The separation
+ // makes the code compile even when the generated files are missing.
+ SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
+ AddToScheme = SchemeBuilder.AddToScheme
+)
+
+// Adds the list of known types to the given scheme.
+func addKnownTypes(scheme *runtime.Scheme) error {
+ scheme.AddKnownTypes(SchemeGroupVersion,
+ &DeviceClass{},
+ &DeviceClassList{},
+ &ResourceClaim{},
+ &ResourceClaimList{},
+ &ResourceClaimTemplate{},
+ &ResourceClaimTemplateList{},
+ &ResourceSlice{},
+ &ResourceSliceList{},
+ )
+
+ // Add the watch version that applies
+ metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
+ return nil
+}
diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go
new file mode 100644
index 00000000000..0d8d42a0854
--- /dev/null
+++ b/staging/src/k8s.io/api/resource/v1beta2/types.go
@@ -0,0 +1,1552 @@
+/*
+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 v1beta2
+
+import (
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/validation"
+)
+
+const (
+ // Finalizer is the finalizer that gets set for claims
+ // which were allocated through a builtin controller.
+ // Reserved for use by Kubernetes, DRA driver controllers must
+ // use their own finalizer.
+ Finalizer = "resource.kubernetes.io/delete-protection"
+)
+
+// +genclient
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +k8s:prerelease-lifecycle-gen:introduced=1.33
+
+// 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
+// ResourceSlices comprise a pool is determined by the driver.
+//
+// At the moment, the only supported resources are devices with attributes and capacities.
+// Each device in a given pool, regardless of how many ResourceSlices, must have a unique name.
+// The ResourceSlice in which a device gets published may change over time. The unique identifier
+// for a device is the tuple , , .
+//
+// Whenever a driver needs to update a pool, it increments the pool.Spec.Pool.Generation number
+// and updates all ResourceSlices with that new number and new resource definitions. A consumer
+// must only use ResourceSlices with the highest generation number and ignore all others.
+//
+// When allocating all resources in a pool matching certain criteria or when
+// looking for the best solution among several different alternatives, a
+// consumer should check the number of ResourceSlices in a pool (included in
+// each ResourceSlice) to determine whether its view of a pool is complete and
+// if not, should wait until the driver has completed updating the pool.
+//
+// For resources that are not local to a node, the node name is not set. Instead,
+// the driver may use a node selector to specify where the devices are available.
+//
+// This is an alpha type and requires enabling the DynamicResourceAllocation
+// feature gate.
+type ResourceSlice struct {
+ metav1.TypeMeta `json:",inline"`
+ // Standard object metadata
+ // +optional
+ metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+ // Contains the information published by the driver.
+ //
+ // Changing the spec automatically increments the metadata.generation number.
+ Spec ResourceSliceSpec `json:"spec" protobuf:"bytes,2,name=spec"`
+}
+
+const (
+ // ResourceSliceSelectorNodeName can be used in a [metav1.ListOptions]
+ // field selector to filter based on [ResourceSliceSpec.NodeName].
+ ResourceSliceSelectorNodeName = "spec.nodeName"
+ // ResourceSliceSelectorDriver can be used in a [metav1.ListOptions]
+ // field selector to filter based on [ResourceSliceSpec.Driver].
+ ResourceSliceSelectorDriver = "spec.driver"
+)
+
+// ResourceSliceSpec contains the information published by the driver in one ResourceSlice.
+type ResourceSliceSpec struct {
+ // Driver identifies the DRA driver providing the capacity information.
+ // A field selector can be used to list only ResourceSlice
+ // objects with a certain driver name.
+ //
+ // Must be a DNS subdomain and should end with a DNS domain owned by the
+ // vendor of the driver. This field is immutable.
+ //
+ // +required
+ Driver string `json:"driver" protobuf:"bytes,1,name=driver"`
+
+ // Pool describes the pool that this ResourceSlice belongs to.
+ //
+ // +required
+ Pool ResourcePool `json:"pool" protobuf:"bytes,2,name=pool"`
+
+ // NodeName identifies the node which provides the resources in this pool.
+ // A field selector can be used to list only ResourceSlice
+ // objects belonging to a certain node.
+ //
+ // This field can be used to limit access from nodes to ResourceSlices with
+ // the same node name. It also indicates to autoscalers that adding
+ // new nodes of the same type as some old node might also make new
+ // resources available.
+ //
+ // Exactly one of NodeName, NodeSelector, AllNodes, and PerDeviceNodeSelection must be set.
+ // This field is immutable.
+ //
+ // +optional
+ // +oneOf=NodeSelection
+ NodeName *string `json:"nodeName,omitempty" protobuf:"bytes,3,opt,name=nodeName"`
+
+ // NodeSelector defines which nodes have access to the resources in the pool,
+ // when that pool is not limited to a single node.
+ //
+ // Must use exactly one term.
+ //
+ // Exactly one of NodeName, NodeSelector, AllNodes, and PerDeviceNodeSelection must be set.
+ //
+ // +optional
+ // +oneOf=NodeSelection
+ NodeSelector *v1.NodeSelector `json:"nodeSelector,omitempty" protobuf:"bytes,4,opt,name=nodeSelector"`
+
+ // AllNodes indicates that all nodes have access to the resources in the pool.
+ //
+ // Exactly one of NodeName, NodeSelector, AllNodes, and PerDeviceNodeSelection must be set.
+ //
+ // +optional
+ // +oneOf=NodeSelection
+ AllNodes *bool `json:"allNodes,omitempty" protobuf:"bytes,5,opt,name=allNodes"`
+
+ // Devices lists some or all of the devices in this pool.
+ //
+ // Must not have more than 128 entries.
+ //
+ // +optional
+ // +listType=atomic
+ Devices []Device `json:"devices" protobuf:"bytes,6,name=devices"`
+
+ // PerDeviceNodeSelection defines whether the access from nodes to
+ // resources in the pool is set on the ResourceSlice level or on each
+ // device. If it is set to true, every device defined the ResourceSlice
+ // must specify this individually.
+ //
+ // Exactly one of NodeName, NodeSelector, AllNodes, and PerDeviceNodeSelection must be set.
+ //
+ // +optional
+ // +oneOf=NodeSelection
+ // +featureGate=DRAPartitionableDevices
+ PerDeviceNodeSelection *bool `json:"perDeviceNodeSelection,omitempty" protobuf:"bytes,7,name=perDeviceNodeSelection"`
+
+ // SharedCounters defines a list of counter sets, each of which
+ // has a name and a list of counters available.
+ //
+ // The names of the SharedCounters must be unique in the ResourceSlice.
+ //
+ // The maximum number of counters in all sets is 32.
+ //
+ // +optional
+ // +listType=atomic
+ // +featureGate=DRAPartitionableDevices
+ SharedCounters []CounterSet `json:"sharedCounters,omitempty" protobuf:"bytes,8,name=sharedCounters"`
+}
+
+// CounterSet defines a named set of counters
+// that are available to be used by devices defined in the
+// ResourceSlice.
+//
+// The counters are not allocatable by themselves, but
+// can be referenced by devices. When a device is allocated,
+// the portion of counters it uses will no longer be available for use
+// by other devices.
+type CounterSet struct {
+ // Name defines the name of the counter set.
+ // It must be a DNS label.
+ //
+ // +required
+ Name string `json:"name" protobuf:"bytes,1,name=name"`
+
+ // Counters defines the set of counters for this CounterSet
+ // The name of each counter must be unique in that set and must be a DNS label.
+ //
+ // The maximum number of counters in all sets is 32.
+ //
+ // +required
+ Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"`
+}
+
+// DriverNameMaxLength is the maximum valid length of a driver name in the
+// ResourceSliceSpec and other places. It's the same as for CSI driver names.
+const DriverNameMaxLength = 63
+
+// ResourcePool describes the pool that ResourceSlices belong to.
+type ResourcePool struct {
+ // Name is used to identify the pool. For node-local devices, this
+ // is often the node name, but this is not required.
+ //
+ // It must not be longer than 253 characters and must consist of one or more DNS sub-domains
+ // separated by slashes. This field is immutable.
+ //
+ // +required
+ Name string `json:"name" protobuf:"bytes,1,name=name"`
+
+ // Generation tracks the change in a pool over time. Whenever a driver
+ // changes something about one or more of the resources in a pool, it
+ // must change the generation in all ResourceSlices which are part of
+ // that pool. Consumers of ResourceSlices should only consider
+ // resources from the pool with the highest generation number. The
+ // generation may be reset by drivers, which should be fine for
+ // consumers, assuming that all ResourceSlices in a pool are updated to
+ // match or deleted.
+ //
+ // Combined with ResourceSliceCount, this mechanism enables consumers to
+ // detect pools which are comprised of multiple ResourceSlices and are
+ // in an incomplete state.
+ //
+ // +required
+ Generation int64 `json:"generation" protobuf:"bytes,2,name=generation"`
+
+ // ResourceSliceCount is the total number of ResourceSlices in the pool at this
+ // generation number. Must be greater than zero.
+ //
+ // Consumers can use this to check whether they have seen all ResourceSlices
+ // belonging to the same pool.
+ //
+ // +required
+ ResourceSliceCount int64 `json:"resourceSliceCount" protobuf:"bytes,3,name=resourceSliceCount"`
+}
+
+const ResourceSliceMaxSharedCapacity = 128
+const ResourceSliceMaxDevices = 128
+const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
+
+// Defines the max number of shared counters that can be specified
+// in a ResourceSlice. The number is summed up across all sets.
+const ResourceSliceMaxSharedCounters = 32
+
+// Device represents one individual hardware instance that can be selected based
+// on its attributes. Besides the name, exactly one field must be set.
+type Device struct {
+ // Name is unique identifier among all devices managed by
+ // the driver in the pool. It must be a DNS label.
+ //
+ // +required
+ Name string `json:"name" protobuf:"bytes,1,name=name"`
+
+ // Attributes defines the set of attributes for this device.
+ // The name of each attribute must be unique in that set.
+ //
+ // The maximum number of attributes and capacities combined is 32.
+ //
+ // +optional
+ Attributes map[QualifiedName]DeviceAttribute `json:"attributes,omitempty" protobuf:"bytes,2,rep,name=attributes"`
+
+ // Capacity defines the set of capacities for this device.
+ // The name of each capacity must be unique in that set.
+ //
+ // The maximum number of attributes and capacities combined is 32.
+ //
+ // +optional
+ Capacity map[QualifiedName]DeviceCapacity `json:"capacity,omitempty" protobuf:"bytes,3,rep,name=capacity"`
+
+ // ConsumesCounters defines a list of references to sharedCounters
+ // and the set of counters that the device will
+ // consume from those counter sets.
+ //
+ // There can only be a single entry per counterSet.
+ //
+ // The total number of device counter consumption entries
+ // must be <= 32. In addition, the total number in the
+ // entire ResourceSlice must be <= 1024 (for example,
+ // 64 devices with 16 counters each).
+ //
+ // +optional
+ // +listType=atomic
+ // +featureGate=DRAPartitionableDevices
+ ConsumesCounters []DeviceCounterConsumption `json:"consumesCounters,omitempty" protobuf:"bytes,4,rep,name=consumesCounters"`
+
+ // NodeName identifies the node where the device is available.
+ //
+ // Must only be set if Spec.PerDeviceNodeSelection is set to true.
+ // At most one of NodeName, NodeSelector and AllNodes can be set.
+ //
+ // +optional
+ // +oneOf=DeviceNodeSelection
+ // +featureGate=DRAPartitionableDevices
+ NodeName *string `json:"nodeName,omitempty" protobuf:"bytes,5,opt,name=nodeName"`
+
+ // NodeSelector defines the nodes where the device is available.
+ //
+ // Must use exactly one term.
+ //
+ // Must only be set if Spec.PerDeviceNodeSelection is set to true.
+ // At most one of NodeName, NodeSelector and AllNodes can be set.
+ //
+ // +optional
+ // +oneOf=DeviceNodeSelection
+ // +featureGate=DRAPartitionableDevices
+ NodeSelector *v1.NodeSelector `json:"nodeSelector,omitempty" protobuf:"bytes,6,opt,name=nodeSelector"`
+
+ // AllNodes indicates that all nodes have access to the device.
+ //
+ // Must only be set if Spec.PerDeviceNodeSelection is set to true.
+ // At most one of NodeName, NodeSelector and AllNodes can be set.
+ //
+ // +optional
+ // +oneOf=DeviceNodeSelection
+ // +featureGate=DRAPartitionableDevices
+ AllNodes *bool `json:"allNodes,omitempty" protobuf:"bytes,7,opt,name=allNodes"`
+
+ // If specified, these are the driver-defined taints.
+ //
+ // The maximum number of taints is 4.
+ //
+ // This is an alpha field and requires enabling the DRADeviceTaints
+ // feature gate.
+ //
+ // +optional
+ // +listType=atomic
+ // +featureGate=DRADeviceTaints
+ Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,8,rep,name=taints"`
+}
+
+// DeviceCounterConsumption defines a set of counters that
+// a device will consume from a CounterSet.
+type DeviceCounterConsumption struct {
+ // CounterSet is the name of the set from which the
+ // counters defined will be consumed.
+ //
+ // +required
+ CounterSet string `json:"counterSet" protobuf:"bytes,1,opt,name=counterSet"`
+
+ // Counters defines the counters that will be consumed by the device.
+ //
+ // The maximum number counters in a device is 32.
+ // In addition, the maximum number of all counters
+ // in all devices is 1024 (for example, 64 devices with
+ // 16 counters each).
+ //
+ // +required
+ Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
+}
+
+// DeviceCapacity describes a quantity associated with a device.
+type DeviceCapacity struct {
+ // Value defines how much of a certain device capacity is available.
+ //
+ // +required
+ Value resource.Quantity `json:"value" protobuf:"bytes,1,rep,name=value"`
+
+ // potential future addition: fields which define how to "consume"
+ // capacity (= share a single device between different consumers).
+}
+
+// Counter describes a quantity associated with a device.
+type Counter struct {
+ // Value defines how much of a certain device counter is available.
+ //
+ // +required
+ Value resource.Quantity `json:"value" protobuf:"bytes,1,rep,name=value"`
+}
+
+// Limit for the sum of the number of entries in both attributes and capacity.
+const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
+
+// Limit for the total number of counters in each device.
+const ResourceSliceMaxCountersPerDevice = 32
+
+// Limit for the total number of counters defined in devices in
+// a ResourceSlice. We want to allow up to 64 devices to specify
+// up to 16 counters, so the limit for the ResourceSlice will be 1024.
+const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
+
+// QualifiedName is the name of a device attribute or capacity.
+//
+// Attributes and capacities are defined either by the owner of the specific
+// driver (usually the vendor) or by some 3rd party (e.g. the Kubernetes
+// project). Because they are sometimes compared across devices, a given name
+// is expected to mean the same thing and have the same type on all devices.
+//
+// Names must be either a C identifier (e.g. "theName") or a DNS subdomain
+// followed by a slash ("/") followed by a C identifier
+// (e.g. "dra.example.com/theName"). Names which do not include the
+// domain prefix are assumed to be part of the driver's domain. Attributes
+// or capacities defined by 3rd parties must include the domain prefix.
+//
+// The maximum length for the DNS subdomain is 63 characters (same as
+// for driver names) and the maximum length of the C identifier
+// is 32.
+type QualifiedName string
+
+// FullyQualifiedName is a QualifiedName where the domain is set.
+type FullyQualifiedName string
+
+// DeviceMaxDomainLength is the maximum length of the domain prefix in a fully-qualified name.
+const DeviceMaxDomainLength = 63
+
+// DeviceMaxIDLength is the maximum length of the identifier in a device attribute or capacity name (`/`).
+const DeviceMaxIDLength = 32
+
+// DeviceAttribute must have exactly one field set.
+type DeviceAttribute struct {
+ // The Go field names below have a Value suffix to avoid a conflict between the
+ // field "String" and the corresponding method. That method is required.
+ // The Kubernetes API is defined without that suffix to keep it more natural.
+
+ // IntValue is a number.
+ //
+ // +optional
+ // +oneOf=ValueType
+ IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"`
+
+ // BoolValue is a true/false value.
+ //
+ // +optional
+ // +oneOf=ValueType
+ BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"`
+
+ // StringValue is a string. Must not be longer than 64 characters.
+ //
+ // +optional
+ // +oneOf=ValueType
+ StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"`
+
+ // VersionValue is a semantic version according to semver.org spec 2.0.0.
+ // Must not be longer than 64 characters.
+ //
+ // +optional
+ // +oneOf=ValueType
+ VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"`
+}
+
+// DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value.
+const DeviceAttributeMaxValueLength = 64
+
+// DeviceTaintsMaxLength is the maximum number of taints per device.
+const DeviceTaintsMaxLength = 4
+
+// The device this taint is attached to has the "effect" on
+// any claim which does not tolerate the taint and, through the claim,
+// to pods using the claim.
+//
+// +protobuf.options.(gogoproto.goproto_stringer)=false
+type DeviceTaint struct {
+ // The taint key to be applied to a device.
+ // Must be a label name.
+ //
+ // +required
+ Key string `json:"key" protobuf:"bytes,1,name=key"`
+
+ // The taint value corresponding to the taint key.
+ // Must be a label value.
+ //
+ // +optional
+ Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
+
+ // The effect of the taint on claims that do not tolerate the taint
+ // and through such claims on the pods using them.
+ // Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
+ // nodes is not valid here.
+ //
+ // +required
+ Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"`
+
+ // ^^^^
+ //
+ // Implementing PreferNoSchedule would depend on a scoring solution for DRA.
+ // It might get added as part of that.
+
+ // TimeAdded represents the time at which the taint was added.
+ // Added automatically during create or update if not set.
+ //
+ // +optional
+ TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"`
+
+ // ^^^
+ //
+ // This field was defined as "It is only written for NoExecute taints." for node taints.
+ // But in practice, Kubernetes never did anything with it (no validation, no defaulting,
+ // ignored during pod eviction in pkg/controller/tainteviction).
+}
+
+// +enum
+type DeviceTaintEffect string
+
+const (
+ // Do not allow new pods to schedule which use a tainted device unless they tolerate the taint,
+ // but allow all pods submitted to Kubelet without going through the scheduler
+ // to start, and allow all already-running pods to continue running.
+ DeviceTaintEffectNoSchedule DeviceTaintEffect = "NoSchedule"
+
+ // Evict any already-running pods that do not tolerate the device taint.
+ DeviceTaintEffectNoExecute DeviceTaintEffect = "NoExecute"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +k8s:prerelease-lifecycle-gen:introduced=1.33
+
+// ResourceSliceList is a collection of ResourceSlices.
+type ResourceSliceList struct {
+ metav1.TypeMeta `json:",inline"`
+ // Standard list metadata
+ // +optional
+ metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+ // Items is the list of resource ResourceSlices.
+ Items []ResourceSlice `json:"items" protobuf:"bytes,2,rep,name=items"`
+}
+
+// +genclient
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +k8s:prerelease-lifecycle-gen:introduced=1.33
+
+// ResourceClaim describes a request for access to resources in the cluster,
+// for use by workloads. For example, if a workload needs an accelerator device
+// with specific properties, this is how that request is expressed. The status
+// stanza tracks whether this claim has been satisfied and what specific
+// resources have been allocated.
+//
+// This is an alpha type and requires enabling the DynamicResourceAllocation
+// feature gate.
+type ResourceClaim struct {
+ metav1.TypeMeta `json:",inline"`
+ // Standard object metadata
+ // +optional
+ metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+
+ // Spec describes what is being requested and how to configure it.
+ // The spec is immutable.
+ Spec ResourceClaimSpec `json:"spec" protobuf:"bytes,2,name=spec"`
+
+ // Status describes whether the claim is ready to use and what has been allocated.
+ // +optional
+ Status ResourceClaimStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
+}
+
+// ResourceClaimSpec defines what is being requested in a ResourceClaim and how to configure it.
+type ResourceClaimSpec struct {
+ // Devices defines how to request devices.
+ //
+ // +optional
+ Devices DeviceClaim `json:"devices" protobuf:"bytes,1,name=devices"`
+
+ // Controller is tombstoned since Kubernetes 1.32 where
+ // it got removed. May be reused once decoding v1alpha3 is no longer
+ // supported.
+ // Controller string `json:"controller,omitempty" protobuf:"bytes,2,opt,name=controller"`
+}
+
+// DeviceClaim defines how to request devices with a ResourceClaim.
+type DeviceClaim struct {
+ // Requests represent individual requests for distinct devices which
+ // must all be satisfied. If empty, nothing needs to be allocated.
+ //
+ // +optional
+ // +listType=atomic
+ Requests []DeviceRequest `json:"requests" protobuf:"bytes,1,name=requests"`
+
+ // These constraints must be satisfied by the set of devices that get
+ // allocated for the claim.
+ //
+ // +optional
+ // +listType=atomic
+ Constraints []DeviceConstraint `json:"constraints,omitempty" protobuf:"bytes,2,opt,name=constraints"`
+
+ // This field holds configuration for multiple potential drivers which
+ // could satisfy requests in this claim. It is ignored while allocating
+ // the claim.
+ //
+ // +optional
+ // +listType=atomic
+ Config []DeviceClaimConfiguration `json:"config,omitempty" protobuf:"bytes,3,opt,name=config"`
+
+ // Potential future extension, ignored by older schedulers. This is
+ // fine because scoring allows users to define a preference, without
+ // making it a hard requirement.
+ //
+ // Score *SomeScoringStruct
+}
+
+const (
+ DeviceRequestsMaxSize = AllocationResultsMaxSize
+ DeviceConstraintsMaxSize = 32
+ DeviceConfigMaxSize = 32
+)
+
+// DRAAdminNamespaceLabelKey is a label key used to grant administrative access
+// to certain resource.k8s.io API types within a namespace. When this label is
+// set on a namespace with the value "true" (case-sensitive), it allows the use
+// of adminAccess: true in any namespaced resource.k8s.io API types. Currently,
+// this permission applies to ResourceClaim and ResourceClaimTemplate objects.
+const (
+ DRAAdminNamespaceLabelKey = "resource.k8s.io/admin-access"
+)
+
+// DeviceRequest is a request for devices required for a claim.
+// This is typically a request for a single resource like a device, but can
+// also ask for several identical devices. With FirstAvailable it is also
+// possible to provide a prioritized list of requests.
+type DeviceRequest struct {
+ // Name can be used to reference this request in a pod.spec.containers[].resources.claims
+ // entry and in a constraint of the claim.
+ //
+ // References using the name in the DeviceRequest will uniquely
+ // identify a request when the Exactly field is set. When the
+ // FirstAvailable field is set, a reference to the name of the
+ // DeviceRequest will match whatever subrequest is chosen by the
+ // scheduler.
+ //
+ // Must be a DNS label.
+ //
+ // +required
+ Name string `json:"name" protobuf:"bytes,1,name=name"`
+
+ // Exactly specifies the details for a single request that must
+ // be met exactly for the request to be satisfied.
+ //
+ // One of Exactly or FirstAvailable must be set.
+ //
+ // +optional
+ // +oneOf=deviceRequestType
+ Exactly *ExactDeviceRequest `json:"exactly,omitempty" protobuf:"bytes,2,name=exactly"`
+
+ // FirstAvailable contains subrequests, of which exactly one will be
+ // selected by the scheduler. It tries to
+ // satisfy them in the order in which they are listed here. So if
+ // there are two entries in the list, the scheduler will only check
+ // the second one if it determines that the first one can not be used.
+ //
+ // DRA does not yet implement scoring, so the scheduler will
+ // select the first set of devices that satisfies all the
+ // requests in the claim. And if the requirements can
+ // be satisfied on more than one node, other scheduling features
+ // will determine which node is chosen. This means that the set of
+ // devices allocated to a claim might not be the optimal set
+ // available to the cluster. Scoring will be implemented later.
+ //
+ // +optional
+ // +oneOf=deviceRequestType
+ // +listType=atomic
+ // +featureGate=DRAPrioritizedList
+ FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,3,name=firstAvailable"`
+}
+
+// ExactDeviceRequest is a request for one or more identical devices.
+type ExactDeviceRequest struct {
+ // DeviceClassName references a specific DeviceClass, which can define
+ // additional configuration and selectors to be inherited by this
+ // request.
+ //
+ // A DeviceClassName is required.
+ //
+ // Administrators may use this to restrict which devices may get
+ // requested by only installing classes with selectors for permitted
+ // devices. If users are free to request anything without restrictions,
+ // then administrators can create an empty DeviceClass for users
+ // to reference.
+ //
+ // +required
+ DeviceClassName string `json:"deviceClassName" protobuf:"bytes,1,name=deviceClassName"`
+
+ // Selectors define criteria which must be satisfied by a specific
+ // device in order for that device to be considered for this
+ // request. All selectors must be satisfied for a device to be
+ // considered.
+ //
+ // +optional
+ // +listType=atomic
+ Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,2,name=selectors"`
+
+ // AllocationMode and its related fields define how devices are allocated
+ // to satisfy this request. Supported values are:
+ //
+ // - ExactCount: This request is for a specific number of devices.
+ // This is the default. The exact number is provided in the
+ // count field.
+ //
+ // - All: This request is for all of the matching devices in a pool.
+ // At least one device must exist on the node for the allocation to succeed.
+ // Allocation will fail if some devices are already allocated,
+ // unless adminAccess is requested.
+ //
+ // If AllocationMode is not specified, the default mode is ExactCount. If
+ // the mode is ExactCount and count is not specified, the default count is
+ // one. Any other requests must specify this field.
+ //
+ // More modes may get added in the future. Clients must refuse to handle
+ // requests with unknown modes.
+ //
+ // +optional
+ AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,3,opt,name=allocationMode"`
+
+ // Count is used only when the count mode is "ExactCount". Must be greater than zero.
+ // If AllocationMode is ExactCount and this field is not specified, the default is one.
+ //
+ // +optional
+ // +oneOf=AllocationMode
+ Count int64 `json:"count,omitempty" protobuf:"bytes,4,opt,name=count"`
+
+ // AdminAccess indicates that this is a claim for administrative access
+ // to the device(s). Claims with AdminAccess are expected to be used for
+ // monitoring or other management services for a device. They ignore
+ // all ordinary claims to the device with respect to access modes and
+ // any resource allocations.
+ //
+ // This is an alpha field and requires enabling the DRAAdminAccess
+ // feature gate. Admin access is disabled if this field is unset or
+ // set to false, otherwise it is enabled.
+ //
+ // +optional
+ // +featureGate=DRAAdminAccess
+ AdminAccess *bool `json:"adminAccess,omitempty" protobuf:"bytes,5,opt,name=adminAccess"`
+
+ // If specified, the request's tolerations.
+ //
+ // Tolerations for NoSchedule are required to allocate a
+ // device which has a taint with that effect. The same applies
+ // to NoExecute.
+ //
+ // In addition, should any of the allocated devices get tainted
+ // with NoExecute after allocation and that effect is not tolerated,
+ // then all pods consuming the ResourceClaim get deleted to evict
+ // them. The scheduler will not let new pods reserve the claim while
+ // it has these tainted devices. Once all pods are evicted, the
+ // claim will get deallocated.
+ //
+ // The maximum number of tolerations is 16.
+ //
+ // This is an alpha field and requires enabling the DRADeviceTaints
+ // feature gate.
+ //
+ // +optional
+ // +listType=atomic
+ // +featureGate=DRADeviceTaints
+ Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"`
+}
+
+// DeviceSubRequest describes a request for device provided in the
+// claim.spec.devices.requests[].firstAvailable array. Each
+// is typically a request for a single resource like a device, but can
+// also ask for several identical devices.
+//
+// DeviceSubRequest is similar to ExactDeviceRequest, but doesn't expose the
+// AdminAccess field as that one is only supported when requesting a
+// specific device.
+type DeviceSubRequest struct {
+ // Name can be used to reference this subrequest in the list of constraints
+ // or the list of configurations for the claim. References must use the
+ // format /.
+ //
+ // Must be a DNS label.
+ //
+ // +required
+ Name string `json:"name" protobuf:"bytes,1,name=name"`
+
+ // DeviceClassName references a specific DeviceClass, which can define
+ // additional configuration and selectors to be inherited by this
+ // subrequest.
+ //
+ // A class is required. Which classes are available depends on the cluster.
+ //
+ // Administrators may use this to restrict which devices may get
+ // requested by only installing classes with selectors for permitted
+ // devices. If users are free to request anything without restrictions,
+ // then administrators can create an empty DeviceClass for users
+ // to reference.
+ //
+ // +required
+ DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"`
+
+ // Selectors define criteria which must be satisfied by a specific
+ // device in order for that device to be considered for this
+ // subrequest. All selectors must be satisfied for a device to be
+ // considered.
+ //
+ // +optional
+ // +listType=atomic
+ Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"`
+
+ // AllocationMode and its related fields define how devices are allocated
+ // to satisfy this subrequest. Supported values are:
+ //
+ // - ExactCount: This request is for a specific number of devices.
+ // This is the default. The exact number is provided in the
+ // count field.
+ //
+ // - All: This subrequest is for all of the matching devices in a pool.
+ // Allocation will fail if some devices are already allocated,
+ // unless adminAccess is requested.
+ //
+ // If AllocationMode is not specified, the default mode is ExactCount. If
+ // the mode is ExactCount and count is not specified, the default count is
+ // one. Any other subrequests must specify this field.
+ //
+ // More modes may get added in the future. Clients must refuse to handle
+ // requests with unknown modes.
+ //
+ // +optional
+ AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"`
+
+ // Count is used only when the count mode is "ExactCount". Must be greater than zero.
+ // If AllocationMode is ExactCount and this field is not specified, the default is one.
+ //
+ // +optional
+ // +oneOf=AllocationMode
+ Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"`
+
+ // If specified, the request's tolerations.
+ //
+ // Tolerations for NoSchedule are required to allocate a
+ // device which has a taint with that effect. The same applies
+ // to NoExecute.
+ //
+ // In addition, should any of the allocated devices get tainted
+ // with NoExecute after allocation and that effect is not tolerated,
+ // then all pods consuming the ResourceClaim get deleted to evict
+ // them. The scheduler will not let new pods reserve the claim while
+ // it has these tainted devices. Once all pods are evicted, the
+ // claim will get deallocated.
+ //
+ // The maximum number of tolerations is 16.
+ //
+ // This is an alpha field and requires enabling the DRADeviceTaints
+ // feature gate.
+ //
+ // +optional
+ // +listType=atomic
+ // +featureGate=DRADeviceTaints
+ Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"`
+}
+
+const (
+ DeviceSelectorsMaxSize = 32
+ FirstAvailableDeviceRequestMaxSize = 8
+ DeviceTolerationsMaxLength = 16
+)
+
+type DeviceAllocationMode string
+
+// Valid [DeviceRequest.CountMode] values.
+const (
+ DeviceAllocationModeExactCount = DeviceAllocationMode("ExactCount")
+ DeviceAllocationModeAll = DeviceAllocationMode("All")
+)
+
+// DeviceSelector must have exactly one field set.
+type DeviceSelector struct {
+ // CEL contains a CEL expression for selecting a device.
+ //
+ // +optional
+ // +oneOf=SelectorType
+ CEL *CELDeviceSelector `json:"cel,omitempty" protobuf:"bytes,1,opt,name=cel"`
+}
+
+// CELDeviceSelector contains a CEL expression for selecting a device.
+type CELDeviceSelector struct {
+ // Expression is a CEL expression which evaluates a single device. It
+ // must evaluate to true when the device under consideration satisfies
+ // the desired criteria, and false when it does not. Any other result
+ // is an error and causes allocation of devices to abort.
+ //
+ // The expression's input is an object named "device", which carries
+ // the following properties:
+ // - driver (string): the name of the driver which defines this device.
+ // - attributes (map[string]object): the device's attributes, grouped by prefix
+ // (e.g. device.attributes["dra.example.com"] evaluates to an object with all
+ // of the attributes which were prefixed by "dra.example.com".
+ // - capacity (map[string]object): the device's capacities, grouped by prefix.
+ //
+ // Example: Consider a device with driver="dra.example.com", which exposes
+ // two attributes named "model" and "ext.example.com/family" and which
+ // exposes one capacity named "modules". This input to this expression
+ // would have the following fields:
+ //
+ // device.driver
+ // device.attributes["dra.example.com"].model
+ // device.attributes["ext.example.com"].family
+ // device.capacity["dra.example.com"].modules
+ //
+ // The device.driver field can be used to check for a specific driver,
+ // either as a high-level precondition (i.e. you only want to consider
+ // devices from this driver) or as part of a multi-clause expression
+ // that is meant to consider devices from different drivers.
+ //
+ // The value type of each attribute is defined by the device
+ // definition, and users who write these expressions must consult the
+ // documentation for their specific drivers. The value type of each
+ // capacity is Quantity.
+ //
+ // If an unknown prefix is used as a lookup in either device.attributes
+ // or device.capacity, an empty map will be returned. Any reference to
+ // an unknown field will cause an evaluation error and allocation to
+ // abort.
+ //
+ // A robust expression should check for the existence of attributes
+ // before referencing them.
+ //
+ // For ease of use, the cel.bind() function is enabled, and can be used
+ // to simplify expressions that access multiple attributes with the
+ // same domain. For example:
+ //
+ // cel.bind(dra, device.attributes["dra.example.com"], dra.someBool && dra.anotherBool)
+ //
+ // The length of the expression must be smaller or equal to 10 Ki. The
+ // cost of evaluating it is also limited based on the estimated number
+ // of logical steps.
+ //
+ // +required
+ Expression string `json:"expression" protobuf:"bytes,1,name=expression"`
+}
+
+// CELSelectorExpressionMaxCost specifies the cost limit for a single CEL selector
+// evaluation.
+//
+// There is no overall budget for selecting a device, so the actual time
+// required for that is proportional to the number of CEL selectors and how
+// often they need to be evaluated, which can vary depending on several factors
+// (number of devices, cluster utilization, additional constraints).
+//
+// Validation against this limit and [CELSelectorExpressionMaxLength] happens
+// only when setting an expression for the first time or when changing it. If
+// the limits are changed in a future Kubernetes release, existing users are
+// guaranteed that existing expressions will continue to be valid.
+//
+// However, the kube-scheduler also applies this cost limit at runtime, so it
+// could happen that a valid expression fails at runtime after an up- or
+// downgrade. This can also happen without version skew when the cost estimate
+// underestimated the actual cost. That this might happen is the reason why
+// kube-scheduler enforces the runtime limit instead of relying on validation.
+//
+// According to
+// https://github.com/kubernetes/kubernetes/blob/4aeaf1e99e82da8334c0d6dddd848a194cd44b4f/staging/src/k8s.io/apiserver/pkg/apis/cel/config.go#L20-L22,
+// this gives roughly 0.1 second for each expression evaluation.
+// However, this depends on how fast the machine is.
+const CELSelectorExpressionMaxCost = 1000000
+
+// CELSelectorExpressionMaxLength is the maximum length of a CEL selector expression string.
+const CELSelectorExpressionMaxLength = 10 * 1024
+
+// DeviceConstraint must have exactly one field set besides Requests.
+type DeviceConstraint struct {
+ // Requests is a list of the one or more requests in this claim which
+ // must co-satisfy this constraint. If a request is fulfilled by
+ // multiple devices, then all of the devices must satisfy the
+ // constraint. If this is not specified, this constraint applies to all
+ // requests in this claim.
+ //
+ // References to subrequests must include the name of the main request
+ // and may include the subrequest using the format [/]. If just
+ // the main request is given, the constraint applies to all subrequests.
+ //
+ // +optional
+ // +listType=atomic
+ Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
+
+ // MatchAttribute requires that all devices in question have this
+ // attribute and that its type and value are the same across those
+ // devices.
+ //
+ // For example, if you specified "dra.example.com/numa" (a hypothetical example!),
+ // then only devices in the same NUMA node will be chosen. A device which
+ // does not have that attribute will not be chosen. All devices should
+ // use a value of the same type for this attribute because that is part of
+ // its specification, but if one device doesn't, then it also will not be
+ // chosen.
+ //
+ // Must include the domain qualifier.
+ //
+ // +optional
+ // +oneOf=ConstraintType
+ MatchAttribute *FullyQualifiedName `json:"matchAttribute,omitempty" protobuf:"bytes,2,opt,name=matchAttribute"`
+
+ // Potential future extension, not part of the current design:
+ // A CEL expression which compares different devices and returns
+ // true if they match.
+ //
+ // Because it would be part of a one-of, old schedulers will not
+ // accidentally ignore this additional, for them unknown match
+ // criteria.
+ //
+ // MatchExpression string
+}
+
+// DeviceClaimConfiguration is used for configuration parameters in DeviceClaim.
+type DeviceClaimConfiguration struct {
+ // Requests lists the names of requests where the configuration applies.
+ // If empty, it applies to all requests.
+ //
+ // References to subrequests must include the name of the main request
+ // and may include the subrequest using the format [/]. If just
+ // the main request is given, the configuration applies to all subrequests.
+ //
+ // +optional
+ // +listType=atomic
+ Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
+
+ DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"`
+}
+
+// DeviceConfiguration must have exactly one field set. It gets embedded
+// inline in some other structs which have other fields, so field names must
+// not conflict with those.
+type DeviceConfiguration struct {
+ // Opaque provides driver-specific configuration parameters.
+ //
+ // +optional
+ // +oneOf=ConfigurationType
+ Opaque *OpaqueDeviceConfiguration `json:"opaque,omitempty" protobuf:"bytes,1,opt,name=opaque"`
+}
+
+// OpaqueDeviceConfiguration contains configuration parameters for a driver
+// in a format defined by the driver vendor.
+type OpaqueDeviceConfiguration struct {
+ // Driver is used to determine which kubelet plugin needs
+ // to be passed these configuration parameters.
+ //
+ // An admission policy provided by the driver developer could use this
+ // to decide whether it needs to validate them.
+ //
+ // Must be a DNS subdomain and should end with a DNS domain owned by the
+ // vendor of the driver.
+ //
+ // +required
+ Driver string `json:"driver" protobuf:"bytes,1,name=driver"`
+
+ // Parameters can contain arbitrary data. It is the responsibility of
+ // the driver developer to handle validation and versioning. Typically this
+ // includes self-identification and a version ("kind" + "apiVersion" for
+ // Kubernetes types), with conversion between different versions.
+ //
+ // The length of the raw data must be smaller or equal to 10 Ki.
+ //
+ // +required
+ Parameters runtime.RawExtension `json:"parameters" protobuf:"bytes,2,name=parameters"`
+}
+
+// OpaqueParametersMaxLength is the maximum length of the raw data in an
+// [OpaqueDeviceConfiguration.Parameters] field.
+const OpaqueParametersMaxLength = 10 * 1024
+
+// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
+// the triple using the matching operator .
+type DeviceToleration struct {
+ // Key is the taint key that the toleration applies to. Empty means match all taint keys.
+ // If the key is empty, operator must be Exists; this combination means to match all values and all keys.
+ // Must be a label name.
+ //
+ // +optional
+ Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"`
+
+ // Operator represents a key's relationship to the value.
+ // Valid operators are Exists and Equal. Defaults to Equal.
+ // Exists is equivalent to wildcard for value, so that a ResourceClaim can
+ // tolerate all taints of a particular category.
+ //
+ // +optional
+ // +default="Equal"
+ Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"`
+
+ // Value is the taint value the toleration matches to.
+ // If the operator is Exists, the value must be empty, otherwise just a regular string.
+ // Must be a label value.
+ //
+ // +optional
+ Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"`
+
+ // Effect indicates the taint effect to match. Empty means match all taint effects.
+ // When specified, allowed values are NoSchedule and NoExecute.
+ //
+ // +optional
+ Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"`
+
+ // TolerationSeconds represents the period of time the toleration (which must be
+ // of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
+ // it is not set, which means tolerate the taint forever (do not evict). Zero and
+ // negative values will be treated as 0 (evict immediately) by the system.
+ // If larger than zero, the time when the pod needs to be evicted is calculated as