From 67fc342e79ea80620759b2d20aee030c69efc8ec Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 26 Oct 2026 10:37:53 +0300 Subject: [PATCH] feat(cozystack-basics): let tenant admins order and release GPUClaims Tenant admins and above can now create, change and delete GPUClaim objects in their namespaces where GPUs are on. The order webhook judges each order against its class, capacity, budgets and the vGPU entitlement, and the claim tamper policy keeps tenants off a claim's finalizers and template mark, so the write is granted only where that policy renders. Status, which carries the binding and the attachment, stays the controller's. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- .../templates/clusterroles.yaml | 9 ++++++ ...clusterroles-gpu-capability-gate_test.yaml | 21 +++++++++++++ .../tests/clusterroles-gpu_test.yaml | 30 +++++++++++++++---- 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml index 5567a6ff2..a3ef49eb7 100644 --- a/packages/system/cozystack-basics/templates/clusterroles.yaml +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -293,6 +293,15 @@ rules: - update - patch - delete +{{- if and (eq (toString ((.Values.gpu).enabled | default false)) "true") (.Capabilities.APIVersions.Has "admissionregistration.k8s.io/v1/ValidatingAdmissionPolicy") }} +{{/* Rendered under the same conditions as cozystack-gpu-claim-tamper, which + keeps tenants off a claim's finalizers and template mark. Status, which + carries the binding and attachment, is a subresource and stays the + controller's. */}} +- apiGroups: ["gpu.cozystack.io"] + resources: ["gpuclaims"] + verbs: ["create", "update", "patch", "delete"] +{{- end }} --- # == super admin cluster role == # Aggregates admin + all roles labeled for super-admin access diff --git a/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml b/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml new file mode 100644 index 000000000..ba4818239 --- /dev/null +++ b/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml @@ -0,0 +1,21 @@ +suite: cozystack-basics GPUClaim write follows the policies that guard it +templates: + - templates/clusterroles.yaml +release: + name: cozystack-basics + namespace: cozy-system +# The GPU policies render only where the API server serves +# ValidatingAdmissionPolicy, so the write they guard does too. +capabilities: + apiVersions: [] +tests: + - it: grants no GPUClaim write without the ValidatingAdmissionPolicy API + set: + gpu: + enabled: true + documentSelector: + path: metadata.name + value: cozy:tenant:admin:base + asserts: + - notExists: + path: rules[?(@.apiGroups[*] == "gpu.cozystack.io")] diff --git a/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml b/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml index f5288ff4d..47a680e50 100644 --- a/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml +++ b/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml @@ -7,11 +7,12 @@ release: name: cozystack-basics namespace: cozy-system -# Tenant roles read GPUClaim orders in their namespaces; no tenant role writes -# them yet, since nothing checks an order's budget or entitlement here. -# GPUClass is cluster-scoped, and tenant roles are bound per namespace, so its -# read goes to every authenticated user through a ClusterRoleBinding. GPUNode, -# which names physical devices, is granted to no tenant role. +# Tenants with admin or above manage GPUClaim orders in their namespaces +# where GPUs are on, and lower roles read them. GPUClass is cluster-scoped, +# and tenant roles are bound per namespace, so its read goes to every +# authenticated user through a ClusterRoleBinding. Neither the status of a +# claim, which holds its binding, nor GPUNode, which names physical devices, +# is granted to any tenant role. tests: # The filter returns the rule itself when exactly one rule names the group # and a list otherwise, so equal pins both the rule and that it is the only @@ -28,10 +29,27 @@ tests: resources: ["gpuclaims"] verbs: ["get", "list", "watch"] - - it: cozy:tenant:admin:base grants nothing in gpu.cozystack.io + - it: cozy:tenant:admin:base grants only write on gpuclaims when GPUs are on set: gpu: enabled: true + capabilities: + apiVersions: + - admissionregistration.k8s.io/v1/ValidatingAdmissionPolicy + documentSelector: + path: metadata.name + value: cozy:tenant:admin:base + asserts: + - equal: + path: rules[?(@.apiGroups[*] == "gpu.cozystack.io")] + value: + apiGroups: ["gpu.cozystack.io"] + resources: ["gpuclaims"] + verbs: ["create", "update", "patch", "delete"] + + # The write ships with the GPU policies that guard it, which render only + # where GPUs are on. + - it: cozy:tenant:admin:base grants nothing in gpu.cozystack.io while GPUs are off documentSelector: path: metadata.name value: cozy:tenant:admin:base