Merge pull request #134113 from jpbetz/resource-claim-status-pool

Enable Declarative Validation Pool status field of ResourceClaim
This commit is contained in:
Kubernetes Prow Robot 2025-09-26 09:16:18 -07:00 committed by GitHub
commit 4f72f0fdbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1141 additions and 8 deletions

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1
import (
context "context"
fmt "fmt"
resourcev1 "k8s.io/api/resource/v1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,189 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ResourceClaim
scheme.AddValidationFunc((*resourcev1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/", "/status":
return Validate_ResourceClaim(ctx, op, nil /* fldPath */, obj.(*resourcev1.ResourceClaim), safe.Cast[*resourcev1.ResourceClaim](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ResourceClaimList
scheme.AddValidationFunc((*resourcev1.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1.ResourceClaimList), safe.Cast[*resourcev1.ResourceClaimList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.AllocationResult) (errs field.ErrorList) {
// field resourcev1.AllocationResult.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1.DeviceAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_DeviceAllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("devices"), &obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1.AllocationResult) *resourcev1.DeviceAllocationResult { return &oldObj.Devices }))...)
// field resourcev1.AllocationResult.NodeSelector has no validation
// field resourcev1.AllocationResult.AllocationTimestamp has no validation
return errs
}
// Validate_DeviceAllocationResult validates an instance of DeviceAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceAllocationResult) (errs field.ErrorList) {
// field resourcev1.DeviceAllocationResult.Results
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceRequestAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequestAllocationResult)...)
return
}(fldPath.Child("results"), obj.Results, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAllocationResult) []resourcev1.DeviceRequestAllocationResult {
return oldObj.Results
}))...)
// field resourcev1.DeviceAllocationResult.Config has no validation
return errs
}
// Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceRequestAllocationResult) (errs field.ErrorList) {
// field resourcev1.DeviceRequestAllocationResult.Request has no validation
// field resourcev1.DeviceRequestAllocationResult.Driver has no validation
// field resourcev1.DeviceRequestAllocationResult.Pool
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
return // do not proceed
}
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequestAllocationResult) *string { return &oldObj.Pool }))...)
// field resourcev1.DeviceRequestAllocationResult.Device has no validation
// field resourcev1.DeviceRequestAllocationResult.AdminAccess has no validation
// field resourcev1.DeviceRequestAllocationResult.Tolerations has no validation
// field resourcev1.DeviceRequestAllocationResult.BindingConditions has no validation
// field resourcev1.DeviceRequestAllocationResult.BindingFailureConditions has no validation
// field resourcev1.DeviceRequestAllocationResult.ShareID has no validation
// field resourcev1.DeviceRequestAllocationResult.ConsumedCapacity has no validation
return errs
}
// Validate_ResourceClaim validates an instance of ResourceClaim according
// to declarative validation rules in the API schema.
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaim) (errs field.ErrorList) {
// field resourcev1.ResourceClaim.TypeMeta has no validation
// field resourcev1.ResourceClaim.ObjectMeta has no validation
// field resourcev1.ResourceClaim.Spec has no validation
// field resourcev1.ResourceClaim.Status
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_ResourceClaimStatus(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("status"), &obj.Status, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaim) *resourcev1.ResourceClaimStatus { return &oldObj.Status }))...)
return errs
}
// Validate_ResourceClaimList validates an instance of ResourceClaimList according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimList) (errs field.ErrorList) {
// field resourcev1.ResourceClaimList.TypeMeta has no validation
// field resourcev1.ResourceClaimList.ListMeta has no validation
// field resourcev1.ResourceClaimList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.ResourceClaim) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimList) []resourcev1.ResourceClaim { return oldObj.Items }))...)
return errs
}
// Validate_ResourceClaimStatus validates an instance of ResourceClaimStatus according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaimStatus) (errs field.ErrorList) {
// field resourcev1.ResourceClaimStatus.Allocation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1.AllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("allocation"), obj.Allocation, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimStatus) *resourcev1.AllocationResult { return oldObj.Allocation }))...)
// field resourcev1.ResourceClaimStatus.ReservedFor
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.ResourceClaimConsumerReference) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimStatus) []resourcev1.ResourceClaimConsumerReference {
return oldObj.ReservedFor
}))...)
// field resourcev1.ResourceClaimStatus.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.AllocatedDeviceStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1.ResourceClaimStatus) []resourcev1.AllocatedDeviceStatus { return oldObj.Devices }))...)
return errs
}

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1beta1
import (
context "context"
fmt "fmt"
resourcev1beta1 "k8s.io/api/resource/v1beta1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,197 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ResourceClaim
scheme.AddValidationFunc((*resourcev1beta1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/", "/status":
return Validate_ResourceClaim(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.ResourceClaim), safe.Cast[*resourcev1beta1.ResourceClaim](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ResourceClaimList
scheme.AddValidationFunc((*resourcev1beta1.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.ResourceClaimList), safe.Cast[*resourcev1beta1.ResourceClaimList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationResult) (errs field.ErrorList) {
// field resourcev1beta1.AllocationResult.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_DeviceAllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("devices"), &obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta1.AllocationResult) *resourcev1beta1.DeviceAllocationResult {
return &oldObj.Devices
}))...)
// field resourcev1beta1.AllocationResult.NodeSelector has no validation
// field resourcev1beta1.AllocationResult.AllocationTimestamp has no validation
return errs
}
// Validate_DeviceAllocationResult validates an instance of DeviceAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAllocationResult) (errs field.ErrorList) {
// field resourcev1beta1.DeviceAllocationResult.Results
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceRequestAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequestAllocationResult)...)
return
}(fldPath.Child("results"), obj.Results, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAllocationResult) []resourcev1beta1.DeviceRequestAllocationResult {
return oldObj.Results
}))...)
// field resourcev1beta1.DeviceAllocationResult.Config has no validation
return errs
}
// Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceRequestAllocationResult) (errs field.ErrorList) {
// field resourcev1beta1.DeviceRequestAllocationResult.Request has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.Driver has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.Pool
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
return // do not proceed
}
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequestAllocationResult) *string { return &oldObj.Pool }))...)
// field resourcev1beta1.DeviceRequestAllocationResult.Device has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.AdminAccess has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.Tolerations has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.BindingConditions has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.BindingFailureConditions has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.ShareID has no validation
// field resourcev1beta1.DeviceRequestAllocationResult.ConsumedCapacity has no validation
return errs
}
// Validate_ResourceClaim validates an instance of ResourceClaim according
// to declarative validation rules in the API schema.
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaim) (errs field.ErrorList) {
// field resourcev1beta1.ResourceClaim.TypeMeta has no validation
// field resourcev1beta1.ResourceClaim.ObjectMeta has no validation
// field resourcev1beta1.ResourceClaim.Spec has no validation
// field resourcev1beta1.ResourceClaim.Status
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_ResourceClaimStatus(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("status"), &obj.Status, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaim) *resourcev1beta1.ResourceClaimStatus {
return &oldObj.Status
}))...)
return errs
}
// Validate_ResourceClaimList validates an instance of ResourceClaimList according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimList) (errs field.ErrorList) {
// field resourcev1beta1.ResourceClaimList.TypeMeta has no validation
// field resourcev1beta1.ResourceClaimList.ListMeta has no validation
// field resourcev1beta1.ResourceClaimList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.ResourceClaim) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimList) []resourcev1beta1.ResourceClaim { return oldObj.Items }))...)
return errs
}
// Validate_ResourceClaimStatus validates an instance of ResourceClaimStatus according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaimStatus) (errs field.ErrorList) {
// field resourcev1beta1.ResourceClaimStatus.Allocation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("allocation"), obj.Allocation, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimStatus) *resourcev1beta1.AllocationResult {
return oldObj.Allocation
}))...)
// field resourcev1beta1.ResourceClaimStatus.ReservedFor
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.ResourceClaimConsumerReference) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimStatus) []resourcev1beta1.ResourceClaimConsumerReference {
return oldObj.ReservedFor
}))...)
// field resourcev1beta1.ResourceClaimStatus.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.AllocatedDeviceStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceClaimStatus) []resourcev1beta1.AllocatedDeviceStatus {
return oldObj.Devices
}))...)
return errs
}

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1beta2
import (
context "context"
fmt "fmt"
resourcev1beta2 "k8s.io/api/resource/v1beta2"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,197 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ResourceClaim
scheme.AddValidationFunc((*resourcev1beta2.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/", "/status":
return Validate_ResourceClaim(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.ResourceClaim), safe.Cast[*resourcev1beta2.ResourceClaim](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ResourceClaimList
scheme.AddValidationFunc((*resourcev1beta2.ResourceClaimList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ResourceClaimList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.ResourceClaimList), safe.Cast[*resourcev1beta2.ResourceClaimList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationResult) (errs field.ErrorList) {
// field resourcev1beta2.AllocationResult.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_DeviceAllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("devices"), &obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta2.AllocationResult) *resourcev1beta2.DeviceAllocationResult {
return &oldObj.Devices
}))...)
// field resourcev1beta2.AllocationResult.NodeSelector has no validation
// field resourcev1beta2.AllocationResult.AllocationTimestamp has no validation
return errs
}
// Validate_DeviceAllocationResult validates an instance of DeviceAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAllocationResult) (errs field.ErrorList) {
// field resourcev1beta2.DeviceAllocationResult.Results
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceRequestAllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequestAllocationResult)...)
return
}(fldPath.Child("results"), obj.Results, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAllocationResult) []resourcev1beta2.DeviceRequestAllocationResult {
return oldObj.Results
}))...)
// field resourcev1beta2.DeviceAllocationResult.Config has no validation
return errs
}
// Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according
// to declarative validation rules in the API schema.
func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceRequestAllocationResult) (errs field.ErrorList) {
// field resourcev1beta2.DeviceRequestAllocationResult.Request has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.Driver has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.Pool
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
return // do not proceed
}
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("pool"), &obj.Pool, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequestAllocationResult) *string { return &oldObj.Pool }))...)
// field resourcev1beta2.DeviceRequestAllocationResult.Device has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.AdminAccess has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.Tolerations has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.BindingConditions has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.BindingFailureConditions has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.ShareID has no validation
// field resourcev1beta2.DeviceRequestAllocationResult.ConsumedCapacity has no validation
return errs
}
// Validate_ResourceClaim validates an instance of ResourceClaim according
// to declarative validation rules in the API schema.
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaim) (errs field.ErrorList) {
// field resourcev1beta2.ResourceClaim.TypeMeta has no validation
// field resourcev1beta2.ResourceClaim.ObjectMeta has no validation
// field resourcev1beta2.ResourceClaim.Spec has no validation
// field resourcev1beta2.ResourceClaim.Status
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_ResourceClaimStatus(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("status"), &obj.Status, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaim) *resourcev1beta2.ResourceClaimStatus {
return &oldObj.Status
}))...)
return errs
}
// Validate_ResourceClaimList validates an instance of ResourceClaimList according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimList) (errs field.ErrorList) {
// field resourcev1beta2.ResourceClaimList.TypeMeta has no validation
// field resourcev1beta2.ResourceClaimList.ListMeta has no validation
// field resourcev1beta2.ResourceClaimList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.ResourceClaim) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ResourceClaim)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimList) []resourcev1beta2.ResourceClaim { return oldObj.Items }))...)
return errs
}
// Validate_ResourceClaimStatus validates an instance of ResourceClaimStatus according
// to declarative validation rules in the API schema.
func Validate_ResourceClaimStatus(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaimStatus) (errs field.ErrorList) {
// field resourcev1beta2.ResourceClaimStatus.Allocation
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationResult) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationResult(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("allocation"), obj.Allocation, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimStatus) *resourcev1beta2.AllocationResult {
return oldObj.Allocation
}))...)
// field resourcev1beta2.ResourceClaimStatus.ReservedFor
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.ResourceClaimConsumerReference) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("reservedFor"), obj.ReservedFor, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimStatus) []resourcev1beta2.ResourceClaimConsumerReference {
return oldObj.ReservedFor
}))...)
// field resourcev1beta2.ResourceClaimStatus.Devices
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.AllocatedDeviceStatus) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
return // do not proceed
}
return
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceClaimStatus) []resourcev1beta2.AllocatedDeviceStatus {
return oldObj.Devices
}))...)
return errs
}

