Files
cozystack/packages/system/cozystack-controller/definitions/gpu.cozystack.io_gpuclasses.yaml
Aleksei Sviridkin 68ba964f07 fix(api): refuse GPU class resource names a device plugin cannot have
A resource name in gpu.cozystack.io would read as a GPU budget key, so
a raw quota key on it would be dropped from the namespace quota and
counted as a budget at once. A name part over 63 characters is not a
valid extended resource, so such a class could never match a node.

Fixes: 0d58dedfe9 ("feat(api): add gpu.cozystack.io GPUClass, GPUDevice and GPUNode")

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-20 15:56:34 +03:00

241 lines
10 KiB
YAML

---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.4
helm.sh/resource-policy: keep
name: gpuclasses.gpu.cozystack.io
spec:
group: gpu.cozystack.io
names:
kind: GPUClass
listKind: GPUClassList
plural: gpuclasses
singular: gpuclass
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.mode
name: Mode
type: string
- jsonPath: .spec.model
name: Model
type: string
- jsonPath: .spec.profile
name: Profile
type: string
- jsonPath: .status.free
name: Free
type: integer
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: GPUClass is one GPU offer of the hoster's catalog.
properties:
apiVersion:
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
kind:
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
metadata:
type: object
spec:
description: GPUClassSpec defines one GPU offer of the hoster's catalog.
properties:
displayName:
description: DisplayName is the human-readable name of the offer.
maxLength: 256
type: string
maxCount:
description: |-
MaxCount is how many GPUs of this class one VM may take. Unset means 1
for mode vgpu and, for mode passthrough, no limit beyond the GPUs of
one node; read it through the shared model rather than the raw field.
format: int32
minimum: 1
type: integer
memory:
anyOf:
- type: integer
- type: string
description: Memory is the GPU memory of one unit of the class.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
mode:
description: GPUMode is how a class's GPUs reach a VM.
enum:
- passthrough
- vgpu
type: string
model:
maxLength: 63
minLength: 1
type: string
nodeSelector:
additionalProperties:
type: string
description: NodeSelector selects the nodes that serve this class.
type: object
onCordon:
default: Move
description: OnCordon decides whether an unattached device leaves
a cordoned node.
enum:
- Move
- Stay
type: string
profile:
description: Profile is the vGPU profile, such as L40S-24C. Set only
for mode vgpu.
maxLength: 63
minLength: 1
type: string
source:
description: |-
GPUClassSource is the backend that serves a class. Exactly one member is
set. The name "dra" is reserved for a DRA-backed member.
properties:
devicePlugin:
description: DevicePluginSource serves a class through a Kubernetes
device plugin.
properties:
resourceName:
description: |-
ResourceName is the extended resource the device plugin advertises for
this class's GPUs. Two classes may share it only when their node
selectors do not overlap.
maxLength: 317
minLength: 3
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/[A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?$
type: string
x-kubernetes-validations:
- message: resourceName must not be in gpu.cozystack.io, whose
keys are GPU budgets
rule: '!self.startsWith(''gpu.cozystack.io/'')'
required:
- resourceName
type: object
type: object
x-kubernetes-validations:
- message: exactly one source must be set; devicePlugin is the only
source supported, dra is reserved
rule: '[has(self.devicePlugin)].filter(x, x).size() == 1'
vendor:
maxLength: 63
minLength: 1
type: string
required:
- mode
- model
- source
- vendor
type: object
x-kubernetes-validations:
- message: profile is required for mode vgpu and not allowed for mode
passthrough
rule: 'self.mode == ''vgpu'' ? has(self.profile) : !has(self.profile)'
status:
description: |-
GPUClassStatus is readable by every authenticated user, so it carries no
node names and no counts beyond free capacity.
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
free:
description: |-
Free is the number of free units of the class on eligible nodes.
Absent until the controller has computed it.
format: int32
type: integer
maxFreePerNode:
description: MaxFreePerNode is the largest number of free units on
one node.
format: int32
type: integer
type: object
required:
- spec
type: object
x-kubernetes-validations:
- message: GPUClass name must be a DNS label, since it becomes part of a quota
key
rule: size(self.metadata.name) <= 63 && self.metadata.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')
- message: GPUClass name total is reserved for the gpu.cozystack.io/total
quota key
rule: self.metadata.name != 'total'
served: true
storage: true
subresources:
status: {}