mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 11:28:58 +00:00
DRA: Update validation for Partitionable Devices
This commit is contained in:
@@ -249,15 +249,11 @@ type ResourcePool struct {
|
||||
|
||||
const ResourceSliceMaxSharedCapacity = 128
|
||||
const ResourceSliceMaxDevices = 128
|
||||
const ResourceSliceMaxDevicesWithTaints = 64
|
||||
const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64
|
||||
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
|
||||
const BindingConditionsMaxSize = 4
|
||||
const BindingFailureConditionsMaxSize = 4
|
||||
|
||||
// Defines the max number of shared counters that can be specified
|
||||
// in a ResourceSlice. The number is summed up across all sets.
|
||||
const ResourceSliceMaxSharedCounters = 32
|
||||
|
||||
// Defines the maximum number of counter sets (through the
|
||||
// SharedCounters field) that can be defined in a ResourceSlice.
|
||||
const ResourceSliceMaxCounterSets = 8
|
||||
@@ -275,11 +271,6 @@ const ResourceSliceMaxDeviceCounterConsumptionsPerDevice = 2
|
||||
// per device counter consumption.
|
||||
const ResourceSliceMaxCountersPerDeviceCounterConsumption = 32
|
||||
|
||||
// Defines the maximum number of counters that can be defined
|
||||
// in device counter consumptions across all devices in a
|
||||
// ResourceSlice.
|
||||
const ResourceSliceMaxConsumedCountersPerResourceSlice = 2048
|
||||
|
||||
// Device represents one individual hardware instance that can be selected based
|
||||
// on its attributes. Besides the name, exactly one field must be set.
|
||||
type Device struct {
|
||||
@@ -557,14 +548,6 @@ type CapacityRequestPolicyRange struct {
|
||||
// Limit for the sum of the number of entries in both attributes and capacity.
|
||||
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters in each device.
|
||||
const ResourceSliceMaxCountersPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters defined in devices in
|
||||
// a ResourceSlice. We want to allow up to 64 devices to specify
|
||||
// up to 16 counters, so the limit for the ResourceSlice will be 1024.
|
||||
const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
|
||||
|
||||
// QualifiedName is the name of a device attribute or capacity.
|
||||
//
|
||||
// Attributes and capacities are defined either by the owner of the specific
|
||||
|
||||
@@ -703,44 +703,28 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat
|
||||
"exactly one of `nodeName`, `nodeSelector`, `allNodes`, `perDeviceNodeSelection` is required, but multiple fields are set"))
|
||||
}
|
||||
|
||||
sharedCounterToCounterNames := gatherSharedCounterCounterNames(spec.SharedCounters)
|
||||
if spec.SharedCounters != nil && spec.Devices != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", "only one of `sharedCounters` or `devices` is allowed"))
|
||||
}
|
||||
|
||||
maxDevices := resource.ResourceSliceMaxDevices
|
||||
if haveDeviceTaints(spec) {
|
||||
maxDevices = resource.ResourceSliceMaxDevicesWithTaints
|
||||
if haveDeviceTaints(spec) || haveConsumesCounters(spec) {
|
||||
maxDevices = resource.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters
|
||||
}
|
||||
allErrs = append(allErrs, validateSet(spec.Devices, maxDevices,
|
||||
func(device resource.Device, fldPath *field.Path) field.ErrorList {
|
||||
oldDevice := lookupDevice(oldSpec, device.Name)
|
||||
return validateDevice(device, oldDevice, fldPath, sharedCounterToCounterNames, spec.PerDeviceNodeSelection)
|
||||
return validateDevice(device, oldDevice, fldPath, spec.PerDeviceNodeSelection)
|
||||
},
|
||||
func(device resource.Device) string {
|
||||
return device.Name
|
||||
}, fldPath.Child("devices"))...)
|
||||
|
||||
// Size limit for total number of counters in devices enforced here.
|
||||
numDeviceCounters := 0
|
||||
for _, device := range spec.Devices {
|
||||
for _, c := range device.ConsumesCounters {
|
||||
numDeviceCounters += len(c.Counters)
|
||||
}
|
||||
}
|
||||
if numDeviceCounters > resource.ResourceSliceMaxDeviceCountersPerSlice {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("devices"), numDeviceCounters, fmt.Sprintf("the total number of counters in devices must not exceed %d", resource.ResourceSliceMaxDeviceCountersPerSlice)))
|
||||
}
|
||||
|
||||
// Size limit for all shared counters enforced here.
|
||||
numCounters := 0
|
||||
for _, set := range spec.SharedCounters {
|
||||
numCounters += len(set.Counters)
|
||||
}
|
||||
if numCounters > resource.ResourceSliceMaxSharedCounters {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("sharedCounters"), numCounters, fmt.Sprintf("the total number of shared counters must not exceed %d", resource.ResourceSliceMaxSharedCounters)))
|
||||
}
|
||||
allErrs = append(allErrs, validateSet(spec.SharedCounters, -1,
|
||||
allErrs = append(allErrs, validateSet(spec.SharedCounters, resource.ResourceSliceMaxCounterSets,
|
||||
validateCounterSet,
|
||||
func(counterSet resource.CounterSet) string {
|
||||
return counterSet.Name
|
||||
}, fldPath.Child("sharedCounters"))...)
|
||||
}, fldPath.Child("sharedCounters"), sizeCovered, uniquenessCovered)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@@ -758,6 +742,19 @@ func haveDeviceTaints(spec *resource.ResourceSliceSpec) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func haveConsumesCounters(spec *resource.ResourceSliceSpec) bool {
|
||||
if spec == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, device := range spec.Devices {
|
||||
if len(device.ConsumesCounters) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func lookupDevice(spec *resource.ResourceSliceSpec, deviceName string) *resource.Device {
|
||||
if spec == nil {
|
||||
return nil
|
||||
@@ -774,33 +771,21 @@ func lookupDevice(spec *resource.ResourceSliceSpec, deviceName string) *resource
|
||||
func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if counterSet.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "").MarkCoveredByDeclarative())
|
||||
} else {
|
||||
allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name"))...)
|
||||
allErrs = append(allErrs, validateCounterName(counterSet.Name, fldPath.Child("name"))...).MarkCoveredByDeclarative()
|
||||
}
|
||||
if len(counterSet.Counters) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("counters"), ""))
|
||||
} else {
|
||||
// The size limit is enforced for across all sets by the caller.
|
||||
allErrs = append(allErrs, validateMap(counterSet.Counters, -1, validation.DNS1123LabelMaxLength,
|
||||
allErrs = append(allErrs, validateMap(counterSet.Counters, resource.ResourceSliceMaxCountersPerCounterSet, validation.DNS1123LabelMaxLength,
|
||||
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func gatherSharedCounterCounterNames(sharedCounters []resource.CounterSet) map[string]sets.Set[string] {
|
||||
sharedCounterToCounterMap := make(map[string]sets.Set[string])
|
||||
for _, counterSet := range sharedCounters {
|
||||
counterNames := sets.New[string]()
|
||||
for counterName := range counterSet.Counters {
|
||||
counterNames.Insert(counterName)
|
||||
}
|
||||
sharedCounterToCounterMap[counterSet.Name] = counterNames
|
||||
}
|
||||
return sharedCounterToCounterMap
|
||||
}
|
||||
|
||||
func validateResourcePool(pool resource.ResourcePool, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allErrs = append(allErrs, validatePoolName(pool.Name, fldPath.Child("name"))...)
|
||||
@@ -813,7 +798,7 @@ func validateResourcePool(pool resource.ResourcePool, fldPath *field.Path) field
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDevice(device resource.Device, oldDevice *resource.Device, fldPath *field.Path, sharedCounterToCounterNames map[string]sets.Set[string], perDeviceNodeSelection *bool) field.ErrorList {
|
||||
func validateDevice(device resource.Device, oldDevice *resource.Device, fldPath *field.Path, perDeviceNodeSelection *bool) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allowMultipleAllocations := device.AllowMultipleAllocations != nil && *device.AllowMultipleAllocations
|
||||
allErrs = append(allErrs, validateDeviceName(device.Name, fldPath.Child("name"))...)
|
||||
@@ -840,33 +825,11 @@ func validateDevice(device resource.Device, oldDevice *resource.Device, fldPath
|
||||
fldPath.Child("taints"))...)
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateSet(device.ConsumesCounters, -1,
|
||||
allErrs = append(allErrs, validateSet(device.ConsumesCounters, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice,
|
||||
validateDeviceCounterConsumption,
|
||||
func(deviceCapacityConsumption resource.DeviceCounterConsumption) string {
|
||||
return deviceCapacityConsumption.CounterSet
|
||||
}, fldPath.Child("consumesCounters"))...)
|
||||
|
||||
var countersLength int
|
||||
for _, set := range device.ConsumesCounters {
|
||||
countersLength += len(set.Counters)
|
||||
}
|
||||
if countersLength > resource.ResourceSliceMaxCountersPerDevice {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, countersLength, fmt.Sprintf("the total number of counters must not exceed %d", resource.ResourceSliceMaxCountersPerDevice)))
|
||||
}
|
||||
|
||||
for i, deviceCounterConsumption := range device.ConsumesCounters {
|
||||
if capacityNames, exists := sharedCounterToCounterNames[deviceCounterConsumption.CounterSet]; exists {
|
||||
for capacityName := range deviceCounterConsumption.Counters {
|
||||
if !capacityNames.Has(string(capacityName)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounters").Index(i).Child("counters"),
|
||||
capacityName, "must reference a counter defined in the ResourceSlice sharedCounters"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("consumesCounters").Index(i).Child("counterSet"),
|
||||
deviceCounterConsumption.CounterSet, "must reference a counterSet defined in the ResourceSlice sharedCounters"))
|
||||
}
|
||||
}
|
||||
}, fldPath.Child("consumesCounters"), sizeCovered, uniquenessCovered)...)
|
||||
|
||||
if perDeviceNodeSelection != nil && *perDeviceNodeSelection {
|
||||
setFields := make([]string, 0, 3)
|
||||
@@ -909,14 +872,15 @@ func validateDeviceCounterConsumption(deviceCounterConsumption resource.DeviceCo
|
||||
var allErrs field.ErrorList
|
||||
|
||||
if len(deviceCounterConsumption.CounterSet) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("counterSet"), ""))
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("counterSet"), "").MarkCoveredByDeclarative())
|
||||
} else {
|
||||
allErrs = append(allErrs, validateCounterName(deviceCounterConsumption.CounterSet, fldPath.Child("counterSet"))...).MarkCoveredByDeclarative()
|
||||
}
|
||||
if len(deviceCounterConsumption.Counters) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("counters"), ""))
|
||||
} else {
|
||||
// The size limit is enforced for the entire device.
|
||||
allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, -1, validation.DNS1123LabelMaxLength,
|
||||
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
|
||||
allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, resource.ResourceSliceMaxCountersPerDeviceCounterConsumption,
|
||||
validation.DNS1123LabelMaxLength, validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ func testCounters() map[string]resourceapi.Counter {
|
||||
}
|
||||
}
|
||||
|
||||
func testMultipleCounters(count int) map[string]resourceapi.Counter {
|
||||
counters := make(map[string]resourceapi.Counter)
|
||||
for i := 0; i < count; i++ {
|
||||
counters[fmt.Sprintf("memory-%d", i)] = resourceapi.Counter{Value: resource.MustParse("1Gi")}
|
||||
}
|
||||
return counters
|
||||
}
|
||||
|
||||
func testResourceSlice(name, nodeName, driverName string, numDevices int) *resourceapi.ResourceSlice {
|
||||
slice := &resourceapi.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -80,6 +88,19 @@ func testResourceSlice(name, nodeName, driverName string, numDevices int) *resou
|
||||
return slice
|
||||
}
|
||||
|
||||
func testResourceSliceWithSharedCounters(name, poolName, driverName string, numCounterSets int) *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(name, poolName, driverName, 1)
|
||||
slice.Spec.Devices = nil
|
||||
for i := 0; i < numCounterSets; i++ {
|
||||
counterSet := resourceapi.CounterSet{
|
||||
Name: fmt.Sprintf("counterset-%d", i),
|
||||
Counters: testCounters(),
|
||||
}
|
||||
slice.Spec.SharedCounters = append(slice.Spec.SharedCounters, counterSet)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func testResourceSliceWithBindingConditions(name, nodeName, driverName string, numDevices int, bindingConditions, bindingFailureConditions []string) *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(name, nodeName, driverName, numDevices)
|
||||
for i := range slice.Spec.Devices {
|
||||
@@ -110,7 +131,7 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
},
|
||||
"good-taints": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaints)
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)
|
||||
for i := range slice.Spec.Devices {
|
||||
slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}}
|
||||
}
|
||||
@@ -118,9 +139,9 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
}(),
|
||||
},
|
||||
"too-large-taints": {
|
||||
wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaints+1, resourceapi.ResourceSliceMaxDevicesWithTaints)},
|
||||
wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaints+1)
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1)
|
||||
for i := range slice.Spec.Devices {
|
||||
slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}}
|
||||
}
|
||||
@@ -342,6 +363,21 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "driver"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
|
||||
slice: testResourceSlice(goodName, goodName, badName, 1),
|
||||
},
|
||||
"both-shared-counters-and-devices": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec"), "", "only one of `sharedCounters` or `devices` is allowed"),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Name: "counterset",
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"bad-devices": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||
@@ -665,16 +701,44 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-name-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "").MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"bad-name-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')").MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 0)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Name: badName,
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-counter-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), ""),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 0)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Name: badName,
|
||||
Name: goodName,
|
||||
},
|
||||
}
|
||||
return slice
|
||||
@@ -685,7 +749,7 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters").Key(badName), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Name: goodName,
|
||||
@@ -697,26 +761,12 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-name-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), ""),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"duplicate-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec", "sharedCounters").Index(1), goodName),
|
||||
field.Duplicate(field.NewPath("spec", "sharedCounters").Index(1), goodName).MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = []resourceapi.CounterSet{
|
||||
{
|
||||
Name: goodName,
|
||||
@@ -730,24 +780,46 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-large-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxSharedCounters+1, fmt.Sprintf("the total number of shared counters must not exceed %d", resourceapi.ResourceSliceMaxSharedCounters)),
|
||||
},
|
||||
"max-shared-counters": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxSharedCounters + 1)
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxCounterSets)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-counterset-consumes-counter": {
|
||||
"too-many-shared-counters": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""),
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
|
||||
field.TooMany(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxCounterSets+1, resourceapi.ResourceSliceMaxCounterSets).MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxCounterSets + 1)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"max-counters-in-counter-set": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters[0].Counters = testMultipleCounters(resourceapi.ResourceSliceMaxCountersPerCounterSet)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-many-counters-in-counter-set": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), resourceapi.ResourceSliceMaxCountersPerCounterSet+1, resourceapi.ResourceSliceMaxCountersPerCounterSet),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSliceWithSharedCounters(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters[0].Counters = testMultipleCounters(resourceapi.ResourceSliceMaxCountersPerCounterSet + 1)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-name-counterset-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "").MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
Counters: testCounters(),
|
||||
@@ -756,13 +828,45 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"bad-name-counterset-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')").MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: badName,
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"duplicate-counterset-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(1), goodName).MarkCoveredByDeclarative()},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: goodName,
|
||||
Counters: testCounters(),
|
||||
},
|
||||
{
|
||||
CounterSet: goodName,
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"missing-counter-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), ""),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: "counterset-0",
|
||||
@@ -771,65 +875,82 @@ func TestValidateResourceSlice(t *testing.T) {
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"wrong-counterref-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "fake", "must reference a counter defined in the ResourceSlice sharedCounters"),
|
||||
},
|
||||
"max-device-counter-consumption-for-device": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
Counters: map[string]resourceapi.Counter{
|
||||
"fake": {Value: resource.MustParse("1Gi")},
|
||||
},
|
||||
CounterSet: "counterset-0",
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"wrong-sharedcounterref-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "fake", "must reference a counterSet defined in the ResourceSlice sharedCounters"),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
Counters: testCounters(),
|
||||
CounterSet: "fake",
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-large-consumes-counter": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0), resourceapi.ResourceSliceMaxCountersPerDevice+1, fmt.Sprintf("the total number of counters must not exceed %d", resourceapi.ResourceSliceMaxCountersPerDevice)),
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters"), resourceapi.ResourceSliceMaxSharedCounters+1, fmt.Sprintf("the total number of shared counters must not exceed %d", resourceapi.ResourceSliceMaxSharedCounters)),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.SharedCounters = createSharedCounters(resourceapi.ResourceSliceMaxSharedCounters + 1)
|
||||
slice.Spec.Devices[0].Attributes = nil
|
||||
slice.Spec.Devices[0].Capacity = nil
|
||||
slice.Spec.Devices[0].ConsumesCounters = createConsumesCounters(resourceapi.ResourceSliceMaxCountersPerDevice + 1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = createConsumesCounters(resourceapi.ResourceSliceMaxDeviceCounterConsumptionsPerDevice)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-many-device-counters-in-slice": {
|
||||
"too-many-device-counter-consumption-for-device": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDeviceCountersPerSlice+1, fmt.Sprintf("the total number of counters in devices must not exceed %d", resourceapi.ResourceSliceMaxDeviceCountersPerSlice)),
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resourceapi.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resourceapi.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).MarkCoveredByDeclarative(),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 65)
|
||||
slice.Spec.SharedCounters = createSharedCounters(16)
|
||||
for i := 0; i < 64; i++ {
|
||||
slice.Spec.Devices[i].ConsumesCounters = createConsumesCounters(16)
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.Devices[0].Attributes = nil
|
||||
slice.Spec.Devices[0].Capacity = nil
|
||||
slice.Spec.Devices[0].ConsumesCounters = createConsumesCounters(resourceapi.ResourceSliceMaxDeviceCounterConsumptionsPerDevice + 1)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"max-counters-in-device-counter-consumption": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.Devices[0].Attributes = nil
|
||||
slice.Spec.Devices[0].Capacity = nil
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: "counterset-0",
|
||||
Counters: testMultipleCounters(resourceapi.ResourceSliceMaxCountersPerDeviceCounterConsumption),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-many-counters-in-device-counter-consumption": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), resourceapi.ResourceSliceMaxCountersPerDeviceCounterConsumption+1, resourceapi.ResourceSliceMaxCountersPerDeviceCounterConsumption),
|
||||
},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, driverName, 1)
|
||||
slice.Spec.Devices[0].Attributes = nil
|
||||
slice.Spec.Devices[0].Capacity = nil
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: "counterset-0",
|
||||
Counters: testMultipleCounters(resourceapi.ResourceSliceMaxCountersPerDeviceCounterConsumption + 1),
|
||||
},
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"max-number-of-devices-with-consumes-counters": {
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)
|
||||
for i := range slice.Spec.Devices {
|
||||
slice.Spec.Devices[i].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: "counterset-0",
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
"too-many-devices-with-consumes-counters": {
|
||||
wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)},
|
||||
slice: func() *resourceapi.ResourceSlice {
|
||||
slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1)
|
||||
slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{
|
||||
{
|
||||
CounterSet: "counterset-0",
|
||||
Counters: testCounters(),
|
||||
},
|
||||
}
|
||||
slice.Spec.Devices[64].ConsumesCounters = createConsumesCounters(1)
|
||||
return slice
|
||||
}(),
|
||||
},
|
||||
|
||||
@@ -48,43 +48,43 @@ func TestDeclarativeValidate(t *testing.T) {
|
||||
expectedErrs field.ErrorList
|
||||
}{
|
||||
"valid": {
|
||||
input: mkResourceSlice(),
|
||||
input: mkResourceSliceWithDevices(),
|
||||
},
|
||||
// spec.devices[%d].bindingConditions
|
||||
"valid: one binding condition": {
|
||||
input: mkResourceSlice(tweakBindingConditions(1)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingConditions(1)),
|
||||
},
|
||||
"valid: at limit binding conditions": {
|
||||
input: mkResourceSlice(tweakBindingConditions(resource.BindingConditionsMaxSize)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize)),
|
||||
},
|
||||
"invalid: too many binding conditions": {
|
||||
input: mkResourceSlice(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.devices[%d].bindingFailureConditions
|
||||
"valid: one binding failure conditions": {
|
||||
input: mkResourceSlice(tweakBindingFailureConditions(1)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingFailureConditions(1)),
|
||||
},
|
||||
"valid: at limit binding failure conditions": {
|
||||
input: mkResourceSlice(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize)),
|
||||
},
|
||||
"invalid: too many binding failure conditions": {
|
||||
input: mkResourceSlice(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)),
|
||||
input: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.Devices[%d].Taints[%d].Effect
|
||||
"valid: taint NoSchedule": {
|
||||
input: mkResourceSlice(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoSchedule))),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoSchedule))),
|
||||
},
|
||||
"valid: taint NoExecute": {
|
||||
input: mkResourceSlice(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoExecute))),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoExecute))),
|
||||
},
|
||||
"invalid: taint Invalid": {
|
||||
input: mkResourceSlice(tweakDeviceTaintEffect("Invalid")),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceTaintEffect("Invalid")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.NotSupported(
|
||||
field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"),
|
||||
@@ -92,36 +92,108 @@ func TestDeclarativeValidate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
"invalid: taint empty": {
|
||||
input: mkResourceSlice(tweakDeviceTaintEffect("")),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceTaintEffect("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""),
|
||||
},
|
||||
},
|
||||
// spec.Devices[%].attribute
|
||||
"valid: device attribute int": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})),
|
||||
},
|
||||
"valid: device attribute bool": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/bool", resource.DeviceAttribute{BoolValue: ptr.To(true)})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/bool", resource.DeviceAttribute{BoolValue: ptr.To(true)})),
|
||||
},
|
||||
"valid: device attribute string": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/string", resource.DeviceAttribute{StringValue: ptr.To("value")})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/string", resource.DeviceAttribute{StringValue: ptr.To("value")})),
|
||||
},
|
||||
"valid: device attribute version": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/version", resource.DeviceAttribute{VersionValue: ptr.To("1.2.3")})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/version", resource.DeviceAttribute{VersionValue: ptr.To("1.2.3")})),
|
||||
},
|
||||
"invalid: device attribute with multiple values": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""),
|
||||
},
|
||||
},
|
||||
"invalid: device attribute no value": {
|
||||
input: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{})),
|
||||
input: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{})),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters
|
||||
"valid: at limit shared counters": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets)),
|
||||
},
|
||||
"invalid: too many shared counters": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters
|
||||
"valid: at limit device consumes counters": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice)),
|
||||
},
|
||||
"invalid: too many device consumes counters": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters.name
|
||||
"valid: counter set name": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("valid-key")),
|
||||
},
|
||||
"invalid: counter set name": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("InvalidKey")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
|
||||
},
|
||||
},
|
||||
"invalid: counter set name not set": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), ""),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters.counterSet
|
||||
"valid: device consumes counters counter set name": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("valid-key")),
|
||||
},
|
||||
"invalid: device consumes counters counter set name": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("InvalidKey")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
|
||||
},
|
||||
},
|
||||
"invalid: device consumes counters counter set name not set": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters
|
||||
"valid: distinct names for shared counters": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("valid-key-1", "valid-key-2")),
|
||||
},
|
||||
"invalid: duplicate names for shared counters": {
|
||||
input: mkResourceSliceWithSharedCounters(tweakSharedCountersName("duplicate-key", "duplicate-key")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key"),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters
|
||||
"valid: distinct names for counter set in device counter consumption": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("valid-key-1", "valid-key-2")),
|
||||
},
|
||||
"invalid: duplicate names for counter set in device counter consumption": {
|
||||
input: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("duplicate-key", "duplicate-key")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key"),
|
||||
},
|
||||
},
|
||||
// TODO: Add more test cases
|
||||
}
|
||||
|
||||
@@ -151,67 +223,153 @@ func TestDeclarativeValidateUpdate(t *testing.T) {
|
||||
expectedErrs field.ErrorList
|
||||
}{
|
||||
"valid no changes": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(),
|
||||
},
|
||||
// spec.devices[%d].bindingConditions
|
||||
"valid update: at limit binding conditions": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakBindingConditions(resource.BindingConditionsMaxSize)),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize)),
|
||||
},
|
||||
"invalid update:update with too many binding conditions": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakBindingConditions(resource.BindingConditionsMaxSize + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingConditions"), resource.BindingConditionsMaxSize+1, resource.BindingConditionsMaxSize).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.devices[%d].bindingFailureConditions
|
||||
"valid update: at limit binding failure conditions": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize)),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize)),
|
||||
},
|
||||
"update with too many binding failure conditions": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakBindingFailureConditions(resource.BindingFailureConditionsMaxSize + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("bindingFailureConditions"), resource.BindingFailureConditionsMaxSize+1, resource.BindingFailureConditionsMaxSize).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.devices.taints.effect
|
||||
"valid update: NoSchedule taint effect": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoSchedule))),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoSchedule))),
|
||||
},
|
||||
"valid update: NoExecute taint effect": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoExecute))),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceTaintEffect(string(resource.DeviceTaintEffectNoExecute))),
|
||||
},
|
||||
"invalid update: unsupported taint effect": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakDeviceTaintEffect("InvalidEffect")),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceTaintEffect("InvalidEffect")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.NotSupported(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), "InvalidEffect", []string{string(resource.DeviceTaintEffectNoSchedule), string(resource.DeviceTaintEffectNoExecute)}),
|
||||
},
|
||||
},
|
||||
"invalid update: empty taint effect": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakDeviceTaintEffect("")),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceTaintEffect("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""),
|
||||
},
|
||||
},
|
||||
"valid update: device attribute": {
|
||||
old: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})),
|
||||
update: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{BoolValue: ptr.To(true)})),
|
||||
old: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{BoolValue: ptr.To(true)})),
|
||||
},
|
||||
"invalid update: device attribute with multiple values": {
|
||||
old: mkResourceSlice(),
|
||||
update: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})),
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "may have only one of the following fields set: bool, int, string, version"),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters
|
||||
"valid update: at limit shared counters": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets)),
|
||||
},
|
||||
"invalid update: too many shared counters": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCounters(resource.ResourceSliceMaxCounterSets + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec").Child("sharedCounters"), resource.ResourceSliceMaxCounterSets+1, resource.ResourceSliceMaxCounterSets).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters
|
||||
"valid update: at limit device consumes counters": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice)),
|
||||
},
|
||||
"invalid update: too many device consumes counters": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCounters(resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice + 1)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices").Index(0).Child("consumesCounters"), resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice+1, resource.ResourceSliceMaxDeviceCounterConsumptionsPerDevice).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters.name
|
||||
"valid update: counter set name": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("valid-key")),
|
||||
},
|
||||
"invalid update: counter set name": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("InvalidKey")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
|
||||
},
|
||||
},
|
||||
"invalid update: counter set name not set": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "sharedCounters").Index(0).Child("name"), ""),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters.counterSet
|
||||
"valid update: device consumes counters counter set name": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("valid-key")),
|
||||
},
|
||||
"invalid update: device consumes counters counter set name": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("InvalidKey")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
|
||||
},
|
||||
},
|
||||
"invalidupdate: device consumes counters counter set name not set": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counterSet"), ""),
|
||||
},
|
||||
},
|
||||
// spec.sharedCounters
|
||||
"valid update: distinct names for shared counters": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("valid-key-1", "valid-key-2")),
|
||||
},
|
||||
"invalid update: duplicate names for shared counters": {
|
||||
old: mkResourceSliceWithSharedCounters(),
|
||||
update: mkResourceSliceWithSharedCounters(tweakSharedCountersName("duplicate-key", "duplicate-key")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec").Child("sharedCounters").Index(1), "duplicate-key"),
|
||||
},
|
||||
},
|
||||
// spec.devices.consumesCounters
|
||||
"valid update: distinct names for counter set in device counter consumption": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("valid-key-1", "valid-key-2")),
|
||||
},
|
||||
"invalid update: duplicate names for counter set in device counter consumption": {
|
||||
old: mkResourceSliceWithDevices(),
|
||||
update: mkResourceSliceWithDevices(tweakDeviceConsumesCountersCounterSetName("duplicate-key", "duplicate-key")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Duplicate(field.NewPath("spec").Child("devices").Index(0).Child("consumesCounters").Index(1), "duplicate-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, tc := range testCases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
@@ -224,7 +382,7 @@ func TestDeclarativeValidateUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mkResourceSlice(mutators ...func(*resource.ResourceSlice)) resource.ResourceSlice {
|
||||
func mkResourceSliceWithDevices(mutators ...func(*resource.ResourceSlice)) resource.ResourceSlice {
|
||||
rs := resource.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-slice",
|
||||
@@ -256,6 +414,34 @@ func mkResourceSlice(mutators ...func(*resource.ResourceSlice)) resource.Resourc
|
||||
return rs
|
||||
}
|
||||
|
||||
func mkResourceSliceWithSharedCounters(mutators ...func(*resource.ResourceSlice)) resource.ResourceSlice {
|
||||
rs := resource.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-slice",
|
||||
},
|
||||
Spec: resource.ResourceSliceSpec{
|
||||
Driver: "test.driver.io",
|
||||
NodeName: ptr.To("test-node"),
|
||||
Pool: resource.ResourcePool{
|
||||
Name: "test-pool",
|
||||
ResourceSliceCount: 5,
|
||||
},
|
||||
SharedCounters: []resource.CounterSet{
|
||||
{
|
||||
Name: "shared-counter-set",
|
||||
Counters: map[string]resource.Counter{
|
||||
"valid-key": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, mutate := range mutators {
|
||||
mutate(&rs)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func tweakBindingFailureConditions(count int) func(*resource.ResourceSlice) {
|
||||
return func(rs *resource.ResourceSlice) {
|
||||
if rs.Spec.Devices[0].BindingConditions == nil {
|
||||
@@ -294,3 +480,63 @@ func tweakDeviceAttribute(name resource.QualifiedName, value resource.DeviceAttr
|
||||
rs.Spec.Devices[0].Attributes[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func tweakSharedCountersName(names ...string) func(*resource.ResourceSlice) {
|
||||
return func(rs *resource.ResourceSlice) {
|
||||
var sharedCounters []resource.CounterSet
|
||||
for _, name := range names {
|
||||
sharedCounters = append(sharedCounters, resource.CounterSet{
|
||||
Name: name,
|
||||
Counters: map[string]resource.Counter{
|
||||
"valid-key": {},
|
||||
},
|
||||
})
|
||||
}
|
||||
rs.Spec.SharedCounters = sharedCounters
|
||||
}
|
||||
}
|
||||
|
||||
func tweakSharedCounters(count int) func(*resource.ResourceSlice) {
|
||||
return func(rs *resource.ResourceSlice) {
|
||||
var counterSets []resource.CounterSet
|
||||
for i := 0; i < count; i++ {
|
||||
counterSets = append(counterSets, resource.CounterSet{
|
||||
Name: fmt.Sprintf("shared-counter-set-%d", i),
|
||||
Counters: map[string]resource.Counter{
|
||||
"valid-key": {},
|
||||
},
|
||||
})
|
||||
}
|
||||
rs.Spec.SharedCounters = counterSets
|
||||
}
|
||||
}
|
||||
|
||||
func tweakDeviceConsumesCounters(count int) func(*resource.ResourceSlice) {
|
||||
return func(rs *resource.ResourceSlice) {
|
||||
var consumesCounters []resource.DeviceCounterConsumption
|
||||
for i := 0; i < count; i++ {
|
||||
consumesCounters = append(consumesCounters, resource.DeviceCounterConsumption{
|
||||
CounterSet: fmt.Sprintf("shared-counter-set-%d", i),
|
||||
Counters: map[string]resource.Counter{
|
||||
"valid-key": {},
|
||||
},
|
||||
})
|
||||
}
|
||||
rs.Spec.Devices[0].ConsumesCounters = consumesCounters
|
||||
}
|
||||
}
|
||||
|
||||
func tweakDeviceConsumesCountersCounterSetName(counterSets ...string) func(*resource.ResourceSlice) {
|
||||
return func(rs *resource.ResourceSlice) {
|
||||
var consumesCounters []resource.DeviceCounterConsumption
|
||||
for _, counterSet := range counterSets {
|
||||
consumesCounters = append(consumesCounters, resource.DeviceCounterConsumption{
|
||||
CounterSet: counterSet,
|
||||
Counters: map[string]resource.Counter{
|
||||
"valid-key": {},
|
||||
},
|
||||
})
|
||||
}
|
||||
rs.Spec.Devices[0].ConsumesCounters = consumesCounters
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ var sliceWithBindingConditions = func() *resource.ResourceSlice {
|
||||
return slice
|
||||
}()
|
||||
|
||||
var sliceWithPartitionableDevices = &resource.ResourceSlice{
|
||||
var sliceWithPartitionableDevicesPerDeviceNodeSelection = &resource.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-resource-slice",
|
||||
},
|
||||
@@ -81,16 +81,30 @@ var sliceWithPartitionableDevices = &resource.ResourceSlice{
|
||||
ResourceSliceCount: 1,
|
||||
Generation: 1,
|
||||
},
|
||||
SharedCounters: []resource.CounterSet{
|
||||
Devices: []resource.Device{
|
||||
{
|
||||
Name: "pool-1",
|
||||
Counters: map[string]resource.Counter{
|
||||
"memory": {
|
||||
Value: k8sresource.MustParse("40Gi"),
|
||||
},
|
||||
},
|
||||
Name: "device",
|
||||
NodeName: func() *string {
|
||||
r := "valid-node-name"
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var sliceWithPartitionableDevicesConsumesCounters = &resource.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-resource-slice",
|
||||
},
|
||||
Spec: resource.ResourceSliceSpec{
|
||||
NodeName: ptr.To("valid-node-name"),
|
||||
Driver: "testdriver.example.com",
|
||||
Pool: resource.ResourcePool{
|
||||
Name: "valid-pool-name",
|
||||
ResourceSliceCount: 1,
|
||||
Generation: 1,
|
||||
},
|
||||
Devices: []resource.Device{
|
||||
{
|
||||
Name: "device",
|
||||
@@ -104,10 +118,6 @@ var sliceWithPartitionableDevices = &resource.ResourceSlice{
|
||||
},
|
||||
},
|
||||
},
|
||||
NodeName: func() *string {
|
||||
r := "valid-node-name"
|
||||
return &r
|
||||
}(),
|
||||
Attributes: map[resource.QualifiedName]resource.DeviceAttribute{
|
||||
resource.QualifiedName("version"): {
|
||||
StringValue: func() *string {
|
||||
@@ -126,6 +136,31 @@ var sliceWithPartitionableDevices = &resource.ResourceSlice{
|
||||
},
|
||||
}
|
||||
|
||||
var sliceWithPartitionableDevicesSharedCounters = &resource.ResourceSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-resource-slice",
|
||||
},
|
||||
Spec: resource.ResourceSliceSpec{
|
||||
NodeName: ptr.To("valid-node-name"),
|
||||
Driver: "testdriver.example.com",
|
||||
Pool: resource.ResourcePool{
|
||||
Name: "valid-pool-name",
|
||||
ResourceSliceCount: 1,
|
||||
Generation: 1,
|
||||
},
|
||||
SharedCounters: []resource.CounterSet{
|
||||
{
|
||||
Name: "pool-1",
|
||||
Counters: map[string]resource.Counter{
|
||||
"memory": {
|
||||
Value: k8sresource.MustParse("40Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var sliceWithCapacity = func() *resource.ResourceSlice {
|
||||
obj := slice.DeepCopy()
|
||||
obj.Spec.Devices[0].Capacity = map[resource.QualifiedName]resource.DeviceCapacity{
|
||||
@@ -207,26 +242,15 @@ func TestResourceSliceStrategyCreate(t *testing.T) {
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"drop-fields-partitionable-devices": {
|
||||
"drop-fields-partitionable-devices-with-consumes-counters": {
|
||||
obj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj.Spec.PerDeviceNodeSelection = func() *bool {
|
||||
r := false
|
||||
return &r
|
||||
}()
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ObjectMeta.Generation = 1
|
||||
obj.Spec.SharedCounters = nil
|
||||
obj.Spec.PerDeviceNodeSelection = nil
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
obj.Spec.Devices[0].NodeName = nil
|
||||
obj.Spec.Devices[0].NodeSelector = nil
|
||||
obj.Spec.Devices[0].AllNodes = nil
|
||||
obj.Spec.Devices[0].ConsumesCounters = nil
|
||||
return obj
|
||||
}(),
|
||||
@@ -235,15 +259,46 @@ func TestResourceSliceStrategyCreate(t *testing.T) {
|
||||
// have a node selector after the perDeviceNodeSelection field got
|
||||
// dropped.
|
||||
"drop-fields-partitionable-devices-with-per-device-node-selection": {
|
||||
obj: sliceWithPartitionableDevices,
|
||||
obj: sliceWithPartitionableDevicesPerDeviceNodeSelection,
|
||||
partitionableDevices: false,
|
||||
expectedValidationError: true,
|
||||
},
|
||||
"keep-fields-partitionable-devices": {
|
||||
obj: sliceWithPartitionableDevices,
|
||||
"drop-fields-partitionable-devices-with-shared-counters": {
|
||||
obj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ObjectMeta.Generation = 1
|
||||
obj.Spec.SharedCounters = nil
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-consumes-counters": {
|
||||
obj: sliceWithPartitionableDevicesConsumesCounters,
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-per-device-node-selection": {
|
||||
obj: sliceWithPartitionableDevicesPerDeviceNodeSelection,
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-shared-counters": {
|
||||
obj: sliceWithPartitionableDevicesSharedCounters,
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
@@ -420,86 +475,168 @@ func TestResourceSliceStrategyUpdate(t *testing.T) {
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"drop-fields-partitionable-devices": {
|
||||
"drop-fields-partitionable-devices-with-consumes-counters": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Spec.PerDeviceNodeSelection = func() *bool {
|
||||
r := false
|
||||
return &r
|
||||
}()
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
obj.Spec.SharedCounters = nil
|
||||
obj.Spec.PerDeviceNodeSelection = nil
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
obj.Spec.Devices[0].ConsumesCounters = nil
|
||||
obj.Spec.Devices[0].NodeName = nil
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"drop-fields-partitionable-devices-with-per-device-node-selection": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectValidationError: true,
|
||||
},
|
||||
"keep-fields-partitionable-devices": {
|
||||
"drop-fields-partitionable-devices-with-shared-counters": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
obj.Spec.PerDeviceNodeSelection = nil
|
||||
obj.Spec.Devices[0].NodeName = nil
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
obj.Spec.NodeName = ptr.To("valid-node-name")
|
||||
obj.Spec.PerDeviceNodeSelection = nil
|
||||
obj.Spec.Devices[0].NodeName = nil
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices": {
|
||||
oldObj: sliceWithPartitionableDevices,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-disabled-feature": {
|
||||
oldObj: sliceWithPartitionableDevices,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevices.DeepCopy()
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
obj.Spec.SharedCounters = nil
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-consumes-counters": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-per-device-node-selection": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectValidationError: true, // Spec.NodeName is immutable.
|
||||
},
|
||||
"keep-fields-partitionable-devices-with-shared-counters": {
|
||||
oldObj: slice,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
obj.Generation = 1
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-with-consumes-counters": {
|
||||
oldObj: sliceWithPartitionableDevicesConsumesCounters,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-with-per-device-node-selection": {
|
||||
oldObj: sliceWithPartitionableDevicesPerDeviceNodeSelection,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-with-shared-counters": {
|
||||
oldObj: sliceWithPartitionableDevicesSharedCounters,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: true,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-consumes-counters-disabled-feature": {
|
||||
oldObj: sliceWithPartitionableDevicesConsumesCounters,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesConsumesCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-per-device-node-selection-disabled-feature": {
|
||||
oldObj: sliceWithPartitionableDevicesPerDeviceNodeSelection,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesPerDeviceNodeSelection.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
},
|
||||
"keep-existing-fields-partitionable-devices-shared-counters-disabled-feature": {
|
||||
oldObj: sliceWithPartitionableDevicesSharedCounters,
|
||||
newObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
partitionableDevices: false,
|
||||
expectObj: func() *resource.ResourceSlice {
|
||||
obj := sliceWithPartitionableDevicesSharedCounters.DeepCopy()
|
||||
obj.ResourceVersion = "4"
|
||||
return obj
|
||||
}(),
|
||||
|
||||
@@ -261,15 +261,11 @@ type ResourcePool struct {
|
||||
|
||||
const ResourceSliceMaxSharedCapacity = 128
|
||||
const ResourceSliceMaxDevices = 128
|
||||
const ResourceSliceMaxDevicesWithTaints = 64
|
||||
const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64
|
||||
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
|
||||
const BindingConditionsMaxSize = 4
|
||||
const BindingFailureConditionsMaxSize = 4
|
||||
|
||||
// Defines the max number of shared counters that can be specified
|
||||
// in a ResourceSlice. The number is summed up across all sets.
|
||||
const ResourceSliceMaxSharedCounters = 32
|
||||
|
||||
// Defines the maximum number of counter sets (through the
|
||||
// SharedCounters field) that can be defined in a ResourceSlice.
|
||||
const ResourceSliceMaxCounterSets = 8
|
||||
@@ -287,11 +283,6 @@ const ResourceSliceMaxDeviceCounterConsumptionsPerDevice = 2
|
||||
// per device counter consumption.
|
||||
const ResourceSliceMaxCountersPerDeviceCounterConsumption = 32
|
||||
|
||||
// Defines the maximum number of counters that can be defined
|
||||
// in device counter consumptions across all devices in a
|
||||
// ResourceSlice.
|
||||
const ResourceSliceMaxConsumedCountersPerResourceSlice = 2048
|
||||
|
||||
// Device represents one individual hardware instance that can be selected based
|
||||
// on its attributes. Besides the name, exactly one field must be set.
|
||||
type Device struct {
|
||||
@@ -577,14 +568,6 @@ type CapacityRequestPolicyRange struct {
|
||||
// Limit for the sum of the number of entries in both attributes and capacity.
|
||||
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters in each device.
|
||||
const ResourceSliceMaxCountersPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters defined in devices in
|
||||
// a ResourceSlice. We want to allow up to 64 devices to specify
|
||||
// up to 16 counters, so the limit for the ResourceSlice will be 1024.
|
||||
const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
|
||||
|
||||
// QualifiedName is the name of a device attribute or capacity.
|
||||
//
|
||||
// Attributes and capacities are defined either by the owner of the specific
|
||||
|
||||
@@ -269,15 +269,11 @@ type ResourcePool struct {
|
||||
|
||||
const ResourceSliceMaxSharedCapacity = 128
|
||||
const ResourceSliceMaxDevices = 128
|
||||
const ResourceSliceMaxDevicesWithTaints = 64
|
||||
const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64
|
||||
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
|
||||
const BindingConditionsMaxSize = 4
|
||||
const BindingFailureConditionsMaxSize = 4
|
||||
|
||||
// Defines the max number of shared counters that can be specified
|
||||
// in a ResourceSlice. The number is summed up across all sets.
|
||||
const ResourceSliceMaxSharedCounters = 32
|
||||
|
||||
// Defines the maximum number of counter sets (through the
|
||||
// SharedCounters field) that can be defined in a ResourceSlice.
|
||||
const ResourceSliceMaxCounterSets = 8
|
||||
@@ -295,11 +291,6 @@ const ResourceSliceMaxDeviceCounterConsumptionsPerDevice = 2
|
||||
// per device counter consumption.
|
||||
const ResourceSliceMaxCountersPerDeviceCounterConsumption = 32
|
||||
|
||||
// Defines the maximum number of counters that can be defined
|
||||
// in device counter consumptions across all devices in a
|
||||
// ResourceSlice.
|
||||
const ResourceSliceMaxConsumedCountersPerResourceSlice = 2048
|
||||
|
||||
// Device represents one individual hardware instance that can be selected based
|
||||
// on its attributes. Besides the name, exactly one field must be set.
|
||||
type Device struct {
|
||||
@@ -585,14 +576,6 @@ type CapacityRequestPolicyRange struct {
|
||||
// Limit for the sum of the number of entries in both attributes and capacity.
|
||||
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters in each device.
|
||||
const ResourceSliceMaxCountersPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters defined in devices in
|
||||
// a ResourceSlice. We want to allow up to 64 devices to specify
|
||||
// up to 16 counters, so the limit for the ResourceSlice will be 1024.
|
||||
const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
|
||||
|
||||
// QualifiedName is the name of a device attribute or capacity.
|
||||
//
|
||||
// Attributes and capacities are defined either by the owner of the specific
|
||||
|
||||
@@ -261,15 +261,11 @@ type ResourcePool struct {
|
||||
|
||||
const ResourceSliceMaxSharedCapacity = 128
|
||||
const ResourceSliceMaxDevices = 128
|
||||
const ResourceSliceMaxDevicesWithTaints = 64
|
||||
const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64
|
||||
const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.
|
||||
const BindingConditionsMaxSize = 4
|
||||
const BindingFailureConditionsMaxSize = 4
|
||||
|
||||
// Defines the max number of shared counters that can be specified
|
||||
// in a ResourceSlice. The number is summed up across all sets.
|
||||
const ResourceSliceMaxSharedCounters = 32
|
||||
|
||||
// Defines the maximum number of counter sets (through the
|
||||
// SharedCounters field) that can be defined in a ResourceSlice.
|
||||
const ResourceSliceMaxCounterSets = 8
|
||||
@@ -287,11 +283,6 @@ const ResourceSliceMaxDeviceCounterConsumptionsPerDevice = 2
|
||||
// per device counter consumption.
|
||||
const ResourceSliceMaxCountersPerDeviceCounterConsumption = 32
|
||||
|
||||
// Defines the maximum number of counters that can be defined
|
||||
// in device counter consumptions across all devices in a
|
||||
// ResourceSlice.
|
||||
const ResourceSliceMaxConsumedCountersPerResourceSlice = 2048
|
||||
|
||||
// Device represents one individual hardware instance that can be selected based
|
||||
// on its attributes. Besides the name, exactly one field must be set.
|
||||
type Device struct {
|
||||
@@ -577,14 +568,6 @@ type CapacityRequestPolicyRange struct {
|
||||
// Limit for the sum of the number of entries in both attributes and capacity.
|
||||
const ResourceSliceMaxAttributesAndCapacitiesPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters in each device.
|
||||
const ResourceSliceMaxCountersPerDevice = 32
|
||||
|
||||
// Limit for the total number of counters defined in devices in
|
||||
// a ResourceSlice. We want to allow up to 64 devices to specify
|
||||
// up to 16 counters, so the limit for the ResourceSlice will be 1024.
|
||||
const ResourceSliceMaxDeviceCountersPerSlice = 1024 // 64 * 16
|
||||
|
||||
// QualifiedName is the name of a device attribute or capacity.
|
||||
//
|
||||
// Attributes and capacities are defined either by the owner of the specific
|
||||
|
||||
Reference in New Issue
Block a user