View file

@ -26,6 +26,7 @@ import (
"strings"
"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
apiresource "k8s.io/apimachinery/pkg/api/resource"
@ -66,11 +67,11 @@ func validatePoolName(name string, fldPath *field.Path) field.ErrorList {
allErrs = append(allErrs, field.Required(fldPath, ""))
} else {
if len(name) > resource.PoolNameMaxLength {
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, resource.PoolNameMaxLength))
allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, resource.PoolNameMaxLength).WithOrigin("format=k8s-resource-pool-name"))
}
parts := strings.Split(name, "/")
for _, part := range parts {
allErrs = append(allErrs, corevalidation.ValidateDNS1123Subdomain(part, fldPath)...)
allErrs = append(allErrs, corevalidation.ValidateDNS1123Subdomain(part, fldPath).WithOrigin("format=k8s-resource-pool-name")...)
}
}
return allErrs
@ -1207,7 +1208,7 @@ func truncateIfTooLong(str string, maxLen int) string {
func validateDeviceStatus(device resource.AllocatedDeviceStatus, fldPath *field.Path, allocatedDevices sets.Set[structured.SharedDeviceID]) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, validateDriverName(device.Driver, fldPath.Child("driver"))...)
allErrs = append(allErrs, validatePoolName(device.Pool, fldPath.Child("pool"))...)
allErrs = append(allErrs, validatePoolName(device.Pool, fldPath.Child("pool")).MarkCoveredByDeclarative()...)
allErrs = append(allErrs, validateDeviceName(device.Device, fldPath.Child("device"))...)
if device.ShareID != nil {
allErrs = append(allErrs, validateUID(*device.ShareID, fldPath.Child("shareID"))...)

View file

@ -17,6 +17,7 @@ limitations under the License.
package resourceclaim
import (
"strings"
"testing"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -100,11 +101,87 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
}
}
func TestValidateStatusUpdateForDeclarative(t *testing.T) {
fakeClient := fake.NewClientset()
mockNSClient := fakeClient.CoreV1().Namespaces()
Strategy := NewStrategy(mockNSClient)
strategy := NewStatusStrategy(Strategy)
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIGroup: "resource.k8s.io",
APIVersion: "v1",
Subresource: "status",
})
poolPath := field.NewPath("status", "allocation", "devices", "results").Index(0).Child("pool")
testCases := map[string]struct {
old resource.ResourceClaim
update resource.ResourceClaim
expectedErrs field.ErrorList
}{
"valid pool name": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), "dra.example.com/pool-a"),
},
"valid pool name, max length": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), strings.Repeat("a", 63)+"."+strings.Repeat("b", 63)+"."+strings.Repeat("c", 63)+"."+strings.Repeat("d", 55)),
},
"invalid pool name, required": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), ""),
expectedErrs: field.ErrorList{
field.Required(poolPath, ""),
},
},
"invalid pool name, too long": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), strings.Repeat("a", 253)+"/"+strings.Repeat("a", 253)),
expectedErrs: field.ErrorList{
field.TooLong(poolPath, "", 253).WithOrigin("format=k8s-resource-pool-name"),
},
},
"invalid pool name, format": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), "a/Not_Valid"),
expectedErrs: field.ErrorList{
field.Invalid(poolPath, "Not_Valid", "").WithOrigin("format=k8s-resource-pool-name"),
},
},
"invalid pool name, leading slash": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), "/a"),
expectedErrs: field.ErrorList{
field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"),
},
},
"invalid pool name, trailing slash": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), "a/"),
expectedErrs: field.ErrorList{
field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"),
},
},
"invalid pool name, double slash": {
old: mkValidResourceClaim(),
update: tweakStatusDeviceRequestAllocationResultPool(mkResourceClaimWithStatus(), "a//b"),
expectedErrs: field.ErrorList{
field.Invalid(poolPath, "", "").WithOrigin("format=k8s-resource-pool-name"),
},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, strategy.ValidateUpdate, tc.expectedErrs)
})
}
}
func mkValidResourceClaim() resource.ResourceClaim {
return resource.ResourceClaim{
ObjectMeta: v1.ObjectMeta{
Name: "valid-claim",
Namespace: "default",
Name: "valid-claim",
Namespace: "default",
ResourceVersion: "0",
},
Spec: resource.ResourceClaimSpec{
Devices: resource.DeviceClaim{
@ -121,3 +198,47 @@ func mkValidResourceClaim() resource.ResourceClaim {
},
}
}
func mkResourceClaimWithStatus() resource.ResourceClaim {
return resource.ResourceClaim{
ObjectMeta: v1.ObjectMeta{
Name: "valid-claim",
Namespace: "default",
ResourceVersion: "1",
},
Spec: resource.ResourceClaimSpec{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
Name: "req-0",
Exactly: &resource.ExactDeviceRequest{
DeviceClassName: "class",
AllocationMode: resource.DeviceAllocationModeAll,
},
},
},
},
},
Status: resource.ResourceClaimStatus{
Allocation: &resource.AllocationResult{
Devices: resource.DeviceAllocationResult{
Results: []resource.DeviceRequestAllocationResult{
{
Request: "req-0",
Driver: "dra.example.com",
Pool: "pool-0",
Device: "device-0",
},
},
},
},
},
}
}
func tweakStatusDeviceRequestAllocationResultPool(obj resource.ResourceClaim, pool string) resource.ResourceClaim {
for i := range obj.Status.Allocation.Devices.Results {
obj.Status.Allocation.Devices.Results[i].Pool = pool
}
return obj
}

