93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
/*
|
|
Copyright 2026.
|
|
|
|
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 v1alpha1
|
|
|
|
import (
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
)
|
|
|
|
// ValidateSpec validates the CacheBlendEngineSpec and returns any validation
|
|
// errors. It mirrors LMCacheEngine.ValidateSpec and additionally enforces the
|
|
// CacheBlend invariants: chunkSize == 256, recompRatio in (0, 1], and
|
|
// checkLayer >= 0.
|
|
func (e *CacheBlendEngine) ValidateSpec() field.ErrorList {
|
|
var errs field.ErrorList
|
|
spec := &e.Spec
|
|
|
|
// l1.sizeGB must be > 0
|
|
if spec.L1.SizeGB <= 0 {
|
|
errs = append(errs, field.Invalid(field.NewPath("spec", "l1", "sizeGB"), spec.L1.SizeGB, "must be greater than 0"))
|
|
}
|
|
|
|
errs = append(errs, validateEvictionSpec(spec.Eviction)...)
|
|
|
|
// Server validation
|
|
if spec.Server != nil {
|
|
serverPath := field.NewPath("spec", "server")
|
|
|
|
if spec.Server.Port != nil {
|
|
port := *spec.Server.Port
|
|
if port < 1024 || port > 65535 {
|
|
errs = append(errs, field.Invalid(serverPath.Child("port"), port, "must be in [1024, 65535]"))
|
|
}
|
|
}
|
|
|
|
// CacheBlend requires chunk_size == 256 (block_size 64 * 4).
|
|
if spec.Server.ChunkSize != nil && *spec.Server.ChunkSize != CacheBlendChunkSize {
|
|
errs = append(errs, field.Invalid(serverPath.Child("chunkSize"), *spec.Server.ChunkSize,
|
|
"must be 256 for CacheBlend (chunk_size == block_size 64 * 4)"))
|
|
}
|
|
}
|
|
|
|
// Blend validation
|
|
if spec.Blend != nil {
|
|
blendPath := field.NewPath("spec", "blend")
|
|
|
|
if spec.Blend.CheckLayer != nil && *spec.Blend.CheckLayer < 0 {
|
|
errs = append(errs, field.Invalid(blendPath.Child("checkLayer"), *spec.Blend.CheckLayer, "must be >= 0"))
|
|
}
|
|
|
|
if spec.Blend.RecompRatio != nil {
|
|
rr := *spec.Blend.RecompRatio
|
|
if rr <= 0.0 || rr > 1.0 {
|
|
errs = append(errs, field.Invalid(blendPath.Child("recompRatio"), rr, "must be in (0.0, 1.0]"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Injection validation. injection.payloadImage.repository is functionally
|
|
// required: the mutating webhook needs it to inject a valid init container.
|
|
// Without it the webhook would produce a Pod with an empty init-container
|
|
// image, which the API server rejects at Pod creation. Enforce it here so
|
|
// the misconfiguration is caught at `kubectl apply` time instead.
|
|
injPath := field.NewPath("spec", "injection")
|
|
if spec.Injection == nil {
|
|
errs = append(errs, field.Required(injPath, "must be specified for CacheBlend injection"))
|
|
} else if spec.Injection.PayloadImage == nil {
|
|
errs = append(errs, field.Required(injPath.Child("payloadImage"),
|
|
"must be specified for CacheBlend injection"))
|
|
} else if spec.Injection.PayloadImage.Repository == nil || *spec.Injection.PayloadImage.Repository == "" {
|
|
errs = append(errs, field.Required(injPath.Child("payloadImage", "repository"),
|
|
"must be a non-empty string"))
|
|
}
|
|
|
|
errs = append(errs, validateL2BackendSpec(spec.L2Backend)...)
|
|
errs = append(errs, validateCoordinatorConnectionSpec(spec.Coordinator)...)
|
|
|
|
return errs
|
|
}
|