mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-30 16:47:20 +09:00
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. Pod usage follows the scheduler's arithmetic, sidecars included, so the binder never books a unit a raw-name pod holds. A node takes binds only when it is schedulable, Ready and carries no NoSchedule or NoExecute taint the pod does not tolerate; the unschedulable, not-ready and unreachable taints are left to the first two checks, and a taint never counts as a cordon. Gt and Lt tolerations do not count, since the scheduler ignores them where their feature gate is off. Template device names are derived from the VMI and shortened with a hash when the VMI name leaves no room, so they stay valid subdomains. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
/*
|
|
Copyright 2026 The Cozystack 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 model
|
|
|
|
import (
|
|
"strings"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"github.com/cozystack/cozystack/internal/gpu"
|
|
)
|
|
|
|
// QuotaKey is a parsed gpu.cozystack.io budget key.
|
|
type QuotaKey struct {
|
|
// Class is the class the key limits; empty when Total is set.
|
|
Class string
|
|
Total bool
|
|
}
|
|
|
|
// ParseQuotaKey parses a resourceQuotas key of the form
|
|
// gpu.cozystack.io/<class> or gpu.cozystack.io/total.
|
|
func ParseQuotaKey(key corev1.ResourceName) (QuotaKey, bool) {
|
|
rest, ok := strings.CutPrefix(string(key), gpu.QuotaKeyPrefix)
|
|
if !ok || rest == "" || strings.Contains(rest, "/") {
|
|
return QuotaKey{}, false
|
|
}
|
|
if rest == gpu.QuotaTotal {
|
|
return QuotaKey{Total: true}, true
|
|
}
|
|
return QuotaKey{Class: rest}, true
|
|
}
|
|
|
|
// ClassQuotaKey is the budget key of one class.
|
|
func ClassQuotaKey(class string) corev1.ResourceName {
|
|
return corev1.ResourceName(gpu.QuotaKeyPrefix + class)
|
|
}
|
|
|
|
// IsGPUQuotaKey reports whether key is in the gpu.cozystack.io namespace,
|
|
// well-formed or not. Such keys are budgets of GPUDevice, never pod
|
|
// quota, so they stay out of the namespace ResourceQuota, the declaration
|
|
// gate and the quota pool model.
|
|
func IsGPUQuotaKey(key corev1.ResourceName) bool {
|
|
return strings.HasPrefix(string(key), gpu.QuotaKeyPrefix)
|
|
}
|
|
|
|
// WithoutGPUQuotaKeys returns a copy of quota without GPU budget keys.
|
|
func WithoutGPUQuotaKeys(quota corev1.ResourceList) corev1.ResourceList {
|
|
out := corev1.ResourceList{}
|
|
for k, v := range quota {
|
|
if !IsGPUQuotaKey(k) {
|
|
out[k] = v
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// RawQuotaLimit returns the limit a tenant's resourceQuotas puts on the
|
|
// GPU resource name directly (a raw key), in any of the forms a quota
|
|
// accepts for an extended resource. When more than one form is present
|
|
// the smallest wins, as each caps the same pods.
|
|
func RawQuotaLimit(quota corev1.ResourceList, name corev1.ResourceName) (int64, bool) {
|
|
var limit int64
|
|
found := false
|
|
for _, k := range []corev1.ResourceName{name, "requests." + name, "limits." + name} {
|
|
q, ok := quota[k]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if v := q.Value(); !found || v < limit {
|
|
limit = v
|
|
}
|
|
found = true
|
|
}
|
|
return limit, found
|
|
}
|