mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-04-26 00:29:44 -04:00
Merge pull request #130447 from pohly/dra-device-taints
device taints and tolerations (KEP 5055)
This commit is contained in:
commit
ab3cec0701
149 changed files with 18346 additions and 484 deletions
|
|
@ -1915,6 +1915,26 @@
|
|||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"resource": "devicetaintrules",
|
||||
"responseKind": {
|
||||
"group": "",
|
||||
"kind": "DeviceTaintRule",
|
||||
"version": ""
|
||||
},
|
||||
"scope": "Cluster",
|
||||
"singularResource": "devicetaintrule",
|
||||
"verbs": [
|
||||
"create",
|
||||
"delete",
|
||||
"deletecollection",
|
||||
"get",
|
||||
"list",
|
||||
"patch",
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"resource": "resourceclaims",
|
||||
"responseKind": {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,23 @@
|
|||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "DeviceTaintRule",
|
||||
"name": "devicetaintrules",
|
||||
"namespaced": false,
|
||||
"singularName": "devicetaintrule",
|
||||
"storageVersionHash": "DJ3UJ0fj8MI=",
|
||||
"verbs": [
|
||||
"create",
|
||||
"delete",
|
||||
"deletecollection",
|
||||
"get",
|
||||
"list",
|
||||
"patch",
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "ResourceClaim",
|
||||
"name": "resourceclaims",
|
||||
|
|
|
|||
938
api/openapi-spec/swagger.json
generated
938
api/openapi-spec/swagger.json
generated
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -193,6 +193,19 @@
|
|||
},
|
||||
"description": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.",
|
||||
"type": "object"
|
||||
},
|
||||
"taints": {
|
||||
"description": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 8.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceTaint"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -607,6 +620,19 @@
|
|||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
},
|
||||
"tolerations": {
|
||||
"description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceToleration"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -640,6 +666,19 @@
|
|||
"default": "",
|
||||
"description": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format <main request>/<subrequest>.\n\nMultiple devices may have been allocated per request.",
|
||||
"type": "string"
|
||||
},
|
||||
"tolerations": {
|
||||
"description": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceToleration"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -698,6 +737,19 @@
|
|||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
},
|
||||
"tolerations": {
|
||||
"description": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceToleration"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "atomic"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -706,6 +758,66 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.resource.v1beta1.DeviceTaint": {
|
||||
"description": "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.",
|
||||
"properties": {
|
||||
"effect": {
|
||||
"default": "",
|
||||
"description": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"default": "",
|
||||
"description": "The taint key to be applied to a device. Must be a label name.",
|
||||
"type": "string"
|
||||
},
|
||||
"timeAdded": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
|
||||
}
|
||||
],
|
||||
"description": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set."
|
||||
},
|
||||
"value": {
|
||||
"description": "The taint value corresponding to the taint key. Must be a label value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"effect"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.resource.v1beta1.DeviceToleration": {
|
||||
"description": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.",
|
||||
"properties": {
|
||||
"effect": {
|
||||
"description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.",
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"default": "Equal",
|
||||
"description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.",
|
||||
"type": "string"
|
||||
},
|
||||
"tolerationSeconds": {
|
||||
"description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as <time when taint was adedd> + <toleration seconds>.",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"value": {
|
||||
"description": "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.resource.v1beta1.NetworkDeviceData": {
|
||||
"description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -588,6 +588,7 @@ func NewControllerDescriptors() map[string]*ControllerDescriptor {
|
|||
// feature gated
|
||||
register(newStorageVersionGarbageCollectorControllerDescriptor())
|
||||
register(newResourceClaimControllerDescriptor())
|
||||
register(newDeviceTaintEvictionControllerDescriptor())
|
||||
register(newLegacyServiceAccountTokenCleanerControllerDescriptor())
|
||||
register(newValidatingAdmissionPolicyStatusControllerDescriptor())
|
||||
register(newTaintEvictionControllerDescriptor())
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ func TestControllerNamesDeclaration(t *testing.T) {
|
|||
names.EphemeralVolumeController,
|
||||
names.StorageVersionGarbageCollectorController,
|
||||
names.ResourceClaimController,
|
||||
names.DeviceTaintEvictionController,
|
||||
names.LegacyServiceAccountTokenCleanerController,
|
||||
names.ValidatingAdmissionPolicyStatusController,
|
||||
names.ServiceCIDRController,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
||||
pkgcontroller "k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/controller/devicetainteviction"
|
||||
endpointcontroller "k8s.io/kubernetes/pkg/controller/endpoint"
|
||||
"k8s.io/kubernetes/pkg/controller/garbagecollector"
|
||||
namespacecontroller "k8s.io/kubernetes/pkg/controller/namespace"
|
||||
|
|
@ -231,6 +232,32 @@ func startTaintEvictionController(ctx context.Context, controllerContext Control
|
|||
return nil, true, nil
|
||||
}
|
||||
|
||||
func newDeviceTaintEvictionControllerDescriptor() *ControllerDescriptor {
|
||||
return &ControllerDescriptor{
|
||||
name: names.DeviceTaintEvictionController,
|
||||
initFunc: startDeviceTaintEvictionController,
|
||||
requiredFeatureGates: []featuregate.Feature{
|
||||
// TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing these feature gates.
|
||||
features.DynamicResourceAllocation,
|
||||
features.DRADeviceTaints,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func startDeviceTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) {
|
||||
deviceTaintEvictionController := devicetainteviction.New(
|
||||
controllerContext.ClientBuilder.ClientOrDie(names.DeviceTaintEvictionController),
|
||||
controllerContext.InformerFactory.Core().V1().Pods(),
|
||||
controllerContext.InformerFactory.Resource().V1beta1().ResourceClaims(),
|
||||
controllerContext.InformerFactory.Resource().V1beta1().ResourceSlices(),
|
||||
controllerContext.InformerFactory.Resource().V1alpha3().DeviceTaintRules(),
|
||||
controllerContext.InformerFactory.Resource().V1beta1().DeviceClasses(),
|
||||
controllerName,
|
||||
)
|
||||
go deviceTaintEvictionController.Run(ctx)
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func newCloudNodeLifecycleControllerDescriptor() *ControllerDescriptor {
|
||||
return &ControllerDescriptor{
|
||||
name: cpnames.CloudNodeLifecycleController,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const (
|
|||
NodeIpamController = "node-ipam-controller"
|
||||
NodeLifecycleController = "node-lifecycle-controller"
|
||||
TaintEvictionController = "taint-eviction-controller"
|
||||
DeviceTaintEvictionController = "device-taint-eviction-controller"
|
||||
PersistentVolumeBinderController = "persistentvolume-binder-controller"
|
||||
PersistentVolumeAttachDetachController = "persistentvolume-attach-detach-controller"
|
||||
PersistentVolumeExpanderController = "persistentvolume-expander-controller"
|
||||
|
|
|
|||
|
|
@ -135,14 +135,20 @@ func TestDefaulting(t *testing.T) {
|
|||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBindingList"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"}: {},
|
||||
{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBindingList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "DeviceTaintRule"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "DeviceTaintRuleList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceClaim"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceClaimList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceClaimTemplate"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceClaimTemplateList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceSlice"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1alpha3", Kind: "ResourceSliceList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceClaim"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceClaimList"}: {},
|
||||
{Group: "resource.k8s.io", Version: "v1beta1", Kind: "ResourceClaimTemplate"}: {},
|
||||
{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: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicy"}: {},
|
||||
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyList"}: {},
|
||||
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingAdmissionPolicyBinding"}: {},
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||
package fuzzer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
|
|
@ -57,6 +60,24 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||
}[c.Int31n(2)]
|
||||
}
|
||||
},
|
||||
func(r *resource.DeviceToleration, c randfill.Continue) {
|
||||
c.FillNoCustom(r)
|
||||
if r.Operator == "" {
|
||||
r.Operator = []resource.DeviceTolerationOperator{
|
||||
resource.DeviceTolerationOpEqual,
|
||||
resource.DeviceTolerationOpExists,
|
||||
}[c.Int31n(2)]
|
||||
}
|
||||
},
|
||||
func(r *resource.DeviceTaint, c randfill.Continue) {
|
||||
c.FillNoCustom(r)
|
||||
if r.TimeAdded == nil {
|
||||
// Current time is more or less random.
|
||||
// Truncate to seconds because sub-second resolution
|
||||
// does not survive round-tripping.
|
||||
r.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)}
|
||||
}
|
||||
},
|
||||
func(r *resource.OpaqueDeviceConfiguration, c randfill.Continue) {
|
||||
c.FillNoCustom(r)
|
||||
// Match the fuzzer default content for runtime.Object.
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&DeviceClass{},
|
||||
&DeviceClassList{},
|
||||
&DeviceTaintRule{},
|
||||
&DeviceTaintRuleList{},
|
||||
&ResourceClaim{},
|
||||
&ResourceClaimList{},
|
||||
&ResourceClaimTemplate{},
|
||||
|
|
|
|||
|
|
@ -219,6 +219,18 @@ type BasicDevice struct {
|
|||
//
|
||||
// +optional
|
||||
Capacity map[QualifiedName]DeviceCapacity
|
||||
|
||||
// If specified, these are the driver-defined taints.
|
||||
//
|
||||
// The maximum number of taints is 8.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Taints []DeviceTaint
|
||||
}
|
||||
|
||||
// DeviceCapacity describes a quantity associated with a device.
|
||||
|
|
@ -297,6 +309,64 @@ type DeviceAttribute struct {
|
|||
// DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value.
|
||||
const DeviceAttributeMaxValueLength = 64
|
||||
|
||||
// DeviceTaintsMaxLength is the maximum number of taints per device.
|
||||
const DeviceTaintsMaxLength = 8
|
||||
|
||||
// The device this taint is attached to has the "effect" on
|
||||
// any claim which does not tolerate the taint and, through the claim,
|
||||
// to pods using the claim.
|
||||
type DeviceTaint struct {
|
||||
// The taint key to be applied to a device.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +required
|
||||
Key string
|
||||
|
||||
// The taint value corresponding to the taint key.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string
|
||||
|
||||
// The effect of the taint on claims that do not tolerate the taint
|
||||
// and through such claims on the pods using them.
|
||||
// Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
|
||||
// nodes is not valid here.
|
||||
//
|
||||
// +required
|
||||
Effect DeviceTaintEffect
|
||||
|
||||
// ^^^^
|
||||
//
|
||||
// Implementing PreferNoSchedule would depend on a scoring solution for DRA.
|
||||
// It might get added as part of that.
|
||||
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// Added automatically during create or update if not set.
|
||||
//
|
||||
// +optional
|
||||
TimeAdded *metav1.Time
|
||||
|
||||
// ^^^
|
||||
//
|
||||
// This field was defined as "It is only written for NoExecute taints." for node taints.
|
||||
// But in practice, Kubernetes never did anything with it (no validation, no defaulting,
|
||||
// ignored during pod eviction in pkg/controller/tainteviction).
|
||||
}
|
||||
|
||||
// +enum
|
||||
type DeviceTaintEffect string
|
||||
|
||||
const (
|
||||
// Do not allow new pods to schedule which use a tainted device unless they tolerate the taint,
|
||||
// but allow all pods submitted to Kubelet without going through the scheduler
|
||||
// to start, and allow all already-running pods to continue running.
|
||||
DeviceTaintEffectNoSchedule DeviceTaintEffect = "NoSchedule"
|
||||
|
||||
// Evict any already-running pods that do not tolerate the device taint.
|
||||
DeviceTaintEffectNoExecute DeviceTaintEffect = "NoExecute"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ResourceSliceList is a collection of ResourceSlices.
|
||||
|
|
@ -511,6 +581,32 @@ type DeviceRequest struct {
|
|||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
FirstAvailable []DeviceSubRequest
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This 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.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration
|
||||
}
|
||||
|
||||
// DeviceSubRequest describes a request for device provided in the
|
||||
|
|
@ -583,11 +679,35 @@ type DeviceSubRequest struct {
|
|||
// +optional
|
||||
// +oneOf=AllocationMode
|
||||
Count int64
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration
|
||||
}
|
||||
|
||||
const (
|
||||
DeviceSelectorsMaxSize = 32
|
||||
FirstAvailableDeviceRequestMaxSize = 8
|
||||
DeviceTolerationsMaxLength = 16
|
||||
)
|
||||
|
||||
type DeviceAllocationMode string
|
||||
|
|
@ -793,6 +913,59 @@ type OpaqueDeviceConfiguration struct {
|
|||
// [OpaqueDeviceConfiguration.Parameters] field.
|
||||
const OpaqueParametersMaxLength = 10 * 1024
|
||||
|
||||
// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
|
||||
// the triple <key,value,effect> using the matching operator <operator>.
|
||||
type DeviceToleration struct {
|
||||
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +optional
|
||||
Key string
|
||||
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a ResourceClaim can
|
||||
// tolerate all taints of a particular category.
|
||||
//
|
||||
// +optional
|
||||
// +default="Equal"
|
||||
Operator DeviceTolerationOperator
|
||||
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value must be empty, otherwise just a regular string.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string
|
||||
|
||||
// Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||
// When specified, allowed values are NoSchedule and NoExecute.
|
||||
//
|
||||
// +optional
|
||||
Effect DeviceTaintEffect
|
||||
|
||||
// TolerationSeconds represents the period of time the toleration (which must be
|
||||
// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||
// it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||
// negative values will be treated as 0 (evict immediately) by the system.
|
||||
// If larger than zero, the time when the pod needs to be evicted is calculated as <time when
|
||||
// taint was adedd> + <toleration seconds>.
|
||||
//
|
||||
// +optional
|
||||
TolerationSeconds *int64
|
||||
}
|
||||
|
||||
// A toleration operator is the set of operators that can be used in a toleration.
|
||||
//
|
||||
// +enum
|
||||
type DeviceTolerationOperator string
|
||||
|
||||
const (
|
||||
DeviceTolerationOpExists DeviceTolerationOperator = "Exists"
|
||||
DeviceTolerationOpEqual DeviceTolerationOperator = "Equal"
|
||||
)
|
||||
|
||||
// ResourceClaimStatus tracks whether the resource has been allocated and what
|
||||
// the result of that was.
|
||||
type ResourceClaimStatus struct {
|
||||
|
|
@ -963,6 +1136,19 @@ type DeviceRequestAllocationResult struct {
|
|||
// +optional
|
||||
// +featureGate=DRAAdminAccess
|
||||
AdminAccess *bool
|
||||
|
||||
// A copy of all tolerations specified in the request at the time
|
||||
// when the device got allocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration
|
||||
}
|
||||
|
||||
// DeviceAllocationConfiguration gets embedded in an AllocationResult.
|
||||
|
|
@ -1227,3 +1413,103 @@ type NetworkDeviceData struct {
|
|||
// +optional
|
||||
HardwareAddress string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// DeviceTaintRule adds one taint to all devices which match the selector.
|
||||
// This has the same effect as if the taint was specified directly
|
||||
// in the ResourceSlice by the DRA driver.
|
||||
type DeviceTaintRule struct {
|
||||
metav1.TypeMeta
|
||||
// Standard object metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta
|
||||
|
||||
// Spec specifies the selector and one taint.
|
||||
//
|
||||
// Changing the spec automatically increments the metadata.generation number.
|
||||
Spec DeviceTaintRuleSpec
|
||||
|
||||
// ^^^
|
||||
// A spec gets added because adding a status seems likely.
|
||||
// Such a status could provide feedback on applying the
|
||||
// eviction and/or statistics (number of matching devices,
|
||||
// affected allocated claims, pods remaining to be evicted,
|
||||
// etc.).
|
||||
}
|
||||
|
||||
// DeviceTaintRuleSpec specifies the selector and one taint.
|
||||
type DeviceTaintRuleSpec struct {
|
||||
// DeviceSelector defines which device(s) the taint is applied to.
|
||||
// All selector criteria must be satified for a device to
|
||||
// match. The empty selector matches all devices. Without
|
||||
// a selector, no devices are matches.
|
||||
//
|
||||
// +optional
|
||||
DeviceSelector *DeviceTaintSelector
|
||||
|
||||
// The taint that gets applied to matching devices.
|
||||
//
|
||||
// +required
|
||||
Taint DeviceTaint
|
||||
}
|
||||
|
||||
// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to.
|
||||
// The empty selector matches all devices. Without a selector, no devices
|
||||
// are matched.
|
||||
type DeviceTaintSelector struct {
|
||||
// If DeviceClassName is set, the selectors defined there must be
|
||||
// satisfied by a device to be selected. This field corresponds
|
||||
// to class.metadata.name.
|
||||
//
|
||||
// +optional
|
||||
DeviceClassName *string
|
||||
|
||||
// If driver is set, only devices from that driver are selected.
|
||||
// This fields corresponds to slice.spec.driver.
|
||||
//
|
||||
// +optional
|
||||
Driver *string
|
||||
|
||||
// If pool is set, only devices in that pool are selected.
|
||||
//
|
||||
// Also setting the driver name may be useful to avoid
|
||||
// ambiguity when different drivers use the same pool name,
|
||||
// but this is not required because selecting pools from
|
||||
// different drivers may also be useful, for example when
|
||||
// drivers with node-local devices use the node name as
|
||||
// their pool name.
|
||||
//
|
||||
// +optional
|
||||
Pool *string
|
||||
|
||||
// If device is set, only devices with that name are selected.
|
||||
// This field corresponds to slice.spec.devices[].name.
|
||||
//
|
||||
// Setting also driver and pool may be required to avoid ambiguity,
|
||||
// but is not required.
|
||||
//
|
||||
// +optional
|
||||
Device *string
|
||||
|
||||
// Selectors contains the same selection criteria as a ResourceClaim.
|
||||
// Currently, CEL expressions are supported. All of these selectors
|
||||
// must be satisfied.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
Selectors []DeviceSelector
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// DeviceTaintRuleList is a collection of DeviceTaintRules.
|
||||
type DeviceTaintRuleList struct {
|
||||
metav1.TypeMeta
|
||||
// Standard list metadata
|
||||
// +optional
|
||||
metav1.ListMeta
|
||||
|
||||
// Items is the list of DeviceTaintRules.
|
||||
Items []DeviceTaintRule
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|||
package v1alpha3
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
resourceapi "k8s.io/api/resource/v1alpha3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -50,3 +53,9 @@ func SetDefaults_DeviceSubRequest(obj *resourceapi.DeviceSubRequest) {
|
|||
obj.Count = 1
|
||||
}
|
||||
}
|
||||
|
||||
func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) {
|
||||
if obj.TimeAdded == nil {
|
||||
obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ package v1alpha3_test
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
v1alpha3 "k8s.io/api/resource/v1alpha3"
|
||||
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"
|
||||
|
|
@ -134,6 +137,29 @@ func TestSetDefaultAllocationModeWithSubRequests(t *testing.T) {
|
|||
assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count)
|
||||
}
|
||||
|
||||
func TestSetDefaultDeviceTaint(t *testing.T) {
|
||||
slice := &v1alpha3.ResourceSlice{
|
||||
Spec: v1alpha3.ResourceSliceSpec{
|
||||
Devices: []v1alpha3.Device{{
|
||||
Name: "device-0",
|
||||
Basic: &v1alpha3.BasicDevice{
|
||||
Taints: []v1alpha3.DeviceTaint{{}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// fields should be defaulted
|
||||
output := roundTrip(t, slice).(*v1alpha3.ResourceSlice)
|
||||
assert.WithinDuration(t, time.Now(), ptr.Deref(output.Spec.Devices[0].Basic.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].Basic.Taints[0].TimeAdded = &metav1.Time{Time: timeAdded}
|
||||
output = roundTrip(t, slice).(*v1alpha3.ResourceSlice)
|
||||
assert.WithinDuration(t, timeAdded, ptr.Deref(output.Spec.Devices[0].Basic.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(v1alpha3.SchemeGroupVersion)
|
||||
data, err := runtime.Encode(codec, obj)
|
||||
|
|
|
|||
224
pkg/apis/resource/v1alpha3/zz_generated.conversion.go
generated
224
pkg/apis/resource/v1alpha3/zz_generated.conversion.go
generated
|
|
@ -242,6 +242,66 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceTaint)(nil), (*resource.DeviceTaint)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(a.(*resourcev1alpha3.DeviceTaint), b.(*resource.DeviceTaint), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaint)(nil), (*resourcev1alpha3.DeviceTaint)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(a.(*resource.DeviceTaint), b.(*resourcev1alpha3.DeviceTaint), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceTaintRule)(nil), (*resource.DeviceTaintRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceTaintRule_To_resource_DeviceTaintRule(a.(*resourcev1alpha3.DeviceTaintRule), b.(*resource.DeviceTaintRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRule)(nil), (*resourcev1alpha3.DeviceTaintRule)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaintRule_To_v1alpha3_DeviceTaintRule(a.(*resource.DeviceTaintRule), b.(*resourcev1alpha3.DeviceTaintRule), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceTaintRuleList)(nil), (*resource.DeviceTaintRuleList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(a.(*resourcev1alpha3.DeviceTaintRuleList), b.(*resource.DeviceTaintRuleList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRuleList)(nil), (*resourcev1alpha3.DeviceTaintRuleList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaintRuleList_To_v1alpha3_DeviceTaintRuleList(a.(*resource.DeviceTaintRuleList), b.(*resourcev1alpha3.DeviceTaintRuleList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceTaintRuleSpec)(nil), (*resource.DeviceTaintRuleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(a.(*resourcev1alpha3.DeviceTaintRuleSpec), b.(*resource.DeviceTaintRuleSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintRuleSpec)(nil), (*resourcev1alpha3.DeviceTaintRuleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec(a.(*resource.DeviceTaintRuleSpec), b.(*resourcev1alpha3.DeviceTaintRuleSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceTaintSelector)(nil), (*resource.DeviceTaintSelector)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceTaintSelector_To_resource_DeviceTaintSelector(a.(*resourcev1alpha3.DeviceTaintSelector), b.(*resource.DeviceTaintSelector), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaintSelector)(nil), (*resourcev1alpha3.DeviceTaintSelector)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(a.(*resource.DeviceTaintSelector), b.(*resourcev1alpha3.DeviceTaintSelector), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceToleration)(nil), (*resource.DeviceToleration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_DeviceToleration_To_resource_DeviceToleration(a.(*resourcev1alpha3.DeviceToleration), b.(*resource.DeviceToleration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceToleration)(nil), (*resourcev1alpha3.DeviceToleration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceToleration_To_v1alpha3_DeviceToleration(a.(*resource.DeviceToleration), b.(*resourcev1alpha3.DeviceToleration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.NetworkDeviceData)(nil), (*resource.NetworkDeviceData)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_NetworkDeviceData_To_resource_NetworkDeviceData(a.(*resourcev1alpha3.NetworkDeviceData), b.(*resource.NetworkDeviceData), scope)
|
||||
}); err != nil {
|
||||
|
|
@ -466,6 +526,7 @@ func autoConvert_v1alpha3_BasicDevice_To_resource_BasicDevice(in *resourcev1alph
|
|||
} else {
|
||||
out.Capacity = nil
|
||||
}
|
||||
out.Taints = *(*[]resource.DeviceTaint)(unsafe.Pointer(&in.Taints))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -489,6 +550,7 @@ func autoConvert_resource_BasicDevice_To_v1alpha3_BasicDevice(in *resource.Basic
|
|||
} else {
|
||||
out.Capacity = nil
|
||||
}
|
||||
out.Taints = *(*[]resourcev1alpha3.DeviceTaint)(unsafe.Pointer(&in.Taints))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -825,6 +887,7 @@ func autoConvert_v1alpha3_DeviceRequest_To_resource_DeviceRequest(in *resourcev1
|
|||
out.Count = in.Count
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.FirstAvailable = *(*[]resource.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable))
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -841,6 +904,7 @@ func autoConvert_resource_DeviceRequest_To_v1alpha3_DeviceRequest(in *resource.D
|
|||
out.Count = in.Count
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.FirstAvailable = *(*[]resourcev1alpha3.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable))
|
||||
out.Tolerations = *(*[]resourcev1alpha3.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -855,6 +919,7 @@ func autoConvert_v1alpha3_DeviceRequestAllocationResult_To_resource_DeviceReques
|
|||
out.Pool = in.Pool
|
||||
out.Device = in.Device
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +934,7 @@ func autoConvert_resource_DeviceRequestAllocationResult_To_v1alpha3_DeviceReques
|
|||
out.Pool = in.Pool
|
||||
out.Device = in.Device
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.Tolerations = *(*[]resourcev1alpha3.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -903,6 +969,7 @@ func autoConvert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest(in *reso
|
|||
out.Selectors = *(*[]resource.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode)
|
||||
out.Count = in.Count
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -917,6 +984,7 @@ func autoConvert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in *reso
|
|||
out.Selectors = *(*[]resourcev1alpha3.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
out.AllocationMode = resourcev1alpha3.DeviceAllocationMode(in.AllocationMode)
|
||||
out.Count = in.Count
|
||||
out.Tolerations = *(*[]resourcev1alpha3.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -925,6 +993,162 @@ func Convert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in *resource
|
|||
return autoConvert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(in *resourcev1alpha3.DeviceTaint, out *resource.DeviceTaint, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Value = in.Value
|
||||
out.Effect = resource.DeviceTaintEffect(in.Effect)
|
||||
out.TimeAdded = (*v1.Time)(unsafe.Pointer(in.TimeAdded))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceTaint_To_resource_DeviceTaint is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(in *resourcev1alpha3.DeviceTaint, out *resource.DeviceTaint, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(in *resource.DeviceTaint, out *resourcev1alpha3.DeviceTaint, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Value = in.Value
|
||||
out.Effect = resourcev1alpha3.DeviceTaintEffect(in.Effect)
|
||||
out.TimeAdded = (*v1.Time)(unsafe.Pointer(in.TimeAdded))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaint_To_v1alpha3_DeviceTaint is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(in *resource.DeviceTaint, out *resourcev1alpha3.DeviceTaint, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceTaintRule_To_resource_DeviceTaintRule(in *resourcev1alpha3.DeviceTaintRule, out *resource.DeviceTaintRule, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceTaintRule_To_resource_DeviceTaintRule is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceTaintRule_To_resource_DeviceTaintRule(in *resourcev1alpha3.DeviceTaintRule, out *resource.DeviceTaintRule, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceTaintRule_To_resource_DeviceTaintRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaintRule_To_v1alpha3_DeviceTaintRule(in *resource.DeviceTaintRule, out *resourcev1alpha3.DeviceTaintRule, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaintRule_To_v1alpha3_DeviceTaintRule is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaintRule_To_v1alpha3_DeviceTaintRule(in *resource.DeviceTaintRule, out *resourcev1alpha3.DeviceTaintRule, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaintRule_To_v1alpha3_DeviceTaintRule(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in *resourcev1alpha3.DeviceTaintRuleList, out *resource.DeviceTaintRuleList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]resource.DeviceTaintRule)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceTaintRuleList_To_resource_DeviceTaintRuleList is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in *resourcev1alpha3.DeviceTaintRuleList, out *resource.DeviceTaintRuleList, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceTaintRuleList_To_resource_DeviceTaintRuleList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaintRuleList_To_v1alpha3_DeviceTaintRuleList(in *resource.DeviceTaintRuleList, out *resourcev1alpha3.DeviceTaintRuleList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]resourcev1alpha3.DeviceTaintRule)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaintRuleList_To_v1alpha3_DeviceTaintRuleList is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaintRuleList_To_v1alpha3_DeviceTaintRuleList(in *resource.DeviceTaintRuleList, out *resourcev1alpha3.DeviceTaintRuleList, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaintRuleList_To_v1alpha3_DeviceTaintRuleList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in *resourcev1alpha3.DeviceTaintRuleSpec, out *resource.DeviceTaintRuleSpec, s conversion.Scope) error {
|
||||
out.DeviceSelector = (*resource.DeviceTaintSelector)(unsafe.Pointer(in.DeviceSelector))
|
||||
if err := Convert_v1alpha3_DeviceTaint_To_resource_DeviceTaint(&in.Taint, &out.Taint, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in *resourcev1alpha3.DeviceTaintRuleSpec, out *resource.DeviceTaintRuleSpec, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceTaintRuleSpec_To_resource_DeviceTaintRuleSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec(in *resource.DeviceTaintRuleSpec, out *resourcev1alpha3.DeviceTaintRuleSpec, s conversion.Scope) error {
|
||||
out.DeviceSelector = (*resourcev1alpha3.DeviceTaintSelector)(unsafe.Pointer(in.DeviceSelector))
|
||||
if err := Convert_resource_DeviceTaint_To_v1alpha3_DeviceTaint(&in.Taint, &out.Taint, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec(in *resource.DeviceTaintRuleSpec, out *resourcev1alpha3.DeviceTaintRuleSpec, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaintRuleSpec_To_v1alpha3_DeviceTaintRuleSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceTaintSelector_To_resource_DeviceTaintSelector(in *resourcev1alpha3.DeviceTaintSelector, out *resource.DeviceTaintSelector, s conversion.Scope) error {
|
||||
out.DeviceClassName = (*string)(unsafe.Pointer(in.DeviceClassName))
|
||||
out.Driver = (*string)(unsafe.Pointer(in.Driver))
|
||||
out.Pool = (*string)(unsafe.Pointer(in.Pool))
|
||||
out.Device = (*string)(unsafe.Pointer(in.Device))
|
||||
out.Selectors = *(*[]resource.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceTaintSelector_To_resource_DeviceTaintSelector is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceTaintSelector_To_resource_DeviceTaintSelector(in *resourcev1alpha3.DeviceTaintSelector, out *resource.DeviceTaintSelector, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceTaintSelector_To_resource_DeviceTaintSelector(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in *resource.DeviceTaintSelector, out *resourcev1alpha3.DeviceTaintSelector, s conversion.Scope) error {
|
||||
out.DeviceClassName = (*string)(unsafe.Pointer(in.DeviceClassName))
|
||||
out.Driver = (*string)(unsafe.Pointer(in.Driver))
|
||||
out.Pool = (*string)(unsafe.Pointer(in.Pool))
|
||||
out.Device = (*string)(unsafe.Pointer(in.Device))
|
||||
out.Selectors = *(*[]resourcev1alpha3.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in *resource.DeviceTaintSelector, out *resourcev1alpha3.DeviceTaintSelector, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaintSelector_To_v1alpha3_DeviceTaintSelector(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_DeviceToleration_To_resource_DeviceToleration(in *resourcev1alpha3.DeviceToleration, out *resource.DeviceToleration, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Operator = resource.DeviceTolerationOperator(in.Operator)
|
||||
out.Value = in.Value
|
||||
out.Effect = resource.DeviceTaintEffect(in.Effect)
|
||||
out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha3_DeviceToleration_To_resource_DeviceToleration is an autogenerated conversion function.
|
||||
func Convert_v1alpha3_DeviceToleration_To_resource_DeviceToleration(in *resourcev1alpha3.DeviceToleration, out *resource.DeviceToleration, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha3_DeviceToleration_To_resource_DeviceToleration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceToleration_To_v1alpha3_DeviceToleration(in *resource.DeviceToleration, out *resourcev1alpha3.DeviceToleration, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Operator = resourcev1alpha3.DeviceTolerationOperator(in.Operator)
|
||||
out.Value = in.Value
|
||||
out.Effect = resourcev1alpha3.DeviceTaintEffect(in.Effect)
|
||||
out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceToleration_To_v1alpha3_DeviceToleration is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceToleration_To_v1alpha3_DeviceToleration(in *resource.DeviceToleration, out *resourcev1alpha3.DeviceToleration, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceToleration_To_v1alpha3_DeviceToleration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_NetworkDeviceData_To_resource_NetworkDeviceData(in *resourcev1alpha3.NetworkDeviceData, out *resource.NetworkDeviceData, s conversion.Scope) error {
|
||||
out.InterfaceName = in.InterfaceName
|
||||
out.IPs = *(*[]string)(unsafe.Pointer(&in.IPs))
|
||||
|
|
|
|||
71
pkg/apis/resource/v1alpha3/zz_generated.defaults.go
generated
71
pkg/apis/resource/v1alpha3/zz_generated.defaults.go
generated
|
|
@ -30,6 +30,10 @@ import (
|
|||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.DeviceTaintRule{}, func(obj interface{}) { SetObjectDefaults_DeviceTaintRule(obj.(*resourcev1alpha3.DeviceTaintRule)) })
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.DeviceTaintRuleList{}, func(obj interface{}) {
|
||||
SetObjectDefaults_DeviceTaintRuleList(obj.(*resourcev1alpha3.DeviceTaintRuleList))
|
||||
})
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceClaim{}, func(obj interface{}) { SetObjectDefaults_ResourceClaim(obj.(*resourcev1alpha3.ResourceClaim)) })
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceClaimList{}, func(obj interface{}) { SetObjectDefaults_ResourceClaimList(obj.(*resourcev1alpha3.ResourceClaimList)) })
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceClaimTemplate{}, func(obj interface{}) {
|
||||
|
|
@ -38,9 +42,22 @@ func RegisterDefaults(scheme *runtime.Scheme) error {
|
|||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceClaimTemplateList{}, func(obj interface{}) {
|
||||
SetObjectDefaults_ResourceClaimTemplateList(obj.(*resourcev1alpha3.ResourceClaimTemplateList))
|
||||
})
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceSlice{}, func(obj interface{}) { SetObjectDefaults_ResourceSlice(obj.(*resourcev1alpha3.ResourceSlice)) })
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1alpha3.ResourceSliceList{}, func(obj interface{}) { SetObjectDefaults_ResourceSliceList(obj.(*resourcev1alpha3.ResourceSliceList)) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetObjectDefaults_DeviceTaintRule(in *resourcev1alpha3.DeviceTaintRule) {
|
||||
SetDefaults_DeviceTaint(&in.Spec.Taint)
|
||||
}
|
||||
|
||||
func SetObjectDefaults_DeviceTaintRuleList(in *resourcev1alpha3.DeviceTaintRuleList) {
|
||||
for i := range in.Items {
|
||||
a := &in.Items[i]
|
||||
SetObjectDefaults_DeviceTaintRule(a)
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_ResourceClaim(in *resourcev1alpha3.ResourceClaim) {
|
||||
for i := range in.Spec.Devices.Requests {
|
||||
a := &in.Spec.Devices.Requests[i]
|
||||
|
|
@ -48,6 +65,29 @@ func SetObjectDefaults_ResourceClaim(in *resourcev1alpha3.ResourceClaim) {
|
|||
for j := range a.FirstAvailable {
|
||||
b := &a.FirstAvailable[j]
|
||||
SetDefaults_DeviceSubRequest(b)
|
||||
for k := range b.Tolerations {
|
||||
c := &b.Tolerations[k]
|
||||
if c.Operator == "" {
|
||||
c.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.Status.Allocation != nil {
|
||||
for i := range in.Status.Allocation.Devices.Results {
|
||||
a := &in.Status.Allocation.Devices.Results[i]
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +106,18 @@ func SetObjectDefaults_ResourceClaimTemplate(in *resourcev1alpha3.ResourceClaimT
|
|||
for j := range a.FirstAvailable {
|
||||
b := &a.FirstAvailable[j]
|
||||
SetDefaults_DeviceSubRequest(b)
|
||||
for k := range b.Tolerations {
|
||||
c := &b.Tolerations[k]
|
||||
if c.Operator == "" {
|
||||
c.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,3 +128,22 @@ func SetObjectDefaults_ResourceClaimTemplateList(in *resourcev1alpha3.ResourceCl
|
|||
SetObjectDefaults_ResourceClaimTemplate(a)
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_ResourceSlice(in *resourcev1alpha3.ResourceSlice) {
|
||||
for i := range in.Spec.Devices {
|
||||
a := &in.Spec.Devices[i]
|
||||
if a.Basic != nil {
|
||||
for j := range a.Basic.Taints {
|
||||
b := &a.Basic.Taints[j]
|
||||
SetDefaults_DeviceTaint(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_ResourceSliceList(in *resourcev1alpha3.ResourceSliceList) {
|
||||
for i := range in.Items {
|
||||
a := &in.Items[i]
|
||||
SetObjectDefaults_ResourceSlice(a)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|||
package v1beta1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -50,3 +53,9 @@ func SetDefaults_DeviceSubRequest(obj *resourceapi.DeviceSubRequest) {
|
|||
obj.Count = 1
|
||||
}
|
||||
}
|
||||
|
||||
func SetDefaults_DeviceTaint(obj *resourceapi.DeviceTaint) {
|
||||
if obj.TimeAdded == nil {
|
||||
obj.TimeAdded = &metav1.Time{Time: time.Now().Truncate(time.Second)}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ package v1beta1_test
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
v1beta1 "k8s.io/api/resource/v1beta1"
|
||||
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"
|
||||
|
|
@ -134,6 +137,29 @@ func TestSetDefaultAllocationModeWithSubRequests(t *testing.T) {
|
|||
assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count)
|
||||
}
|
||||
|
||||
func TestSetDefaultDeviceTaint(t *testing.T) {
|
||||
slice := &v1beta1.ResourceSlice{
|
||||
Spec: v1beta1.ResourceSliceSpec{
|
||||
Devices: []v1beta1.Device{{
|
||||
Name: "device-0",
|
||||
Basic: &v1beta1.BasicDevice{
|
||||
Taints: []v1beta1.DeviceTaint{{}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// fields should be defaulted
|
||||
output := roundTrip(t, slice).(*v1beta1.ResourceSlice)
|
||||
assert.WithinDuration(t, time.Now(), ptr.Deref(output.Spec.Devices[0].Basic.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].Basic.Taints[0].TimeAdded = &metav1.Time{Time: timeAdded}
|
||||
output = roundTrip(t, slice).(*v1beta1.ResourceSlice)
|
||||
assert.WithinDuration(t, timeAdded, ptr.Deref(output.Spec.Devices[0].Basic.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(v1beta1.SchemeGroupVersion)
|
||||
data, err := runtime.Encode(codec, obj)
|
||||
|
|
|
|||
82
pkg/apis/resource/v1beta1/zz_generated.conversion.go
generated
82
pkg/apis/resource/v1beta1/zz_generated.conversion.go
generated
|
|
@ -251,6 +251,26 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1beta1.DeviceTaint)(nil), (*resource.DeviceTaint)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_DeviceTaint_To_resource_DeviceTaint(a.(*resourcev1beta1.DeviceTaint), b.(*resource.DeviceTaint), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceTaint)(nil), (*resourcev1beta1.DeviceTaint)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceTaint_To_v1beta1_DeviceTaint(a.(*resource.DeviceTaint), b.(*resourcev1beta1.DeviceTaint), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1beta1.DeviceToleration)(nil), (*resource.DeviceToleration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_DeviceToleration_To_resource_DeviceToleration(a.(*resourcev1beta1.DeviceToleration), b.(*resource.DeviceToleration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resource.DeviceToleration)(nil), (*resourcev1beta1.DeviceToleration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_resource_DeviceToleration_To_v1beta1_DeviceToleration(a.(*resource.DeviceToleration), b.(*resourcev1beta1.DeviceToleration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*resourcev1beta1.NetworkDeviceData)(nil), (*resource.NetworkDeviceData)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_NetworkDeviceData_To_resource_NetworkDeviceData(a.(*resourcev1beta1.NetworkDeviceData), b.(*resource.NetworkDeviceData), scope)
|
||||
}); err != nil {
|
||||
|
|
@ -453,6 +473,7 @@ func Convert_resource_AllocationResult_To_v1beta1_AllocationResult(in *resource.
|
|||
func autoConvert_v1beta1_BasicDevice_To_resource_BasicDevice(in *resourcev1beta1.BasicDevice, out *resource.BasicDevice, s conversion.Scope) error {
|
||||
out.Attributes = *(*map[resource.QualifiedName]resource.DeviceAttribute)(unsafe.Pointer(&in.Attributes))
|
||||
out.Capacity = *(*map[resource.QualifiedName]resource.DeviceCapacity)(unsafe.Pointer(&in.Capacity))
|
||||
out.Taints = *(*[]resource.DeviceTaint)(unsafe.Pointer(&in.Taints))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -464,6 +485,7 @@ func Convert_v1beta1_BasicDevice_To_resource_BasicDevice(in *resourcev1beta1.Bas
|
|||
func autoConvert_resource_BasicDevice_To_v1beta1_BasicDevice(in *resource.BasicDevice, out *resourcev1beta1.BasicDevice, s conversion.Scope) error {
|
||||
out.Attributes = *(*map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute)(unsafe.Pointer(&in.Attributes))
|
||||
out.Capacity = *(*map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceCapacity)(unsafe.Pointer(&in.Capacity))
|
||||
out.Taints = *(*[]resourcev1beta1.DeviceTaint)(unsafe.Pointer(&in.Taints))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -804,6 +826,7 @@ func autoConvert_v1beta1_DeviceRequest_To_resource_DeviceRequest(in *resourcev1b
|
|||
out.Count = in.Count
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.FirstAvailable = *(*[]resource.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable))
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -820,6 +843,7 @@ func autoConvert_resource_DeviceRequest_To_v1beta1_DeviceRequest(in *resource.De
|
|||
out.Count = in.Count
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.FirstAvailable = *(*[]resourcev1beta1.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable))
|
||||
out.Tolerations = *(*[]resourcev1beta1.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -834,6 +858,7 @@ func autoConvert_v1beta1_DeviceRequestAllocationResult_To_resource_DeviceRequest
|
|||
out.Pool = in.Pool
|
||||
out.Device = in.Device
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -848,6 +873,7 @@ func autoConvert_resource_DeviceRequestAllocationResult_To_v1beta1_DeviceRequest
|
|||
out.Pool = in.Pool
|
||||
out.Device = in.Device
|
||||
out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess))
|
||||
out.Tolerations = *(*[]resourcev1beta1.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -882,6 +908,7 @@ func autoConvert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest(in *resou
|
|||
out.Selectors = *(*[]resource.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode)
|
||||
out.Count = in.Count
|
||||
out.Tolerations = *(*[]resource.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -896,6 +923,7 @@ func autoConvert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in *resou
|
|||
out.Selectors = *(*[]resourcev1beta1.DeviceSelector)(unsafe.Pointer(&in.Selectors))
|
||||
out.AllocationMode = resourcev1beta1.DeviceAllocationMode(in.AllocationMode)
|
||||
out.Count = in.Count
|
||||
out.Tolerations = *(*[]resourcev1beta1.DeviceToleration)(unsafe.Pointer(&in.Tolerations))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -904,6 +932,60 @@ func Convert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in *resource.
|
|||
return autoConvert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_DeviceTaint_To_resource_DeviceTaint(in *resourcev1beta1.DeviceTaint, out *resource.DeviceTaint, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Value = in.Value
|
||||
out.Effect = resource.DeviceTaintEffect(in.Effect)
|
||||
out.TimeAdded = (*v1.Time)(unsafe.Pointer(in.TimeAdded))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_DeviceTaint_To_resource_DeviceTaint is an autogenerated conversion function.
|
||||
func Convert_v1beta1_DeviceTaint_To_resource_DeviceTaint(in *resourcev1beta1.DeviceTaint, out *resource.DeviceTaint, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_DeviceTaint_To_resource_DeviceTaint(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceTaint_To_v1beta1_DeviceTaint(in *resource.DeviceTaint, out *resourcev1beta1.DeviceTaint, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Value = in.Value
|
||||
out.Effect = resourcev1beta1.DeviceTaintEffect(in.Effect)
|
||||
out.TimeAdded = (*v1.Time)(unsafe.Pointer(in.TimeAdded))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceTaint_To_v1beta1_DeviceTaint is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceTaint_To_v1beta1_DeviceTaint(in *resource.DeviceTaint, out *resourcev1beta1.DeviceTaint, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceTaint_To_v1beta1_DeviceTaint(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_DeviceToleration_To_resource_DeviceToleration(in *resourcev1beta1.DeviceToleration, out *resource.DeviceToleration, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Operator = resource.DeviceTolerationOperator(in.Operator)
|
||||
out.Value = in.Value
|
||||
out.Effect = resource.DeviceTaintEffect(in.Effect)
|
||||
out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_DeviceToleration_To_resource_DeviceToleration is an autogenerated conversion function.
|
||||
func Convert_v1beta1_DeviceToleration_To_resource_DeviceToleration(in *resourcev1beta1.DeviceToleration, out *resource.DeviceToleration, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_DeviceToleration_To_resource_DeviceToleration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_resource_DeviceToleration_To_v1beta1_DeviceToleration(in *resource.DeviceToleration, out *resourcev1beta1.DeviceToleration, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Operator = resourcev1beta1.DeviceTolerationOperator(in.Operator)
|
||||
out.Value = in.Value
|
||||
out.Effect = resourcev1beta1.DeviceTaintEffect(in.Effect)
|
||||
out.TolerationSeconds = (*int64)(unsafe.Pointer(in.TolerationSeconds))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_resource_DeviceToleration_To_v1beta1_DeviceToleration is an autogenerated conversion function.
|
||||
func Convert_resource_DeviceToleration_To_v1beta1_DeviceToleration(in *resource.DeviceToleration, out *resourcev1beta1.DeviceToleration, s conversion.Scope) error {
|
||||
return autoConvert_resource_DeviceToleration_To_v1beta1_DeviceToleration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_NetworkDeviceData_To_resource_NetworkDeviceData(in *resourcev1beta1.NetworkDeviceData, out *resource.NetworkDeviceData, s conversion.Scope) error {
|
||||
out.InterfaceName = in.InterfaceName
|
||||
out.IPs = *(*[]string)(unsafe.Pointer(&in.IPs))
|
||||
|
|
|
|||
56
pkg/apis/resource/v1beta1/zz_generated.defaults.go
generated
56
pkg/apis/resource/v1beta1/zz_generated.defaults.go
generated
|
|
@ -38,6 +38,8 @@ func RegisterDefaults(scheme *runtime.Scheme) error {
|
|||
scheme.AddTypeDefaultingFunc(&resourcev1beta1.ResourceClaimTemplateList{}, func(obj interface{}) {
|
||||
SetObjectDefaults_ResourceClaimTemplateList(obj.(*resourcev1beta1.ResourceClaimTemplateList))
|
||||
})
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1beta1.ResourceSlice{}, func(obj interface{}) { SetObjectDefaults_ResourceSlice(obj.(*resourcev1beta1.ResourceSlice)) })
|
||||
scheme.AddTypeDefaultingFunc(&resourcev1beta1.ResourceSliceList{}, func(obj interface{}) { SetObjectDefaults_ResourceSliceList(obj.(*resourcev1beta1.ResourceSliceList)) })
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +50,29 @@ func SetObjectDefaults_ResourceClaim(in *resourcev1beta1.ResourceClaim) {
|
|||
for j := range a.FirstAvailable {
|
||||
b := &a.FirstAvailable[j]
|
||||
SetDefaults_DeviceSubRequest(b)
|
||||
for k := range b.Tolerations {
|
||||
c := &b.Tolerations[k]
|
||||
if c.Operator == "" {
|
||||
c.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.Status.Allocation != nil {
|
||||
for i := range in.Status.Allocation.Devices.Results {
|
||||
a := &in.Status.Allocation.Devices.Results[i]
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +91,18 @@ func SetObjectDefaults_ResourceClaimTemplate(in *resourcev1beta1.ResourceClaimTe
|
|||
for j := range a.FirstAvailable {
|
||||
b := &a.FirstAvailable[j]
|
||||
SetDefaults_DeviceSubRequest(b)
|
||||
for k := range b.Tolerations {
|
||||
c := &b.Tolerations[k]
|
||||
if c.Operator == "" {
|
||||
c.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range a.Tolerations {
|
||||
b := &a.Tolerations[j]
|
||||
if b.Operator == "" {
|
||||
b.Operator = "Equal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,3 +113,22 @@ func SetObjectDefaults_ResourceClaimTemplateList(in *resourcev1beta1.ResourceCla
|
|||
SetObjectDefaults_ResourceClaimTemplate(a)
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_ResourceSlice(in *resourcev1beta1.ResourceSlice) {
|
||||
for i := range in.Spec.Devices {
|
||||
a := &in.Spec.Devices[i]
|
||||
if a.Basic != nil {
|
||||
for j := range a.Basic.Taints {
|
||||
b := &a.Basic.Taints[j]
|
||||
SetDefaults_DeviceTaint(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_ResourceSliceList(in *resourcev1beta1.ResourceSliceList) {
|
||||
for i := range in.Items {
|
||||
a := &in.Items[i]
|
||||
SetObjectDefaults_ResourceSlice(a)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,6 +199,10 @@ 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))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +211,9 @@ func validateDeviceSubRequest(subRequest resource.DeviceSubRequest, fldPath *fie
|
|||
allErrs = append(allErrs, validateDeviceClass(subRequest.DeviceClassName, fldPath.Child("deviceClassName"))...)
|
||||
allErrs = append(allErrs, validateSelectorSlice(subRequest.Selectors, fldPath.Child("selectors"), stored)...)
|
||||
allErrs = append(allErrs, validateDeviceAllocationMode(subRequest.AllocationMode, subRequest.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
|
||||
for i, toleration := range subRequest.Tolerations {
|
||||
allErrs = append(allErrs, validateDeviceToleration(toleration, fldPath.Child("tolerations").Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
@ -642,6 +649,9 @@ func validateBasicDevice(device resource.BasicDevice, fldPath *field.Path) field
|
|||
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))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
|
@ -675,19 +685,12 @@ func validateDeviceAttribute(attribute resource.DeviceAttribute, fldPath *field.
|
|||
numFields++
|
||||
}
|
||||
if attribute.StringValue != nil {
|
||||
if len(*attribute.StringValue) > resource.DeviceAttributeMaxValueLength {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath.Child("string"), "" /*unused*/, resource.DeviceAttributeMaxValueLength))
|
||||
}
|
||||
numFields++
|
||||
allErrs = append(allErrs, validateDeviceAttributeStringValue(attribute.StringValue, fldPath.Child("string"))...)
|
||||
}
|
||||
if attribute.VersionValue != nil {
|
||||
numFields++
|
||||
if !semverRe.MatchString(*attribute.VersionValue) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("version"), *attribute.VersionValue, "must be a string compatible with semver.org spec 2.0.0"))
|
||||
}
|
||||
if len(*attribute.VersionValue) > resource.DeviceAttributeMaxValueLength {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath.Child("version"), "" /*unused*/, resource.DeviceAttributeMaxValueLength))
|
||||
}
|
||||
allErrs = append(allErrs, validateDeviceAttributeVersionValue(attribute.VersionValue, fldPath.Child("version"))...)
|
||||
}
|
||||
|
||||
switch numFields {
|
||||
|
|
@ -701,6 +704,25 @@ func validateDeviceAttribute(attribute resource.DeviceAttribute, fldPath *field.
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceAttributeStringValue(value *string, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if len(*value) > resource.DeviceAttributeMaxValueLength {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, resource.DeviceAttributeMaxValueLength))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceAttributeVersionValue(value *string, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if !semverRe.MatchString(*value) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, "must be a string compatible with semver.org spec 2.0.0"))
|
||||
}
|
||||
if len(*value) > resource.DeviceAttributeMaxValueLength {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, resource.DeviceAttributeMaxValueLength))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceCapacity(capacity resource.DeviceCapacity, fldPath *field.Path) field.ErrorList {
|
||||
// Any parsed quantity is valid.
|
||||
return nil
|
||||
|
|
@ -893,3 +915,123 @@ func validateNetworkDeviceData(networkDeviceData *resource.NetworkDeviceData, fl
|
|||
}, stringKey, fldPath.Child("ips"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateDeviceTaintRule tests if a DeviceTaintRule object is valid.
|
||||
func ValidateDeviceTaintRule(deviceTaint *resource.DeviceTaintRule) field.ErrorList {
|
||||
allErrs := corevalidation.ValidateObjectMeta(&deviceTaint.ObjectMeta, false, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, validateDeviceTaintRuleSpec(&deviceTaint.Spec, nil, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateDeviceTaintRuleUpdate tests if a DeviceTaintRule update is valid.
|
||||
func ValidateDeviceTaintRuleUpdate(deviceTaint, oldDeviceTaint *resource.DeviceTaintRule) field.ErrorList {
|
||||
allErrs := corevalidation.ValidateObjectMetaUpdate(&deviceTaint.ObjectMeta, &oldDeviceTaint.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, validateDeviceTaintRuleSpec(&deviceTaint.Spec, &oldDeviceTaint.Spec, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceTaintRuleSpec(spec, oldSpec *resource.DeviceTaintRuleSpec, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
var oldFilter *resource.DeviceTaintSelector
|
||||
if oldSpec != nil {
|
||||
oldFilter = oldSpec.DeviceSelector // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
allErrs = append(allErrs, validateDeviceTaintSelector(spec.DeviceSelector, oldFilter, fldPath.Child("deviceSelector"))...)
|
||||
allErrs = append(allErrs, validateDeviceTaint(spec.Taint, fldPath.Child("taint"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceTaintSelector(filter, oldFilter *resource.DeviceTaintSelector, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
if filter == nil {
|
||||
return allErrs
|
||||
}
|
||||
if filter.DeviceClassName != nil {
|
||||
allErrs = append(allErrs, validateDeviceClassName(*filter.DeviceClassName, fldPath.Child("deviceClassName"))...)
|
||||
}
|
||||
if filter.Driver != nil {
|
||||
allErrs = append(allErrs, validateDriverName(*filter.Driver, fldPath.Child("driver"))...)
|
||||
}
|
||||
if filter.Pool != nil {
|
||||
allErrs = append(allErrs, validatePoolName(*filter.Pool, fldPath.Child("pool"))...)
|
||||
}
|
||||
if filter.Device != nil {
|
||||
allErrs = append(allErrs, validateDeviceName(*filter.Device, fldPath.Child("device"))...)
|
||||
}
|
||||
|
||||
// If the selectors are exactly as before, we treat the CEL expressions as "stored".
|
||||
// Any change, including merely reordering selectors, triggers validation as new
|
||||
// expressions.
|
||||
stored := false
|
||||
if oldFilter != nil {
|
||||
stored = apiequality.Semantic.DeepEqual(filter.Selectors, oldFilter.Selectors)
|
||||
}
|
||||
allErrs = append(allErrs, validateSlice(filter.Selectors, resource.DeviceSelectorsMaxSize,
|
||||
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
|
||||
return validateSelector(selector, fldPath, stored)
|
||||
},
|
||||
fldPath.Child("selectors"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var validDeviceTolerationOperators = []resource.DeviceTolerationOperator{resource.DeviceTolerationOpEqual, resource.DeviceTolerationOpExists}
|
||||
var validDeviceTaintEffects = sets.New(resource.DeviceTaintEffectNoSchedule, resource.DeviceTaintEffectNoExecute)
|
||||
|
||||
func validateDeviceTaint(taint resource.DeviceTaint, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allErrs = append(allErrs, metav1validation.ValidateLabelName(taint.Key, fldPath.Child("key"))...) // Includes checking for non-empty.
|
||||
if taint.Value != "" {
|
||||
allErrs = append(allErrs, validateLabelValue(taint.Value, fldPath.Child("value"))...)
|
||||
}
|
||||
switch {
|
||||
case taint.Effect == "":
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("effect"), "")) // Required in a taint.
|
||||
case !validDeviceTaintEffects.Has(taint.Effect):
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("effect"), taint.Effect, sets.List(validDeviceTaintEffects)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDeviceToleration(toleration resource.DeviceToleration, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
if toleration.Key != "" {
|
||||
allErrs = append(allErrs, metav1validation.ValidateLabelName(toleration.Key, fldPath.Child("key"))...)
|
||||
}
|
||||
switch toleration.Operator {
|
||||
case resource.DeviceTolerationOpExists:
|
||||
if toleration.Value != "" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("value"), toleration.Value, "must be empty for operator `Exists`"))
|
||||
}
|
||||
case resource.DeviceTolerationOpEqual:
|
||||
allErrs = append(allErrs, validateLabelValue(toleration.Value, fldPath.Child("value"))...)
|
||||
case "":
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("operator"), ""))
|
||||
default:
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("operator"), toleration.Operator, validDeviceTolerationOperators))
|
||||
}
|
||||
switch {
|
||||
case toleration.Effect == "":
|
||||
// Optional in a toleration.
|
||||
case !validDeviceTaintEffects.Has(toleration.Effect):
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("effect"), toleration.Effect, sets.List(validDeviceTaintEffects)))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateLabelValue(value string, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
// There's no metav1validation.ValidateLabelValue.
|
||||
for _, msg := range validation.IsValidLabelValue(value) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
|
|
|||
351
pkg/apis/resource/validation/validation_devicetaintrule_test.go
Normal file
351
pkg/apis/resource/validation/validation_devicetaintrule_test.go
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
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 validation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func testDeviceTaintRule(name string, spec resourceapi.DeviceTaintRuleSpec) *resourceapi.DeviceTaintRule {
|
||||
return &resourceapi.DeviceTaintRule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: *spec.DeepCopy(),
|
||||
}
|
||||
}
|
||||
|
||||
var validDeviceTaintRuleSpec = resourceapi.DeviceTaintRuleSpec{
|
||||
DeviceSelector: &resourceapi.DeviceTaintSelector{
|
||||
DeviceClassName: ptr.To(goodName),
|
||||
Driver: ptr.To("test.example.com"),
|
||||
Pool: ptr.To(goodName),
|
||||
Device: ptr.To(goodName),
|
||||
},
|
||||
Taint: resourceapi.DeviceTaint{
|
||||
Key: "example.com/taint",
|
||||
Value: "tainted",
|
||||
Effect: resourceapi.DeviceTaintEffectNoSchedule,
|
||||
},
|
||||
}
|
||||
|
||||
func TestValidateDeviceTaint(t *testing.T) {
|
||||
goodName := "foo"
|
||||
now := metav1.Now()
|
||||
badName := "!@#$%^"
|
||||
badValue := "spaces not allowed"
|
||||
|
||||
scenarios := map[string]struct {
|
||||
taintRule *resourceapi.DeviceTaintRule
|
||||
wantFailures field.ErrorList
|
||||
}{
|
||||
"good": {
|
||||
taintRule: testDeviceTaintRule(goodName, validDeviceTaintRuleSpec),
|
||||
},
|
||||
"missing-name": {
|
||||
wantFailures: field.ErrorList{field.Required(field.NewPath("metadata", "name"), "name or generateName is required")},
|
||||
taintRule: testDeviceTaintRule("", validDeviceTaintRuleSpec),
|
||||
},
|
||||
"bad-name": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), 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])?)*')")},
|
||||
taintRule: testDeviceTaintRule(badName, validDeviceTaintRuleSpec),
|
||||
},
|
||||
"generate-name": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.GenerateName = "pvc-"
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"uid": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"resource-version": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.ResourceVersion = "1"
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"generation": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Generation = 100
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"creation-timestamp": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.CreationTimestamp = now
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"deletion-grace-period-seconds": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.DeletionGracePeriodSeconds = ptr.To(int64(10))
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"owner-references": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "pod",
|
||||
Name: "foo",
|
||||
UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
|
||||
},
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"finalizers": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Finalizers = []string{
|
||||
"example.com/foo",
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"managed-fields": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.ManagedFields = []metav1.ManagedFieldsEntry{
|
||||
{
|
||||
FieldsType: "FieldsV1",
|
||||
Operation: "Apply",
|
||||
APIVersion: "apps/v1",
|
||||
Manager: "foo",
|
||||
},
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"good-labels": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Labels = map[string]string{
|
||||
"apps.kubernetes.io/name": "test",
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-labels": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "labels"), badValue, "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])?')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Labels = map[string]string{
|
||||
"hello-world": badValue,
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"good-annotations": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Annotations = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-annotations": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"), badName, "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]')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Annotations = map[string]string{
|
||||
badName: "hello world",
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-class": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "deviceSelector", "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])?)*')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Spec.DeviceSelector.DeviceClassName = ptr.To(badName)
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-driver": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "deviceSelector", "driver"), 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])?)*')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Spec.DeviceSelector.Driver = ptr.To(badName)
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-pool": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "deviceSelector", "pool"), 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])?)*')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Spec.DeviceSelector.Pool = ptr.To(badName)
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"bad-device": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "deviceSelector", "device"), 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])?')")},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Spec.DeviceSelector.Device = ptr.To(badName)
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"CEL-compile-errors": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "deviceSelector", "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 | .................^"),
|
||||
},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
taintRule.Spec.DeviceSelector.Selectors = []resourceapi.DeviceSelector{
|
||||
{
|
||||
// Good selector.
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: `device.driver == "dra.example.com"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Bad selector.
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: `device.attributes[true].someBoolean`,
|
||||
},
|
||||
},
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"CEL-length": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.TooLong(field.NewPath("spec", "deviceSelector", "selectors").Index(1).Child("cel", "expression"), "" /*unused*/, resourceapi.CELSelectorExpressionMaxLength),
|
||||
},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
taintRule := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
expression := `device.driver == ""`
|
||||
taintRule.Spec.DeviceSelector.Selectors = []resourceapi.DeviceSelector{
|
||||
{
|
||||
// Good selector.
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: strings.ReplaceAll(expression, `""`, `"`+strings.Repeat("x", resourceapi.CELSelectorExpressionMaxLength-len(expression))+`"`),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Too long by one selector.
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
Expression: strings.ReplaceAll(expression, `""`, `"`+strings.Repeat("x", resourceapi.CELSelectorExpressionMaxLength-len(expression)+1)+`"`),
|
||||
},
|
||||
},
|
||||
}
|
||||
return taintRule
|
||||
}(),
|
||||
},
|
||||
"CEL-cost": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Forbidden(field.NewPath("spec", "deviceSelector", "selectors").Index(0).Child("cel", "expression"), "too complex, exceeds cost limit"),
|
||||
},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
claim := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
claim.Spec.DeviceSelector.Selectors = []resourceapi.DeviceSelector{
|
||||
{
|
||||
CEL: &resourceapi.CELDeviceSelector{
|
||||
// From https://github.com/kubernetes/kubernetes/blob/50fc400f178d2078d0ca46aee955ee26375fc437/test/integration/apiserver/cel/validatingadmissionpolicy_test.go#L2150.
|
||||
Expression: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(x, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(y, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(z, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(z2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(z3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(z4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].all(z5, int('1'.find('[0-9]*')) < 100)))))))`,
|
||||
},
|
||||
},
|
||||
}
|
||||
return claim
|
||||
}(),
|
||||
},
|
||||
"valid-taint": {
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
claim := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
claim.Spec.Taint = resourceapi.DeviceTaint{
|
||||
Key: goodName,
|
||||
Value: goodName,
|
||||
Effect: resourceapi.DeviceTaintEffectNoExecute,
|
||||
}
|
||||
return claim
|
||||
}(),
|
||||
},
|
||||
"invalid-taint": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "taint", "effect"), ""),
|
||||
},
|
||||
taintRule: func() *resourceapi.DeviceTaintRule {
|
||||
claim := testDeviceTaintRule(goodName, validDeviceTaintRuleSpec)
|
||||
claim.Spec.Taint = resourceapi.DeviceTaint{
|
||||
// Minimal test. Full coverage of validateDeviceTaint is in ResourceSlice test.
|
||||
Key: goodName,
|
||||
Value: goodName,
|
||||
}
|
||||
return claim
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
errs := ValidateDeviceTaintRule(scenario.taintRule)
|
||||
assertFailures(t, scenario.wantFailures, errs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDeviceTaintUpdate(t *testing.T) {
|
||||
name := "valid"
|
||||
validTaintRule := testDeviceTaintRule(name, validDeviceTaintRuleSpec)
|
||||
|
||||
scenarios := map[string]struct {
|
||||
old *resourceapi.DeviceTaintRule
|
||||
update func(patch *resourceapi.DeviceTaintRule) *resourceapi.DeviceTaintRule
|
||||
wantFailures field.ErrorList
|
||||
}{
|
||||
"valid-no-op-update": {
|
||||
old: validTaintRule,
|
||||
update: func(taintRule *resourceapi.DeviceTaintRule) *resourceapi.DeviceTaintRule { return taintRule },
|
||||
},
|
||||
"invalid-name-update": {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), name+"-update", "field is immutable")},
|
||||
old: validTaintRule,
|
||||
update: func(taintRule *resourceapi.DeviceTaintRule) *resourceapi.DeviceTaintRule {
|
||||
taintRule.Name += "-update"
|
||||
return taintRule
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
scenario.old.ResourceVersion = "1"
|
||||
errs := ValidateDeviceTaintRuleUpdate(scenario.update(scenario.old.DeepCopy()), scenario.old)
|
||||
assertFailures(t, scenario.wantFailures, errs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -735,6 +735,70 @@ func TestValidateClaim(t *testing.T) {
|
|||
return claim
|
||||
}(),
|
||||
},
|
||||
"tolerations": {
|
||||
wantFailures: func() field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
fldPath := field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("tolerations")
|
||||
allErrs = append(allErrs,
|
||||
field.Required(fldPath.Index(0).Child("operator"), ""),
|
||||
)
|
||||
fldPath = field.NewPath("spec", "devices", "requests").Index(1).Child("tolerations")
|
||||
allErrs = append(allErrs,
|
||||
field.Required(fldPath.Index(3).Child("operator"), ""),
|
||||
|
||||
field.NotSupported(fldPath.Index(4).Child("operator"), resource.DeviceTolerationOperator("some-other-op"), []resource.DeviceTolerationOperator{resource.DeviceTolerationOpEqual, resource.DeviceTolerationOpExists}),
|
||||
|
||||
field.Invalid(fldPath.Index(5).Child("key"), badName, "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]')"),
|
||||
field.Invalid(fldPath.Index(5).Child("value"), badName, "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])?')"),
|
||||
field.NotSupported(fldPath.Index(5).Child("effect"), resource.DeviceTaintEffect("some-other-effect"), []resource.DeviceTaintEffect{resource.DeviceTaintEffectNoExecute, resource.DeviceTaintEffectNoSchedule}),
|
||||
)
|
||||
return allErrs
|
||||
}(),
|
||||
claim: func() *resource.ResourceClaim {
|
||||
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||
claim.Spec.Devices.Requests[0].FirstAvailable[0].Tolerations = []resource.DeviceToleration{
|
||||
{
|
||||
// One invalid case to verify the field path.
|
||||
// Full test for validateDeviceToleration is below.
|
||||
Operator: "",
|
||||
},
|
||||
}
|
||||
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{
|
||||
{
|
||||
// Minimal valid toleration: match all taints.
|
||||
Operator: resource.DeviceTolerationOpExists,
|
||||
},
|
||||
{
|
||||
Key: "example.com/taint",
|
||||
Operator: resource.DeviceTolerationOpExists,
|
||||
Effect: resource.DeviceTaintEffectNoExecute,
|
||||
},
|
||||
{
|
||||
Key: "example.com/taint",
|
||||
Operator: resource.DeviceTolerationOpEqual,
|
||||
Value: "tainted",
|
||||
Effect: resource.DeviceTaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
// Invalid, operator is required.
|
||||
Operator: "",
|
||||
},
|
||||
{
|
||||
Key: goodName,
|
||||
Operator: "some-other-op",
|
||||
},
|
||||
{
|
||||
Key: badName,
|
||||
Operator: resource.DeviceTolerationOpEqual,
|
||||
Value: badName,
|
||||
Effect: "some-other-effect",
|
||||
},
|
||||
}
|
||||
return claim
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
|
|
@ -1645,11 +1709,6 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
|
|||
|
||||
scenario.oldClaim.ResourceVersion = "1"
|
||||
errs := ValidateResourceClaimStatusUpdate(scenario.update(scenario.oldClaim.DeepCopy()), scenario.oldClaim)
|
||||
|
||||
if name == "invalid-data-device-status-limits-feature-gate" {
|
||||
fmt.Println(errs)
|
||||
fmt.Println(scenario.wantFailures)
|
||||
}
|
||||
assertFailures(t, scenario.wantFailures, errs)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,6 +447,47 @@ func TestValidateResourceSlice(t *testing.T) {
|
|||
return slice
|
||||
}(),
|
||||
},
|
||||
"taints": {
|
||||
wantFailures: func() field.ErrorList {
|
||||
fldPath := field.NewPath("spec", "devices").Index(0).Child("basic", "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]')"),
|
||||
field.Required(fldPath.Index(2).Child("effect"), ""),
|
||||
|
||||
field.Invalid(fldPath.Index(3).Child("key"), badName, "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]')"),
|
||||
field.Invalid(fldPath.Index(3).Child("value"), badName, "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])?')"),
|
||||
field.NotSupported(fldPath.Index(3).Child("effect"), resourceapi.DeviceTaintEffect("some-other-op"), []resourceapi.DeviceTaintEffect{resourceapi.DeviceTaintEffectNoExecute, resourceapi.DeviceTaintEffectNoSchedule}),
|
||||
}
|
||||
}(),
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, goodName, 3)
|
||||
slice.Spec.Devices[0].Basic.Taints = []resourceapi.DeviceTaint{
|
||||
{
|
||||
// Minimal valid taint.
|
||||
Key: "example.com/taint",
|
||||
Effect: resourceapi.DeviceTaintEffectNoExecute,
|
||||
},
|
||||
{
|
||||
// Full valid taint, other key and effect.
|
||||
Key: "taint",
|
||||
Value: "tainted",
|
||||
Effect: resourceapi.DeviceTaintEffectNoSchedule,
|
||||
TimeAdded: ptr.To(metav1.Now()),
|
||||
},
|
||||
{
|
||||
// Invalid, all empty!
|
||||
},
|
||||
{
|
||||
// Invalid strings.
|
||||
Key: badName,
|
||||
Value: badName,
|
||||
Effect: "some-other-op",
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
|
|
|
|||
194
pkg/apis/resource/zz_generated.deepcopy.go
generated
194
pkg/apis/resource/zz_generated.deepcopy.go
generated
|
|
@ -99,6 +99,13 @@ func (in *BasicDevice) DeepCopyInto(out *BasicDevice) {
|
|||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
if in.Taints != nil {
|
||||
in, out := &in.Taints, &out.Taints
|
||||
*out = make([]DeviceTaint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -489,6 +496,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -510,6 +524,13 @@ func (in *DeviceRequestAllocationResult) DeepCopyInto(out *DeviceRequestAllocati
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -554,6 +575,13 @@ func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -567,6 +595,172 @@ func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaint) DeepCopyInto(out *DeviceTaint) {
|
||||
*out = *in
|
||||
if in.TimeAdded != nil {
|
||||
in, out := &in.TimeAdded, &out.TimeAdded
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaint.
|
||||
func (in *DeviceTaint) DeepCopy() *DeviceTaint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRule) DeepCopyInto(out *DeviceTaintRule) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRule.
|
||||
func (in *DeviceTaintRule) DeepCopy() *DeviceTaintRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DeviceTaintRule) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRuleList) DeepCopyInto(out *DeviceTaintRuleList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]DeviceTaintRule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleList.
|
||||
func (in *DeviceTaintRuleList) DeepCopy() *DeviceTaintRuleList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRuleList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DeviceTaintRuleList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRuleSpec) DeepCopyInto(out *DeviceTaintRuleSpec) {
|
||||
*out = *in
|
||||
if in.DeviceSelector != nil {
|
||||
in, out := &in.DeviceSelector, &out.DeviceSelector
|
||||
*out = new(DeviceTaintSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Taint.DeepCopyInto(&out.Taint)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleSpec.
|
||||
func (in *DeviceTaintRuleSpec) DeepCopy() *DeviceTaintRuleSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRuleSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintSelector) DeepCopyInto(out *DeviceTaintSelector) {
|
||||
*out = *in
|
||||
if in.DeviceClassName != nil {
|
||||
in, out := &in.DeviceClassName, &out.DeviceClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Driver != nil {
|
||||
in, out := &in.Driver, &out.Driver
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Pool != nil {
|
||||
in, out := &in.Pool, &out.Pool
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Device != nil {
|
||||
in, out := &in.Device, &out.Device
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Selectors != nil {
|
||||
in, out := &in.Selectors, &out.Selectors
|
||||
*out = make([]DeviceSelector, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintSelector.
|
||||
func (in *DeviceTaintSelector) DeepCopy() *DeviceTaintSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceToleration) DeepCopyInto(out *DeviceToleration) {
|
||||
*out = *in
|
||||
if in.TolerationSeconds != nil {
|
||||
in, out := &in.TolerationSeconds, &out.TolerationSeconds
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceToleration.
|
||||
func (in *DeviceToleration) DeepCopy() *DeviceToleration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceToleration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) {
|
||||
*out = *in
|
||||
|
|
|
|||
8
pkg/controller/devicetainteviction/OWNERS
Normal file
8
pkg/controller/devicetainteviction/OWNERS
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-scheduling-maintainers
|
||||
reviewers:
|
||||
- sig-scheduling
|
||||
labels:
|
||||
- sig/scheduling
|
||||
916
pkg/controller/devicetainteviction/device_taint_eviction.go
Normal file
916
pkg/controller/devicetainteviction/device_taint_eviction.go
Normal file
|
|
@ -0,0 +1,916 @@
|
|||
/*
|
||||
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 devicetainteviction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp" //nolint:depguard // Discouraged for production use (https://github.com/kubernetes/kubernetes/issues/104821) but has no good alternative for logging.
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
resourcealphainformers "k8s.io/client-go/informers/resource/v1alpha3"
|
||||
resourceinformers "k8s.io/client-go/informers/resource/v1beta1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/dynamic-resource-allocation/resourceclaim"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/klog/v2"
|
||||
apipod "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/controller/devicetainteviction/metrics"
|
||||
"k8s.io/kubernetes/pkg/controller/tainteviction"
|
||||
utilpod "k8s.io/kubernetes/pkg/util/pod"
|
||||
)
|
||||
|
||||
const (
|
||||
// retries is the number of times that the controller tries to delete a pod
|
||||
// that needs to be evicted.
|
||||
retries = 5
|
||||
)
|
||||
|
||||
// Controller listens to Taint changes of DRA devices and Toleration changes of ResourceClaims,
|
||||
// then deletes Pods which use ResourceClaims that don't tolerate a NoExecute taint.
|
||||
// Pods which have already reached a final state (aka terminated) don't need to be deleted.
|
||||
//
|
||||
// All of the logic which identifies pods which need to be evicted runs in the
|
||||
// handle* event handlers. They don't call any blocking method. All the blocking
|
||||
// calls happen in a [tainteviction.TimedWorkerQueue], using the context passed to Run.
|
||||
//
|
||||
// The [resourceslicetracker] takes care of applying taints defined in DeviceTaintRules
|
||||
// to ResourceSlices. This controller here receives modified ResourceSlices with all
|
||||
// applicable taints from that tracker and doesn't need to care about where a
|
||||
// taint came from, the DRA driver or a DeviceTaintRule.
|
||||
type Controller struct {
|
||||
name string
|
||||
|
||||
// logger is the general-purpose logger to be used for background activities.
|
||||
logger klog.Logger
|
||||
|
||||
// handlerLogger is specifically for logging during event handling. It may be nil
|
||||
// if no such logging is desired.
|
||||
eventLogger *klog.Logger
|
||||
|
||||
client clientset.Interface
|
||||
recorder record.EventRecorder
|
||||
podInformer coreinformers.PodInformer
|
||||
podLister corelisters.PodLister
|
||||
claimInformer resourceinformers.ResourceClaimInformer
|
||||
sliceInformer resourceinformers.ResourceSliceInformer
|
||||
taintInformer resourcealphainformers.DeviceTaintRuleInformer
|
||||
classInformer resourceinformers.DeviceClassInformer
|
||||
haveSynced []cache.InformerSynced
|
||||
metrics metrics.Metrics
|
||||
|
||||
// evictPod ensures that the pod gets evicted at the specified time.
|
||||
// It doesn't block.
|
||||
evictPod func(pod tainteviction.NamespacedObject, fireAt time.Time)
|
||||
|
||||
// cancelEvict cancels eviction set up with evictPod earlier.
|
||||
// Idempotent, returns false if there was nothing to cancel.
|
||||
cancelEvict func(pod tainteviction.NamespacedObject) bool
|
||||
|
||||
// allocatedClaims holds all currently known allocated claims.
|
||||
allocatedClaims map[types.NamespacedName]allocatedClaim // A value is slightly more efficient in BenchmarkTaintUntaint (less allocations!).
|
||||
|
||||
// pools indexes all slices by driver and pool name.
|
||||
pools map[poolID]pool
|
||||
|
||||
hasSynced atomic.Int32
|
||||
}
|
||||
|
||||
type poolID struct {
|
||||
driverName, poolName string
|
||||
}
|
||||
|
||||
type pool struct {
|
||||
slices sets.Set[*resourceapi.ResourceSlice]
|
||||
maxGeneration int64
|
||||
}
|
||||
|
||||
// addSlice adds one slice to the pool.
|
||||
func (p *pool) addSlice(slice *resourceapi.ResourceSlice) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if p.slices == nil {
|
||||
p.slices = sets.New[*resourceapi.ResourceSlice]()
|
||||
p.maxGeneration = math.MinInt64
|
||||
}
|
||||
p.slices.Insert(slice)
|
||||
|
||||
// Adding a slice can only increase the generation.
|
||||
if slice.Spec.Pool.Generation > p.maxGeneration {
|
||||
p.maxGeneration = slice.Spec.Pool.Generation
|
||||
}
|
||||
}
|
||||
|
||||
// removeSlice removes a slice. It must have been added before.
|
||||
func (p *pool) removeSlice(slice *resourceapi.ResourceSlice) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
p.slices.Delete(slice)
|
||||
|
||||
// Removing a slice might have decreased the generation to
|
||||
// that of some other slice.
|
||||
if slice.Spec.Pool.Generation == p.maxGeneration {
|
||||
maxGeneration := int64(math.MinInt64)
|
||||
for slice := range p.slices {
|
||||
if slice.Spec.Pool.Generation > maxGeneration {
|
||||
maxGeneration = slice.Spec.Pool.Generation
|
||||
}
|
||||
}
|
||||
p.maxGeneration = maxGeneration
|
||||
}
|
||||
}
|
||||
|
||||
// getTaintedDevices appends all device taints with NoExecute effect.
|
||||
// The result is sorted by device name.
|
||||
func (p pool) getTaintedDevices() []taintedDevice {
|
||||
var buffer []taintedDevice
|
||||
for slice := range p.slices {
|
||||
if slice.Spec.Pool.Generation != p.maxGeneration {
|
||||
continue
|
||||
}
|
||||
for _, device := range slice.Spec.Devices {
|
||||
if device.Basic == nil {
|
||||
// Unknown device type, not supported.
|
||||
continue
|
||||
}
|
||||
for _, taint := range device.Basic.Taints {
|
||||
if taint.Effect != resourceapi.DeviceTaintEffectNoExecute {
|
||||
continue
|
||||
}
|
||||
buffer = append(buffer, taintedDevice{deviceName: device.Name, taint: taint})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// slices.SortFunc is more efficient than sort.Slice here.
|
||||
slices.SortFunc(buffer, func(a, b taintedDevice) int {
|
||||
return strings.Compare(a.deviceName, b.deviceName)
|
||||
})
|
||||
return buffer
|
||||
}
|
||||
|
||||
// getDevice looks up one device by name. Out-dated slices are ignored.
|
||||
func (p pool) getDevice(deviceName string) *resourceapi.BasicDevice {
|
||||
for slice := range p.slices {
|
||||
if slice.Spec.Pool.Generation != p.maxGeneration {
|
||||
continue
|
||||
}
|
||||
for _, device := range slice.Spec.Devices {
|
||||
if device.Basic == nil {
|
||||
// Unknown device type, not supported.
|
||||
continue
|
||||
}
|
||||
if device.Name == deviceName {
|
||||
return device.Basic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type taintedDevice struct {
|
||||
deviceName string
|
||||
taint resourceapi.DeviceTaint
|
||||
}
|
||||
|
||||
// allocatedClaim is a ResourceClaim which has an allocation result. It
|
||||
// may or may not be tainted such that pods need to be evicted.
|
||||
type allocatedClaim struct {
|
||||
*resourceapi.ResourceClaim
|
||||
|
||||
// evictionTime, if non-nil, is the time at which pods using this claim need to be evicted.
|
||||
// This is the smallest value of all such per-device values.
|
||||
// For each device, the value is calculated as `<time of setting the taint> +
|
||||
// <toleration seconds, 0 if not set>`.
|
||||
evictionTime *metav1.Time
|
||||
}
|
||||
|
||||
func (tc *Controller) deletePodHandler(c clientset.Interface, emitEventFunc func(tainteviction.NamespacedObject)) func(ctx context.Context, fireAt time.Time, args *tainteviction.WorkArgs) error {
|
||||
return func(ctx context.Context, fireAt time.Time, args *tainteviction.WorkArgs) error {
|
||||
klog.FromContext(ctx).Info("Deleting pod", "pod", args.Object)
|
||||
var err error
|
||||
for i := 0; i < retries; i++ {
|
||||
err = addConditionAndDeletePod(ctx, c, args.Object, &emitEventFunc)
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Not a problem, the work is done.
|
||||
// But we didn't do it, so don't
|
||||
// bump the metric.
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
tc.metrics.PodDeletionsTotal.Inc()
|
||||
tc.metrics.PodDeletionsLatency.Observe(float64(time.Since(fireAt).Seconds()))
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func addConditionAndDeletePod(ctx context.Context, c clientset.Interface, podRef tainteviction.NamespacedObject, emitEventFunc *func(tainteviction.NamespacedObject)) (err error) {
|
||||
pod, err := c.CoreV1().Pods(podRef.Namespace).Get(ctx, podRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pod.UID != podRef.UID {
|
||||
// This special error suppresses event logging in our caller and prevents further retries.
|
||||
// We can stop because the pod we were meant to evict is already gone and happens to
|
||||
// be replaced by some other pod which reuses the same name.
|
||||
return apierrors.NewNotFound(v1.SchemeGroupVersion.WithResource("pods").GroupResource(), pod.Name)
|
||||
}
|
||||
|
||||
// Emit the event only once, and only if we are actually doing something.
|
||||
if *emitEventFunc != nil {
|
||||
(*emitEventFunc)(podRef)
|
||||
*emitEventFunc = nil
|
||||
}
|
||||
|
||||
newStatus := pod.Status.DeepCopy()
|
||||
updated := apipod.UpdatePodCondition(newStatus, &v1.PodCondition{
|
||||
Type: v1.DisruptionTarget,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "DeletionByDeviceTaintManager",
|
||||
Message: "Device Taint manager: deleting due to NoExecute taint",
|
||||
})
|
||||
if updated {
|
||||
if _, _, _, err := utilpod.PatchPodStatus(ctx, c, pod.Namespace, pod.Name, pod.UID, pod.Status, *newStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Unlikely, but it could happen that the pod we got above got replaced with
|
||||
// another pod using the same name in the meantime. Include a precondition
|
||||
// to prevent that race. This delete attempt then fails and the next one detects
|
||||
// the new pod and stops retrying.
|
||||
return c.CoreV1().Pods(podRef.Namespace).Delete(ctx, podRef.Name, metav1.DeleteOptions{
|
||||
Preconditions: &metav1.Preconditions{
|
||||
UID: &podRef.UID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// New creates a new Controller that will use passed clientset to communicate with the API server.
|
||||
// Spawns no goroutines. That happens in Run.
|
||||
func New(c clientset.Interface, podInformer coreinformers.PodInformer, claimInformer resourceinformers.ResourceClaimInformer, sliceInformer resourceinformers.ResourceSliceInformer, taintInformer resourcealphainformers.DeviceTaintRuleInformer, classInformer resourceinformers.DeviceClassInformer, controllerName string) *Controller {
|
||||
metrics.Register() // It would be nicer to pass the controller name here, but that probably would break generating https://kubernetes.io/docs/reference/instrumentation/metrics.
|
||||
|
||||
tc := &Controller{
|
||||
name: controllerName,
|
||||
|
||||
client: c,
|
||||
podInformer: podInformer,
|
||||
podLister: podInformer.Lister(),
|
||||
claimInformer: claimInformer,
|
||||
sliceInformer: sliceInformer,
|
||||
taintInformer: taintInformer,
|
||||
classInformer: classInformer,
|
||||
allocatedClaims: make(map[types.NamespacedName]allocatedClaim),
|
||||
pools: make(map[poolID]pool),
|
||||
// Instantiate all informers now to ensure that they get started.
|
||||
haveSynced: []cache.InformerSynced{
|
||||
podInformer.Informer().HasSynced,
|
||||
claimInformer.Informer().HasSynced,
|
||||
sliceInformer.Informer().HasSynced,
|
||||
taintInformer.Informer().HasSynced,
|
||||
classInformer.Informer().HasSynced,
|
||||
},
|
||||
metrics: metrics.Global,
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
// Run starts the controller which will run until the context is done.
|
||||
func (tc *Controller) Run(ctx context.Context) {
|
||||
defer utilruntime.HandleCrash()
|
||||
logger := klog.FromContext(ctx)
|
||||
logger.Info("Starting", "controller", tc.name)
|
||||
defer logger.Info("Shutting down controller", "controller", tc.name)
|
||||
tc.logger = logger
|
||||
|
||||
// Doing debug logging?
|
||||
if loggerV := logger.V(6); loggerV.Enabled() {
|
||||
tc.eventLogger = &loggerV
|
||||
}
|
||||
|
||||
// Delayed construction of broadcaster because it spawns goroutines.
|
||||
// tc.recorder.Eventf is a local in-memory operation which never
|
||||
// blocks, so it is safe to call from an event handler. The
|
||||
// actual API calls then happen in those spawned goroutines.
|
||||
eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx))
|
||||
tc.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: tc.name}).WithLogger(logger)
|
||||
defer eventBroadcaster.Shutdown()
|
||||
|
||||
taintEvictionQueue := tainteviction.CreateWorkerQueue(tc.deletePodHandler(tc.client, tc.emitPodDeletionEvent))
|
||||
evictPod := tc.evictPod
|
||||
tc.evictPod = func(podRef tainteviction.NamespacedObject, fireAt time.Time) {
|
||||
// Only relevant for testing.
|
||||
if evictPod != nil {
|
||||
evictPod(podRef, fireAt)
|
||||
}
|
||||
taintEvictionQueue.UpdateWork(ctx, &tainteviction.WorkArgs{Object: podRef}, time.Now(), fireAt)
|
||||
}
|
||||
cancelEvict := tc.cancelEvict
|
||||
tc.cancelEvict = func(podRef tainteviction.NamespacedObject) bool {
|
||||
if cancelEvict != nil {
|
||||
cancelEvict(podRef)
|
||||
}
|
||||
return taintEvictionQueue.CancelWork(logger, podRef.NamespacedName.String())
|
||||
}
|
||||
|
||||
// Start events processing pipeline.
|
||||
eventBroadcaster.StartStructuredLogging(3)
|
||||
if tc.client != nil {
|
||||
logger.Info("Sending events to api server")
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: tc.client.CoreV1().Events("")})
|
||||
} else {
|
||||
logger.Error(nil, "kubeClient is nil", "controller", tc.name)
|
||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||
}
|
||||
defer eventBroadcaster.Shutdown()
|
||||
|
||||
// mutex serializes event processing.
|
||||
var mutex sync.Mutex
|
||||
|
||||
claimHandler, _ := tc.claimInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {
|
||||
claim, ok := obj.(*resourceapi.ResourceClaim)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceClaim", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleClaimChange(nil, claim)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj any) {
|
||||
oldClaim, ok := oldObj.(*resourceapi.ResourceClaim)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceClaim", "actual", fmt.Sprintf("%T", oldObj))
|
||||
return
|
||||
}
|
||||
newClaim, ok := newObj.(*resourceapi.ResourceClaim)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceClaim", "actual", fmt.Sprintf("%T", newObj))
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleClaimChange(oldClaim, newClaim)
|
||||
},
|
||||
DeleteFunc: func(obj any) {
|
||||
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||
obj = tombstone.Obj
|
||||
}
|
||||
claim, ok := obj.(*resourceapi.ResourceClaim)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceClaim", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleClaimChange(claim, nil)
|
||||
},
|
||||
})
|
||||
defer func() {
|
||||
_ = tc.claimInformer.Informer().RemoveEventHandler(claimHandler)
|
||||
}()
|
||||
tc.haveSynced = append(tc.haveSynced, claimHandler.HasSynced)
|
||||
|
||||
podHandler, _ := tc.podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {
|
||||
pod, ok := obj.(*v1.Pod)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourcePod", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handlePodChange(nil, pod)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj any) {
|
||||
oldPod, ok := oldObj.(*v1.Pod)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected Pod", "actual", fmt.Sprintf("%T", oldObj))
|
||||
return
|
||||
}
|
||||
newPod, ok := newObj.(*v1.Pod)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected Pod", "actual", fmt.Sprintf("%T", newObj))
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handlePodChange(oldPod, newPod)
|
||||
},
|
||||
DeleteFunc: func(obj any) {
|
||||
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||
obj = tombstone.Obj
|
||||
}
|
||||
pod, ok := obj.(*v1.Pod)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected Pod", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handlePodChange(pod, nil)
|
||||
},
|
||||
})
|
||||
defer func() {
|
||||
_ = tc.podInformer.Informer().RemoveEventHandler(podHandler)
|
||||
}()
|
||||
tc.haveSynced = append(tc.haveSynced, podHandler.HasSynced)
|
||||
|
||||
opts := resourceslicetracker.Options{
|
||||
EnableDeviceTaints: true,
|
||||
SliceInformer: tc.sliceInformer,
|
||||
TaintInformer: tc.taintInformer,
|
||||
ClassInformer: tc.classInformer,
|
||||
KubeClient: tc.client,
|
||||
}
|
||||
sliceTracker, err := resourceslicetracker.StartTracker(ctx, opts)
|
||||
if err != nil {
|
||||
logger.Info("Failed to initialize ResourceSlice tracker; device taint processing leading to Pod eviction is now paused", "err", err)
|
||||
return
|
||||
}
|
||||
tc.haveSynced = append(tc.haveSynced, sliceTracker.HasSynced)
|
||||
defer sliceTracker.Stop()
|
||||
|
||||
// Wait for tracker to sync before we react to events.
|
||||
// This doesn't have to be perfect, it merely avoids unnecessary
|
||||
// work which might be done as events get emitted for intermediate
|
||||
// state.
|
||||
if !cache.WaitForNamedCacheSyncWithContext(ctx, tc.haveSynced...) {
|
||||
return
|
||||
}
|
||||
logger.V(1).Info("Underlying informers have synced")
|
||||
|
||||
_, _ = sliceTracker.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {
|
||||
slice, ok := obj.(*resourceapi.ResourceSlice)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceSlice", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleSliceChange(nil, slice)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj any) {
|
||||
oldSlice, ok := oldObj.(*resourceapi.ResourceSlice)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceSlice", "actual", fmt.Sprintf("%T", oldObj))
|
||||
return
|
||||
}
|
||||
newSlice, ok := newObj.(*resourceapi.ResourceSlice)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceSlice", "actual", fmt.Sprintf("%T", newObj))
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleSliceChange(oldSlice, newSlice)
|
||||
},
|
||||
DeleteFunc: func(obj any) {
|
||||
// No need to check for DeletedFinalStateUnknown here, the resourceslicetracker doesn't use that.
|
||||
slice, ok := obj.(*resourceapi.ResourceSlice)
|
||||
if !ok {
|
||||
logger.Error(nil, "Expected ResourceSlice", "actual", fmt.Sprintf("%T", obj))
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
tc.handleSliceChange(slice, nil)
|
||||
},
|
||||
})
|
||||
|
||||
// sliceTracker.AddEventHandler blocked while delivering events for all known
|
||||
// ResourceSlices. Therefore our own state is up-to-date once we get here.
|
||||
tc.hasSynced.Store(1)
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
func (tc *Controller) handleClaimChange(oldClaim, newClaim *resourceapi.ResourceClaim) {
|
||||
claim := newClaim
|
||||
if claim == nil {
|
||||
claim = oldClaim
|
||||
}
|
||||
name := newNamespacedName(claim)
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("ResourceClaim changed", "claimObject", name, "oldClaim", klog.Format(oldClaim), "newClaim", klog.Format(newClaim), "diff", cmp.Diff(oldClaim, newClaim))
|
||||
}
|
||||
|
||||
// Deleted?
|
||||
if newClaim == nil {
|
||||
delete(tc.allocatedClaims, name)
|
||||
tc.handlePods(claim)
|
||||
return
|
||||
}
|
||||
|
||||
// Added?
|
||||
if oldClaim == nil {
|
||||
if claim.Status.Allocation == nil {
|
||||
return
|
||||
}
|
||||
tc.allocatedClaims[name] = allocatedClaim{
|
||||
ResourceClaim: claim,
|
||||
evictionTime: tc.evictionTime(claim.Status.Allocation),
|
||||
}
|
||||
tc.handlePods(claim)
|
||||
return
|
||||
}
|
||||
|
||||
// If we have two claims, the UID might still be different. Unlikely, but not impossible...
|
||||
// Treat this like a remove + add.
|
||||
if oldClaim.UID != newClaim.UID {
|
||||
tc.handleClaimChange(oldClaim, nil)
|
||||
tc.handleClaimChange(nil, newClaim)
|
||||
return
|
||||
}
|
||||
|
||||
syncBothClaims := func() {
|
||||
// ReservedFor may have changed. If it did, sync both old and new lists,
|
||||
// otherwise only once (same list).
|
||||
if !slices.Equal(oldClaim.Status.ReservedFor, newClaim.Status.ReservedFor) {
|
||||
tc.handlePods(oldClaim)
|
||||
tc.handlePods(newClaim)
|
||||
} else {
|
||||
tc.handlePods(claim)
|
||||
}
|
||||
}
|
||||
|
||||
// Allocation added?
|
||||
if oldClaim.Status.Allocation == nil && newClaim.Status.Allocation != nil {
|
||||
tc.allocatedClaims[name] = allocatedClaim{
|
||||
ResourceClaim: claim,
|
||||
evictionTime: tc.evictionTime(claim.Status.Allocation),
|
||||
}
|
||||
syncBothClaims()
|
||||
return
|
||||
}
|
||||
|
||||
// Allocation removed?
|
||||
if oldClaim.Status.Allocation != nil && newClaim.Status.Allocation == nil {
|
||||
delete(tc.allocatedClaims, name)
|
||||
syncBothClaims()
|
||||
return
|
||||
}
|
||||
|
||||
// Allocated before and after?
|
||||
if claim.Status.Allocation != nil {
|
||||
// The Allocation is immutable, so we don't need to recompute the eviction
|
||||
// time. Storing the newer claim is enough.
|
||||
tc.allocatedClaims[name] = allocatedClaim{
|
||||
ResourceClaim: claim,
|
||||
evictionTime: tc.allocatedClaims[name].evictionTime,
|
||||
}
|
||||
syncBothClaims()
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, nothing changed.
|
||||
}
|
||||
|
||||
// evictionTime returns the earliest TimeAdded of any NoExecute taint in any allocated device
|
||||
// unless that taint is tolerated, nil if none.
|
||||
func (tc *Controller) evictionTime(allocation *resourceapi.AllocationResult) *metav1.Time {
|
||||
var evictionTime *metav1.Time
|
||||
|
||||
for _, allocatedDevice := range allocation.Devices.Results {
|
||||
device := tc.pools[poolID{driverName: allocatedDevice.Driver, poolName: allocatedDevice.Pool}].getDevice(allocatedDevice.Device)
|
||||
if device == nil {
|
||||
// Unknown device? Can't be tainted...
|
||||
continue
|
||||
}
|
||||
|
||||
nextTaint:
|
||||
for _, taint := range device.Taints {
|
||||
if taint.Effect != resourceapi.DeviceTaintEffectNoExecute {
|
||||
continue
|
||||
}
|
||||
|
||||
newEvictionTime := taint.TimeAdded
|
||||
haveToleration := false
|
||||
tolerationSeconds := int64(math.MaxInt64)
|
||||
for _, toleration := range allocatedDevice.Tolerations {
|
||||
if toleration.Effect == resourceapi.DeviceTaintEffectNoExecute &&
|
||||
resourceclaim.ToleratesTaint(toleration, taint) {
|
||||
if toleration.TolerationSeconds == nil {
|
||||
// Tolerate forever -> ignore taint.
|
||||
continue nextTaint
|
||||
}
|
||||
newTolerationSeconds := *toleration.TolerationSeconds
|
||||
if newTolerationSeconds < 0 {
|
||||
newTolerationSeconds = 0
|
||||
}
|
||||
if newTolerationSeconds < tolerationSeconds {
|
||||
tolerationSeconds = newTolerationSeconds
|
||||
}
|
||||
haveToleration = true
|
||||
}
|
||||
}
|
||||
if haveToleration {
|
||||
newEvictionTime = &metav1.Time{Time: newEvictionTime.Add(time.Duration(tolerationSeconds) * time.Second)}
|
||||
}
|
||||
|
||||
if evictionTime == nil {
|
||||
evictionTime = newEvictionTime
|
||||
continue
|
||||
}
|
||||
if newEvictionTime != nil && newEvictionTime.Before(evictionTime) {
|
||||
evictionTime = newEvictionTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return evictionTime
|
||||
}
|
||||
|
||||
func (tc *Controller) handleSliceChange(oldSlice, newSlice *resourceapi.ResourceSlice) {
|
||||
slice := newSlice
|
||||
if slice == nil {
|
||||
slice = oldSlice
|
||||
}
|
||||
poolID := poolID{
|
||||
driverName: slice.Spec.Driver,
|
||||
poolName: slice.Spec.Pool.Name,
|
||||
}
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("ResourceSlice changed", "pool", poolID, "oldSlice", klog.Format(oldSlice), "newSlice", klog.Format(newSlice), "diff", cmp.Diff(oldSlice, newSlice))
|
||||
}
|
||||
|
||||
// Determine old and new device taints. Only devices
|
||||
// where something changes trigger additional checks for claims
|
||||
// using them.
|
||||
//
|
||||
// The pre-allocated slices are small enough to be allocated on
|
||||
// the stack (https://stackoverflow.com/a/69187698/222305).
|
||||
p := tc.pools[poolID]
|
||||
oldDeviceTaints := p.getTaintedDevices()
|
||||
p.removeSlice(oldSlice)
|
||||
p.addSlice(newSlice)
|
||||
if len(p.slices) == 0 {
|
||||
delete(tc.pools, poolID)
|
||||
} else {
|
||||
tc.pools[poolID] = p
|
||||
}
|
||||
newDeviceTaints := p.getTaintedDevices()
|
||||
|
||||
// Now determine differences. This depends on both slices having been sorted
|
||||
// by device name.
|
||||
if len(oldDeviceTaints) == 0 && len(newDeviceTaints) == 0 {
|
||||
// Both empty, no changes.
|
||||
return
|
||||
}
|
||||
modifiedDevices := sets.New[string]()
|
||||
o, n := 0, 0
|
||||
for o < len(oldDeviceTaints) || n < len(newDeviceTaints) {
|
||||
// Iterate over devices in both slices with the same name.
|
||||
for o < len(oldDeviceTaints) && n < len(newDeviceTaints) && oldDeviceTaints[o].deviceName == newDeviceTaints[n].deviceName {
|
||||
if !apiequality.Semantic.DeepEqual(oldDeviceTaints[o].taint, newDeviceTaints[n].taint) { // TODO: hard-code the comparison?
|
||||
modifiedDevices.Insert(oldDeviceTaints[o].deviceName)
|
||||
}
|
||||
o++
|
||||
n++
|
||||
}
|
||||
|
||||
// Step over old devices which were removed.
|
||||
newDeviceName := ""
|
||||
if n < len(newDeviceTaints) {
|
||||
newDeviceName = newDeviceTaints[n].deviceName
|
||||
}
|
||||
for o < len(oldDeviceTaints) && oldDeviceTaints[o].deviceName != newDeviceName {
|
||||
modifiedDevices.Insert(oldDeviceTaints[o].deviceName)
|
||||
o++
|
||||
}
|
||||
|
||||
// Step over new devices which were added.
|
||||
oldDeviceName := ""
|
||||
if o < len(oldDeviceTaints) {
|
||||
oldDeviceName = oldDeviceTaints[o].deviceName
|
||||
}
|
||||
if n < len(newDeviceTaints) && newDeviceTaints[n].deviceName != oldDeviceName {
|
||||
modifiedDevices.Insert(newDeviceTaints[n].deviceName)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
// Now find all claims using at least one modified device,
|
||||
// update their eviction time, and handle their consuming pods.
|
||||
for name, claim := range tc.allocatedClaims {
|
||||
if !usesDevice(claim.Status.Allocation, poolID, modifiedDevices) {
|
||||
continue
|
||||
}
|
||||
newEvictionTime := tc.evictionTime(claim.ResourceClaim.Status.Allocation)
|
||||
if newEvictionTime.Equal(claim.evictionTime) {
|
||||
// No change.
|
||||
continue
|
||||
}
|
||||
claim.evictionTime = newEvictionTime
|
||||
tc.allocatedClaims[name] = claim
|
||||
// We could collect pods which depend on claims with changes.
|
||||
// In practice, most pods probably depend on one claim, so
|
||||
// it is probably more efficient to avoid building such a map
|
||||
// to make the common case simple.
|
||||
tc.handlePods(claim.ResourceClaim)
|
||||
}
|
||||
}
|
||||
|
||||
func usesDevice(allocation *resourceapi.AllocationResult, pool poolID, modifiedDevices sets.Set[string]) bool {
|
||||
for _, device := range allocation.Devices.Results {
|
||||
if device.Driver == pool.driverName &&
|
||||
device.Pool == pool.poolName &&
|
||||
modifiedDevices.Has(device.Device) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (tc *Controller) handlePodChange(oldPod, newPod *v1.Pod) {
|
||||
pod := newPod
|
||||
if pod == nil {
|
||||
pod = oldPod
|
||||
}
|
||||
if tc.eventLogger != nil {
|
||||
// This is intentionally very verbose for debugging.
|
||||
tc.eventLogger.Info("Pod changed", "pod", klog.KObj(pod), "oldPod", klog.Format(oldPod), "newPod", klog.Format(newPod), "diff", cmp.Diff(oldPod, newPod))
|
||||
}
|
||||
if newPod == nil {
|
||||
// Nothing left to do for it. No need to emit an event here, it's gone.
|
||||
tc.cancelEvict(newObject(oldPod))
|
||||
return
|
||||
}
|
||||
|
||||
// Pods get updated quite frequently. There's no need
|
||||
// to check them again unless something changed regarding
|
||||
// their claims.
|
||||
//
|
||||
// In particular this prevents adding the pod again
|
||||
// directly after the eviction condition got added
|
||||
// to it.
|
||||
if oldPod != nil &&
|
||||
apiequality.Semantic.DeepEqual(oldPod.Status.ResourceClaimStatuses, newPod.Status.ResourceClaimStatuses) {
|
||||
return
|
||||
}
|
||||
|
||||
tc.handlePod(newPod)
|
||||
}
|
||||
|
||||
func (tc *Controller) handlePods(claim *resourceapi.ResourceClaim) {
|
||||
for _, consumer := range claim.Status.ReservedFor {
|
||||
if consumer.APIGroup == "" && consumer.Resource == "pods" {
|
||||
pod, err := tc.podInformer.Lister().Pods(claim.Namespace).Get(consumer.Name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return
|
||||
}
|
||||
// Should not happen.
|
||||
utilruntime.HandleErrorWithLogger(tc.logger, err, "retrieve pod from cache")
|
||||
return
|
||||
}
|
||||
if pod.UID != consumer.UID {
|
||||
// Not the pod we were looking for.
|
||||
return
|
||||
}
|
||||
tc.handlePod(pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) handlePod(pod *v1.Pod) {
|
||||
// Not scheduled yet? No need to evict.
|
||||
if pod.Spec.NodeName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// If any claim in use by the pod is tainted such that the taint is not tolerated,
|
||||
// the pod needs to be evicted.
|
||||
var evictionTime *metav1.Time
|
||||
for i := range pod.Spec.ResourceClaims {
|
||||
claimName, mustCheckOwner, err := resourceclaim.Name(pod, &pod.Spec.ResourceClaims[i])
|
||||
if err != nil {
|
||||
// Not created yet or unsupported. Definitely not tainted.
|
||||
continue
|
||||
}
|
||||
if claimName == nil {
|
||||
// Claim not needed.
|
||||
continue
|
||||
}
|
||||
allocatedClaim, ok := tc.allocatedClaims[types.NamespacedName{Namespace: pod.Namespace, Name: *claimName}]
|
||||
if !ok {
|
||||
// Referenced, but not found or not allocated. Also not tainted.
|
||||
continue
|
||||
}
|
||||
if mustCheckOwner && resourceclaim.IsForPod(pod, allocatedClaim.ResourceClaim) != nil {
|
||||
// Claim and pod don't match. Ignore the claim.
|
||||
continue
|
||||
}
|
||||
if !resourceclaim.IsReservedForPod(pod, allocatedClaim.ResourceClaim) {
|
||||
// The pod isn't the one which is allowed and/or supposed to use the claim.
|
||||
// Perhaps that pod instance already got deleted and we are looking at its
|
||||
// replacement under the same name. Either way, ignore.
|
||||
continue
|
||||
}
|
||||
if allocatedClaim.evictionTime == nil {
|
||||
continue
|
||||
}
|
||||
if evictionTime == nil || allocatedClaim.evictionTime.Before(evictionTime) {
|
||||
evictionTime = allocatedClaim.evictionTime
|
||||
}
|
||||
}
|
||||
|
||||
podRef := newObject(pod)
|
||||
if evictionTime != nil {
|
||||
tc.evictPod(podRef, evictionTime.Time)
|
||||
} else {
|
||||
tc.cancelWorkWithEvent(podRef)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) cancelWorkWithEvent(podRef tainteviction.NamespacedObject) {
|
||||
if tc.cancelEvict(podRef) {
|
||||
tc.emitCancelPodDeletionEvent(podRef)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) emitPodDeletionEvent(podRef tainteviction.NamespacedObject) {
|
||||
if tc.recorder == nil {
|
||||
return
|
||||
}
|
||||
ref := &v1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: podRef.Name,
|
||||
Namespace: podRef.Namespace,
|
||||
UID: podRef.UID,
|
||||
}
|
||||
tc.recorder.Eventf(ref, v1.EventTypeNormal, "DeviceTaintManagerEviction", "Marking for deletion")
|
||||
}
|
||||
|
||||
func (tc *Controller) emitCancelPodDeletionEvent(podRef tainteviction.NamespacedObject) {
|
||||
if tc.recorder == nil {
|
||||
return
|
||||
}
|
||||
ref := &v1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: podRef.Name,
|
||||
Namespace: podRef.Namespace,
|
||||
UID: podRef.UID,
|
||||
}
|
||||
tc.recorder.Eventf(ref, v1.EventTypeNormal, "DeviceTaintManagerEviction", "Cancelling deletion")
|
||||
}
|
||||
|
||||
func newNamespacedName(obj metav1.Object) types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: obj.GetNamespace(),
|
||||
Name: obj.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
func newObject(obj metav1.Object) tainteviction.NamespacedObject {
|
||||
return tainteviction.NamespacedObject{
|
||||
NamespacedName: newNamespacedName(obj),
|
||||
UID: obj.GetUID(),
|
||||
}
|
||||
}
|
||||
1752
pkg/controller/devicetainteviction/device_taint_eviction_test.go
Normal file
1752
pkg/controller/devicetainteviction/device_taint_eviction_test.go
Normal file
File diff suppressed because it is too large
Load diff
19
pkg/controller/devicetainteviction/doc.go
Normal file
19
pkg/controller/devicetainteviction/doc.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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 devicetainteviction contains the logic implementing taint-based eviction
|
||||
// for Pods using tainted devices (https://github.com/kubernetes/enhancements/issues/5055).
|
||||
package devicetainteviction
|
||||
88
pkg/controller/devicetainteviction/metrics/metrics.go
Normal file
88
pkg/controller/devicetainteviction/metrics/metrics.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// controllerSubsystem must be kept in sync with the controller name in cmd/kube-controller-manager/names.
|
||||
const controllerSubsystem = "device_taint_eviction_controller"
|
||||
|
||||
var (
|
||||
Global = New()
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
|
||||
// Register registers TaintEvictionController metrics.
|
||||
func Register() {
|
||||
registerMetrics.Do(func() {
|
||||
legacyregistry.MustRegister(Global.PodDeletionsTotal)
|
||||
legacyregistry.MustRegister(Global.PodDeletionsLatency)
|
||||
})
|
||||
}
|
||||
|
||||
// New returns new instances of all metrics for testing in parallel.
|
||||
// Optionally, buckets for the histograms can be specified.
|
||||
func New(buckets ...float64) Metrics {
|
||||
if len(buckets) == 0 {
|
||||
buckets = []float64{0.005, 0.025, 0.1, 0.5, 1, 2.5, 10, 30, 60, 120, 180, 240} // 5ms to 4m
|
||||
}
|
||||
m := Metrics{
|
||||
KubeRegistry: metrics.NewKubeRegistry(),
|
||||
PodDeletionsTotal: metrics.NewCounter(
|
||||
&metrics.CounterOpts{
|
||||
Subsystem: controllerSubsystem,
|
||||
Name: "pod_deletions_total",
|
||||
Help: "Total number of Pods deleted by DeviceTaintEvictionController since its start.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
),
|
||||
PodDeletionsLatency: metrics.NewHistogram(
|
||||
&metrics.HistogramOpts{
|
||||
Subsystem: controllerSubsystem,
|
||||
Name: "pod_deletion_duration_seconds",
|
||||
Help: "Latency, in seconds, between the time when a device taint effect has been activated and a Pod's deletion via DeviceTaintEvictionController.",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1, 2.5, 10, 30, 60, 120, 180, 240}, // 5ms to 4m,
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
// This has to be done after construction, otherwise ./hack/update-generated-stable-metrics.sh
|
||||
// fails to find the default buckets.
|
||||
if len(buckets) > 0 {
|
||||
m.PodDeletionsLatency.HistogramOpts.Buckets = buckets
|
||||
}
|
||||
|
||||
m.KubeRegistry.MustRegister(m.PodDeletionsTotal, m.PodDeletionsLatency)
|
||||
return m
|
||||
}
|
||||
|
||||
// Metrics contains all metrics supported by the device taint eviction controller.
|
||||
// It implements [metrics.Gatherer].
|
||||
type Metrics struct {
|
||||
metrics.KubeRegistry
|
||||
PodDeletionsTotal *metrics.Counter
|
||||
PodDeletionsLatency *metrics.Histogram
|
||||
}
|
||||
|
||||
var _ metrics.Gatherer = Metrics{}
|
||||
50
pkg/controller/tainteviction/namespacedobject.go
Normal file
50
pkg/controller/tainteviction/namespacedobject.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 tainteviction
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// NamespacedObject comprises a resource name with a mandatory namespace
|
||||
// and optional UID. It gets rendered as "<namespace>/<name>[:<uid>]"
|
||||
// (text output) or as an object (JSON output).
|
||||
type NamespacedObject struct {
|
||||
types.NamespacedName
|
||||
UID types.UID
|
||||
}
|
||||
|
||||
// String returns the general purpose string representation
|
||||
func (n NamespacedObject) String() string {
|
||||
if n.UID != "" {
|
||||
return n.Namespace + string(types.Separator) + n.Name + ":" + string(n.UID)
|
||||
}
|
||||
return n.Namespace + string(types.Separator) + n.Name
|
||||
}
|
||||
|
||||
// MarshalLog emits a struct containing required key/value pair
|
||||
func (n NamespacedObject) MarshalLog() interface{} {
|
||||
return struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
UID types.UID `json:"uid,omitempty"`
|
||||
}{
|
||||
Name: n.Name,
|
||||
Namespace: n.Namespace,
|
||||
UID: n.UID,
|
||||
}
|
||||
}
|
||||
|
|
@ -106,11 +106,11 @@ type Controller struct {
|
|||
|
||||
func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName), controllerName string) func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||
return func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||
ns := args.NamespacedName.Namespace
|
||||
name := args.NamespacedName.Name
|
||||
klog.FromContext(ctx).Info("Deleting pod", "controller", controllerName, "pod", args.NamespacedName)
|
||||
ns := args.Object.Namespace
|
||||
name := args.Object.Name
|
||||
klog.FromContext(ctx).Info("Deleting pod", "controller", controllerName, "pod", args.Object)
|
||||
if emitEventFunc != nil {
|
||||
emitEventFunc(args.NamespacedName)
|
||||
emitEventFunc(args.Object.NamespacedName)
|
||||
}
|
||||
var err error
|
||||
for i := 0; i < retries; i++ {
|
||||
|
|
|
|||
|
|
@ -28,18 +28,23 @@ import (
|
|||
|
||||
// WorkArgs keeps arguments that will be passed to the function executed by the worker.
|
||||
type WorkArgs struct {
|
||||
NamespacedName types.NamespacedName
|
||||
// Object is the work item. The UID is only set if it was set when adding the work item.
|
||||
Object NamespacedObject
|
||||
}
|
||||
|
||||
// KeyFromWorkArgs creates a key for the given `WorkArgs`
|
||||
// KeyFromWorkArgs creates a key for the given `WorkArgs`.
|
||||
//
|
||||
// The key is the same as the NamespacedName of the object in the work item,
|
||||
// i.e. the UID is ignored. There cannot be two different
|
||||
// work items with the same NamespacedName and different UIDs.
|
||||
func (w *WorkArgs) KeyFromWorkArgs() string {
|
||||
return w.NamespacedName.String()
|
||||
return w.Object.NamespacedName.String()
|
||||
}
|
||||
|
||||
// NewWorkArgs is a helper function to create new `WorkArgs`
|
||||
// NewWorkArgs is a helper function to create new `WorkArgs` without a UID.
|
||||
func NewWorkArgs(name, namespace string) *WorkArgs {
|
||||
return &WorkArgs{
|
||||
NamespacedName: types.NamespacedName{Namespace: namespace, Name: name},
|
||||
Object: NamespacedObject{NamespacedName: types.NamespacedName{Namespace: namespace, Name: name}},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,31 +107,59 @@ func CreateWorkerQueue(f func(ctx context.Context, fireAt time.Time, args *WorkA
|
|||
|
||||
func (q *TimedWorkerQueue) getWrappedWorkerFunc(key string) func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||
return func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||
logger := klog.FromContext(ctx)
|
||||
logger.V(4).Info("Firing worker", "item", key, "firedTime", fireAt)
|
||||
err := q.workFunc(ctx, fireAt, args)
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
logger.V(4).Info("Worker finished, removing", "item", key, "err", err)
|
||||
delete(q.workers, key)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// AddWork adds a work to the WorkerQueue which will be executed not earlier than `fireAt`.
|
||||
// If replace is false, an existing work item will not get replaced, otherwise it
|
||||
// gets canceled and the new one is added instead.
|
||||
func (q *TimedWorkerQueue) AddWork(ctx context.Context, args *WorkArgs, createdAt time.Time, fireAt time.Time) {
|
||||
key := args.KeyFromWorkArgs()
|
||||
logger := klog.FromContext(ctx)
|
||||
logger.V(4).Info("Adding TimedWorkerQueue item and to be fired at firedTime", "item", key, "createTime", createdAt, "firedTime", fireAt)
|
||||
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
if _, exists := q.workers[key]; exists {
|
||||
logger.Info("Trying to add already existing work, skipping", "args", args)
|
||||
logger.V(4).Info("Trying to add already existing work, skipping", "item", key, "createTime", createdAt, "firedTime", fireAt)
|
||||
return
|
||||
}
|
||||
logger.V(4).Info("Adding TimedWorkerQueue item and to be fired at firedTime", "item", key, "createTime", createdAt, "firedTime", fireAt)
|
||||
worker := createWorker(ctx, args, createdAt, fireAt, q.getWrappedWorkerFunc(key), q.clock)
|
||||
q.workers[key] = worker
|
||||
}
|
||||
|
||||
// UpdateWork adds or replaces a work item such that it will be executed not earlier than `fireAt`.
|
||||
// This is a cheap no-op when the old and new fireAt are the same.
|
||||
func (q *TimedWorkerQueue) UpdateWork(ctx context.Context, args *WorkArgs, createdAt time.Time, fireAt time.Time) {
|
||||
key := args.KeyFromWorkArgs()
|
||||
logger := klog.FromContext(ctx)
|
||||
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
if worker, exists := q.workers[key]; exists {
|
||||
if worker.FireAt.Compare(fireAt) == 0 {
|
||||
logger.V(4).Info("Keeping existing work, same time", "item", key, "createTime", worker.CreatedAt, "firedTime", worker.FireAt)
|
||||
return
|
||||
}
|
||||
logger.V(4).Info("Replacing existing work", "item", key, "createTime", worker.CreatedAt, "firedTime", worker.FireAt)
|
||||
worker.Cancel()
|
||||
}
|
||||
logger.V(4).Info("Adding TimedWorkerQueue item and to be fired at firedTime", "item", key, "createTime", createdAt, "firedTime", fireAt)
|
||||
worker := createWorker(ctx, args, createdAt, fireAt, q.getWrappedWorkerFunc(key), q.clock)
|
||||
q.workers[key] = worker
|
||||
}
|
||||
|
||||
// CancelWork removes scheduled function execution from the queue. Returns true if work was cancelled.
|
||||
// The key must be the same as the one returned by WorkArgs.KeyFromWorkArgs, i.e.
|
||||
// the result of NamespacedName.String.
|
||||
func (q *TimedWorkerQueue) CancelWork(logger klog.Logger, key string) bool {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
|
|
|||
|
|
@ -220,6 +220,13 @@ const (
|
|||
// is to move it into a separate KEP.
|
||||
DRAAdminAccess featuregate.Feature = "DRAAdminAccess"
|
||||
|
||||
// owner: @pohly
|
||||
// kep: http://kep.k8s.io/5055
|
||||
//
|
||||
// Marking devices as tainted can prevent using them for new pods and/or
|
||||
// cause pods using them to stop. Users can decide to tolerate taints.
|
||||
DRADeviceTaints featuregate.Feature = "DRADeviceTaints"
|
||||
|
||||
// owner: @mortent
|
||||
// kep: http://kep.k8s.io/4816
|
||||
//
|
||||
|
|
@ -228,6 +235,13 @@ const (
|
|||
// be selected.
|
||||
DRAPrioritizedList featuregate.Feature = "DRAPrioritizedList"
|
||||
|
||||
// owner: @LionelJouin
|
||||
// kep: http://kep.k8s.io/4817
|
||||
//
|
||||
// Enables support the ResourceClaim.status.devices field and for setting this
|
||||
// status from DRA drivers.
|
||||
DRAResourceClaimDeviceStatus featuregate.Feature = "DRAResourceClaimDeviceStatus"
|
||||
|
||||
// owner: @pohly
|
||||
// kep: http://kep.k8s.io/4381
|
||||
//
|
||||
|
|
@ -236,13 +250,6 @@ const (
|
|||
// based on "structured parameters".
|
||||
DynamicResourceAllocation featuregate.Feature = "DynamicResourceAllocation"
|
||||
|
||||
// owner: @LionelJouin
|
||||
// kep: http://kep.k8s.io/4817
|
||||
//
|
||||
// Enables support the ResourceClaim.status.devices field and for setting this
|
||||
// status from DRA drivers.
|
||||
DRAResourceClaimDeviceStatus featuregate.Feature = "DRAResourceClaimDeviceStatus"
|
||||
|
||||
// owner: @lauralorenz
|
||||
// kep: https://kep.k8s.io/4603
|
||||
//
|
||||
|
|
|
|||
|
|
@ -178,13 +178,12 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
|
|||
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
|
||||
},
|
||||
|
||||
DRAPrioritizedList: {
|
||||
DRADeviceTaints: {
|
||||
{Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha},
|
||||
},
|
||||
|
||||
DynamicResourceAllocation: {
|
||||
{Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha},
|
||||
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Beta},
|
||||
DRAPrioritizedList: {
|
||||
{Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha},
|
||||
},
|
||||
|
||||
DRAResourceClaimDeviceStatus: {
|
||||
|
|
@ -192,6 +191,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
|
|||
{Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta},
|
||||
},
|
||||
|
||||
DynamicResourceAllocation: {
|
||||
{Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha},
|
||||
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Beta},
|
||||
},
|
||||
|
||||
KubeletCrashLoopBackOffMax: {
|
||||
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
|
||||
},
|
||||
|
|
|
|||
554
pkg/generated/openapi/zz_generated.openapi.go
generated
554
pkg/generated/openapi/zz_generated.openapi.go
generated
|
|
@ -935,6 +935,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||
"k8s.io/api/resource/v1alpha3.DeviceRequestAllocationResult": schema_k8sio_api_resource_v1alpha3_DeviceRequestAllocationResult(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSelector": schema_k8sio_api_resource_v1alpha3_DeviceSelector(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSubRequest": schema_k8sio_api_resource_v1alpha3_DeviceSubRequest(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaint": schema_k8sio_api_resource_v1alpha3_DeviceTaint(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintRule": schema_k8sio_api_resource_v1alpha3_DeviceTaintRule(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintRuleList": schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleList(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintRuleSpec": schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleSpec(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintSelector": schema_k8sio_api_resource_v1alpha3_DeviceTaintSelector(ref),
|
||||
"k8s.io/api/resource/v1alpha3.DeviceToleration": schema_k8sio_api_resource_v1alpha3_DeviceToleration(ref),
|
||||
"k8s.io/api/resource/v1alpha3.NetworkDeviceData": schema_k8sio_api_resource_v1alpha3_NetworkDeviceData(ref),
|
||||
"k8s.io/api/resource/v1alpha3.OpaqueDeviceConfiguration": schema_k8sio_api_resource_v1alpha3_OpaqueDeviceConfiguration(ref),
|
||||
"k8s.io/api/resource/v1alpha3.ResourceClaim": schema_k8sio_api_resource_v1alpha3_ResourceClaim(ref),
|
||||
|
|
@ -970,6 +976,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||
"k8s.io/api/resource/v1beta1.DeviceRequestAllocationResult": schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref),
|
||||
"k8s.io/api/resource/v1beta1.DeviceSelector": schema_k8sio_api_resource_v1beta1_DeviceSelector(ref),
|
||||
"k8s.io/api/resource/v1beta1.DeviceSubRequest": schema_k8sio_api_resource_v1beta1_DeviceSubRequest(ref),
|
||||
"k8s.io/api/resource/v1beta1.DeviceTaint": schema_k8sio_api_resource_v1beta1_DeviceTaint(ref),
|
||||
"k8s.io/api/resource/v1beta1.DeviceToleration": schema_k8sio_api_resource_v1beta1_DeviceToleration(ref),
|
||||
"k8s.io/api/resource/v1beta1.NetworkDeviceData": schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref),
|
||||
"k8s.io/api/resource/v1beta1.OpaqueDeviceConfiguration": schema_k8sio_api_resource_v1beta1_OpaqueDeviceConfiguration(ref),
|
||||
"k8s.io/api/resource/v1beta1.ResourceClaim": schema_k8sio_api_resource_v1beta1_ResourceClaim(ref),
|
||||
|
|
@ -47168,11 +47176,30 @@ func schema_k8sio_api_resource_v1alpha3_BasicDevice(ref common.ReferenceCallback
|
|||
},
|
||||
},
|
||||
},
|
||||
"taints": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 8.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceTaint"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceAttribute", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
|
||||
"k8s.io/api/resource/v1alpha3.DeviceAttribute", "k8s.io/api/resource/v1alpha3.DeviceTaint", "k8s.io/apimachinery/pkg/api/resource.Quantity"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47797,12 +47824,31 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref common.ReferenceCallba
|
|||
},
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSelector", "k8s.io/api/resource/v1alpha3.DeviceSubRequest"},
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSelector", "k8s.io/api/resource/v1alpha3.DeviceSubRequest", "k8s.io/api/resource/v1alpha3.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47852,10 +47898,31 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequestAllocationResult(ref common
|
|||
Format: "",
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"request", "driver", "pool", "device"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47936,15 +48003,317 @@ func schema_k8sio_api_resource_v1alpha3_DeviceSubRequest(ref common.ReferenceCal
|
|||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "deviceClassName"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSelector", "k8s.io/api/resource/v1alpha3.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceTaint(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"key": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The taint key to be applied to a device. Must be a label name.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The taint value corresponding to the taint key. Must be a label value.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"effect": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.\n\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the device taint.\n - `\"NoSchedule\"` Do not allow new pods to schedule which use a tainted device unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"NoExecute", "NoSchedule"},
|
||||
},
|
||||
},
|
||||
"timeAdded": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"key", "effect"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceTaintRule(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard object metadata",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceTaintRuleSpec"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintRuleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DeviceTaintRuleList is a collection of DeviceTaintRules.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard list metadata",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Items is the list of DeviceTaintRules.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceTaintRule"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaintRule", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceTaintRuleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DeviceTaintRuleSpec specifies the selector and one taint.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"deviceSelector": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satified for a device to match. The empty selector matches all devices. Without a selector, no devices are matches.",
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceTaintSelector"),
|
||||
},
|
||||
},
|
||||
"taint": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The taint that gets applied to matching devices.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceTaint"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"taint"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceTaint", "k8s.io/api/resource/v1alpha3.DeviceTaintSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceTaintSelector(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"deviceClassName": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If DeviceClassName is set, the selectors defined there must be satisfied by a device to be selected. This field corresponds to class.metadata.name.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"driver": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"pool": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"device": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"selectors": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Selectors contains the same selection criteria as a ResourceClaim. Currently, CEL expressions are supported. All of these selectors must be satisfied.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1alpha3.DeviceSelector"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1alpha3.DeviceSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_DeviceToleration(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"key": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"operator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.\n\n\nPossible enum values:\n - `\"Equal\"`\n - `\"Exists\"`",
|
||||
Default: "Equal",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"Equal", "Exists"},
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"effect": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.\n\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the device taint.\n - `\"NoSchedule\"` Do not allow new pods to schedule which use a tainted device unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"NoExecute", "NoSchedule"},
|
||||
},
|
||||
},
|
||||
"tolerationSeconds": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as <time when taint was adedd> + <toleration seconds>.",
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1alpha3_NetworkDeviceData(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
|
@ -48727,11 +49096,30 @@ func schema_k8sio_api_resource_v1beta1_BasicDevice(ref common.ReferenceCallback)
|
|||
},
|
||||
},
|
||||
},
|
||||
"taints": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 8.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1beta1.DeviceTaint"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1beta1.DeviceAttribute", "k8s.io/api/resource/v1beta1.DeviceCapacity"},
|
||||
"k8s.io/api/resource/v1beta1.DeviceAttribute", "k8s.io/api/resource/v1beta1.DeviceCapacity", "k8s.io/api/resource/v1beta1.DeviceTaint"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49378,12 +49766,31 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac
|
|||
},
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1beta1.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1beta1.DeviceSelector", "k8s.io/api/resource/v1beta1.DeviceSubRequest"},
|
||||
"k8s.io/api/resource/v1beta1.DeviceSelector", "k8s.io/api/resource/v1beta1.DeviceSubRequest", "k8s.io/api/resource/v1beta1.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49433,10 +49840,31 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref common.
|
|||
Format: "",
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1beta1.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"request", "driver", "pool", "device"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1beta1.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49517,12 +49945,128 @@ func schema_k8sio_api_resource_v1beta1_DeviceSubRequest(ref common.ReferenceCall
|
|||
Format: "int64",
|
||||
},
|
||||
},
|
||||
"tolerations": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/resource/v1beta1.DeviceToleration"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "deviceClassName"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/resource/v1beta1.DeviceSelector"},
|
||||
"k8s.io/api/resource/v1beta1.DeviceSelector", "k8s.io/api/resource/v1beta1.DeviceToleration"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1beta1_DeviceTaint(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"key": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The taint key to be applied to a device. Must be a label name.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The taint value corresponding to the taint key. Must be a label value.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"effect": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.\n\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the device taint.\n - `\"NoSchedule\"` Do not allow new pods to schedule which use a tainted device unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"NoExecute", "NoSchedule"},
|
||||
},
|
||||
},
|
||||
"timeAdded": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"key", "effect"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_resource_v1beta1_DeviceToleration(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"key": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"operator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.\n\n\nPossible enum values:\n - `\"Equal\"`\n - `\"Exists\"`",
|
||||
Default: "Equal",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"Equal", "Exists"},
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"effect": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.\n\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the device taint.\n - `\"NoSchedule\"` Do not allow new pods to schedule which use a tainted device unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"NoExecute", "NoSchedule"},
|
||||
},
|
||||
},
|
||||
"tolerationSeconds": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as <time when taint was adedd> + <toleration seconds>.",
|
||||
Type: []string{"integer"},
|
||||
Format: "int64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apis/networking"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
"k8s.io/kubernetes/pkg/apis/storagemigration"
|
||||
)
|
||||
|
|
@ -89,6 +90,7 @@ func NewStorageFactoryConfigEffectiveVersion(effectiveVersion basecompatibility.
|
|||
certificates.Resource("clustertrustbundles").WithVersion("v1beta1"),
|
||||
storage.Resource("volumeattributesclasses").WithVersion("v1beta1"),
|
||||
storagemigration.Resource("storagemigrations").WithVersion("v1alpha1"),
|
||||
resource.Resource("devicetaintrules").WithVersion("v1alpha3"),
|
||||
}
|
||||
|
||||
return &StorageFactoryConfig{
|
||||
|
|
|
|||
|
|
@ -687,6 +687,22 @@ func AddHandlers(h printers.PrintHandler) {
|
|||
_ = h.TableHandler(nodeResourceSliceColumnDefinitions, printResourceSlice)
|
||||
_ = h.TableHandler(nodeResourceSliceColumnDefinitions, printResourceSliceList)
|
||||
|
||||
deviceTaintColumnDefinitions := []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
// The filter criteria are not printed. They could be lengthy (CEL!) and in practice many of them
|
||||
// will be empty. Instead, the admin could pick a descriptive name.
|
||||
//
|
||||
// The taint is more useful.
|
||||
{Name: "Key", Type: "string", Description: resourceapi.DeviceTaint{}.SwaggerDoc()["key"]},
|
||||
{Name: "Value", Type: "string", Description: resourceapi.DeviceTaint{}.SwaggerDoc()["value"]},
|
||||
{Name: "Effect", Type: "string", Description: resourceapi.DeviceTaint{}.SwaggerDoc()["effect"]},
|
||||
// TimeAdded and Age are often the same, but not necessarily.
|
||||
{Name: "TimeAdded", Type: "string", Description: resourceapi.DeviceTaint{}.SwaggerDoc()["timeAdded"]},
|
||||
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
|
||||
}
|
||||
_ = h.TableHandler(deviceTaintColumnDefinitions, printDeviceTaint)
|
||||
_ = h.TableHandler(deviceTaintColumnDefinitions, printDeviceTaintRuleList)
|
||||
|
||||
serviceCIDRColumnDefinitions := []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
{Name: "CIDRs", Type: "string", Description: networkingv1beta1.ServiceCIDRSpec{}.SwaggerDoc()["cidrs"]},
|
||||
|
|
@ -3172,6 +3188,32 @@ func printResourceSliceList(list *resource.ResourceSliceList, options printers.G
|
|||
return rows, nil
|
||||
}
|
||||
|
||||
func printDeviceTaint(obj *resource.DeviceTaintRule, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
row := metav1.TableRow{
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
}
|
||||
|
||||
timeAdded := ""
|
||||
if added := obj.Spec.Taint.TimeAdded; added != nil {
|
||||
timeAdded = translateTimestampSince(*added)
|
||||
}
|
||||
row.Cells = append(row.Cells, obj.Name, string(obj.Spec.Taint.Key), obj.Spec.Taint.Value, string(obj.Spec.Taint.Effect), timeAdded, translateTimestampSince(obj.CreationTimestamp))
|
||||
|
||||
return []metav1.TableRow{row}, nil
|
||||
}
|
||||
|
||||
func printDeviceTaintRuleList(list *resource.DeviceTaintRuleList, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
rows := make([]metav1.TableRow, 0, len(list.Items))
|
||||
for i := range list.Items {
|
||||
r, err := printDeviceTaint(&list.Items[i], options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows = append(rows, r...)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func printStorageVersionMigration(obj *svmv1alpha1.StorageVersionMigration, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
row := metav1.TableRow{
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
|
|
|
|||
56
pkg/registry/resource/devicetaintrule/storage/storage.go
Normal file
56
pkg/registry/resource/devicetaintrule/storage/storage.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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 storage
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/resource/devicetaintrule"
|
||||
)
|
||||
|
||||
// REST implements a RESTStorage for DeviceTaintRule.
|
||||
type REST struct {
|
||||
*genericregistry.Store
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against DeviceTaintRule.
|
||||
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &resource.DeviceTaintRule{} },
|
||||
NewListFunc: func() runtime.Object { return &resource.DeviceTaintRuleList{} },
|
||||
DefaultQualifiedResource: resource.Resource("devicetaintrules"),
|
||||
SingularQualifiedResource: resource.Resource("devicetaintrule"),
|
||||
|
||||
CreateStrategy: devicetaintrule.Strategy,
|
||||
UpdateStrategy: devicetaintrule.Strategy,
|
||||
DeleteStrategy: devicetaintrule.Strategy,
|
||||
ReturnDeletedObject: true,
|
||||
|
||||
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &REST{store}, nil
|
||||
}
|
||||
144
pkg/registry/resource/devicetaintrule/storage/storage_test.go
Normal file
144
pkg/registry/resource/devicetaintrule/storage/storage_test.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
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 storage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
_ "k8s.io/kubernetes/pkg/apis/resource/install"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
)
|
||||
|
||||
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
|
||||
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, resource.Resource("devicetaintrules"))
|
||||
restOptions := generic.RESTOptions{
|
||||
StorageConfig: etcdStorage,
|
||||
Decorator: generic.UndecoratedStorage,
|
||||
DeleteCollectionWorkers: 1,
|
||||
ResourcePrefix: "devicetaintrules",
|
||||
}
|
||||
deviceTaintStorage, err := NewREST(restOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
||||
}
|
||||
return deviceTaintStorage, server
|
||||
}
|
||||
|
||||
func validNewDeviceTaint(name string) *resource.DeviceTaintRule {
|
||||
return &resource.DeviceTaintRule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: resource.DeviceTaintRuleSpec{
|
||||
Taint: resource.DeviceTaint{
|
||||
Key: "example.com/taint",
|
||||
Effect: resource.DeviceTaintEffectNoExecute,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||
patch := validNewDeviceTaint("foo")
|
||||
patch.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"}
|
||||
test.TestCreate(
|
||||
// valid
|
||||
patch,
|
||||
// invalid
|
||||
&resource.DeviceTaintRule{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "*BadName!"},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||
test.TestUpdate(
|
||||
// valid
|
||||
validNewDeviceTaint("foo"),
|
||||
// updateFunc
|
||||
func(obj runtime.Object) runtime.Object {
|
||||
object := obj.(*resource.DeviceTaintRule)
|
||||
object.Labels = map[string]string{"foo": "bar"}
|
||||
return object
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject()
|
||||
test.TestDelete(validNewDeviceTaint("foo"))
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||
test.TestGet(validNewDeviceTaint("foo"))
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||
test.TestList(validNewDeviceTaint("foo"))
|
||||
}
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
test := genericregistrytest.New(t, storage.Store).ClusterScope()
|
||||
test.TestWatch(
|
||||
validNewDeviceTaint("foo"),
|
||||
// matching labels
|
||||
[]labels.Set{},
|
||||
// not matching labels
|
||||
[]labels.Set{
|
||||
{"foo": "bar"},
|
||||
},
|
||||
// matching fields
|
||||
[]fields.Set{
|
||||
{"metadata.name": "foo"},
|
||||
},
|
||||
// not matching fields
|
||||
[]fields.Set{
|
||||
{"metadata.name": "bar"},
|
||||
},
|
||||
)
|
||||
}
|
||||
84
pkg/registry/resource/devicetaintrule/strategy.go
Normal file
84
pkg/registry/resource/devicetaintrule/strategy.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
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 devicetaintrule
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/apis/resource/validation"
|
||||
)
|
||||
|
||||
// deviceTaintRuleStrategy implements behavior for DeviceTaintRule objects
|
||||
type deviceTaintRuleStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
}
|
||||
|
||||
var Strategy = deviceTaintRuleStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
||||
|
||||
func (deviceTaintRuleStrategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
patch := obj.(*resource.DeviceTaintRule)
|
||||
patch.Generation = 1
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
patch := obj.(*resource.DeviceTaintRule)
|
||||
return validation.ValidateDeviceTaintRule(patch)
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) Canonicalize(obj runtime.Object) {
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
patch := obj.(*resource.DeviceTaintRule)
|
||||
oldPatch := old.(*resource.DeviceTaintRule)
|
||||
|
||||
// Any changes to the spec increment the generation number.
|
||||
if !apiequality.Semantic.DeepEqual(oldPatch.Spec, patch.Spec) {
|
||||
patch.Generation = oldPatch.Generation + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return validation.ValidateDeviceTaintRuleUpdate(obj.(*resource.DeviceTaintRule), old.(*resource.DeviceTaintRule))
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (deviceTaintRuleStrategy) AllowUnconditionalUpdate() bool {
|
||||
return true
|
||||
}
|
||||
86
pkg/registry/resource/devicetaintrule/strategy_test.go
Normal file
86
pkg/registry/resource/devicetaintrule/strategy_test.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
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 devicetaintrule
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
)
|
||||
|
||||
var patch = &resource.DeviceTaintRule{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-patch",
|
||||
},
|
||||
Spec: resource.DeviceTaintRuleSpec{
|
||||
Taint: resource.DeviceTaint{
|
||||
Key: "example.com/tainted",
|
||||
Effect: resource.DeviceTaintEffectNoExecute,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDeviceTaintRuleStrategy(t *testing.T) {
|
||||
if Strategy.NamespaceScoped() {
|
||||
t.Errorf("DeviceTaintRule must not be namespace scoped")
|
||||
}
|
||||
if Strategy.AllowCreateOnUpdate() {
|
||||
t.Errorf("DeviceTaintRule should not allow create on update")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeviceTaintRuleStrategyCreate(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
patch := patch.DeepCopy()
|
||||
|
||||
Strategy.PrepareForCreate(ctx, patch)
|
||||
errs := Strategy.Validate(ctx, patch)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected error validating for create %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeviceTaintRuleStrategyUpdate(t *testing.T) {
|
||||
t.Run("no-changes-okay", func(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
patch := patch.DeepCopy()
|
||||
newPatch := patch.DeepCopy()
|
||||
newPatch.ResourceVersion = "4"
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newPatch, patch)
|
||||
errs := Strategy.ValidateUpdate(ctx, newPatch, patch)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("name-change-not-allowed", func(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
patch := patch.DeepCopy()
|
||||
newPatch := patch.DeepCopy()
|
||||
newPatch.Name = "valid-patch-2"
|
||||
newPatch.ResourceVersion = "4"
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newPatch, patch)
|
||||
errs := Strategy.ValidateUpdate(ctx, newPatch, patch)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected a validation error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -335,3 +335,5 @@ func dropDeallocatedStatusDevices(newClaim, oldClaim *resource.ResourceClaim) {
|
|||
newClaim.Status.Devices = nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add tests after partitionable devices is merged (code conflict!)
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@ import (
|
|||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/apis/resource/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// resourceSliceStrategy implements behavior for ResourceSlice objects
|
||||
|
|
@ -49,6 +51,8 @@ func (resourceSliceStrategy) NamespaceScoped() bool {
|
|||
func (resourceSliceStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
slice := obj.(*resource.ResourceSlice)
|
||||
slice.Generation = 1
|
||||
|
||||
dropDisabledFields(slice, nil)
|
||||
}
|
||||
|
||||
func (resourceSliceStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
|
|
@ -75,6 +79,8 @@ func (resourceSliceStrategy) PrepareForUpdate(ctx context.Context, obj, old runt
|
|||
if !apiequality.Semantic.DeepEqual(oldSlice.Spec, slice.Spec) {
|
||||
slice.Generation = oldSlice.Generation + 1
|
||||
}
|
||||
|
||||
dropDisabledFields(slice, oldSlice)
|
||||
}
|
||||
|
||||
func (resourceSliceStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
|
|
@ -147,3 +153,33 @@ func toSelectableFields(slice *resource.ResourceSlice) fields.Set {
|
|||
// Adds one field.
|
||||
return generic.AddObjectMetaFieldsSet(fields, &slice.ObjectMeta, false)
|
||||
}
|
||||
|
||||
// dropDisabledFields removes fields which are covered by a feature gate.
|
||||
func dropDisabledFields(newSlice, oldSlice *resource.ResourceSlice) {
|
||||
dropDisabledDRADeviceTaintsFields(newSlice, oldSlice)
|
||||
}
|
||||
|
||||
func dropDisabledDRADeviceTaintsFields(newSlice, oldSlice *resource.ResourceSlice) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaints) || draDeviceTaintsFeatureInUse(oldSlice) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, device := range newSlice.Spec.Devices {
|
||||
if device.Basic != nil {
|
||||
device.Basic.Taints = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func draDeviceTaintsFeatureInUse(slice *resource.ResourceSlice) bool {
|
||||
if slice == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, device := range slice.Spec.Devices {
|
||||
if device.Basic != nil && len(device.Basic.Taints) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,14 @@ package resourceslice
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
var slice = &resource.ResourceSlice{
|
||||
|
|
@ -35,9 +40,22 @@ var slice = &resource.ResourceSlice{
|
|||
Name: "valid-pool-name",
|
||||
ResourceSliceCount: 1,
|
||||
},
|
||||
Devices: []resource.Device{{
|
||||
Name: "device-0",
|
||||
Basic: &resource.BasicDevice{},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
var sliceWithDeviceTaints = func() *resource.ResourceSlice {
|
||||
slice := slice.DeepCopy()
|
||||
slice.Spec.Devices[0].Basic.Taints = []resource.DeviceTaint{{
|
||||
Key: "example.com/tainted",
|
||||
Effect: resource.DeviceTaintEffectNoSchedule,
|
||||
}}
|
||||
return slice
|
||||
}()
|
||||
|
||||
func TestResourceSliceStrategy(t *testing.T) {
|
||||
if Strategy.NamespaceScoped() {
|
||||
t.Errorf("ResourceSlice must not be namespace scoped")
|
||||
|
|
@ -49,40 +67,186 @@ func TestResourceSliceStrategy(t *testing.T) {
|
|||
|
||||
func TestResourceSliceStrategyCreate(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
slice := slice.DeepCopy()
|
||||
testCases := map[string]struct {
|
||||
obj *resource.ResourceSlice
|
||||
deviceTaints bool
|
||||
expectedValidationError bool
|
||||
expectObj *resource.ResourceSlice
|
||||
}{
|
||||
"simple": {
|
||||
obj: slice,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.ObjectMeta.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"validation error": {
|
||||
obj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.Name = "%#@$%$"
|
||||
return obj
|
||||
}(),
|
||||
expectedValidationError: true,
|
||||
},
|
||||
"drop-fields-device-taints": {
|
||||
obj: sliceWithDeviceTaints,
|
||||
deviceTaints: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-device-taints": {
|
||||
obj: sliceWithDeviceTaints,
|
||||
deviceTaints: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ObjectMeta.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
Strategy.PrepareForCreate(ctx, slice)
|
||||
errs := Strategy.Validate(ctx, slice)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected error validating for create %v", errs)
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRADeviceTaints, tc.deviceTaints)
|
||||
|
||||
obj := tc.obj.DeepCopy()
|
||||
|
||||
Strategy.PrepareForCreate(ctx, obj)
|
||||
if errs := Strategy.Validate(ctx, obj); len(errs) != 0 {
|
||||
if !tc.expectedValidationError {
|
||||
t.Fatalf("unexpected validation errors: %q", errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
if warnings := Strategy.WarningsOnCreate(ctx, obj); len(warnings) != 0 {
|
||||
t.Fatalf("unexpected warnings: %q", warnings)
|
||||
}
|
||||
Strategy.Canonicalize(obj)
|
||||
assert.Equal(t, tc.expectObj, obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceSliceStrategyUpdate(t *testing.T) {
|
||||
t.Run("no-changes-okay", func(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
slice := slice.DeepCopy()
|
||||
newSlice := slice.DeepCopy()
|
||||
newSlice.ResourceVersion = "4"
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newSlice, slice)
|
||||
errs := Strategy.ValidateUpdate(ctx, newSlice, slice)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
})
|
||||
testcases := map[string]struct {
|
||||
oldObj *resource.ResourceSlice
|
||||
newObj *resource.ResourceSlice
|
||||
deviceTaints bool
|
||||
expectValidationError bool
|
||||
expectObj *resource.ResourceSlice
|
||||
}{
|
||||
"no-changes-okay": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"name-change-not-allowed": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.Name = "valid-slice-2"
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
expectValidationError: true,
|
||||
},
|
||||
"drop-fields-device-taints": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
deviceTaints: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-device-taints": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
deviceTaints: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-device-taints": {
|
||||
oldObj: sliceWithDeviceTaints,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
deviceTaints: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-device-taints-disabled-feature": {
|
||||
oldObj: sliceWithDeviceTaints,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
deviceTaints: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithDeviceTaints.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("name-change-not-allowed", func(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
slice := slice.DeepCopy()
|
||||
newSlice := slice.DeepCopy()
|
||||
newSlice.Name = "valid-slice-2"
|
||||
newSlice.ResourceVersion = "4"
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRADeviceTaints, tc.deviceTaints)
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newSlice, slice)
|
||||
errs := Strategy.ValidateUpdate(ctx, newSlice, slice)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected a validation error")
|
||||
}
|
||||
})
|
||||
oldObj := tc.oldObj.DeepCopy()
|
||||
newObj := tc.newObj.DeepCopy()
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newObj, oldObj)
|
||||
if errs := Strategy.ValidateUpdate(ctx, newObj, oldObj); len(errs) != 0 {
|
||||
if !tc.expectValidationError {
|
||||
t.Fatalf("unexpected validation errors: %q", errs)
|
||||
}
|
||||
return
|
||||
} else if tc.expectValidationError {
|
||||
t.Fatal("expected validation error(s), got none")
|
||||
}
|
||||
if warnings := Strategy.WarningsOnUpdate(ctx, newObj, oldObj); len(warnings) != 0 {
|
||||
t.Fatalf("unexpected warnings: %q", warnings)
|
||||
}
|
||||
Strategy.Canonicalize(newObj)
|
||||
|
||||
expectObj := tc.expectObj.DeepCopy()
|
||||
assert.Equal(t, expectObj, newObj)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/resource"
|
||||
deviceclassstore "k8s.io/kubernetes/pkg/registry/resource/deviceclass/storage"
|
||||
devicetaintrulestore "k8s.io/kubernetes/pkg/registry/resource/devicetaintrule/storage"
|
||||
resourceclaimstore "k8s.io/kubernetes/pkg/registry/resource/resourceclaim/storage"
|
||||
resourceclaimtemplatestore "k8s.io/kubernetes/pkg/registry/resource/resourceclaimtemplate/storage"
|
||||
resourceslicestore "k8s.io/kubernetes/pkg/registry/resource/resourceslice/storage"
|
||||
|
|
@ -96,6 +97,14 @@ func (p RESTStorageProvider) v1alpha3Storage(apiResourceConfigSource serverstora
|
|||
storage[resource] = resourceSliceStorage
|
||||
}
|
||||
|
||||
if resource := "devicetaintrules"; apiResourceConfigSource.ResourceEnabled(resourcev1alpha3.SchemeGroupVersion.WithResource(resource)) {
|
||||
deviceTaintStorage, err := devicetaintrulestore.NewREST(restOptionsGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storage[resource] = deviceTaintStorage
|
||||
}
|
||||
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"k8s.io/client-go/tools/cache"
|
||||
corev1helpers "k8s.io/component-helpers/scheduling/corev1"
|
||||
corev1nodeaffinity "k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/scheduler/backend/queue"
|
||||
|
|
@ -366,6 +367,7 @@ func addAllEventHandlers(
|
|||
informerFactory informers.SharedInformerFactory,
|
||||
dynInformerFactory dynamicinformer.DynamicSharedInformerFactory,
|
||||
resourceClaimCache *assumecache.AssumeCache,
|
||||
resourceSliceTracker *resourceslicetracker.Tracker,
|
||||
gvkMap map[framework.EventResource]framework.ActionType,
|
||||
) error {
|
||||
var (
|
||||
|
|
@ -555,7 +557,7 @@ func addAllEventHandlers(
|
|||
}
|
||||
case framework.ResourceSlice:
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
|
||||
if handlerRegistration, err = informerFactory.Resource().V1beta1().ResourceSlices().Informer().AddEventHandler(
|
||||
if handlerRegistration, err = resourceSliceTracker.AddEventHandler(
|
||||
buildEvtResHandler(at, framework.ResourceSlice),
|
||||
); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -28,12 +28,14 @@ import (
|
|||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
resourcealphaapi "k8s.io/api/resource/v1alpha3"
|
||||
resourceapi "k8s.io/api/resource/v1beta1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
|
||||
|
|
@ -395,6 +397,7 @@ func TestAddAllEventHandlers(t *testing.T) {
|
|||
name string
|
||||
gvkMap map[framework.EventResource]framework.ActionType
|
||||
enableDRA bool
|
||||
enableDRADeviceTaints bool
|
||||
expectStaticInformers map[reflect.Type]bool
|
||||
expectDynamicInformers map[schema.GroupVersionResource]bool
|
||||
}{
|
||||
|
|
@ -423,7 +426,7 @@ func TestAddAllEventHandlers(t *testing.T) {
|
|||
expectDynamicInformers: map[schema.GroupVersionResource]bool{},
|
||||
},
|
||||
{
|
||||
name: "all DRA events enabled",
|
||||
name: "core DRA events enabled",
|
||||
gvkMap: map[framework.EventResource]framework.ActionType{
|
||||
framework.ResourceClaim: framework.Add,
|
||||
framework.ResourceSlice: framework.Add,
|
||||
|
|
@ -440,6 +443,26 @@ func TestAddAllEventHandlers(t *testing.T) {
|
|||
},
|
||||
expectDynamicInformers: map[schema.GroupVersionResource]bool{},
|
||||
},
|
||||
{
|
||||
name: "all DRA events enabled",
|
||||
gvkMap: map[framework.EventResource]framework.ActionType{
|
||||
framework.ResourceClaim: framework.Add,
|
||||
framework.ResourceSlice: framework.Add,
|
||||
framework.DeviceClass: framework.Add,
|
||||
},
|
||||
enableDRA: true,
|
||||
enableDRADeviceTaints: true,
|
||||
expectStaticInformers: map[reflect.Type]bool{
|
||||
reflect.TypeOf(&v1.Pod{}): true,
|
||||
reflect.TypeOf(&v1.Node{}): true,
|
||||
reflect.TypeOf(&v1.Namespace{}): true,
|
||||
reflect.TypeOf(&resourceapi.ResourceClaim{}): true,
|
||||
reflect.TypeOf(&resourceapi.ResourceSlice{}): true,
|
||||
reflect.TypeOf(&resourcealphaapi.DeviceTaintRule{}): true,
|
||||
reflect.TypeOf(&resourceapi.DeviceClass{}): true,
|
||||
},
|
||||
expectDynamicInformers: map[schema.GroupVersionResource]bool{},
|
||||
},
|
||||
{
|
||||
name: "add GVKs handlers defined in framework dynamically",
|
||||
gvkMap: map[framework.EventResource]framework.ActionType{
|
||||
|
|
@ -499,6 +522,7 @@ func TestAddAllEventHandlers(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicResourceAllocation, tt.enableDRA)
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRADeviceTaints, tt.enableDRADeviceTaints)
|
||||
|
||||
logger, ctx := ktesting.NewTestContext(t)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
|
@ -515,12 +539,27 @@ func TestAddAllEventHandlers(t *testing.T) {
|
|||
dynclient := dyfake.NewSimpleDynamicClient(scheme)
|
||||
dynInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(dynclient, 0)
|
||||
var resourceClaimCache *assumecache.AssumeCache
|
||||
var resourceSliceTracker *resourceslicetracker.Tracker
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
|
||||
resourceClaimInformer := informerFactory.Resource().V1beta1().ResourceClaims().Informer()
|
||||
resourceClaimCache = assumecache.NewAssumeCache(logger, resourceClaimInformer, "ResourceClaim", "", nil)
|
||||
var err error
|
||||
opts := resourceslicetracker.Options{
|
||||
EnableDeviceTaints: utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaints),
|
||||
SliceInformer: informerFactory.Resource().V1beta1().ResourceSlices(),
|
||||
}
|
||||
if opts.EnableDeviceTaints {
|
||||
opts.TaintInformer = informerFactory.Resource().V1alpha3().DeviceTaintRules()
|
||||
opts.ClassInformer = informerFactory.Resource().V1beta1().DeviceClasses()
|
||||
|
||||
}
|
||||
resourceSliceTracker, err = resourceslicetracker.StartTracker(ctx, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start resource slice tracker: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := addAllEventHandlers(&testSched, informerFactory, dynInformerFactory, resourceClaimCache, tt.gvkMap); err != nil {
|
||||
if err := addAllEventHandlers(&testSched, informerFactory, dynInformerFactory, resourceClaimCache, resourceSliceTracker, tt.gvkMap); err != nil {
|
||||
t.Fatalf("Add event handlers failed, error = %v", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (c *shareListerContract) StorageInfos() framework.StorageInfoLister {
|
|||
|
||||
type resourceSliceListerContract struct{}
|
||||
|
||||
func (c *resourceSliceListerContract) List() ([]*resourceapi.ResourceSlice, error) {
|
||||
func (c *resourceSliceListerContract) ListWithDeviceTaintRules() ([]*resourceapi.ResourceSlice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,13 @@ type SharedLister interface {
|
|||
|
||||
// ResourceSliceLister can be used to obtain ResourceSlices.
|
||||
type ResourceSliceLister interface {
|
||||
// List returns a list of all ResourceSlices.
|
||||
List() ([]*resourceapi.ResourceSlice, error)
|
||||
// ListWithDeviceTaintRules returns a list of all ResourceSlices with DeviceTaintRules applied
|
||||
// if the DRADeviceTaints feature is enabled, otherwise without them.
|
||||
//
|
||||
// k8s.io/dynamic-resource-allocation/resourceslice/tracker provides an implementation
|
||||
// of the necessary logic. That tracker can be instantiated as a replacement for
|
||||
// a normal ResourceSlice informer and provides a ListPatchedResourceSlices method.
|
||||
ListWithDeviceTaintRules() ([]*resourceapi.ResourceSlice, error)
|
||||
}
|
||||
|
||||
// DeviceClassLister can be used to obtain DeviceClasses.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/informers"
|
||||
resourcelisters "k8s.io/client-go/listers/resource/v1beta1"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/dynamic-resource-allocation/structured"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
|
|
@ -44,8 +45,9 @@ type DefaultDRAManager struct {
|
|||
deviceClassLister *deviceClassLister
|
||||
}
|
||||
|
||||
func NewDRAManager(ctx context.Context, claimsCache *assumecache.AssumeCache, informerFactory informers.SharedInformerFactory) *DefaultDRAManager {
|
||||
func NewDRAManager(ctx context.Context, claimsCache *assumecache.AssumeCache, resourceSliceTracker *resourceslicetracker.Tracker, informerFactory informers.SharedInformerFactory) *DefaultDRAManager {
|
||||
logger := klog.FromContext(ctx)
|
||||
|
||||
manager := &DefaultDRAManager{
|
||||
resourceClaimTracker: &claimTracker{
|
||||
cache: claimsCache,
|
||||
|
|
@ -53,7 +55,7 @@ func NewDRAManager(ctx context.Context, claimsCache *assumecache.AssumeCache, in
|
|||
allocatedDevices: newAllocatedDevices(logger),
|
||||
logger: logger,
|
||||
},
|
||||
resourceSliceLister: &resourceSliceLister{sliceLister: informerFactory.Resource().V1beta1().ResourceSlices().Lister()},
|
||||
resourceSliceLister: &resourceSliceLister{tracker: resourceSliceTracker},
|
||||
deviceClassLister: &deviceClassLister{classLister: informerFactory.Resource().V1beta1().DeviceClasses().Lister()},
|
||||
}
|
||||
|
||||
|
|
@ -79,11 +81,11 @@ func (s *DefaultDRAManager) DeviceClasses() framework.DeviceClassLister {
|
|||
var _ framework.ResourceSliceLister = &resourceSliceLister{}
|
||||
|
||||
type resourceSliceLister struct {
|
||||
sliceLister resourcelisters.ResourceSliceLister
|
||||
tracker *resourceslicetracker.Tracker
|
||||
}
|
||||
|
||||
func (l *resourceSliceLister) List() ([]*resourceapi.ResourceSlice, error) {
|
||||
return l.sliceLister.List(labels.Everything())
|
||||
func (l *resourceSliceLister) ListWithDeviceTaintRules() ([]*resourceapi.ResourceSlice, error) {
|
||||
return l.tracker.ListPatchedResourceSlices()
|
||||
}
|
||||
|
||||
var _ framework.DeviceClassLister = &deviceClassLister{}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ type DynamicResources struct {
|
|||
enableAdminAccess bool
|
||||
enablePrioritizedList bool
|
||||
enableSchedulingQueueHint bool
|
||||
enableDeviceTaints bool
|
||||
|
||||
fh framework.Handle
|
||||
clientset kubernetes.Interface
|
||||
|
|
@ -123,6 +124,7 @@ func New(ctx context.Context, plArgs runtime.Object, fh framework.Handle, fts fe
|
|||
pl := &DynamicResources{
|
||||
enabled: true,
|
||||
enableAdminAccess: fts.EnableDRAAdminAccess,
|
||||
enableDeviceTaints: fts.EnableDRADeviceTaints,
|
||||
enablePrioritizedList: fts.EnableDRAPrioritizedList,
|
||||
enableSchedulingQueueHint: fts.EnableSchedulingQueueHint,
|
||||
|
||||
|
|
@ -448,11 +450,11 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.Cycl
|
|||
if err != nil {
|
||||
return nil, statusError(logger, err)
|
||||
}
|
||||
slices, err := pl.draManager.ResourceSlices().List()
|
||||
slices, err := pl.draManager.ResourceSlices().ListWithDeviceTaintRules()
|
||||
if err != nil {
|
||||
return nil, statusError(logger, err)
|
||||
}
|
||||
allocator, err := structured.NewAllocator(ctx, pl.enableAdminAccess, pl.enablePrioritizedList, allocateClaims, allAllocatedDevices, pl.draManager.DeviceClasses(), slices, pl.celCache)
|
||||
allocator, err := structured.NewAllocator(ctx, pl.enableAdminAccess, pl.enablePrioritizedList, pl.enableDeviceTaints, allocateClaims, allAllocatedDevices, pl.draManager.DeviceClasses(), slices, pl.celCache)
|
||||
if err != nil {
|
||||
return nil, statusError(logger, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
cgotesting "k8s.io/client-go/testing"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||
|
|
@ -95,7 +96,7 @@ var (
|
|||
|
||||
// Node with "instance-1" device and no device attributes.
|
||||
workerNode = &st.MakeNode().Name(nodeName).Label("kubernetes.io/hostname", nodeName).Node
|
||||
workerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1", nil).Obj()
|
||||
workerNodeSlice = st.MakeResourceSlice(nodeName, driver).Device("instance-1").Obj()
|
||||
|
||||
// Node with same device, but now with a "healthy" boolean attribute.
|
||||
workerNode2 = &st.MakeNode().Name(node2Name).Label("kubernetes.io/hostname", node2Name).Node
|
||||
|
|
@ -183,8 +184,22 @@ var (
|
|||
otherAllocatedClaim = st.FromResourceClaim(otherClaim).
|
||||
Allocation(allocationResult).
|
||||
Obj()
|
||||
|
||||
deviceTaint = resourceapi.DeviceTaint{
|
||||
Key: "taint-key",
|
||||
Value: "taint-value",
|
||||
Effect: resourceapi.DeviceTaintEffectNoSchedule,
|
||||
}
|
||||
)
|
||||
|
||||
func taintDevices(slice *resourceapi.ResourceSlice) *resourceapi.ResourceSlice {
|
||||
slice = slice.DeepCopy()
|
||||
for i := range slice.Spec.Devices {
|
||||
slice.Spec.Devices[i].Basic.Taints = append(slice.Spec.Devices[i].Basic.Taints, deviceTaint)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func reserve(claim *resourceapi.ResourceClaim, pod *v1.Pod) *resourceapi.ResourceClaim {
|
||||
return st.FromResourceClaim(claim).
|
||||
ReservedForPod(pod.Name, types.UID(pod.UID)).
|
||||
|
|
@ -343,6 +358,7 @@ func TestPlugin(t *testing.T) {
|
|||
disableDRA bool
|
||||
|
||||
enableDRAPrioritizedList bool
|
||||
enableDRADeviceTaints bool
|
||||
}{
|
||||
"empty": {
|
||||
pod: st.MakePod().Name("foo").Namespace("default").Obj(),
|
||||
|
|
@ -604,6 +620,56 @@ func TestPlugin(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
// The two test cases for device tainting only need to cover
|
||||
// whether the feature gate is passed through to the allocator
|
||||
// correctly. The actual logic around device taints and allocation
|
||||
// is in the allocator.
|
||||
"tainted-device-disabled": {
|
||||
enableDRADeviceTaints: false,
|
||||
pod: podWithClaimName,
|
||||
claims: []*resourceapi.ResourceClaim{pendingClaim},
|
||||
classes: []*resourceapi.DeviceClass{deviceClass},
|
||||
objs: []apiruntime.Object{taintDevices(workerNodeSlice)},
|
||||
want: want{
|
||||
reserve: result{
|
||||
inFlightClaim: allocatedClaim,
|
||||
},
|
||||
prebind: result{
|
||||
assumedClaim: reserve(allocatedClaim, podWithClaimName),
|
||||
changes: change{
|
||||
claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim {
|
||||
if claim.Name == claimName {
|
||||
claim = claim.DeepCopy()
|
||||
claim.Finalizers = allocatedClaim.Finalizers
|
||||
claim.Status = inUseClaim.Status
|
||||
}
|
||||
return claim
|
||||
},
|
||||
},
|
||||
},
|
||||
postbind: result{
|
||||
assumedClaim: reserve(allocatedClaim, podWithClaimName),
|
||||
},
|
||||
},
|
||||
},
|
||||
"tainted-device-enabled": {
|
||||
enableDRADeviceTaints: true,
|
||||
pod: podWithClaimName,
|
||||
claims: []*resourceapi.ResourceClaim{pendingClaim},
|
||||
classes: []*resourceapi.DeviceClass{deviceClass},
|
||||
objs: []apiruntime.Object{taintDevices(workerNodeSlice)},
|
||||
want: want{
|
||||
filter: perNodeResult{
|
||||
workerNode.Name: {
|
||||
status: framework.NewStatus(framework.UnschedulableAndUnresolvable, `cannot allocate all claims`),
|
||||
},
|
||||
},
|
||||
postfilter: result{
|
||||
status: framework.NewStatus(framework.Unschedulable, `still not schedulable`),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"request-admin-access-with-DRAAdminAccess-featuregate": {
|
||||
// When the DRAAdminAccess feature gate is enabled,
|
||||
// Because the pending claim asks for admin access,
|
||||
|
|
@ -920,6 +986,7 @@ func TestPlugin(t *testing.T) {
|
|||
}
|
||||
features := feature.Features{
|
||||
EnableDRAAdminAccess: tc.enableDRAAdminAccess,
|
||||
EnableDRADeviceTaints: tc.enableDRADeviceTaints,
|
||||
EnableDynamicResourceAllocation: !tc.disableDRA,
|
||||
EnableDRAPrioritizedList: tc.enableDRAPrioritizedList,
|
||||
}
|
||||
|
|
@ -1191,7 +1258,16 @@ func setup(t *testing.T, nodes []*v1.Node, claims []*resourceapi.ResourceClaim,
|
|||
tc.client.PrependReactor("*", "*", reactor)
|
||||
|
||||
tc.informerFactory = informers.NewSharedInformerFactory(tc.client, 0)
|
||||
tc.draManager = NewDRAManager(tCtx, assumecache.NewAssumeCache(tCtx.Logger(), tc.informerFactory.Resource().V1beta1().ResourceClaims().Informer(), "resource claim", "", nil), tc.informerFactory)
|
||||
resourceSliceTrackerOpts := resourceslicetracker.Options{
|
||||
EnableDeviceTaints: true,
|
||||
SliceInformer: tc.informerFactory.Resource().V1beta1().ResourceSlices(),
|
||||
TaintInformer: tc.informerFactory.Resource().V1alpha3().DeviceTaintRules(),
|
||||
ClassInformer: tc.informerFactory.Resource().V1beta1().DeviceClasses(),
|
||||
KubeClient: tc.client,
|
||||
}
|
||||
resourceSliceTracker, err := resourceslicetracker.StartTracker(tCtx, resourceSliceTrackerOpts)
|
||||
require.NoError(t, err, "couldn't start resource slice tracker")
|
||||
tc.draManager = NewDRAManager(tCtx, assumecache.NewAssumeCache(tCtx.Logger(), tc.informerFactory.Resource().V1beta1().ResourceClaims().Informer(), "resource claim", "", nil), resourceSliceTracker, tc.informerFactory)
|
||||
opts := []runtime.Option{
|
||||
runtime.WithClientSet(tc.client),
|
||||
runtime.WithInformerFactory(tc.informerFactory),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ package feature
|
|||
type Features struct {
|
||||
EnableDRAPrioritizedList bool
|
||||
EnableDRAAdminAccess bool
|
||||
EnableDRADeviceTaints bool
|
||||
EnableDynamicResourceAllocation bool
|
||||
EnableVolumeAttributesClass bool
|
||||
EnableCSIMigrationPortworx bool
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ func NewInTreeRegistry() runtime.Registry {
|
|||
fts := plfeature.Features{
|
||||
EnableDRAPrioritizedList: feature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList),
|
||||
EnableDRAAdminAccess: feature.DefaultFeatureGate.Enabled(features.DRAAdminAccess),
|
||||
EnableDRADeviceTaints: feature.DefaultFeatureGate.Enabled(features.DRADeviceTaints),
|
||||
EnableDynamicResourceAllocation: feature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation),
|
||||
EnableVolumeAttributesClass: feature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass),
|
||||
EnableCSIMigrationPortworx: feature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
clientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
resourceslicetracker "k8s.io/dynamic-resource-allocation/resourceslice/tracker"
|
||||
"k8s.io/klog/v2"
|
||||
configv1 "k8s.io/kube-scheduler/config/v1"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
|
|
@ -307,11 +308,27 @@ func New(ctx context.Context,
|
|||
waitingPods := frameworkruntime.NewWaitingPodsMap()
|
||||
|
||||
var resourceClaimCache *assumecache.AssumeCache
|
||||
var resourceSliceTracker *resourceslicetracker.Tracker
|
||||
var draManager framework.SharedDRAManager
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
|
||||
resourceClaimInformer := informerFactory.Resource().V1beta1().ResourceClaims().Informer()
|
||||
resourceClaimCache = assumecache.NewAssumeCache(logger, resourceClaimInformer, "ResourceClaim", "", nil)
|
||||
draManager = dynamicresources.NewDRAManager(ctx, resourceClaimCache, informerFactory)
|
||||
resourceSliceTrackerOpts := resourceslicetracker.Options{
|
||||
EnableDeviceTaints: utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaints),
|
||||
SliceInformer: informerFactory.Resource().V1beta1().ResourceSlices(),
|
||||
KubeClient: client,
|
||||
}
|
||||
// If device taints are disabled, the additional informers are not needed and
|
||||
// the tracker turns into a simple wrapper around the slice informer.
|
||||
if resourceSliceTrackerOpts.EnableDeviceTaints {
|
||||
resourceSliceTrackerOpts.TaintInformer = informerFactory.Resource().V1alpha3().DeviceTaintRules()
|
||||
resourceSliceTrackerOpts.ClassInformer = informerFactory.Resource().V1beta1().DeviceClasses()
|
||||
}
|
||||
resourceSliceTracker, err = resourceslicetracker.StartTracker(ctx, resourceSliceTrackerOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't start resource slice tracker: %w", err)
|
||||
}
|
||||
draManager = dynamicresources.NewDRAManager(ctx, resourceClaimCache, resourceSliceTracker, informerFactory)
|
||||
}
|
||||
|
||||
profiles, err := profile.NewMap(ctx, options.profiles, registry, recorderFactory,
|
||||
|
|
@ -389,7 +406,7 @@ func New(ctx context.Context,
|
|||
sched.NextPod = podQueue.Pop
|
||||
sched.applyDefaultHandlers()
|
||||
|
||||
if err = addAllEventHandlers(sched, informerFactory, dynInformerFactory, resourceClaimCache, unionedGVKs(queueingHintsPerProfile)); err != nil {
|
||||
if err = addAllEventHandlers(sched, informerFactory, dynInformerFactory, resourceClaimCache, resourceSliceTracker, unionedGVKs(queueingHintsPerProfile)); err != nil {
|
||||
return nil, fmt.Errorf("adding event handlers: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1188,9 +1188,23 @@ func (wrapper *ResourceSliceWrapper) Devices(names ...string) *ResourceSliceWrap
|
|||
return wrapper
|
||||
}
|
||||
|
||||
// Device sets the devices field of the inner object.
|
||||
func (wrapper *ResourceSliceWrapper) Device(name string, attrs map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) *ResourceSliceWrapper {
|
||||
wrapper.Spec.Devices = append(wrapper.Spec.Devices, resourceapi.Device{Name: name, Basic: &resourceapi.BasicDevice{Attributes: attrs}})
|
||||
// Device extends the devices field of the inner object.
|
||||
// The device must have a name and may have arbitrary additional fields.
|
||||
func (wrapper *ResourceSliceWrapper) Device(name string, otherFields ...any) *ResourceSliceWrapper {
|
||||
device := resourceapi.Device{Name: name, Basic: &resourceapi.BasicDevice{}}
|
||||
for _, field := range otherFields {
|
||||
switch typedField := field.(type) {
|
||||
case map[resourceapi.QualifiedName]resourceapi.DeviceAttribute:
|
||||
device.Basic.Attributes = typedField
|
||||
case map[resourceapi.QualifiedName]resourceapi.DeviceCapacity:
|
||||
device.Basic.Capacity = typedField
|
||||
case resourceapi.DeviceTaint:
|
||||
device.Basic.Taints = append(device.Basic.Taints, typedField)
|
||||
default:
|
||||
panic(fmt.Sprintf("expected a type which matches a field in BasicDevice, got %T", field))
|
||||
}
|
||||
}
|
||||
wrapper.Spec.Devices = append(wrapper.Spec.Devices, device)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,24 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
|
|||
eventsRule(),
|
||||
},
|
||||
})
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DRADeviceTaints) {
|
||||
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
||||
// Same name as in k8s.io/kubernetes/cmd/kube-controller-manager/names.
|
||||
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "device-taint-eviction-controller"},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
// Deletes pods to evict them.
|
||||
rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
|
||||
// Sets pod conditions.
|
||||
rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
|
||||
// The rest is read-only.
|
||||
rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("resourceclaims").RuleOrDie(),
|
||||
rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("resourceslices").RuleOrDie(),
|
||||
rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("deviceclasses").RuleOrDie(),
|
||||
rbacv1helpers.NewRule("get", "list", "watch").Groups(resourceGroup).Resources("devicetaintrules").RuleOrDie(),
|
||||
eventsRule(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
||||
|
|
|
|||
|
|
@ -628,6 +628,9 @@ func ClusterRoles() []rbacv1.ClusterRole {
|
|||
rbacv1helpers.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(),
|
||||
rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("resourceslices").RuleOrDie(),
|
||||
)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
|
||||
kubeSchedulerRules = append(kubeSchedulerRules, rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("devicetaintrules").RuleOrDie())
|
||||
}
|
||||
}
|
||||
roles = append(roles, rbacv1.ClusterRole{
|
||||
// a role to use for the kube-scheduler
|
||||
|
|
|
|||
|
|
@ -968,6 +968,14 @@ items:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- resource.k8s.io
|
||||
resources:
|
||||
- devicetaintrules
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
|
|
|
|||
2173
staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go
generated
2173
staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -113,6 +113,18 @@ message BasicDevice {
|
|||
//
|
||||
// +optional
|
||||
map<string, .k8s.io.apimachinery.pkg.api.resource.Quantity> capacity = 2;
|
||||
|
||||
// If specified, these are the driver-defined taints.
|
||||
//
|
||||
// The maximum number of taints is 8.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceTaint taints = 3;
|
||||
}
|
||||
|
||||
// CELDeviceSelector contains a CEL expression for selecting a device.
|
||||
|
|
@ -520,6 +532,32 @@ message DeviceRequest {
|
|||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
repeated DeviceSubRequest firstAvailable = 7;
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This 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.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 8;
|
||||
}
|
||||
|
||||
// DeviceRequestAllocationResult contains the allocation result for one request.
|
||||
|
|
@ -571,6 +609,19 @@ message DeviceRequestAllocationResult {
|
|||
// +optional
|
||||
// +featureGate=DRAAdminAccess
|
||||
optional bool adminAccess = 5;
|
||||
|
||||
// A copy of all tolerations specified in the request at the time
|
||||
// when the device got allocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 6;
|
||||
}
|
||||
|
||||
// DeviceSelector must have exactly one field set.
|
||||
|
|
@ -652,6 +703,190 @@ message DeviceSubRequest {
|
|||
// +optional
|
||||
// +oneOf=AllocationMode
|
||||
optional int64 count = 5;
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 7;
|
||||
}
|
||||
|
||||
// The device this taint is attached to has the "effect" on
|
||||
// any claim which does not tolerate the taint and, through the claim,
|
||||
// to pods using the claim.
|
||||
message DeviceTaint {
|
||||
// The taint key to be applied to a device.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +required
|
||||
optional string key = 1;
|
||||
|
||||
// The taint value corresponding to the taint key.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
optional string value = 2;
|
||||
|
||||
// The effect of the taint on claims that do not tolerate the taint
|
||||
// and through such claims on the pods using them.
|
||||
// Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
|
||||
// nodes is not valid here.
|
||||
//
|
||||
// +required
|
||||
optional string effect = 3;
|
||||
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// Added automatically during create or update if not set.
|
||||
//
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4;
|
||||
}
|
||||
|
||||
// DeviceTaintRule adds one taint to all devices which match the selector.
|
||||
// This has the same effect as if the taint was specified directly
|
||||
// in the ResourceSlice by the DRA driver.
|
||||
message DeviceTaintRule {
|
||||
// Standard object metadata
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// Spec specifies the selector and one taint.
|
||||
//
|
||||
// Changing the spec automatically increments the metadata.generation number.
|
||||
optional DeviceTaintRuleSpec spec = 2;
|
||||
}
|
||||
|
||||
// DeviceTaintRuleList is a collection of DeviceTaintRules.
|
||||
message DeviceTaintRuleList {
|
||||
// Standard list metadata
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||
|
||||
// Items is the list of DeviceTaintRules.
|
||||
repeated DeviceTaintRule items = 2;
|
||||
}
|
||||
|
||||
// DeviceTaintRuleSpec specifies the selector and one taint.
|
||||
message DeviceTaintRuleSpec {
|
||||
// DeviceSelector defines which device(s) the taint is applied to.
|
||||
// All selector criteria must be satified for a device to
|
||||
// match. The empty selector matches all devices. Without
|
||||
// a selector, no devices are matches.
|
||||
//
|
||||
// +optional
|
||||
optional DeviceTaintSelector deviceSelector = 1;
|
||||
|
||||
// The taint that gets applied to matching devices.
|
||||
//
|
||||
// +required
|
||||
optional DeviceTaint taint = 2;
|
||||
}
|
||||
|
||||
// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to.
|
||||
// The empty selector matches all devices. Without a selector, no devices
|
||||
// are matched.
|
||||
message DeviceTaintSelector {
|
||||
// If DeviceClassName is set, the selectors defined there must be
|
||||
// satisfied by a device to be selected. This field corresponds
|
||||
// to class.metadata.name.
|
||||
//
|
||||
// +optional
|
||||
optional string deviceClassName = 1;
|
||||
|
||||
// If driver is set, only devices from that driver are selected.
|
||||
// This fields corresponds to slice.spec.driver.
|
||||
//
|
||||
// +optional
|
||||
optional string driver = 2;
|
||||
|
||||
// If pool is set, only devices in that pool are selected.
|
||||
//
|
||||
// Also setting the driver name may be useful to avoid
|
||||
// ambiguity when different drivers use the same pool name,
|
||||
// but this is not required because selecting pools from
|
||||
// different drivers may also be useful, for example when
|
||||
// drivers with node-local devices use the node name as
|
||||
// their pool name.
|
||||
//
|
||||
// +optional
|
||||
optional string pool = 3;
|
||||
|
||||
// If device is set, only devices with that name are selected.
|
||||
// This field corresponds to slice.spec.devices[].name.
|
||||
//
|
||||
// Setting also driver and pool may be required to avoid ambiguity,
|
||||
// but is not required.
|
||||
//
|
||||
// +optional
|
||||
optional string device = 4;
|
||||
|
||||
// Selectors contains the same selection criteria as a ResourceClaim.
|
||||
// Currently, CEL expressions are supported. All of these selectors
|
||||
// must be satisfied.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated DeviceSelector selectors = 5;
|
||||
}
|
||||
|
||||
// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
|
||||
// the triple <key,value,effect> using the matching operator <operator>.
|
||||
message DeviceToleration {
|
||||
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +optional
|
||||
optional string key = 1;
|
||||
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a ResourceClaim can
|
||||
// tolerate all taints of a particular category.
|
||||
//
|
||||
// +optional
|
||||
// +default="Equal"
|
||||
optional string operator = 2;
|
||||
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value must be empty, otherwise just a regular string.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
optional string value = 3;
|
||||
|
||||
// Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||
// When specified, allowed values are NoSchedule and NoExecute.
|
||||
//
|
||||
// +optional
|
||||
optional string effect = 4;
|
||||
|
||||
// TolerationSeconds represents the period of time the toleration (which must be
|
||||
// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||
// it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||
// negative values will be treated as 0 (evict immediately) by the system.
|
||||
// If larger than zero, the time when the pod needs to be evicted is calculated as <time when
|
||||
// taint was adedd> + <toleration seconds>.
|
||||
//
|
||||
// +optional
|
||||
optional int64 tolerationSeconds = 5;
|
||||
}
|
||||
|
||||
// NetworkDeviceData provides network-related details for the allocated device.
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||
&ResourceClaimTemplateList{},
|
||||
&ResourceSlice{},
|
||||
&ResourceSliceList{},
|
||||
&DeviceTaintRule{},
|
||||
&DeviceTaintRuleList{},
|
||||
)
|
||||
|
||||
// Add the watch version that applies
|
||||
|
|
|
|||
|
|
@ -223,6 +223,18 @@ type BasicDevice struct {
|
|||
//
|
||||
// +optional
|
||||
Capacity map[QualifiedName]resource.Quantity `json:"capacity,omitempty" protobuf:"bytes,2,rep,name=capacity"`
|
||||
|
||||
// If specified, these are the driver-defined taints.
|
||||
//
|
||||
// The maximum number of taints is 8.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,3,rep,name=taints"`
|
||||
}
|
||||
|
||||
// Limit for the sum of the number of entries in both attributes and capacity.
|
||||
|
|
@ -290,6 +302,64 @@ type DeviceAttribute struct {
|
|||
// DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value.
|
||||
const DeviceAttributeMaxValueLength = 64
|
||||
|
||||
// DeviceTaintsMaxLength is the maximum number of taints per device.
|
||||
const DeviceTaintsMaxLength = 8
|
||||
|
||||
// The device this taint is attached to has the "effect" on
|
||||
// any claim which does not tolerate the taint and, through the claim,
|
||||
// to pods using the claim.
|
||||
type DeviceTaint struct {
|
||||
// The taint key to be applied to a device.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +required
|
||||
Key string `json:"key" protobuf:"bytes,1,name=key"`
|
||||
|
||||
// The taint value corresponding to the taint key.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
|
||||
|
||||
// The effect of the taint on claims that do not tolerate the taint
|
||||
// and through such claims on the pods using them.
|
||||
// Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
|
||||
// nodes is not valid here.
|
||||
//
|
||||
// +required
|
||||
Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"`
|
||||
|
||||
// ^^^^
|
||||
//
|
||||
// Implementing PreferNoSchedule would depend on a scoring solution for DRA.
|
||||
// It might get added as part of that.
|
||||
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// Added automatically during create or update if not set.
|
||||
//
|
||||
// +optional
|
||||
TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"`
|
||||
|
||||
// ^^^
|
||||
//
|
||||
// This field was defined as "It is only written for NoExecute taints." for node taints.
|
||||
// But in practice, Kubernetes never did anything with it (no validation, no defaulting,
|
||||
// ignored during pod eviction in pkg/controller/tainteviction).
|
||||
}
|
||||
|
||||
// +enum
|
||||
type DeviceTaintEffect string
|
||||
|
||||
const (
|
||||
// Do not allow new pods to schedule which use a tainted device unless they tolerate the taint,
|
||||
// but allow all pods submitted to Kubelet without going through the scheduler
|
||||
// to start, and allow all already-running pods to continue running.
|
||||
DeviceTaintEffectNoSchedule DeviceTaintEffect = "NoSchedule"
|
||||
|
||||
// Evict any already-running pods that do not tolerate the device taint.
|
||||
DeviceTaintEffectNoExecute DeviceTaintEffect = "NoExecute"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.31
|
||||
// +k8s:prerelease-lifecycle-gen:replacement=resource.k8s.io,v1beta1,ResourceSliceList
|
||||
|
|
@ -508,6 +578,32 @@ type DeviceRequest struct {
|
|||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"`
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This 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.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,8,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
// DeviceSubRequest describes a request for device provided in the
|
||||
|
|
@ -580,11 +676,35 @@ type DeviceSubRequest struct {
|
|||
// +optional
|
||||
// +oneOf=AllocationMode
|
||||
Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"`
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,7,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
const (
|
||||
DeviceSelectorsMaxSize = 32
|
||||
FirstAvailableDeviceRequestMaxSize = 8
|
||||
DeviceTolerationsMaxLength = 16
|
||||
)
|
||||
|
||||
type DeviceAllocationMode string
|
||||
|
|
@ -790,6 +910,59 @@ type OpaqueDeviceConfiguration struct {
|
|||
// [OpaqueDeviceConfiguration.Parameters] field.
|
||||
const OpaqueParametersMaxLength = 10 * 1024
|
||||
|
||||
// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
|
||||
// the triple <key,value,effect> using the matching operator <operator>.
|
||||
type DeviceToleration struct {
|
||||
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +optional
|
||||
Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"`
|
||||
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a ResourceClaim can
|
||||
// tolerate all taints of a particular category.
|
||||
//
|
||||
// +optional
|
||||
// +default="Equal"
|
||||
Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"`
|
||||
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value must be empty, otherwise just a regular string.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"`
|
||||
|
||||
// Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||
// When specified, allowed values are NoSchedule and NoExecute.
|
||||
//
|
||||
// +optional
|
||||
Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"`
|
||||
|
||||
// TolerationSeconds represents the period of time the toleration (which must be
|
||||
// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||
// it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||
// negative values will be treated as 0 (evict immediately) by the system.
|
||||
// If larger than zero, the time when the pod needs to be evicted is calculated as <time when
|
||||
// taint was adedd> + <toleration seconds>.
|
||||
//
|
||||
// +optional
|
||||
TolerationSeconds *int64 `json:"tolerationSeconds,omitempty" protobuf:"varint,5,opt,name=tolerationSeconds"`
|
||||
}
|
||||
|
||||
// A toleration operator is the set of operators that can be used in a toleration.
|
||||
//
|
||||
// +enum
|
||||
type DeviceTolerationOperator string
|
||||
|
||||
const (
|
||||
DeviceTolerationOpExists DeviceTolerationOperator = "Exists"
|
||||
DeviceTolerationOpEqual DeviceTolerationOperator = "Equal"
|
||||
)
|
||||
|
||||
// ResourceClaimStatus tracks whether the resource has been allocated and what
|
||||
// the result of that was.
|
||||
type ResourceClaimStatus struct {
|
||||
|
|
@ -960,6 +1133,19 @@ type DeviceRequestAllocationResult struct {
|
|||
// +optional
|
||||
// +featureGate=DRAAdminAccess
|
||||
AdminAccess *bool `json:"adminAccess" protobuf:"bytes,5,name=adminAccess"`
|
||||
|
||||
// A copy of all tolerations specified in the request at the time
|
||||
// when the device got allocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
// DeviceAllocationConfiguration gets embedded in an AllocationResult.
|
||||
|
|
@ -1237,3 +1423,107 @@ type NetworkDeviceData struct {
|
|||
// +optional
|
||||
HardwareAddress string `json:"hardwareAddress,omitempty" protobuf:"bytes,3,opt,name=hardwareAddress"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.33
|
||||
|
||||
// DeviceTaintRule adds one taint to all devices which match the selector.
|
||||
// This has the same effect as if the taint was specified directly
|
||||
// in the ResourceSlice by the DRA driver.
|
||||
type DeviceTaintRule struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Spec specifies the selector and one taint.
|
||||
//
|
||||
// Changing the spec automatically increments the metadata.generation number.
|
||||
Spec DeviceTaintRuleSpec `json:"spec" protobuf:"bytes,2,name=spec"`
|
||||
|
||||
// ^^^
|
||||
// A spec gets added because adding a status seems likely.
|
||||
// Such a status could provide feedback on applying the
|
||||
// eviction and/or statistics (number of matching devices,
|
||||
// affected allocated claims, pods remaining to be evicted,
|
||||
// etc.).
|
||||
}
|
||||
|
||||
// DeviceTaintRuleSpec specifies the selector and one taint.
|
||||
type DeviceTaintRuleSpec struct {
|
||||
// DeviceSelector defines which device(s) the taint is applied to.
|
||||
// All selector criteria must be satified for a device to
|
||||
// match. The empty selector matches all devices. Without
|
||||
// a selector, no devices are matches.
|
||||
//
|
||||
// +optional
|
||||
DeviceSelector *DeviceTaintSelector `json:"deviceSelector,omitempty" protobuf:"bytes,1,opt,name=deviceSelector"`
|
||||
|
||||
// The taint that gets applied to matching devices.
|
||||
//
|
||||
// +required
|
||||
Taint DeviceTaint `json:"taint,omitempty" protobuf:"bytes,2,rep,name=taint"`
|
||||
}
|
||||
|
||||
// DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to.
|
||||
// The empty selector matches all devices. Without a selector, no devices
|
||||
// are matched.
|
||||
type DeviceTaintSelector struct {
|
||||
// If DeviceClassName is set, the selectors defined there must be
|
||||
// satisfied by a device to be selected. This field corresponds
|
||||
// to class.metadata.name.
|
||||
//
|
||||
// +optional
|
||||
DeviceClassName *string `json:"deviceClassName,omitempty" protobuf:"bytes,1,opt,name=deviceClassName"`
|
||||
|
||||
// If driver is set, only devices from that driver are selected.
|
||||
// This fields corresponds to slice.spec.driver.
|
||||
//
|
||||
// +optional
|
||||
Driver *string `json:"driver,omitempty" protobuf:"bytes,2,opt,name=driver"`
|
||||
|
||||
// If pool is set, only devices in that pool are selected.
|
||||
//
|
||||
// Also setting the driver name may be useful to avoid
|
||||
// ambiguity when different drivers use the same pool name,
|
||||
// but this is not required because selecting pools from
|
||||
// different drivers may also be useful, for example when
|
||||
// drivers with node-local devices use the node name as
|
||||
// their pool name.
|
||||
//
|
||||
// +optional
|
||||
Pool *string `json:"pool,omitempty" protobuf:"bytes,3,opt,name=pool"`
|
||||
|
||||
// If device is set, only devices with that name are selected.
|
||||
// This field corresponds to slice.spec.devices[].name.
|
||||
//
|
||||
// Setting also driver and pool may be required to avoid ambiguity,
|
||||
// but is not required.
|
||||
//
|
||||
// +optional
|
||||
Device *string `json:"device,omitempty" protobuf:"bytes,4,opt,name=device"`
|
||||
|
||||
// Selectors contains the same selection criteria as a ResourceClaim.
|
||||
// Currently, CEL expressions are supported. All of these selectors
|
||||
// must be satisfied.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,5,rep,name=selectors"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.33
|
||||
|
||||
// DeviceTaintRuleList is a collection of DeviceTaintRules.
|
||||
type DeviceTaintRuleList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard list metadata
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Items is the list of DeviceTaintRules.
|
||||
Items []DeviceTaintRule `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ var map_BasicDevice = map[string]string{
|
|||
"": "BasicDevice defines one device instance.",
|
||||
"attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.",
|
||||
"capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.",
|
||||
"taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 8.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (BasicDevice) SwaggerDoc() map[string]string {
|
||||
|
|
@ -198,6 +199,7 @@ var map_DeviceRequest = map[string]string{
|
|||
"count": "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.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.",
|
||||
"adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis 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.",
|
||||
"firstAvailable": "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.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA 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.",
|
||||
"tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceRequest) SwaggerDoc() map[string]string {
|
||||
|
|
@ -211,6 +213,7 @@ var map_DeviceRequestAllocationResult = map[string]string{
|
|||
"pool": "This name together with the driver name and the device name field identify which device was allocated (`<driver name>/<pool name>/<device name>`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.",
|
||||
"device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.",
|
||||
"adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis 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.",
|
||||
"tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceRequestAllocationResult) SwaggerDoc() map[string]string {
|
||||
|
|
@ -233,12 +236,81 @@ var map_DeviceSubRequest = map[string]string{
|
|||
"selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.",
|
||||
"allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode 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.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.",
|
||||
"count": "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.",
|
||||
"tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceSubRequest) SwaggerDoc() map[string]string {
|
||||
return map_DeviceSubRequest
|
||||
}
|
||||
|
||||
var map_DeviceTaint = map[string]string{
|
||||
"": "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.",
|
||||
"key": "The taint key to be applied to a device. Must be a label name.",
|
||||
"value": "The taint value corresponding to the taint key. Must be a label value.",
|
||||
"effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.",
|
||||
"timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.",
|
||||
}
|
||||
|
||||
func (DeviceTaint) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaint
|
||||
}
|
||||
|
||||
var map_DeviceTaintRule = map[string]string{
|
||||
"": "DeviceTaintRule adds one taint to all devices which match the selector. This has the same effect as if the taint was specified directly in the ResourceSlice by the DRA driver.",
|
||||
"metadata": "Standard object metadata",
|
||||
"spec": "Spec specifies the selector and one taint.\n\nChanging the spec automatically increments the metadata.generation number.",
|
||||
}
|
||||
|
||||
func (DeviceTaintRule) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaintRule
|
||||
}
|
||||
|
||||
var map_DeviceTaintRuleList = map[string]string{
|
||||
"": "DeviceTaintRuleList is a collection of DeviceTaintRules.",
|
||||
"metadata": "Standard list metadata",
|
||||
"items": "Items is the list of DeviceTaintRules.",
|
||||
}
|
||||
|
||||
func (DeviceTaintRuleList) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaintRuleList
|
||||
}
|
||||
|
||||
var map_DeviceTaintRuleSpec = map[string]string{
|
||||
"": "DeviceTaintRuleSpec specifies the selector and one taint.",
|
||||
"deviceSelector": "DeviceSelector defines which device(s) the taint is applied to. All selector criteria must be satified for a device to match. The empty selector matches all devices. Without a selector, no devices are matches.",
|
||||
"taint": "The taint that gets applied to matching devices.",
|
||||
}
|
||||
|
||||
func (DeviceTaintRuleSpec) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaintRuleSpec
|
||||
}
|
||||
|
||||
var map_DeviceTaintSelector = map[string]string{
|
||||
"": "DeviceTaintSelector defines which device(s) a DeviceTaintRule applies to. The empty selector matches all devices. Without a selector, no devices are matched.",
|
||||
"deviceClassName": "If DeviceClassName is set, the selectors defined there must be satisfied by a device to be selected. This field corresponds to class.metadata.name.",
|
||||
"driver": "If driver is set, only devices from that driver are selected. This fields corresponds to slice.spec.driver.",
|
||||
"pool": "If pool is set, only devices in that pool are selected.\n\nAlso setting the driver name may be useful to avoid ambiguity when different drivers use the same pool name, but this is not required because selecting pools from different drivers may also be useful, for example when drivers with node-local devices use the node name as their pool name.",
|
||||
"device": "If device is set, only devices with that name are selected. This field corresponds to slice.spec.devices[].name.\n\nSetting also driver and pool may be required to avoid ambiguity, but is not required.",
|
||||
"selectors": "Selectors contains the same selection criteria as a ResourceClaim. Currently, CEL expressions are supported. All of these selectors must be satisfied.",
|
||||
}
|
||||
|
||||
func (DeviceTaintSelector) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaintSelector
|
||||
}
|
||||
|
||||
var map_DeviceToleration = map[string]string{
|
||||
"": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.",
|
||||
"key": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.",
|
||||
"operator": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.",
|
||||
"value": "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.",
|
||||
"effect": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.",
|
||||
"tolerationSeconds": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as <time when taint was adedd> + <toleration seconds>.",
|
||||
}
|
||||
|
||||
func (DeviceToleration) SwaggerDoc() map[string]string {
|
||||
return map_DeviceToleration
|
||||
}
|
||||
|
||||
var map_NetworkDeviceData = map[string]string{
|
||||
"": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.",
|
||||
"interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,13 @@ func (in *BasicDevice) DeepCopyInto(out *BasicDevice) {
|
|||
(*out)[key] = val.DeepCopy()
|
||||
}
|
||||
}
|
||||
if in.Taints != nil {
|
||||
in, out := &in.Taints, &out.Taints
|
||||
*out = make([]DeviceTaint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -473,6 +480,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -494,6 +508,13 @@ func (in *DeviceRequestAllocationResult) DeepCopyInto(out *DeviceRequestAllocati
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -538,6 +559,13 @@ func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -551,6 +579,172 @@ func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaint) DeepCopyInto(out *DeviceTaint) {
|
||||
*out = *in
|
||||
if in.TimeAdded != nil {
|
||||
in, out := &in.TimeAdded, &out.TimeAdded
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaint.
|
||||
func (in *DeviceTaint) DeepCopy() *DeviceTaint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRule) DeepCopyInto(out *DeviceTaintRule) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRule.
|
||||
func (in *DeviceTaintRule) DeepCopy() *DeviceTaintRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DeviceTaintRule) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRuleList) DeepCopyInto(out *DeviceTaintRuleList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]DeviceTaintRule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleList.
|
||||
func (in *DeviceTaintRuleList) DeepCopy() *DeviceTaintRuleList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRuleList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *DeviceTaintRuleList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintRuleSpec) DeepCopyInto(out *DeviceTaintRuleSpec) {
|
||||
*out = *in
|
||||
if in.DeviceSelector != nil {
|
||||
in, out := &in.DeviceSelector, &out.DeviceSelector
|
||||
*out = new(DeviceTaintSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Taint.DeepCopyInto(&out.Taint)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintRuleSpec.
|
||||
func (in *DeviceTaintRuleSpec) DeepCopy() *DeviceTaintRuleSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintRuleSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaintSelector) DeepCopyInto(out *DeviceTaintSelector) {
|
||||
*out = *in
|
||||
if in.DeviceClassName != nil {
|
||||
in, out := &in.DeviceClassName, &out.DeviceClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Driver != nil {
|
||||
in, out := &in.Driver, &out.Driver
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Pool != nil {
|
||||
in, out := &in.Pool, &out.Pool
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Device != nil {
|
||||
in, out := &in.Device, &out.Device
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Selectors != nil {
|
||||
in, out := &in.Selectors, &out.Selectors
|
||||
*out = make([]DeviceSelector, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaintSelector.
|
||||
func (in *DeviceTaintSelector) DeepCopy() *DeviceTaintSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaintSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceToleration) DeepCopyInto(out *DeviceToleration) {
|
||||
*out = *in
|
||||
if in.TolerationSeconds != nil {
|
||||
in, out := &in.TolerationSeconds, &out.TolerationSeconds
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceToleration.
|
||||
func (in *DeviceToleration) DeepCopy() *DeviceToleration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceToleration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) {
|
||||
*out = *in
|
||||
|
|
|
|||
|
|
@ -73,6 +73,42 @@ func (in *DeviceClassList) APILifecycleRemoved() (major, minor int) {
|
|||
return 1, 37
|
||||
}
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *DeviceTaintRule) APILifecycleIntroduced() (major, minor int) {
|
||||
return 1, 33
|
||||
}
|
||||
|
||||
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
|
||||
func (in *DeviceTaintRule) APILifecycleDeprecated() (major, minor int) {
|
||||
return 1, 36
|
||||
}
|
||||
|
||||
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
|
||||
func (in *DeviceTaintRule) APILifecycleRemoved() (major, minor int) {
|
||||
return 1, 39
|
||||
}
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *DeviceTaintRuleList) APILifecycleIntroduced() (major, minor int) {
|
||||
return 1, 33
|
||||
}
|
||||
|
||||
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
|
||||
func (in *DeviceTaintRuleList) APILifecycleDeprecated() (major, minor int) {
|
||||
return 1, 36
|
||||
}
|
||||
|
||||
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
|
||||
func (in *DeviceTaintRuleList) APILifecycleRemoved() (major, minor int) {
|
||||
return 1, 39
|
||||
}
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *ResourceClaim) APILifecycleIntroduced() (major, minor int) {
|
||||
|
|
|
|||
35
staging/src/k8s.io/api/resource/v1beta1/devicetaint.go
Normal file
35
staging/src/k8s.io/api/resource/v1beta1/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 v1beta1
|
||||
|
||||
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)
|
||||
}
|
||||
1137
staging/src/k8s.io/api/resource/v1beta1/generated.pb.go
generated
1137
staging/src/k8s.io/api/resource/v1beta1/generated.pb.go
generated
File diff suppressed because it is too large
Load diff
|
|
@ -113,6 +113,18 @@ message BasicDevice {
|
|||
//
|
||||
// +optional
|
||||
map<string, DeviceCapacity> capacity = 2;
|
||||
|
||||
// If specified, these are the driver-defined taints.
|
||||
//
|
||||
// The maximum number of taints is 8.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceTaint taints = 3;
|
||||
}
|
||||
|
||||
// CELDeviceSelector contains a CEL expression for selecting a device.
|
||||
|
|
@ -529,6 +541,32 @@ message DeviceRequest {
|
|||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
repeated DeviceSubRequest firstAvailable = 7;
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This 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.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 8;
|
||||
}
|
||||
|
||||
// DeviceRequestAllocationResult contains the allocation result for one request.
|
||||
|
|
@ -580,6 +618,19 @@ message DeviceRequestAllocationResult {
|
|||
// +optional
|
||||
// +featureGate=DRAAdminAccess
|
||||
optional bool adminAccess = 5;
|
||||
|
||||
// A copy of all tolerations specified in the request at the time
|
||||
// when the device got allocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 6;
|
||||
}
|
||||
|
||||
// DeviceSelector must have exactly one field set.
|
||||
|
|
@ -661,6 +712,105 @@ message DeviceSubRequest {
|
|||
// +optional
|
||||
// +oneOf=AllocationMode
|
||||
optional int64 count = 5;
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
repeated DeviceToleration tolerations = 7;
|
||||
}
|
||||
|
||||
// The device this taint is attached to has the "effect" on
|
||||
// any claim which does not tolerate the taint and, through the claim,
|
||||
// to pods using the claim.
|
||||
//
|
||||
// +protobuf.options.(gogoproto.goproto_stringer)=false
|
||||
message DeviceTaint {
|
||||
// The taint key to be applied to a device.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +required
|
||||
optional string key = 1;
|
||||
|
||||
// The taint value corresponding to the taint key.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
optional string value = 2;
|
||||
|
||||
// The effect of the taint on claims that do not tolerate the taint
|
||||
// and through such claims on the pods using them.
|
||||
// Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
|
||||
// nodes is not valid here.
|
||||
//
|
||||
// +required
|
||||
optional string effect = 3;
|
||||
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// Added automatically during create or update if not set.
|
||||
//
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time timeAdded = 4;
|
||||
}
|
||||
|
||||
// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
|
||||
// the triple <key,value,effect> using the matching operator <operator>.
|
||||
message DeviceToleration {
|
||||
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +optional
|
||||
optional string key = 1;
|
||||
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a ResourceClaim can
|
||||
// tolerate all taints of a particular category.
|
||||
//
|
||||
// +optional
|
||||
// +default="Equal"
|
||||
optional string operator = 2;
|
||||
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value must be empty, otherwise just a regular string.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
optional string value = 3;
|
||||
|
||||
// Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||
// When specified, allowed values are NoSchedule and NoExecute.
|
||||
//
|
||||
// +optional
|
||||
optional string effect = 4;
|
||||
|
||||
// TolerationSeconds represents the period of time the toleration (which must be
|
||||
// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||
// it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||
// negative values will be treated as 0 (evict immediately) by the system.
|
||||
// If larger than zero, the time when the pod needs to be evicted is calculated as <time when
|
||||
// taint was adedd> + <toleration seconds>.
|
||||
//
|
||||
// +optional
|
||||
optional int64 tolerationSeconds = 5;
|
||||
}
|
||||
|
||||
// NetworkDeviceData provides network-related details for the allocated device.
|
||||
|
|
|
|||
|
|
@ -222,6 +222,18 @@ type BasicDevice struct {
|
|||
//
|
||||
// +optional
|
||||
Capacity map[QualifiedName]DeviceCapacity `json:"capacity,omitempty" protobuf:"bytes,2,rep,name=capacity"`
|
||||
|
||||
// If specified, these are the driver-defined taints.
|
||||
//
|
||||
// The maximum number of taints is 8.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Taints []DeviceTaint `json:"taints,omitempty" protobuf:"bytes,3,rep,name=taints"`
|
||||
}
|
||||
|
||||
// DeviceCapacity describes a quantity associated with a device.
|
||||
|
|
@ -300,6 +312,66 @@ type DeviceAttribute struct {
|
|||
// DeviceAttributeMaxValueLength is the maximum length of a string or version attribute value.
|
||||
const DeviceAttributeMaxValueLength = 64
|
||||
|
||||
// DeviceTaintsMaxLength is the maximum number of taints per device.
|
||||
const DeviceTaintsMaxLength = 8
|
||||
|
||||
// The device this taint is attached to has the "effect" on
|
||||
// any claim which does not tolerate the taint and, through the claim,
|
||||
// to pods using the claim.
|
||||
//
|
||||
// +protobuf.options.(gogoproto.goproto_stringer)=false
|
||||
type DeviceTaint struct {
|
||||
// The taint key to be applied to a device.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +required
|
||||
Key string `json:"key" protobuf:"bytes,1,name=key"`
|
||||
|
||||
// The taint value corresponding to the taint key.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
|
||||
|
||||
// The effect of the taint on claims that do not tolerate the taint
|
||||
// and through such claims on the pods using them.
|
||||
// Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for
|
||||
// nodes is not valid here.
|
||||
//
|
||||
// +required
|
||||
Effect DeviceTaintEffect `json:"effect" protobuf:"bytes,3,name=effect,casttype=DeviceTaintEffect"`
|
||||
|
||||
// ^^^^
|
||||
//
|
||||
// Implementing PreferNoSchedule would depend on a scoring solution for DRA.
|
||||
// It might get added as part of that.
|
||||
|
||||
// TimeAdded represents the time at which the taint was added.
|
||||
// Added automatically during create or update if not set.
|
||||
//
|
||||
// +optional
|
||||
TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"`
|
||||
|
||||
// ^^^
|
||||
//
|
||||
// This field was defined as "It is only written for NoExecute taints." for node taints.
|
||||
// But in practice, Kubernetes never did anything with it (no validation, no defaulting,
|
||||
// ignored during pod eviction in pkg/controller/tainteviction).
|
||||
}
|
||||
|
||||
// +enum
|
||||
type DeviceTaintEffect string
|
||||
|
||||
const (
|
||||
// Do not allow new pods to schedule which use a tainted device unless they tolerate the taint,
|
||||
// but allow all pods submitted to Kubelet without going through the scheduler
|
||||
// to start, and allow all already-running pods to continue running.
|
||||
DeviceTaintEffectNoSchedule DeviceTaintEffect = "NoSchedule"
|
||||
|
||||
// Evict any already-running pods that do not tolerate the device taint.
|
||||
DeviceTaintEffectNoExecute DeviceTaintEffect = "NoExecute"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.32
|
||||
|
||||
|
|
@ -517,6 +589,32 @@ type DeviceRequest struct {
|
|||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"`
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This 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.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,8,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
// DeviceSubRequest describes a request for device provided in the
|
||||
|
|
@ -589,10 +687,35 @@ type DeviceSubRequest struct {
|
|||
// +optional
|
||||
// +oneOf=AllocationMode
|
||||
Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"`
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
//
|
||||
// Tolerations for NoSchedule are required to allocate a
|
||||
// device which has a taint with that effect. The same applies
|
||||
// to NoExecute.
|
||||
//
|
||||
// In addition, should any of the allocated devices get tainted
|
||||
// with NoExecute after allocation and that effect is not tolerated,
|
||||
// then all pods consuming the ResourceClaim get deleted to evict
|
||||
// them. The scheduler will not let new pods reserve the claim while
|
||||
// it has these tainted devices. Once all pods are evicted, the
|
||||
// claim will get deallocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,7,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
const (
|
||||
DeviceSelectorsMaxSize = 32
|
||||
DeviceSelectorsMaxSize = 32
|
||||
FirstAvailableDeviceRequestMaxSize = 8
|
||||
DeviceTolerationsMaxLength = 16
|
||||
)
|
||||
|
||||
type DeviceAllocationMode string
|
||||
|
|
@ -798,6 +921,59 @@ type OpaqueDeviceConfiguration struct {
|
|||
// [OpaqueDeviceConfiguration.Parameters] field.
|
||||
const OpaqueParametersMaxLength = 10 * 1024
|
||||
|
||||
// The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches
|
||||
// the triple <key,value,effect> using the matching operator <operator>.
|
||||
type DeviceToleration struct {
|
||||
// Key is the taint key that the toleration applies to. Empty means match all taint keys.
|
||||
// If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
// Must be a label name.
|
||||
//
|
||||
// +optional
|
||||
Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"`
|
||||
|
||||
// Operator represents a key's relationship to the value.
|
||||
// Valid operators are Exists and Equal. Defaults to Equal.
|
||||
// Exists is equivalent to wildcard for value, so that a ResourceClaim can
|
||||
// tolerate all taints of a particular category.
|
||||
//
|
||||
// +optional
|
||||
// +default="Equal"
|
||||
Operator DeviceTolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=DeviceTolerationOperator"`
|
||||
|
||||
// Value is the taint value the toleration matches to.
|
||||
// If the operator is Exists, the value must be empty, otherwise just a regular string.
|
||||
// Must be a label value.
|
||||
//
|
||||
// +optional
|
||||
Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"`
|
||||
|
||||
// Effect indicates the taint effect to match. Empty means match all taint effects.
|
||||
// When specified, allowed values are NoSchedule and NoExecute.
|
||||
//
|
||||
// +optional
|
||||
Effect DeviceTaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=DeviceTaintEffect"`
|
||||
|
||||
// TolerationSeconds represents the period of time the toleration (which must be
|
||||
// of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
|
||||
// it is not set, which means tolerate the taint forever (do not evict). Zero and
|
||||
// negative values will be treated as 0 (evict immediately) by the system.
|
||||
// If larger than zero, the time when the pod needs to be evicted is calculated as <time when
|
||||
// taint was adedd> + <toleration seconds>.
|
||||
//
|
||||
// +optional
|
||||
TolerationSeconds *int64 `json:"tolerationSeconds,omitempty" protobuf:"varint,5,opt,name=tolerationSeconds"`
|
||||
}
|
||||
|
||||
// A toleration operator is the set of operators that can be used in a toleration.
|
||||
//
|
||||
// +enum
|
||||
type DeviceTolerationOperator string
|
||||
|
||||
const (
|
||||
DeviceTolerationOpExists DeviceTolerationOperator = "Exists"
|
||||
DeviceTolerationOpEqual DeviceTolerationOperator = "Equal"
|
||||
)
|
||||
|
||||
// ResourceClaimStatus tracks whether the resource has been allocated and what
|
||||
// the result of that was.
|
||||
type ResourceClaimStatus struct {
|
||||
|
|
@ -968,6 +1144,19 @@ type DeviceRequestAllocationResult struct {
|
|||
// +optional
|
||||
// +featureGate=DRAAdminAccess
|
||||
AdminAccess *bool `json:"adminAccess" protobuf:"bytes,5,name=adminAccess"`
|
||||
|
||||
// A copy of all tolerations specified in the request at the time
|
||||
// when the device got allocated.
|
||||
//
|
||||
// The maximum number of tolerations is 16.
|
||||
//
|
||||
// This is an alpha field and requires enabling the DRADeviceTaints
|
||||
// feature gate.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"`
|
||||
}
|
||||
|
||||
// DeviceAllocationConfiguration gets embedded in an AllocationResult.
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ var map_BasicDevice = map[string]string{
|
|||
"": "BasicDevice defines one device instance.",
|
||||
"attributes": "Attributes defines the set of attributes for this device. The name of each attribute must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.",
|
||||
"capacity": "Capacity defines the set of capacities for this device. The name of each capacity must be unique in that set.\n\nThe maximum number of attributes and capacities combined is 32.",
|
||||
"taints": "If specified, these are the driver-defined taints.\n\nThe maximum number of taints is 8.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (BasicDevice) SwaggerDoc() map[string]string {
|
||||
|
|
@ -207,6 +208,7 @@ var map_DeviceRequest = map[string]string{
|
|||
"count": "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.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.",
|
||||
"adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis 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.",
|
||||
"firstAvailable": "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.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA 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.",
|
||||
"tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceRequest) SwaggerDoc() map[string]string {
|
||||
|
|
@ -220,6 +222,7 @@ var map_DeviceRequestAllocationResult = map[string]string{
|
|||
"pool": "This name together with the driver name and the device name field identify which device was allocated (`<driver name>/<pool name>/<device name>`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.",
|
||||
"device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.",
|
||||
"adminAccess": "AdminAccess indicates that this device was allocated for administrative access. See the corresponding request field for a definition of mode.\n\nThis 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.",
|
||||
"tolerations": "A copy of all tolerations specified in the request at the time when the device got allocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceRequestAllocationResult) SwaggerDoc() map[string]string {
|
||||
|
|
@ -242,12 +245,38 @@ var map_DeviceSubRequest = map[string]string{
|
|||
"selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.",
|
||||
"allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode 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.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.",
|
||||
"count": "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.",
|
||||
"tolerations": "If specified, the request's tolerations.\n\nTolerations for NoSchedule are required to allocate a device which has a taint with that effect. The same applies to NoExecute.\n\nIn addition, should any of the allocated devices get tainted with NoExecute after allocation and that effect is not tolerated, then all pods consuming the ResourceClaim get deleted to evict them. The scheduler will not let new pods reserve the claim while it has these tainted devices. Once all pods are evicted, the claim will get deallocated.\n\nThe maximum number of tolerations is 16.\n\nThis is an alpha field and requires enabling the DRADeviceTaints feature gate.",
|
||||
}
|
||||
|
||||
func (DeviceSubRequest) SwaggerDoc() map[string]string {
|
||||
return map_DeviceSubRequest
|
||||
}
|
||||
|
||||
var map_DeviceTaint = map[string]string{
|
||||
"": "The device this taint is attached to has the \"effect\" on any claim which does not tolerate the taint and, through the claim, to pods using the claim.",
|
||||
"key": "The taint key to be applied to a device. Must be a label name.",
|
||||
"value": "The taint value corresponding to the taint key. Must be a label value.",
|
||||
"effect": "The effect of the taint on claims that do not tolerate the taint and through such claims on the pods using them. Valid effects are NoSchedule and NoExecute. PreferNoSchedule as used for nodes is not valid here.",
|
||||
"timeAdded": "TimeAdded represents the time at which the taint was added. Added automatically during create or update if not set.",
|
||||
}
|
||||
|
||||
func (DeviceTaint) SwaggerDoc() map[string]string {
|
||||
return map_DeviceTaint
|
||||
}
|
||||
|
||||
var map_DeviceToleration = map[string]string{
|
||||
"": "The ResourceClaim this DeviceToleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.",
|
||||
"key": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. Must be a label name.",
|
||||
"operator": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a ResourceClaim can tolerate all taints of a particular category.",
|
||||
"value": "Value is the taint value the toleration matches to. If the operator is Exists, the value must be empty, otherwise just a regular string. Must be a label value.",
|
||||
"effect": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule and NoExecute.",
|
||||
"tolerationSeconds": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. If larger than zero, the time when the pod needs to be evicted is calculated as <time when taint was adedd> + <toleration seconds>.",
|
||||
}
|
||||
|
||||
func (DeviceToleration) SwaggerDoc() map[string]string {
|
||||
return map_DeviceToleration
|
||||
}
|
||||
|
||||
var map_NetworkDeviceData = map[string]string{
|
||||
"": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.",
|
||||
"interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.",
|
||||
|
|
|
|||
|
|
@ -99,6 +99,13 @@ func (in *BasicDevice) DeepCopyInto(out *BasicDevice) {
|
|||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
if in.Taints != nil {
|
||||
in, out := &in.Taints, &out.Taints
|
||||
*out = make([]DeviceTaint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -489,6 +496,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -510,6 +524,13 @@ func (in *DeviceRequestAllocationResult) DeepCopyInto(out *DeviceRequestAllocati
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -554,6 +575,13 @@ func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]DeviceToleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -567,6 +595,47 @@ func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceTaint) DeepCopyInto(out *DeviceTaint) {
|
||||
*out = *in
|
||||
if in.TimeAdded != nil {
|
||||
in, out := &in.TimeAdded, &out.TimeAdded
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceTaint.
|
||||
func (in *DeviceTaint) DeepCopy() *DeviceTaint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceTaint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeviceToleration) DeepCopyInto(out *DeviceToleration) {
|
||||
*out = *in
|
||||
if in.TolerationSeconds != nil {
|
||||
in, out := &in.TolerationSeconds, &out.TolerationSeconds
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceToleration.
|
||||
func (in *DeviceToleration) DeepCopy() *DeviceToleration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeviceToleration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) {
|
||||
*out = *in
|
||||
|
|
|
|||
67
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.json
vendored
Normal file
67
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.json
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"kind": "DeviceTaintRule",
|
||||
"apiVersion": "resource.k8s.io/v1alpha3",
|
||||
"metadata": {
|
||||
"name": "nameValue",
|
||||
"generateName": "generateNameValue",
|
||||
"namespace": "namespaceValue",
|
||||
"selfLink": "selfLinkValue",
|
||||
"uid": "uidValue",
|
||||
"resourceVersion": "resourceVersionValue",
|
||||
"generation": 7,
|
||||
"creationTimestamp": "2008-01-01T01:01:01Z",
|
||||
"deletionTimestamp": "2009-01-01T01:01:01Z",
|
||||
"deletionGracePeriodSeconds": 10,
|
||||
"labels": {
|
||||
"labelsKey": "labelsValue"
|
||||
},
|
||||
"annotations": {
|
||||
"annotationsKey": "annotationsValue"
|
||||
},
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "apiVersionValue",
|
||||
"kind": "kindValue",
|
||||
"name": "nameValue",
|
||||
"uid": "uidValue",
|
||||
"controller": true,
|
||||
"blockOwnerDeletion": true
|
||||
}
|
||||
],
|
||||
"finalizers": [
|
||||
"finalizersValue"
|
||||
],
|
||||
"managedFields": [
|
||||
{
|
||||
"manager": "managerValue",
|
||||
"operation": "operationValue",
|
||||
"apiVersion": "apiVersionValue",
|
||||
"time": "2004-01-01T01:01:01Z",
|
||||
"fieldsType": "fieldsTypeValue",
|
||||
"fieldsV1": {},
|
||||
"subresource": "subresourceValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spec": {
|
||||
"deviceSelector": {
|
||||
"deviceClassName": "deviceClassNameValue",
|
||||
"driver": "driverValue",
|
||||
"pool": "poolValue",
|
||||
"device": "deviceValue",
|
||||
"selectors": [
|
||||
{
|
||||
"cel": {
|
||||
"expression": "expressionValue"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"taint": {
|
||||
"key": "keyValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"timeAdded": "2004-01-01T01:01:01Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.pb
vendored
Normal file
BIN
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.pb
vendored
Normal file
Binary file not shown.
48
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.yaml
vendored
Normal file
48
staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.DeviceTaintRule.yaml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
apiVersion: resource.k8s.io/v1alpha3
|
||||
kind: DeviceTaintRule
|
||||
metadata:
|
||||
annotations:
|
||||
annotationsKey: annotationsValue
|
||||
creationTimestamp: "2008-01-01T01:01:01Z"
|
||||
deletionGracePeriodSeconds: 10
|
||||
deletionTimestamp: "2009-01-01T01:01:01Z"
|
||||
finalizers:
|
||||
- finalizersValue
|
||||
generateName: generateNameValue
|
||||
generation: 7
|
||||
labels:
|
||||
labelsKey: labelsValue
|
||||
managedFields:
|
||||
- apiVersion: apiVersionValue
|
||||
fieldsType: fieldsTypeValue
|
||||
fieldsV1: {}
|
||||
manager: managerValue
|
||||
operation: operationValue
|
||||
subresource: subresourceValue
|
||||
time: "2004-01-01T01:01:01Z"
|
||||
name: nameValue
|
||||
namespace: namespaceValue
|
||||
ownerReferences:
|
||||
- apiVersion: apiVersionValue
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: kindValue
|
||||
name: nameValue
|
||||
uid: uidValue
|
||||
resourceVersion: resourceVersionValue
|
||||
selfLink: selfLinkValue
|
||||
uid: uidValue
|
||||
spec:
|
||||
deviceSelector:
|
||||
device: deviceValue
|
||||
deviceClassName: deviceClassNameValue
|
||||
driver: driverValue
|
||||
pool: poolValue
|
||||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
taint:
|
||||
effect: effectValue
|
||||
key: keyValue
|
||||
timeAdded: "2004-01-01T01:01:01Z"
|
||||
value: valueValue
|
||||
|
|
@ -71,7 +71,25 @@
|
|||
}
|
||||
],
|
||||
"allocationMode": "allocationModeValue",
|
||||
"count": 5
|
||||
"count": 5,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -115,7 +133,16 @@
|
|||
"driver": "driverValue",
|
||||
"pool": "poolValue",
|
||||
"device": "deviceValue",
|
||||
"adminAccess": true
|
||||
"adminAccess": true,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -63,10 +63,22 @@ spec:
|
|||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
status:
|
||||
allocation:
|
||||
devices:
|
||||
|
|
@ -89,6 +101,12 @@ status:
|
|||
driver: driverValue
|
||||
pool: poolValue
|
||||
request: requestValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
nodeSelector:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,25 @@
|
|||
}
|
||||
],
|
||||
"allocationMode": "allocationModeValue",
|
||||
"count": 5
|
||||
"count": 5,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -96,7 +96,19 @@ spec:
|
|||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
|
|
|
|||
|
|
@ -90,7 +90,15 @@
|
|||
},
|
||||
"capacity": {
|
||||
"capacityKey": "0"
|
||||
}
|
||||
},
|
||||
"taints": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"timeAdded": "2004-01-01T01:01:01Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -44,6 +44,11 @@ spec:
|
|||
version: versionValue
|
||||
capacity:
|
||||
capacityKey: "0"
|
||||
taints:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
timeAdded: "2004-01-01T01:01:01Z"
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
driver: driverValue
|
||||
nodeName: nodeNameValue
|
||||
|
|
|
|||
|
|
@ -71,7 +71,25 @@
|
|||
}
|
||||
],
|
||||
"allocationMode": "allocationModeValue",
|
||||
"count": 5
|
||||
"count": 5,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -115,7 +133,16 @@
|
|||
"driver": "driverValue",
|
||||
"pool": "poolValue",
|
||||
"device": "deviceValue",
|
||||
"adminAccess": true
|
||||
"adminAccess": true,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -63,10 +63,22 @@ spec:
|
|||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
status:
|
||||
allocation:
|
||||
devices:
|
||||
|
|
@ -89,6 +101,12 @@ status:
|
|||
driver: driverValue
|
||||
pool: poolValue
|
||||
request: requestValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
nodeSelector:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,25 @@
|
|||
}
|
||||
],
|
||||
"allocationMode": "allocationModeValue",
|
||||
"count": 5
|
||||
"count": 5,
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tolerations": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"tolerationSeconds": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -96,7 +96,19 @@ spec:
|
|||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
selectors:
|
||||
- cel:
|
||||
expression: expressionValue
|
||||
tolerations:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
operator: operatorValue
|
||||
tolerationSeconds: 5
|
||||
value: valueValue
|
||||
|
|
|
|||
|
|
@ -92,7 +92,15 @@
|
|||
"capacityKey": {
|
||||
"value": "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taints": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"value": "valueValue",
|
||||
"effect": "effectValue",
|
||||
"timeAdded": "2004-01-01T01:01:01Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -45,6 +45,11 @@ spec:
|
|||
capacity:
|
||||
capacityKey:
|
||||
value: "0"
|
||||
taints:
|
||||
- effect: effectValue
|
||||
key: keyValue
|
||||
timeAdded: "2004-01-01T01:01:01Z"
|
||||
value: valueValue
|
||||
name: nameValue
|
||||
driver: driverValue
|
||||
nodeName: nodeNameValue
|
||||
|
|
|
|||
|
|
@ -12590,6 +12590,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
map:
|
||||
elementType:
|
||||
namedType: io.k8s.apimachinery.pkg.api.resource.Quantity
|
||||
- name: taints
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceTaint
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1alpha3.CELDeviceSelector
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -12768,6 +12774,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceSelector
|
||||
elementRelationship: atomic
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceRequestAllocationResult
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -12790,6 +12802,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceSelector
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -12819,6 +12837,96 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceSelector
|
||||
elementRelationship: atomic
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceTaint
|
||||
map:
|
||||
fields:
|
||||
- name: effect
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: key
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: timeAdded
|
||||
type:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
|
||||
- name: value
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceTaintRule
|
||||
map:
|
||||
fields:
|
||||
- name: apiVersion
|
||||
type:
|
||||
scalar: string
|
||||
- name: kind
|
||||
type:
|
||||
scalar: string
|
||||
- name: metadata
|
||||
type:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
|
||||
default: {}
|
||||
- name: spec
|
||||
type:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec
|
||||
default: {}
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceTaintRuleSpec
|
||||
map:
|
||||
fields:
|
||||
- name: deviceSelector
|
||||
type:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceTaintSelector
|
||||
- name: taint
|
||||
type:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceTaint
|
||||
default: {}
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceTaintSelector
|
||||
map:
|
||||
fields:
|
||||
- name: device
|
||||
type:
|
||||
scalar: string
|
||||
- name: deviceClassName
|
||||
type:
|
||||
scalar: string
|
||||
- name: driver
|
||||
type:
|
||||
scalar: string
|
||||
- name: pool
|
||||
type:
|
||||
scalar: string
|
||||
- name: selectors
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1alpha3.DeviceSelector
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1alpha3.DeviceToleration
|
||||
map:
|
||||
fields:
|
||||
- name: effect
|
||||
type:
|
||||
scalar: string
|
||||
- name: key
|
||||
type:
|
||||
scalar: string
|
||||
- name: operator
|
||||
type:
|
||||
scalar: string
|
||||
default: Equal
|
||||
- name: tolerationSeconds
|
||||
type:
|
||||
scalar: numeric
|
||||
- name: value
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.resource.v1alpha3.NetworkDeviceData
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -13052,6 +13160,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
map:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceCapacity
|
||||
- name: taints
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceTaint
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1beta1.CELDeviceSelector
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -13236,6 +13350,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceSelector
|
||||
elementRelationship: atomic
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1beta1.DeviceRequestAllocationResult
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -13258,6 +13378,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1beta1.DeviceSelector
|
||||
map:
|
||||
fields:
|
||||
|
|
@ -13287,6 +13413,48 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceSelector
|
||||
elementRelationship: atomic
|
||||
- name: tolerations
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.resource.v1beta1.DeviceToleration
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.resource.v1beta1.DeviceTaint
|
||||
map:
|
||||
fields:
|
||||
- name: effect
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: key
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: timeAdded
|
||||
type:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
|
||||
- name: value
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.resource.v1beta1.DeviceToleration
|
||||
map:
|
||||
fields:
|
||||
- name: effect
|
||||
type:
|
||||
scalar: string
|
||||
- name: key
|
||||
type:
|
||||
scalar: string
|
||||
- name: operator
|
||||
type:
|
||||
scalar: string
|
||||
default: Equal
|
||||
- name: tolerationSeconds
|
||||
type:
|
||||
scalar: numeric
|
||||
- name: value
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.resource.v1beta1.NetworkDeviceData
|
||||
map:
|
||||
fields:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
type BasicDeviceApplyConfiguration struct {
|
||||
Attributes map[resourcev1alpha3.QualifiedName]DeviceAttributeApplyConfiguration `json:"attributes,omitempty"`
|
||||
Capacity map[resourcev1alpha3.QualifiedName]resource.Quantity `json:"capacity,omitempty"`
|
||||
Taints []DeviceTaintApplyConfiguration `json:"taints,omitempty"`
|
||||
}
|
||||
|
||||
// BasicDeviceApplyConfiguration constructs a declarative configuration of the BasicDevice type for use with
|
||||
|
|
@ -63,3 +64,16 @@ func (b *BasicDeviceApplyConfiguration) WithCapacity(entries map[resourcev1alpha
|
|||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTaints adds the given value to the Taints field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Taints field.
|
||||
func (b *BasicDeviceApplyConfiguration) WithTaints(values ...*DeviceTaintApplyConfiguration) *BasicDeviceApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTaints")
|
||||
}
|
||||
b.Taints = append(b.Taints, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type DeviceRequestApplyConfiguration struct {
|
|||
Count *int64 `json:"count,omitempty"`
|
||||
AdminAccess *bool `json:"adminAccess,omitempty"`
|
||||
FirstAvailable []DeviceSubRequestApplyConfiguration `json:"firstAvailable,omitempty"`
|
||||
Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceRequestApplyConfiguration constructs a declarative configuration of the DeviceRequest type for use with
|
||||
|
|
@ -105,3 +106,16 @@ func (b *DeviceRequestApplyConfiguration) WithFirstAvailable(values ...*DeviceSu
|
|||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTolerations adds the given value to the Tolerations field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Tolerations field.
|
||||
func (b *DeviceRequestApplyConfiguration) WithTolerations(values ...*DeviceTolerationApplyConfiguration) *DeviceRequestApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTolerations")
|
||||
}
|
||||
b.Tolerations = append(b.Tolerations, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ package v1alpha3
|
|||
// DeviceRequestAllocationResultApplyConfiguration represents a declarative configuration of the DeviceRequestAllocationResult type for use
|
||||
// with apply.
|
||||
type DeviceRequestAllocationResultApplyConfiguration struct {
|
||||
Request *string `json:"request,omitempty"`
|
||||
Driver *string `json:"driver,omitempty"`
|
||||
Pool *string `json:"pool,omitempty"`
|
||||
Device *string `json:"device,omitempty"`
|
||||
AdminAccess *bool `json:"adminAccess,omitempty"`
|
||||
Request *string `json:"request,omitempty"`
|
||||
Driver *string `json:"driver,omitempty"`
|
||||
Pool *string `json:"pool,omitempty"`
|
||||
Device *string `json:"device,omitempty"`
|
||||
AdminAccess *bool `json:"adminAccess,omitempty"`
|
||||
Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceRequestAllocationResultApplyConfiguration constructs a declarative configuration of the DeviceRequestAllocationResult type for use with
|
||||
|
|
@ -73,3 +74,16 @@ func (b *DeviceRequestAllocationResultApplyConfiguration) WithAdminAccess(value
|
|||
b.AdminAccess = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTolerations adds the given value to the Tolerations field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Tolerations field.
|
||||
func (b *DeviceRequestAllocationResultApplyConfiguration) WithTolerations(values ...*DeviceTolerationApplyConfiguration) *DeviceRequestAllocationResultApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTolerations")
|
||||
}
|
||||
b.Tolerations = append(b.Tolerations, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type DeviceSubRequestApplyConfiguration struct {
|
|||
Selectors []DeviceSelectorApplyConfiguration `json:"selectors,omitempty"`
|
||||
AllocationMode *resourcev1alpha3.DeviceAllocationMode `json:"allocationMode,omitempty"`
|
||||
Count *int64 `json:"count,omitempty"`
|
||||
Tolerations []DeviceTolerationApplyConfiguration `json:"tolerations,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceSubRequestApplyConfiguration constructs a declarative configuration of the DeviceSubRequest type for use with
|
||||
|
|
@ -82,3 +83,16 @@ func (b *DeviceSubRequestApplyConfiguration) WithCount(value int64) *DeviceSubRe
|
|||
b.Count = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTolerations adds the given value to the Tolerations field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Tolerations field.
|
||||
func (b *DeviceSubRequestApplyConfiguration) WithTolerations(values ...*DeviceTolerationApplyConfiguration) *DeviceSubRequestApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTolerations")
|
||||
}
|
||||
b.Tolerations = append(b.Tolerations, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue