From 8a6b3caaa0dd81a6fe627d8a6e48be5d69d37c21 Mon Sep 17 00:00:00 2001 From: Lalit Chauhan Date: Thu, 23 Oct 2025 17:52:04 +0000 Subject: [PATCH] use union member DV in the DRA --- .../resource/v1/zz_generated.validations.go | 118 +++++++++++++++++- .../v1beta1/zz_generated.validations.go | 117 ++++++++++++++++- .../v1beta2/zz_generated.validations.go | 118 +++++++++++++++++- pkg/apis/resource/validation/validation.go | 4 +- .../validation_resourceslice_test.go | 4 +- .../declarative_validation_test.go | 45 +++++++ .../k8s.io/api/resource/v1/generated.proto | 12 +- staging/src/k8s.io/api/resource/v1/types.go | 12 +- .../api/resource/v1beta1/generated.proto | 12 +- .../src/k8s.io/api/resource/v1beta1/types.go | 12 +- .../api/resource/v1beta2/generated.proto | 12 +- .../src/k8s.io/api/resource/v1beta2/types.go | 12 +- 12 files changed, 447 insertions(+), 31 deletions(-) diff --git a/pkg/apis/resource/v1/zz_generated.validations.go b/pkg/apis/resource/v1/zz_generated.validations.go index 59f14ceb8b6..204b31d5a98 100644 --- a/pkg/apis/resource/v1/zz_generated.validations.go +++ b/pkg/apis/resource/v1/zz_generated.validations.go @@ -174,7 +174,21 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // to declarative validation rules in the API schema. func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.Device) (errs field.ErrorList) { // field resourcev1.Device.Name has no validation - // field resourcev1.Device.Attributes has no validation + + // field resourcev1.Device.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1.QualifiedName]resourcev1.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1.Device) map[resourcev1.QualifiedName]resourcev1.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1.Device.Capacity has no validation // field resourcev1.Device.ConsumesCounters has no validation // field resourcev1.Device.NodeName has no validation @@ -350,6 +364,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1.DeviceAttribute.StringValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1.DeviceAttribute.VersionValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta1/zz_generated.validations.go b/pkg/apis/resource/v1beta1/zz_generated.validations.go index a2e538753fd..f3177af326c 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta1/zz_generated.validations.go @@ -175,7 +175,20 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // Validate_BasicDevice validates an instance of BasicDevice according // to declarative validation rules in the API schema. func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.BasicDevice) (errs field.ErrorList) { - // field resourcev1beta1.BasicDevice.Attributes has no validation + // field resourcev1beta1.BasicDevice.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1beta1.BasicDevice) map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1beta1.BasicDevice.Capacity has no validation // field resourcev1beta1.BasicDevice.ConsumesCounters has no validation // field resourcev1beta1.BasicDevice.NodeName has no validation @@ -371,6 +384,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1beta1.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1beta1.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1beta1.DeviceAttribute.StringValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1beta1.DeviceAttribute.VersionValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta2/zz_generated.validations.go b/pkg/apis/resource/v1beta2/zz_generated.validations.go index 04da3332422..83c38feaf4f 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta2/zz_generated.validations.go @@ -176,7 +176,21 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // to declarative validation rules in the API schema. func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.Device) (errs field.ErrorList) { // field resourcev1beta2.Device.Name has no validation - // field resourcev1beta2.Device.Attributes has no validation + + // field resourcev1beta2.Device.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1beta2.QualifiedName]resourcev1beta2.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1beta2.Device) map[resourcev1beta2.QualifiedName]resourcev1beta2.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1beta2.Device.Capacity has no validation // field resourcev1beta2.Device.ConsumesCounters has no validation // field resourcev1beta2.Device.NodeName has no validation @@ -352,6 +366,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1beta2.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1beta2.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1beta2.DeviceAttribute.StringValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1beta2.DeviceAttribute.VersionValue + 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 + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 42a188467a1..5778a3ef409 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -910,11 +910,11 @@ func validateDeviceAttribute(attribute resource.DeviceAttribute, fldPath *field. switch numFields { case 0: - allErrs = append(allErrs, field.Required(fldPath, "exactly one value must be specified")) + allErrs = append(allErrs, field.Invalid(fldPath, "", "exactly one value must be specified").MarkCoveredByDeclarative()) case 1: // Okay. default: - allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified")) + allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified").MarkCoveredByDeclarative()) } return allErrs } diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go index 7dfe927e8d7..d113d68aad4 100644 --- a/pkg/apis/resource/validation/validation_resourceslice_test.go +++ b/pkg/apis/resource/validation/validation_resourceslice_test.go @@ -336,8 +336,8 @@ func TestValidateResourceSlice(t *testing.T) { "bad-attribute": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), - field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "exactly one value must be specified"), - field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "", "exactly one value must be specified").MarkCoveredByDeclarative(), + field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified").MarkCoveredByDeclarative(), field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"), field.TooLongMaxLength(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength), field.TooLongMaxLength(field.NewPath("spec", "devices").Index(4).Child("attributes").Key(goodName).Child("string"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength), diff --git a/pkg/registry/resource/resourceslice/declarative_validation_test.go b/pkg/registry/resource/resourceslice/declarative_validation_test.go index 84ab9e78fac..44aeba1803f 100644 --- a/pkg/registry/resource/resourceslice/declarative_validation_test.go +++ b/pkg/registry/resource/resourceslice/declarative_validation_test.go @@ -97,6 +97,31 @@ func TestDeclarativeValidate(t *testing.T) { 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)})), + }, + "valid: device attribute bool": { + input: mkResourceSlice(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")})), + }, + "valid: device attribute version": { + input: mkResourceSlice(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)})), + 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{})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""), + }, + }, // TODO: Add more test cases } @@ -176,6 +201,17 @@ func TestDeclarativeValidateUpdate(t *testing.T) { 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)})), + }, + "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)})), + 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"), + }, + }, } for k, tc := range testCases { t.Run(k, func(t *testing.T) { @@ -249,3 +285,12 @@ func tweakDeviceTaintEffect(effect string) func(*resource.ResourceSlice) { rs.Spec.Devices[0].Taints[0].Effect = resource.DeviceTaintEffect(effect) } } + +func tweakDeviceAttribute(name resource.QualifiedName, value resource.DeviceAttribute) func(*resource.ResourceSlice) { + return func(rs *resource.ResourceSlice) { + if rs.Spec.Devices[0].Attributes == nil { + rs.Spec.Devices[0].Attributes = make(map[resource.QualifiedName]resource.DeviceAttribute) + } + rs.Spec.Devices[0].Attributes[name] = value + } +} diff --git a/staging/src/k8s.io/api/resource/v1/generated.proto b/staging/src/k8s.io/api/resource/v1/generated.proto index d598ee64ad7..cca587fa7ff 100644 --- a/staging/src/k8s.io/api/resource/v1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1/generated.proto @@ -513,26 +513,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1/types.go b/staging/src/k8s.io/api/resource/v1/types.go index c6f2717cfd2..a3393ff5d89 100644 --- a/staging/src/k8s.io/api/resource/v1/types.go +++ b/staging/src/k8s.io/api/resource/v1/types.go @@ -581,26 +581,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` } diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.proto b/staging/src/k8s.io/api/resource/v1beta1/generated.proto index 9f1217641ee..bfad4e4357d 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.proto @@ -521,26 +521,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index ab9099323e6..b80a2b9400a 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -585,26 +585,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` } diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.proto b/staging/src/k8s.io/api/resource/v1beta2/generated.proto index ea550bb5464..c48e15ffe20 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta2/generated.proto @@ -513,26 +513,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go index d457daca2b4..be7735b8301 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types.go @@ -581,26 +581,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` }