The rule that Gt and Lt tolerations never tolerate a taint had no case,
nor did an init container that finishes before a sidecar starts, and
the long-name check compared a pure function with itself. Each now has
a case that fails when the behaviour changes, the long name against a
hash computed outside the code under test.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
An init container that always restarts runs beside the main containers
and every later init container, so its GPUs add to theirs. Counting it
as an ordinary init container booked fewer units than the pod holds,
which let the binder overbook a node and undercounted raw quota keys.
Gt and Lt tolerations no longer count as tolerating a taint: where
their feature gate is off the scheduler ignores them, and skipping a
node is safer than binding to one no pod can land on.
The webhook contract now states that every refusal check, template
budgets and entitlement included, runs before anything is written.
Fixes: 5d90af051b ("feat(gpu): add the shared GPU placement and budget model")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
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>
A long VMI name is cut before the hash is appended; when the cut lands
on a dot the result is not a DNS subdomain unless the trailing dot or
dash is dropped, and no input exercised that boundary.
The parser's doc no longer claims to validate names it leaves to the
lookup.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
A decoder's More reports false at a closing bracket or brace, so
a stray "]" or "}" after the list still parsed as valid. Reading one
more token and requiring end of input refuses every trailing form.
With the VMI name gone from the template-device label, a template
device whose webhook died before attaching it carries no link to a
VMI; the contract now says the controller deletes such a device after
the attachment grace, and that the VMI is read from the attachment.
Fixes: ef53ba9070 ("fix(gpu): keep template device names and marks within API limits")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
A VMI name can be a 253-character DNS subdomain, and worker VMIs
named after Cluster API machines do grow long. Carried as a label
value it fails the 63-character limit, and with a suffix appended it
fails the name limit, so the webhook could not create the template
device at all. The mark is now a plain "true", and a name too long
for the suffix is shortened with a hash of the full VMI name.
The device-reference parser also refused nothing after the JSON list,
so a malformed annotation could pass as valid.
Fixes: a11737a2dd ("feat(gpu): fix the shared GPU keys, settings and binding contract")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The case checks that a GPU key on an ancestor limits every tenant
below it; its name now says that and nothing about what the child
declares.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The binder re-checks what admission checked, and cozystack-api shows
the remaining budget a later order is checked against; computing these
in three places would let them disagree. The model is pure, so each
caller builds a snapshot from its own cache.
GPU budgets cap the whole subtree of the tenant that declares them,
walked over the namespace ancestry and independent of the tenant quota
pools. A raw GPU key blocks a class until the same tenant also declares
a GPU key, and then caps devices and raw-name pods together.
A node takes binds only when it is schedulable, Ready and carries no
NoSchedule or NoExecute taint the pod does not tolerate; the
node.kubernetes.io taints are left to the first two checks, and a
taint never counts as a cordon.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The binder, the admission webhooks, the tenant chart, the policies and
cozystack-api all have to agree on the same label, annotation,
finalizer and quota keys and on the settings ConfigMap the policies
take as a parameter, so they are defined once here.
The binder in the leader is the only writer of placement; webhooks
running in every replica record an attachment and a bind request in
the device status and wait for the binder's answer, so capacity and
budgets are decided in one serialized loop. Status is the carrier
because no tenant role can write it.
Register and Setup exist as no-ops so the controller can be wired to
them before the webhooks and reconcilers land.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
GPUs become a catalog of hoster offers, tenant reservations against
them and a per-node inventory, so a tenant can hold a GPU while its VM
is off and an order that does not fit is refused at create time.
The CRDs enforce what the apiserver alone can: a class name is a DNS
label other than total because it becomes part of a quota key, the
source is a union with dra reserved for a later member, a device's
spec is immutable and orders at least one GPU, and bound devices are
listable per node through a field selector on status.nodeName. The
CRDs are kept on chart uninstall, since they ship as plain templates
and deleting GPUDevice would drop every hold and usage record.
maxCount carries no schema default: it defaults to 1 for vgpu classes
and to no limit for passthrough, which a structural default cannot
express, so readers resolve it through the shared model.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>