mirror of
https://github.com/outbackdingo/kubernetes.git
synced 2026-01-27 10:19:35 +00:00
Add resource v1beta2 API
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"}: {},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
333
pkg/apis/resource/v1beta1/conversion_test.go
Normal file
333
pkg/apis/resource/v1beta1/conversion_test.go
Normal file
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
40
pkg/apis/resource/v1beta2/conversion.go
Normal file
40
pkg/apis/resource/v1beta2/conversion.go
Normal file
@@ -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
|
||||
}
|
||||
55
pkg/apis/resource/v1beta2/defaults.go
Normal file
55
pkg/apis/resource/v1beta2/defaults.go
Normal file
@@ -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)}
|
||||
}
|
||||
}
|
||||
179
pkg/apis/resource/v1beta2/defaults_test.go
Normal file
179
pkg/apis/resource/v1beta2/defaults_test.go
Normal file
@@ -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
|
||||
}
|
||||
23
pkg/apis/resource/v1beta2/doc.go
Normal file
23
pkg/apis/resource/v1beta2/doc.go
Normal file
@@ -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
|
||||
46
pkg/apis/resource/v1beta2/register.go
Normal file
46
pkg/apis/resource/v1beta2/register.go
Normal file
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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: <input>: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: <input>: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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}(),
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
35
staging/src/k8s.io/api/resource/v1beta2/devicetaint.go
Normal file
35
staging/src/k8s.io/api/resource/v1beta2/devicetaint.go
Normal file
@@ -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 '<key>=<value>:<effect>', '<key>=<value>:', '<key>:<effect>', or '<key>'.
|
||||
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)
|
||||
}
|
||||
24
staging/src/k8s.io/api/resource/v1beta2/doc.go
Normal file
24
staging/src/k8s.io/api/resource/v1beta2/doc.go
Normal file
@@ -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
|
||||
60
staging/src/k8s.io/api/resource/v1beta2/register.go
Normal file
60
staging/src/k8s.io/api/resource/v1beta2/register.go
Normal file
@@ -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
|
||||
}
|
||||
1552
staging/src/k8s.io/api/resource/v1beta2/types.go
Normal file
1552
staging/src/k8s.io/api/resource/v1beta2/types.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,7 @@ import (
|
||||
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
|
||||
resourcev1alpha3 "k8s.io/api/resource/v1alpha3"
|
||||
resourcev1beta1 "k8s.io/api/resource/v1beta1"
|
||||
resourcev1beta2 "k8s.io/api/resource/v1beta2"
|
||||
schedulingv1 "k8s.io/api/scheduling/v1"
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
schedulingv1beta1 "k8s.io/api/scheduling/v1beta1"
|
||||
@@ -138,6 +139,7 @@ var groups = []runtime.SchemeBuilder{
|
||||
rbacv1.SchemeBuilder,
|
||||
resourcev1alpha3.SchemeBuilder,
|
||||
resourcev1beta1.SchemeBuilder,
|
||||
resourcev1beta2.SchemeBuilder,
|
||||
schedulingv1alpha1.SchemeBuilder,
|
||||
schedulingv1beta1.SchemeBuilder,
|
||||
schedulingv1.SchemeBuilder,
|
||||
|
||||
@@ -56,18 +56,18 @@ type Device struct {
|
||||
}
|
||||
|
||||
type BasicDevice struct {
|
||||
Attributes map[QualifiedName]DeviceAttribute
|
||||
Capacity map[QualifiedName]DeviceCapacity
|
||||
ConsumesCounter []DeviceCounterConsumption
|
||||
NodeName *string
|
||||
NodeSelector *v1.NodeSelector
|
||||
AllNodes *bool
|
||||
Taints []resourceapi.DeviceTaint
|
||||
Attributes map[QualifiedName]DeviceAttribute
|
||||
Capacity map[QualifiedName]DeviceCapacity
|
||||
ConsumesCounters []DeviceCounterConsumption
|
||||
NodeName *string
|
||||
NodeSelector *v1.NodeSelector
|
||||
AllNodes *bool
|
||||
Taints []resourceapi.DeviceTaint
|
||||
}
|
||||
|
||||
type DeviceCounterConsumption struct {
|
||||
SharedCounter UniqueString
|
||||
Counters map[string]Counter
|
||||
CounterSet UniqueString
|
||||
Counters map[string]Counter
|
||||
}
|
||||
|
||||
type QualifiedName string
|
||||
|
||||
@@ -972,8 +972,8 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// The API validation logic has checked the ConsumesCounter referred should exist inside SharedCounters.
|
||||
if alloc.features.PartitionableDevices && len(device.basic.ConsumesCounter) > 0 {
|
||||
// The API validation logic has checked the ConsumesCounters referred should exist inside SharedCounters.
|
||||
if alloc.features.PartitionableDevices && len(device.basic.ConsumesCounters) > 0 {
|
||||
// If a device consumes capacity from a capacity pool, verify that
|
||||
// there is sufficient capacity available.
|
||||
ok, err := alloc.checkAvailableCapacity(device)
|
||||
@@ -1075,8 +1075,8 @@ func (alloc *allocator) checkAvailableCapacity(device deviceWithID) (bool, error
|
||||
slice := device.slice
|
||||
|
||||
referencedSharedCounters := sets.New[draapi.UniqueString]()
|
||||
for _, consumedCounter := range device.basic.ConsumesCounter {
|
||||
referencedSharedCounters.Insert(consumedCounter.SharedCounter)
|
||||
for _, consumedCounter := range device.basic.ConsumesCounters {
|
||||
referencedSharedCounters.Insert(consumedCounter.CounterSet)
|
||||
}
|
||||
|
||||
// Create a structure that captures the initial counter for all sharedCounters
|
||||
@@ -1104,8 +1104,8 @@ func (alloc *allocator) checkAvailableCapacity(device deviceWithID) (bool, error
|
||||
if !alloc.allocatedDevices.Has(deviceID) && !alloc.allocatingDevices[deviceID] {
|
||||
continue
|
||||
}
|
||||
for _, consumedCounter := range device.Basic.ConsumesCounter {
|
||||
counterShared := availableCounters[consumedCounter.SharedCounter]
|
||||
for _, consumedCounter := range device.Basic.ConsumesCounters {
|
||||
counterShared := availableCounters[consumedCounter.CounterSet]
|
||||
for name, cap := range consumedCounter.Counters {
|
||||
existingCap, ok := counterShared[name]
|
||||
if !ok {
|
||||
@@ -1121,8 +1121,8 @@ func (alloc *allocator) checkAvailableCapacity(device deviceWithID) (bool, error
|
||||
}
|
||||
|
||||
// Check if all consumed capacities for the device can be satisfied.
|
||||
for _, deviceConsumedCounter := range device.basic.ConsumesCounter {
|
||||
counterShared := availableCounters[deviceConsumedCounter.SharedCounter]
|
||||
for _, deviceConsumedCounter := range device.basic.ConsumesCounters {
|
||||
counterShared := availableCounters[deviceConsumedCounter.CounterSet]
|
||||
for name, cap := range deviceConsumedCounter.Counters {
|
||||
availableCap, found := counterShared[name]
|
||||
// If the device requests a capacity that doesn't exist in
|
||||
|
||||
@@ -285,7 +285,7 @@ func partitionableDevice(name string, capacity any, attributes map[resourceapi.Q
|
||||
panic(fmt.Sprintf("unexpected capacity type %T: %+v", capacity, capacity))
|
||||
}
|
||||
|
||||
device.Basic.ConsumesCounter = consumesCapacity
|
||||
device.Basic.ConsumesCounters = consumesCapacity
|
||||
return device
|
||||
}
|
||||
|
||||
@@ -328,8 +328,8 @@ func (in wrapDevice) withTaints(taints ...resourceapi.DeviceTaint) wrapDevice {
|
||||
|
||||
func deviceCapacityConsumption(capacityPool string, capacity map[resourceapi.QualifiedName]resource.Quantity) resourceapi.DeviceCounterConsumption {
|
||||
return resourceapi.DeviceCounterConsumption{
|
||||
SharedCounter: capacityPool,
|
||||
Counters: toDeviceCounter(capacity),
|
||||
CounterSet: capacityPool,
|
||||
Counters: toDeviceCounter(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ var resetFieldsStatusData = map[schema.GroupVersionResource]string{
|
||||
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 25}}`,
|
||||
gvr("resource.k8s.io", "v1alpha3", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
|
||||
gvr("resource.k8s.io", "v1beta1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-other-value"]}] }]}}}}`,
|
||||
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
|
||||
// standard for []metav1.Condition
|
||||
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
|
||||
@@ -157,6 +158,9 @@ var resetFieldsSpecData = map[schema.GroupVersionResource]string{
|
||||
gvr("resource.k8s.io", "v1beta1", "deviceclasses"): `{"metadata": {"labels":{"a":"c"}}}`,
|
||||
gvr("resource.k8s.io", "v1beta1", "resourceclaims"): `{"spec": {"devices": {"requests": [{"name": "req-0", "deviceClassName": "other-class"}]}}}`, // spec is immutable, but that doesn't matter for the test.
|
||||
gvr("resource.k8s.io", "v1beta1", "resourceclaimtemplates"): `{"spec": {"spec": {"resourceClassName": "class2name"}}}`,
|
||||
gvr("resource.k8s.io", "v1beta2", "deviceclasses"): `{"metadata": {"labels":{"a":"c"}}}`,
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "other-class"}}]}}}`, // spec is immutable, but that doesn't matter for the test.
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaimtemplates"): `{"spec": {"spec": {"resourceClassName": "class2name"}}}`,
|
||||
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{}`,
|
||||
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"paramKind": {"apiVersion": "apps/v1", "kind": "Deployment"}}}`,
|
||||
gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"paramKind": {"apiVersion": "apps/v1", "kind": "Deployment"}}}`,
|
||||
|
||||
@@ -60,6 +60,7 @@ var statusData = map[schema.GroupVersionResource]string{
|
||||
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
|
||||
gvr("resource.k8s.io", "v1alpha3", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
|
||||
gvr("resource.k8s.io", "v1beta1", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): `{"status": {"allocation": {"nodeSelector": {"nodeSelectorTerms": [{"matchExpressions": [{"key": "some-label", "operator": "In", "values": ["some-value"]}] }]}}}}`,
|
||||
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
|
||||
// standard for []metav1.Condition
|
||||
gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`,
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"testing"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
resourceapi "k8s.io/api/resource/v1beta2"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -168,23 +168,25 @@ func RunAuthzSelectorsLibraryTests(t *testing.T, featureEnabled bool) {
|
||||
Spec: resourceapi.ResourceClaimSpec{
|
||||
Devices: resourceapi.DeviceClaim{
|
||||
Requests: []resourceapi.DeviceRequest{{
|
||||
Name: "req-0",
|
||||
DeviceClassName: "example-class",
|
||||
Selectors: []resourceapi.DeviceSelector{{
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: boolFieldSelectorExpression,
|
||||
},
|
||||
}},
|
||||
Name: "req-0",
|
||||
Exactly: &resourceapi.ExactDeviceRequest{
|
||||
DeviceClassName: "example-class",
|
||||
Selectors: []resourceapi.DeviceSelector{{
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: boolFieldSelectorExpression,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := c.ResourceV1beta1().ResourceClaims("default").Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
_, err := c.ResourceV1beta2().ResourceClaims("default").Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
// authorizer is not available to resource APIs
|
||||
expectErrorsWhenEnabled: []*regexp.Regexp{regexp.MustCompile(`spec\.devices\.requests\[0\]\.selectors\[0\].cel\.expression:.*undeclared reference to 'authorizer'`)},
|
||||
expectErrorsWhenDisabled: []*regexp.Regexp{regexp.MustCompile(`spec\.devices\.requests\[0\]\.selectors\[0\].cel\.expression:.*undeclared reference to 'authorizer'`)},
|
||||
expectErrorsWhenEnabled: []*regexp.Regexp{regexp.MustCompile(`spec\.devices\.requests\[0\]\.exactly\.selectors\[0\].cel\.expression:.*undeclared reference to 'authorizer'`)},
|
||||
expectErrorsWhenDisabled: []*regexp.Regexp{regexp.MustCompile(`spec\.devices\.requests\[0\]\.exactly\.selectors\[0\].cel\.expression:.*undeclared reference to 'authorizer'`)},
|
||||
},
|
||||
{
|
||||
name: "CustomResourceDefinition - rule",
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gstruct"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -34,7 +35,9 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourcealphaapi "k8s.io/api/resource/v1alpha3"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
resourcev1beta2api "k8s.io/api/resource/v1beta2"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
@@ -124,7 +127,8 @@ func TestDRA(t *testing.T) {
|
||||
},
|
||||
"core": {
|
||||
apis: map[schema.GroupVersion]bool{
|
||||
resourceapi.SchemeGroupVersion: true,
|
||||
resourceapi.SchemeGroupVersion: true,
|
||||
resourcev1beta2api.SchemeGroupVersion: true,
|
||||
},
|
||||
features: map[featuregate.Feature]bool{features.DynamicResourceAllocation: true},
|
||||
f: func(tCtx ktesting.TContext) {
|
||||
@@ -136,22 +140,26 @@ func TestDRA(t *testing.T) {
|
||||
},
|
||||
"all": {
|
||||
apis: map[schema.GroupVersion]bool{
|
||||
resourceapi.SchemeGroupVersion: true,
|
||||
resourcealphaapi.SchemeGroupVersion: true,
|
||||
resourceapi.SchemeGroupVersion: true,
|
||||
resourcev1beta2api.SchemeGroupVersion: true,
|
||||
resourcealphaapi.SchemeGroupVersion: true,
|
||||
},
|
||||
features: map[featuregate.Feature]bool{
|
||||
features.DynamicResourceAllocation: true,
|
||||
// Additional DRA feature gates go here,
|
||||
// in alphabetical order,
|
||||
// as needed by tests for them.
|
||||
features.DRAAdminAccess: true,
|
||||
features.DRAPrioritizedList: true,
|
||||
features.DRAAdminAccess: true,
|
||||
features.DRADeviceTaints: true,
|
||||
features.DRAPartitionableDevices: true,
|
||||
features.DRAPrioritizedList: true,
|
||||
},
|
||||
f: func(tCtx ktesting.TContext) {
|
||||
tCtx.Run("AdminAccess", func(tCtx ktesting.TContext) { testAdminAccess(tCtx, true) })
|
||||
tCtx.Run("Convert", testConvert)
|
||||
tCtx.Run("PrioritizedList", func(tCtx ktesting.TContext) { testPrioritizedList(tCtx, true) })
|
||||
tCtx.Run("PublishResourceSlices", func(tCtx ktesting.TContext) { testPublishResourceSlices(tCtx) })
|
||||
tCtx.Run("MaxResourceSlice", testMaxResourceSlice)
|
||||
},
|
||||
},
|
||||
} {
|
||||
@@ -355,6 +363,8 @@ func testPublishResourceSlices(tCtx ktesting.TContext) {
|
||||
Name: "device-tainted-default",
|
||||
Basic: &resourceapi.BasicDevice{
|
||||
Taints: []resourceapi.DeviceTaint{{
|
||||
Key: "dra.example.com/taint",
|
||||
Value: "taint-value",
|
||||
Effect: resourceapi.DeviceTaintEffectNoExecute,
|
||||
// TimeAdded is added by apiserver.
|
||||
}},
|
||||
@@ -364,6 +374,8 @@ func testPublishResourceSlices(tCtx ktesting.TContext) {
|
||||
Name: "device-tainted-time-added",
|
||||
Basic: &resourceapi.BasicDevice{
|
||||
Taints: []resourceapi.DeviceTaint{{
|
||||
Key: "dra.example.com/taint",
|
||||
Value: "taint-value",
|
||||
Effect: resourceapi.DeviceTaintEffectNoExecute,
|
||||
TimeAdded: ptr.To(metav1.Now()),
|
||||
}},
|
||||
@@ -397,3 +409,26 @@ func testPublishResourceSlices(tCtx ktesting.TContext) {
|
||||
// No further changes necessary.
|
||||
ktesting.Consistently(tCtx, getStats).WithTimeout(10 * time.Second).Should(gomega.Equal(expectedStats))
|
||||
}
|
||||
|
||||
// testMaxResourceSlice creates a ResourceSlice that is as large as possible
|
||||
// and prints some information about it.
|
||||
func testMaxResourceSlice(tCtx ktesting.TContext) {
|
||||
slice := NewMaxResourceSlice()
|
||||
createdSlice, err := tCtx.Client().ResourceV1beta2().ResourceSlices().Create(tCtx, slice, metav1.CreateOptions{})
|
||||
tCtx.ExpectNoError(err)
|
||||
totalSize := createdSlice.Size()
|
||||
var managedFieldsSize int
|
||||
for _, f := range createdSlice.ManagedFields {
|
||||
managedFieldsSize += f.Size()
|
||||
}
|
||||
specSize := createdSlice.Spec.Size()
|
||||
tCtx.Logf("\n\nTotal size: %s\nManagedFields size: %s (%.0f%%)\nSpec size: %s (%.0f)%%\n\nManagedFields:\n%s",
|
||||
resource.NewQuantity(int64(totalSize), resource.BinarySI),
|
||||
resource.NewQuantity(int64(managedFieldsSize), resource.BinarySI), float64(managedFieldsSize)*100/float64(totalSize),
|
||||
resource.NewQuantity(int64(specSize), resource.BinarySI), float64(specSize)*100/float64(totalSize),
|
||||
klog.Format(createdSlice.ManagedFields),
|
||||
)
|
||||
if diff := cmp.Diff(slice.Spec, createdSlice.Spec); diff != "" {
|
||||
tCtx.Errorf("ResourceSliceSpec got modified during Create (- want, + got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
160
test/integration/dra/objects.go
Normal file
160
test/integration/dra/objects.go
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
resourceapi "k8s.io/api/resource/v1beta2"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/resource/install"
|
||||
)
|
||||
|
||||
// NewMaxResourceSlice creates a slice that is as large as possible given the current validation constraints.
|
||||
func NewMaxResourceSlice() *resourceapi.ResourceSlice {
|
||||
slice := &resourceapi.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: maxSubDomain(0),
|
||||
// Number of labels is not restricted.
|
||||
Labels: maxKeyValueMap(10),
|
||||
// Total size of annotations is limited to TotalAnnotationSizeLimitB = 256 KB.
|
||||
// Let's be a bit more realistic.
|
||||
Annotations: maxKeyValueMap(10),
|
||||
},
|
||||
|
||||
Spec: resourceapi.ResourceSliceSpec{
|
||||
Driver: strings.Repeat("x", resourceapi.DriverNameMaxLength),
|
||||
Pool: resourceapi.ResourcePool{
|
||||
Name: strings.Repeat("x", resourceapi.PoolNameMaxLength),
|
||||
Generation: math.MaxInt64,
|
||||
ResourceSliceCount: math.MaxInt64,
|
||||
},
|
||||
// use PerDeviceNodeSelection as it requires setting the node selection on
|
||||
// every device and therefore will be the most expensive option in terms of
|
||||
// object size.
|
||||
PerDeviceNodeSelection: ptr.To(true),
|
||||
// The validation caps the total number of counters across all CounterSets. So
|
||||
// the most expensive option is to have a single counter per CounterSet.
|
||||
SharedCounters: func() []resourceapi.CounterSet {
|
||||
var counterSets []resourceapi.CounterSet
|
||||
for i := 0; i < resourceapi.ResourceSliceMaxSharedCounters; i++ {
|
||||
counterSets = append(counterSets, resourceapi.CounterSet{
|
||||
Name: maxDNSLabel(i),
|
||||
Counters: map[string]resourceapi.Counter{
|
||||
maxDNSLabel(0): {
|
||||
Value: resource.MustParse("80Gi"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return counterSets
|
||||
}(),
|
||||
Devices: func() []resourceapi.Device {
|
||||
var devices []resourceapi.Device
|
||||
for i := 0; i < resourceapi.ResourceSliceMaxDevices; i++ {
|
||||
devices = append(devices, resourceapi.Device{
|
||||
Name: maxDNSLabel(i),
|
||||
// Use attributes rather than capacity since it is more expensive.
|
||||
Attributes: func() map[resourceapi.QualifiedName]resourceapi.DeviceAttribute {
|
||||
attributes := make(map[resourceapi.QualifiedName]resourceapi.DeviceAttribute)
|
||||
for i := 0; i < resourceapi.ResourceSliceMaxAttributesAndCapacitiesPerDevice; i++ {
|
||||
attributes[maxResourceQualifiedName(i)] = resourceapi.DeviceAttribute{
|
||||
StringValue: ptr.To(maxDNSLabel(i)),
|
||||
}
|
||||
}
|
||||
return attributes
|
||||
}(),
|
||||
ConsumesCounters: func() []resourceapi.DeviceCounterConsumption {
|
||||
var consumesCounters []resourceapi.DeviceCounterConsumption
|
||||
for i := 0; i < resourceapi.ResourceSliceMaxDeviceCountersPerSlice/resourceapi.ResourceSliceMaxDevices; i++ {
|
||||
consumesCounters = append(consumesCounters, resourceapi.DeviceCounterConsumption{
|
||||
CounterSet: maxDNSLabel(i),
|
||||
Counters: map[string]resourceapi.Counter{
|
||||
maxDNSLabel(0): {
|
||||
Value: resource.MustParse("80Gi"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return consumesCounters
|
||||
}(),
|
||||
NodeName: ptr.To(maxSubDomain(0)),
|
||||
Taints: func() []resourceapi.DeviceTaint {
|
||||
var taints []resourceapi.DeviceTaint
|
||||
for i := 0; i < resourceapi.DeviceTaintsMaxLength; i++ {
|
||||
taints = append(taints, resourceapi.DeviceTaint{
|
||||
Key: maxLabelName(i),
|
||||
Value: maxLabelValue(i),
|
||||
Effect: resourceapi.DeviceTaintEffectNoSchedule,
|
||||
TimeAdded: &metav1.Time{Time: time.Now().Truncate(time.Second)},
|
||||
})
|
||||
}
|
||||
return taints
|
||||
}(),
|
||||
})
|
||||
}
|
||||
return devices
|
||||
}(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// maxKeyValueMap produces a map for labels or annotations.
|
||||
func maxKeyValueMap(n int) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for i := 0; i < n; i++ {
|
||||
m[maxQualifiedName(i)] = maxLabelValue(0)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func maxLabelName(i int) string {
|
||||
// A "label" is a qualified name.
|
||||
return maxQualifiedName(i)
|
||||
}
|
||||
|
||||
func maxResourceQualifiedName(i int) resourceapi.QualifiedName {
|
||||
return resourceapi.QualifiedName(maxString(i, resourceapi.DeviceMaxDomainLength) + "/" + maxString(i, resourceapi.DeviceMaxIDLength))
|
||||
}
|
||||
|
||||
func maxQualifiedName(i int) string {
|
||||
return maxString(0, validation.DNS1123SubdomainMaxLength-4) + ".com/" + maxString(i, 63 /* qualifiedNameMaxLength */)
|
||||
}
|
||||
|
||||
func maxLabelValue(i int) string {
|
||||
return maxString(0, validation.LabelValueMaxLength)
|
||||
}
|
||||
|
||||
func maxSubDomain(i int) string {
|
||||
return maxString(i, validation.DNS1123SubdomainMaxLength)
|
||||
}
|
||||
|
||||
func maxDNSLabel(i int) string {
|
||||
return maxString(i, validation.DNS1123LabelMaxLength)
|
||||
}
|
||||
|
||||
func maxString(i, l int) string {
|
||||
return strings.Repeat("x", l-4) + fmt.Sprintf("%04d", i)
|
||||
}
|
||||
@@ -625,6 +625,37 @@ func GetEtcdStorageDataForNamespaceServedAt(namespace string, v string, removeAl
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/resource/v1beta2
|
||||
gvr("resource.k8s.io", "v1beta2", "deviceclasses"): {
|
||||
Stub: `{"metadata": {"name": "class3name"}}`,
|
||||
ExpectedEtcdPath: "/registry/deviceclasses/class3name",
|
||||
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "DeviceClass"),
|
||||
IntroducedVersion: "1.33",
|
||||
RemovedVersion: "1.39",
|
||||
},
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaims"): {
|
||||
Stub: `{"metadata": {"name": "claim3name"}, "spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}}]}}}`,
|
||||
ExpectedEtcdPath: "/registry/resourceclaims/" + namespace + "/claim3name",
|
||||
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceClaim"),
|
||||
IntroducedVersion: "1.33",
|
||||
RemovedVersion: "1.39",
|
||||
},
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceclaimtemplates"): {
|
||||
Stub: `{"metadata": {"name": "claimtemplate3name"}, "spec": {"spec": {"devices": {"requests": [{"name": "req-0", "exactly": {"deviceClassName": "example-class", "allocationMode": "ExactCount", "count": 1}}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/resourceclaimtemplates/" + namespace + "/claimtemplate3name",
|
||||
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceClaimTemplate"),
|
||||
IntroducedVersion: "1.33",
|
||||
RemovedVersion: "1.39",
|
||||
},
|
||||
gvr("resource.k8s.io", "v1beta2", "resourceslices"): {
|
||||
Stub: `{"metadata": {"name": "node3slice"}, "spec": {"nodeName": "worker1", "driver": "dra.example.com", "pool": {"name": "worker1", "resourceSliceCount": 1}}}`,
|
||||
ExpectedEtcdPath: "/registry/resourceslices/node3slice",
|
||||
ExpectedGVK: gvkP("resource.k8s.io", "v1beta1", "ResourceSlice"),
|
||||
IntroducedVersion: "1.33",
|
||||
RemovedVersion: "1.39",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/apiserver/pkg/apis/apiserverinternal/v1alpha1
|
||||
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): {
|
||||
Stub: `{"metadata":{"name":"sv1.test"},"spec":{}}`,
|
||||
|
||||
Reference in New Issue
Block a user