dra api: add structured parameters

NodeResourceSlice will be used by kubelet to publish resource information on
behalf of DRA drivers on the node. NodeName and DriverName in
NodeResourceSlice must be immutable. This simplifies tracking the different
objects because what they are for cannot change after creation.

The new field in ResourceClass tells scheduler and autoscaler that they are
expected to handle allocation.

ResourceClaimParameters and ResourceClassParameters are new types for telling
in-tree components how to handle claims.
This commit is contained in:
Patrick Ohly
2024-02-14 14:38:42 +01:00
parent eb1470d60d
commit 39bbcedbca
85 changed files with 19855 additions and 120 deletions

View File

@@ -17,6 +17,7 @@ limitations under the License.
package validation
import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
@@ -41,7 +42,7 @@ func validateResourceClaimSpec(spec *resource.ResourceClaimSpec, fldPath *field.
for _, msg := range corevalidation.ValidateClassName(spec.ResourceClassName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClassName"), spec.ResourceClassName, msg))
}
allErrs = append(allErrs, validateResourceClaimParameters(spec.ParametersRef, fldPath.Child("parametersRef"))...)
allErrs = append(allErrs, validateResourceClaimParametersRef(spec.ParametersRef, fldPath.Child("parametersRef"))...)
if !supportedAllocationModes.Has(string(spec.AllocationMode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), spec.AllocationMode, supportedAllocationModes.List()))
}
@@ -54,7 +55,7 @@ var supportedAllocationModes = sets.NewString(string(resource.AllocationModeImme
// function for Kind and Name in both types, but generics cannot be used to
// access common fields in structs.
func validateResourceClaimParameters(ref *resource.ResourceClaimParametersReference, fldPath *field.Path) field.ErrorList {
func validateResourceClaimParametersRef(ref *resource.ResourceClaimParametersReference, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if ref != nil {
if ref.Kind == "" {
@@ -200,6 +201,12 @@ func validateResourceHandles(resourceHandles []resource.ResourceHandle, maxSize
if len(resourceHandle.Data) > resource.ResourceHandleDataMaxSize {
allErrs = append(allErrs, field.TooLongMaxLength(idxPath.Child("data"), len(resourceHandle.Data), resource.ResourceHandleDataMaxSize))
}
if resourceHandle.StructuredData != nil {
allErrs = append(allErrs, validateStructuredResourceHandle(resourceHandle.StructuredData, idxPath.Child("structuredData"))...)
}
if len(resourceHandle.Data) > 0 && resourceHandle.StructuredData != nil {
allErrs = append(allErrs, field.Invalid(idxPath, nil, "data and structuredData are mutually exclusive"))
}
}
if len(resourceHandles) > maxSize {
// Dumping the entire field into the error message is likely to be too long,
@@ -210,6 +217,37 @@ func validateResourceHandles(resourceHandles []resource.ResourceHandle, maxSize
return allErrs
}
func validateStructuredResourceHandle(handle *resource.StructuredResourceHandle, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
allErrs = append(allErrs, validateNodeName(handle.NodeName, fldPath.Child("nodeName"))...)
allErrs = append(allErrs, validateDriverAllocationResults(handle.Results, fldPath.Child("results"))...)
return allErrs
}
func validateDriverAllocationResults(results []resource.DriverAllocationResult, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for index, result := range results {
idxPath := fldPath.Index(index)
allErrs = append(allErrs, validateAllocationResultModel(&result.AllocationResultModel, idxPath)...)
}
return allErrs
}
func validateAllocationResultModel(model *resource.AllocationResultModel, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
entries := sets.New[string]()
// TODO: implement one structured parameter model
switch len(entries) {
case 0:
// allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
case 1:
// Okay.
default:
allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
}
return allErrs
}
func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerReference, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if ref.Resource == "" {
@@ -345,3 +383,170 @@ func validateNodeName(name string, fldPath *field.Path) field.ErrorList {
}
return allErrs
}
// ValidateNodeResourceSlice tests if a NodeResourceSlice object is valid.
func ValidateNodeResourceSlice(nodeResourceSlice *resource.NodeResourceSlice) field.ErrorList {
allErrs := corevalidation.ValidateObjectMeta(&nodeResourceSlice.ObjectMeta, false, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
allErrs = append(allErrs, validateNodeName(nodeResourceSlice.NodeName, field.NewPath("nodeName"))...)
allErrs = append(allErrs, validateResourceDriverName(nodeResourceSlice.DriverName, field.NewPath("driverName"))...)
allErrs = append(allErrs, validateNodeResourceModel(&nodeResourceSlice.NodeResourceModel, nil)...)
return allErrs
}
func validateNodeResourceModel(model *resource.NodeResourceModel, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
entries := sets.New[string]()
// TODO: implement one structured parameter model
switch len(entries) {
case 0:
// allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
case 1:
// Okay.
default:
allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
}
return allErrs
}
// ValidateNodeResourceSlice tests if a NodeResourceSlice update is valid.
func ValidateNodeResourceSliceUpdate(nodeResourceSlice, oldNodeResourceSlice *resource.NodeResourceSlice) field.ErrorList {
allErrs := corevalidation.ValidateObjectMetaUpdate(&nodeResourceSlice.ObjectMeta, &oldNodeResourceSlice.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateNodeResourceSlice(nodeResourceSlice)...)
allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(nodeResourceSlice.NodeName, oldNodeResourceSlice.NodeName, field.NewPath("nodeName"))...)
allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(nodeResourceSlice.DriverName, oldNodeResourceSlice.DriverName, field.NewPath("driverName"))...)
return allErrs
}
// ValidateResourceClaimParameters tests if a ResourceClaimParameters object is valid for creation.
func ValidateResourceClaimParameters(parameters *resource.ResourceClaimParameters) field.ErrorList {
return validateResourceClaimParameters(parameters, false)
}
func validateResourceClaimParameters(parameters *resource.ResourceClaimParameters, requestStored bool) field.ErrorList {
allErrs := corevalidation.ValidateObjectMeta(&parameters.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
allErrs = append(allErrs, validateResourceClaimParametersRef(parameters.GeneratedFrom, field.NewPath("generatedFrom"))...)
allErrs = append(allErrs, validateDriverRequests(parameters.DriverRequests, field.NewPath("driverRequests"), requestStored)...)
return allErrs
}
func validateDriverRequests(requests []resource.DriverRequests, fldPath *field.Path, requestStored bool) field.ErrorList {
var allErrs field.ErrorList
driverNames := sets.New[string]()
for i, request := range requests {
idxPath := fldPath.Index(i)
driverName := request.DriverName
allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
if driverNames.Has(driverName) {
allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
} else {
driverNames.Insert(driverName)
}
allErrs = append(allErrs, validateResourceRequests(request.Requests, idxPath.Child("requests"), requestStored)...)
}
return allErrs
}
func validateResourceRequests(requests []resource.ResourceRequest, fldPath *field.Path, requestStored bool) field.ErrorList {
var allErrs field.ErrorList
for i, request := range requests {
idxPath := fldPath.Index(i)
allErrs = append(allErrs, validateResourceRequestModel(&request.ResourceRequestModel, idxPath, requestStored)...)
}
if len(requests) == 0 {
// We could allow this, it just doesn't make sense: the entire entry would get ignored and thus
// should have been left out entirely.
allErrs = append(allErrs, field.Required(fldPath, "empty entries with no requests are not allowed"))
}
return allErrs
}
func validateResourceRequestModel(model *resource.ResourceRequestModel, fldPath *field.Path, requestStored bool) field.ErrorList {
var allErrs field.ErrorList
entries := sets.New[string]()
// TODO: implement one structured parameter model
switch len(entries) {
case 0:
// allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
case 1:
// Okay.
default:
allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
}
return allErrs
}
// ValidateResourceClaimParameters tests if a ResourceClaimParameters update is valid.
func ValidateResourceClaimParametersUpdate(resourceClaimParameters, oldResourceClaimParameters *resource.ResourceClaimParameters) field.ErrorList {
allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClaimParameters.ObjectMeta, &oldResourceClaimParameters.ObjectMeta, field.NewPath("metadata"))
requestStored := apiequality.Semantic.DeepEqual(oldResourceClaimParameters.DriverRequests, resourceClaimParameters.DriverRequests)
allErrs = append(allErrs, validateResourceClaimParameters(resourceClaimParameters, requestStored)...)
return allErrs
}
// ValidateResourceClassParameters tests if a ResourceClassParameters object is valid for creation.
func ValidateResourceClassParameters(parameters *resource.ResourceClassParameters) field.ErrorList {
return validateResourceClassParameters(parameters, false)
}
func validateResourceClassParameters(parameters *resource.ResourceClassParameters, filtersStored bool) field.ErrorList {
allErrs := corevalidation.ValidateObjectMeta(&parameters.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
allErrs = append(allErrs, validateClassParameters(parameters.GeneratedFrom, field.NewPath("generatedFrom"))...)
allErrs = append(allErrs, validateResourceFilters(parameters.Filters, field.NewPath("filters"), filtersStored)...)
allErrs = append(allErrs, validateVendorParameters(parameters.VendorParameters, field.NewPath("vendorParameters"))...)
return allErrs
}
func validateResourceFilters(filters []resource.ResourceFilter, fldPath *field.Path, filtersStored bool) field.ErrorList {
var allErrs field.ErrorList
driverNames := sets.New[string]()
for i, filter := range filters {
idxPath := fldPath.Index(i)
driverName := filter.DriverName
allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
if driverNames.Has(driverName) {
allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
} else {
driverNames.Insert(driverName)
}
allErrs = append(allErrs, validateResourceFilterModel(&filter.ResourceFilterModel, idxPath, filtersStored)...)
}
return allErrs
}
func validateResourceFilterModel(model *resource.ResourceFilterModel, fldPath *field.Path, filtersStored bool) field.ErrorList {
var allErrs field.ErrorList
entries := sets.New[string]()
// TODO: implement one structured parameter model
switch len(entries) {
case 0:
// allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
case 1:
// Okay.
default:
allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
}
return allErrs
}
func validateVendorParameters(parameters []resource.VendorParameters, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
driverNames := sets.New[string]()
for i, parameters := range parameters {
idxPath := fldPath.Index(i)
driverName := parameters.DriverName
allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
if driverNames.Has(driverName) {
allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
} else {
driverNames.Insert(driverName)
}
}
return allErrs
}
// ValidateResourceClassParameters tests if a ResourceClassParameters update is valid.
func ValidateResourceClassParametersUpdate(resourceClassParameters, oldResourceClassParameters *resource.ResourceClassParameters) field.ErrorList {
allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClassParameters.ObjectMeta, &oldResourceClassParameters.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateResourceClassParameters(resourceClassParameters)...)
return allErrs
}