View file

@ -20,6 +20,8 @@ import (
"context"
"errors"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
@ -40,7 +42,6 @@ import (
"k8s.io/kubernetes/pkg/features"
resourceutils "k8s.io/kubernetes/pkg/registry/resource"
"k8s.io/utils/ptr"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
)
// resourceclaimStrategy implements behavior for ResourceClaim objects
@ -207,8 +208,26 @@ func (r *resourceclaimStatusStrategy) ValidateUpdate(ctx context.Context, obj, o
if oldClaim.Status.Allocation != nil {
oldAllocationResult = oldClaim.Status.Allocation.Devices.Results
}
allErrs := resourceutils.AuthorizedForAdminStatus(ctx, newAllocationResult, oldAllocationResult, newClaim.Namespace, r.nsClient)
return append(allErrs, validation.ValidateResourceClaimStatusUpdate(newClaim, oldClaim)...)
errs := resourceutils.AuthorizedForAdminStatus(ctx, newAllocationResult, oldAllocationResult, newClaim.Namespace, r.nsClient)
errs = append(errs, validation.ValidateResourceClaimStatusUpdate(newClaim, oldClaim)...)
// If DeclarativeValidation feature gate is enabled, also run declarative validation
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
// Determine if takeover is enabled
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
// Run declarative update validation with panic recovery
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newClaim, oldClaim, rest.WithTakeover(takeover))
// Compare imperative and declarative errors and emit metric if there's a mismatch
const validationIdentifier = "resourceclaim_status_update"
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
// Only apply declarative errors if takeover is enabled
if takeover {
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
}
}
return errs
}
// WarningsOnUpdate returns warnings for the given update.

View file

@ -826,6 +826,8 @@ message DeviceRequestAllocationResult {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
optional string pool = 3;
// Device references one device instance via its name in the driver's
@ -1336,6 +1338,7 @@ message ResourceClaimStatus {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
optional AllocationResult allocation = 1;
// ReservedFor indicates which entities are currently allowed to use
@ -1359,6 +1362,7 @@ message ResourceClaimStatus {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1370,6 +1374,7 @@ message ResourceClaimStatus {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device

View file

@ -678,6 +678,7 @@ type ResourceSliceList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.34
// +k8s:supportsSubresource=/status
// ResourceClaim describes a request for access to resources in the cluster,
// for use by workloads. For example, if a workload needs an accelerator device
@ -1346,6 +1347,7 @@ type ResourceClaimStatus struct {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"`
// ReservedFor indicates which entities are currently allowed to use
@ -1369,6 +1371,7 @@ type ResourceClaimStatus struct {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1385,6 +1388,7 @@ type ResourceClaimStatus struct {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device
@ -1502,6 +1506,8 @@ type DeviceRequestAllocationResult struct {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
Pool string `json:"pool" protobuf:"bytes,3,name=pool"`
// Device references one device instance via its name in the driver's

View file

@ -947,6 +947,8 @@ message DeviceRequestAllocationResult {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
optional string pool = 3;
// Device references one device instance via its name in the driver's
@ -1350,6 +1352,7 @@ message ResourceClaimStatus {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
optional AllocationResult allocation = 1;
// ReservedFor indicates which entities are currently allowed to use
@ -1373,6 +1376,7 @@ message ResourceClaimStatus {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1384,6 +1388,7 @@ message ResourceClaimStatus {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device

View file

@ -682,6 +682,7 @@ type ResourceSliceList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.32
// +k8s:supportsSubresource=/status
// ResourceClaim describes a request for access to resources in the cluster,
// for use by workloads. For example, if a workload needs an accelerator device
@ -1354,6 +1355,7 @@ type ResourceClaimStatus struct {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"`
// ReservedFor indicates which entities are currently allowed to use
@ -1377,6 +1379,7 @@ type ResourceClaimStatus struct {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1393,6 +1396,7 @@ type ResourceClaimStatus struct {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device
@ -1510,6 +1514,8 @@ type DeviceRequestAllocationResult struct {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
Pool string `json:"pool" protobuf:"bytes,3,name=pool"`
// Device references one device instance via its name in the driver's

View file

@ -826,6 +826,8 @@ message DeviceRequestAllocationResult {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
optional string pool = 3;
// Device references one device instance via its name in the driver's
@ -1336,6 +1338,7 @@ message ResourceClaimStatus {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
optional AllocationResult allocation = 1;
// ReservedFor indicates which entities are currently allowed to use
@ -1359,6 +1362,7 @@ message ResourceClaimStatus {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1370,6 +1374,7 @@ message ResourceClaimStatus {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device

View file

@ -678,6 +678,7 @@ type ResourceSliceList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.33
// +k8s:supportsSubresource=/status
// ResourceClaim describes a request for access to resources in the cluster,
// for use by workloads. For example, if a workload needs an accelerator device
@ -1346,6 +1347,7 @@ type ResourceClaimStatus struct {
// Allocation is set once the claim has been allocated successfully.
//
// +optional
// +k8s:optional
Allocation *AllocationResult `json:"allocation,omitempty" protobuf:"bytes,1,opt,name=allocation"`
// ReservedFor indicates which entities are currently allowed to use
@ -1369,6 +1371,7 @@ type ResourceClaimStatus struct {
// the future, but not reduced.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=uid
// +patchStrategy=merge
@ -1385,6 +1388,7 @@ type ResourceClaimStatus struct {
// information. Entries are owned by their respective drivers.
//
// +optional
// +k8s:optional
// +listType=map
// +listMapKey=driver
// +listMapKey=device
@ -1502,6 +1506,8 @@ type DeviceRequestAllocationResult struct {
// DNS sub-domains separated by slashes.
//
// +required
// +k8s:required
// +k8s:format=k8s-resource-pool-name
Pool string `json:"pool" protobuf:"bytes,3,name=pool"`
// Device references one device instance via its name in the driver's

View file

@ -18,6 +18,8 @@ package validate
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/api/validate/content"
@ -155,3 +157,26 @@ func UUID[T ~string](_ context.Context, op operation.Operation, fldPath *field.P
}
return nil
}
// ResourcePoolName verifies that the specified value is one or more valid "long name"
// parts separated by a '/' and no longer than 253 characters.
func ResourcePoolName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
if value == nil {
return nil
}
val := (string)(*value)
var allErrs field.ErrorList
if len(val) > 253 {
allErrs = append(allErrs, field.TooLong(fldPath, val, 253))
}
parts := strings.Split(val, "/")
for i, part := range parts {
if len(part) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("segment %d: must not be empty", i)))
continue
}
// Note that we are overwriting the origin from the underlying LongName validation.
allErrs = append(allErrs, LongName(ctx, op, fldPath, &part, nil).PrefixDetail(fmt.Sprintf("segment %d: ", i))...)
}
return allErrs.WithOrigin("format=k8s-resource-pool-name")
}

View file

@ -469,3 +469,106 @@ func TestLongNameCaseless(t *testing.T) {
})
}
}
func TestResourcePoolName(t *testing.T) {
ctx := context.Background()
fldPath := field.NewPath("test")
testCases := []struct {
name string
input string
wantErrs field.ErrorList
}{{
name: "valid: single segment",
input: "a.valid.long-name",
}, {
name: "valid: two segments",
input: "a.valid.long-name/another.one",
}, {
name: "valid: multiple segments",
input: "a/b/c.d.e",
}, {
name: "valid: segments with numbers",
input: "1.2.3/4.5.6",
}, {
name: "invalid: empty string",
input: "",
wantErrs: field.ErrorList{
field.Invalid(fldPath, "", "segment 0: must not be empty").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: leading slash",
input: "/a.b.c",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: trailing slash",
input: "a.b.c/",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: double slash",
input: "a.b.c//d.e.f",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: one segment has uppercase",
input: "a.valid.name/Not.Valid",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: one segment starts with dash",
input: "a.valid.name/-not-valid",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: one segment has special characters",
input: "a.valid.name/not_valid",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: too long",
input: "a.valid.name/" + strings.Repeat("b", 253),
wantErrs: field.ErrorList{
field.TooLong(fldPath, nil, 253).WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: segment too long",
input: strings.Repeat("b", 254),
wantErrs: field.ErrorList{
field.TooLong(fldPath, nil, 253).WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(fldPath, nil, "segment 0: must be no more than 253 bytes").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: multiple invalid segments",
input: "Not/Valid/Either",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "segment 0: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(fldPath, nil, "segment 2: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"),
},
}, {
name: "invalid: just a slash",
input: "/",
wantErrs: field.ErrorList{
field.Invalid(fldPath, nil, "segment 0: must not be empty").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(fldPath, nil, "segment 1: must not be empty").WithOrigin("format=k8s-resource-pool-name"),
},
}}
exactMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
value := &tc.input
gotErrs := ResourcePoolName(ctx, operation.Operation{}, fldPath, value, nil)
exactMatcher.Test(t, tc.wantErrs, gotErrs)
})
}
}

View file

@ -341,6 +341,14 @@ func (list ErrorList) MarkCoveredByDeclarative() ErrorList {
return list
}
// PrefixDetail adds a prefix to the Detail for all errors in the list and returns the updated list.
func (list ErrorList) PrefixDetail(prefix string) ErrorList {
for _, err := range list {
err.Detail = prefix + err.Detail
}
return list
}
// ToAggregate converts the ErrorList into an errors.Aggregate.
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
if len(list) == 0 {

View file

@ -0,0 +1,41 @@
/*
Copyright 2024 The Kubernetes 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.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
// This is a test package.
package format
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type Struct struct {
TypeMeta int
// +k8s:format=k8s-resource-pool-name
ResourcePoolNameField string `json:"resourcePoolNameField"`
// +k8s:format=k8s-resource-pool-name
ResourcePoolNamePtrField *string `json:"resourcePoolNamePtrField"`
// Note: no validation here
ResourcePoolNameTypedefField ResourcePoolNameStringType `json:"resourcePoolNameTypedefField"`
}
// +k8s:format=k8s-resource-pool-name
type ResourcePoolNameStringType string

View file

@ -0,0 +1,80 @@
/*
Copyright 2024 The Kubernetes 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.
*/
// +output_tests
package format
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func Test(t *testing.T) {
st := localSchemeBuilder.Test(t)
st.Value(&Struct{
ResourcePoolNameField: "foo.bar",
ResourcePoolNamePtrField: ptr.To("foo.bar"),
ResourcePoolNameTypedefField: "foo.bar",
}).ExpectValid()
st.Value(&Struct{
ResourcePoolNameField: "1.2.3.4",
ResourcePoolNamePtrField: ptr.To("1.2.3.4"),
ResourcePoolNameTypedefField: "1.2.3.4",
}).ExpectValid()
invalidStruct := &Struct{
ResourcePoolNameField: "",
ResourcePoolNamePtrField: ptr.To(""),
ResourcePoolNameTypedefField: "",
}
st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{
field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
})
// Test validation ratcheting
st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid()
invalidStruct = &Struct{
ResourcePoolNameField: "Not a ResourcePoolName",
ResourcePoolNamePtrField: ptr.To("Not a ResourcePoolName"),
ResourcePoolNameTypedefField: "Not a ResourcePoolName",
}
st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{
field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
})
// Test validation ratcheting
st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid()
invalidStruct = &Struct{
ResourcePoolNameField: "a..b",
ResourcePoolNamePtrField: ptr.To("a..b"),
ResourcePoolNameTypedefField: "a..b",
}
st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{
field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"),
})
// Test validation ratcheting
st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid()
}

View file

@ -0,0 +1,101 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes 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.
*/
// Code generated by validation-gen. DO NOT EDIT.
package format
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
field "k8s.io/apimachinery/pkg/util/validation/field"
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *testscheme.Scheme) error {
// type Struct
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ResourcePoolNameStringType validates an instance of ResourcePoolNameStringType according
// to declarative validation rules in the API schema.
func Validate_ResourcePoolNameStringType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ResourcePoolNameStringType) (errs field.ErrorList) {
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return errs
}
// Validate_Struct validates an instance of Struct according
// to declarative validation rules in the API schema.
func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) {
// field Struct.TypeMeta has no validation
// field Struct.ResourcePoolNameField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("resourcePoolNameField"), &obj.ResourcePoolNameField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.ResourcePoolNameField }))...)
// field Struct.ResourcePoolNamePtrField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
errs = append(errs, validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("resourcePoolNamePtrField"), obj.ResourcePoolNamePtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.ResourcePoolNamePtrField }))...)
// field Struct.ResourcePoolNameTypedefField
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *ResourcePoolNameStringType) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_ResourcePoolNameStringType(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("resourcePoolNameTypedefField"), &obj.ResourcePoolNameTypedefField, safe.Field(oldObj, func(oldObj *Struct) *ResourcePoolNameStringType { return &oldObj.ResourcePoolNameTypedefField }))...)
return errs
}

View file

@ -54,6 +54,7 @@ var (
labelValueValidator = types.Name{Package: libValidationPkg, Name: "LabelValue"}
longNameCaselessValidator = types.Name{Package: libValidationPkg, Name: "LongNameCaseless"}
longNameValidator = types.Name{Package: libValidationPkg, Name: "LongName"}
resourcePoolNameValidator = types.Name{Package: libValidationPkg, Name: "ResourcePoolName"}
shortNameValidator = types.Name{Package: libValidationPkg, Name: "ShortName"}
uuidValidator = types.Name{Package: libValidationPkg, Name: "UUID"}
)
@ -92,6 +93,8 @@ func getFormatValidationFunction(format string) (FunctionGen, error) {
return Function(formatTagName, DefaultFlags, longNameValidator), nil
case "k8s-long-name-caseless":
return Function(formatTagName, DefaultFlags, longNameCaselessValidator), nil
case "k8s-resource-pool-name":
return Function(formatTagName, DefaultFlags, resourcePoolNameValidator), nil
case "k8s-short-name":
return Function(formatTagName, DefaultFlags, shortNameValidator), nil
case "k8s-uuid":
@ -122,6 +125,9 @@ func (ftv formatTagValidator) Docs() TagDoc {
}, {
Description: "k8s-long-name-caseless",
Docs: "Deprecated: This field holds a case-insensitive Kubernetes \"long name\", aka a \"DNS subdomain\" value.",
}, {
Description: "k8s-resource-pool-name",
Docs: "This field holds value with one or more Kubernetes \"long name\" parts separated by `/` and no longer than 253 characters.",
}, {
Description: "k8s-short-name",
Docs: "This field holds a Kubernetes \"short name\", aka a \"DNS label\" value.",