mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
Merge pull request #135890 from CLBRITTON2/discovery-endpoint-dv
Feat: wire discovery group for declarative validation and migrate Endpoint.Addresses + EndpointSlice.AddressType
This commit is contained in:
@@ -53,6 +53,8 @@ func TestVersionedValidationByFuzzing(t *testing.T) {
|
||||
{Group: "autoscaling", Version: "v1beta1"},
|
||||
{Group: "autoscaling", Version: "v1beta2"},
|
||||
{Group: "autoscaling", Version: "v2"},
|
||||
{Group: "discovery.k8s.io", Version: "v1"},
|
||||
{Group: "discovery.k8s.io", Version: "v1beta1"},
|
||||
}
|
||||
|
||||
fuzzIters := *roundtrip.FuzzIters / 10 // TODO: Find a better way to manage test running time
|
||||
|
||||
@@ -18,5 +18,7 @@ limitations under the License.
|
||||
// +k8s:conversion-gen-external-types=k8s.io/api/discovery/v1
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:defaulter-gen-input=k8s.io/api/discovery/v1
|
||||
// +k8s:validation-gen=TypeMeta
|
||||
// +k8s:validation-gen-input=k8s.io/api/discovery/v1
|
||||
|
||||
package v1
|
||||
|
||||
145
pkg/apis/discovery/v1/zz_generated.validations.go
generated
Normal file
145
pkg/apis/discovery/v1/zz_generated.validations.go
generated
Normal file
@@ -0,0 +1,145 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by validation-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
operation "k8s.io/apimachinery/pkg/api/operation"
|
||||
safe "k8s.io/apimachinery/pkg/api/safe"
|
||||
validate "k8s.io/apimachinery/pkg/api/validate"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
sets "k8s.io/apimachinery/pkg/util/sets"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func init() { localSchemeBuilder.Register(RegisterValidations) }
|
||||
|
||||
// RegisterValidations adds validation functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterValidations(scheme *runtime.Scheme) error {
|
||||
// type EndpointSlice
|
||||
scheme.AddValidationFunc((*discoveryv1.EndpointSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
return Validate_EndpointSlice(ctx, op, nil /* fldPath */, obj.(*discoveryv1.EndpointSlice), safe.Cast[*discoveryv1.EndpointSlice](oldObj))
|
||||
}
|
||||
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
var symbolsForAddressType = sets.New(discoveryv1.AddressTypeFQDN, discoveryv1.AddressTypeIPv4, discoveryv1.AddressTypeIPv6)
|
||||
|
||||
// Validate_AddressType validates an instance of AddressType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_AddressType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.AddressType) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAddressType, nil)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Endpoint validates an instance of Endpoint according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.Endpoint) (errs field.ErrorList) {
|
||||
// field discoveryv1.Endpoint.Addresses
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 100); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("addresses"), obj.Addresses, safe.Field(oldObj, func(oldObj *discoveryv1.Endpoint) []string { return oldObj.Addresses }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1.Endpoint.Conditions has no validation
|
||||
// field discoveryv1.Endpoint.Hostname has no validation
|
||||
// field discoveryv1.Endpoint.TargetRef has no validation
|
||||
// field discoveryv1.Endpoint.DeprecatedTopology has no validation
|
||||
// field discoveryv1.Endpoint.NodeName has no validation
|
||||
// field discoveryv1.Endpoint.Zone has no validation
|
||||
// field discoveryv1.Endpoint.Hints has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_EndpointSlice validates an instance of EndpointSlice according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.EndpointSlice) (errs field.ErrorList) {
|
||||
// field discoveryv1.EndpointSlice.TypeMeta has no validation
|
||||
// field discoveryv1.EndpointSlice.ObjectMeta has no validation
|
||||
|
||||
// field discoveryv1.EndpointSlice.AddressType
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *discoveryv1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_AddressType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1.EndpointSlice) *discoveryv1.AddressType { return &oldObj.AddressType }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1.EndpointSlice.Endpoints
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []discoveryv1.Endpoint, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Endpoint)...)
|
||||
return
|
||||
}(fldPath.Child("endpoints"), obj.Endpoints, safe.Field(oldObj, func(oldObj *discoveryv1.EndpointSlice) []discoveryv1.Endpoint { return oldObj.Endpoints }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1.EndpointSlice.Ports has no validation
|
||||
return errs
|
||||
}
|
||||
@@ -18,5 +18,7 @@ limitations under the License.
|
||||
// +k8s:conversion-gen-external-types=k8s.io/api/discovery/v1beta1
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:defaulter-gen-input=k8s.io/api/discovery/v1beta1
|
||||
// +k8s:validation-gen=TypeMeta
|
||||
// +k8s:validation-gen-input=k8s.io/api/discovery/v1beta1
|
||||
|
||||
package v1beta1
|
||||
|
||||
144
pkg/apis/discovery/v1beta1/zz_generated.validations.go
generated
Normal file
144
pkg/apis/discovery/v1beta1/zz_generated.validations.go
generated
Normal file
@@ -0,0 +1,144 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by validation-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
operation "k8s.io/apimachinery/pkg/api/operation"
|
||||
safe "k8s.io/apimachinery/pkg/api/safe"
|
||||
validate "k8s.io/apimachinery/pkg/api/validate"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
sets "k8s.io/apimachinery/pkg/util/sets"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func init() { localSchemeBuilder.Register(RegisterValidations) }
|
||||
|
||||
// RegisterValidations adds validation functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterValidations(scheme *runtime.Scheme) error {
|
||||
// type EndpointSlice
|
||||
scheme.AddValidationFunc((*discoveryv1beta1.EndpointSlice)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
return Validate_EndpointSlice(ctx, op, nil /* fldPath */, obj.(*discoveryv1beta1.EndpointSlice), safe.Cast[*discoveryv1beta1.EndpointSlice](oldObj))
|
||||
}
|
||||
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
var symbolsForAddressType = sets.New(discoveryv1beta1.AddressTypeFQDN, discoveryv1beta1.AddressTypeIPv4, discoveryv1beta1.AddressTypeIPv6)
|
||||
|
||||
// Validate_AddressType validates an instance of AddressType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_AddressType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.AddressType) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAddressType, nil)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_Endpoint validates an instance of Endpoint according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.Endpoint) (errs field.ErrorList) {
|
||||
// field discoveryv1beta1.Endpoint.Addresses
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 100); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("addresses"), obj.Addresses, safe.Field(oldObj, func(oldObj *discoveryv1beta1.Endpoint) []string { return oldObj.Addresses }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1beta1.Endpoint.Conditions has no validation
|
||||
// field discoveryv1beta1.Endpoint.Hostname has no validation
|
||||
// field discoveryv1beta1.Endpoint.TargetRef has no validation
|
||||
// field discoveryv1beta1.Endpoint.Topology has no validation
|
||||
// field discoveryv1beta1.Endpoint.NodeName has no validation
|
||||
// field discoveryv1beta1.Endpoint.Hints has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_EndpointSlice validates an instance of EndpointSlice according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.EndpointSlice) (errs field.ErrorList) {
|
||||
// field discoveryv1beta1.EndpointSlice.TypeMeta has no validation
|
||||
// field discoveryv1beta1.EndpointSlice.ObjectMeta has no validation
|
||||
|
||||
// field discoveryv1beta1.EndpointSlice.AddressType
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *discoveryv1beta1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
earlyReturn := false
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
earlyReturn = true
|
||||
}
|
||||
if earlyReturn {
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_AddressType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1beta1.EndpointSlice) *discoveryv1beta1.AddressType { return &oldObj.AddressType }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1beta1.EndpointSlice.Endpoints
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []discoveryv1beta1.Endpoint, oldValueCorrelated bool) (errs field.ErrorList) {
|
||||
// don't revalidate unchanged data
|
||||
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
|
||||
return nil
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Endpoint)...)
|
||||
return
|
||||
}(fldPath.Child("endpoints"), obj.Endpoints, safe.Field(oldObj, func(oldObj *discoveryv1beta1.EndpointSlice) []discoveryv1beta1.Endpoint { return oldObj.Endpoints }), oldObj != nil)...)
|
||||
|
||||
// field discoveryv1beta1.EndpointSlice.Ports has no validation
|
||||
return errs
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func ValidateEndpointSliceCreate(endpointSlice *discovery.EndpointSlice) field.E
|
||||
// ValidateEndpointSliceUpdate validates an EndpointSlice when it is updated.
|
||||
func ValidateEndpointSliceUpdate(newEndpointSlice, oldEndpointSlice *discovery.EndpointSlice) field.ErrorList {
|
||||
allErrs := ValidateEndpointSlice(newEndpointSlice, oldEndpointSlice)
|
||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType")).WithOrigin("immutable").MarkCoveredByDeclarative()...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@@ -104,11 +104,10 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres
|
||||
addressPath := idxPath.Child("addresses")
|
||||
|
||||
if len(endpoint.Addresses) == 0 {
|
||||
allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address"))
|
||||
allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address").MarkCoveredByDeclarative())
|
||||
} else if len(endpoint.Addresses) > maxAddresses {
|
||||
allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses))
|
||||
allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses).WithOrigin("maxItems").MarkCoveredByDeclarative())
|
||||
}
|
||||
|
||||
for i, address := range endpoint.Addresses {
|
||||
// This validates known address types, unknown types fall through
|
||||
// and do not get validated.
|
||||
@@ -208,9 +207,9 @@ func validateAddressType(addressType discovery.AddressType) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if addressType == "" {
|
||||
allErrs = append(allErrs, field.Required(field.NewPath("addressType"), ""))
|
||||
allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "").MarkCoveredByDeclarative())
|
||||
} else if !supportedAddressTypes.Has(addressType) {
|
||||
allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, sets.List(supportedAddressTypes)))
|
||||
allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, sets.List(supportedAddressTypes)).MarkCoveredByDeclarative())
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
||||
3
pkg/generated/openapi/zz_generated.openapi.go
generated
3
pkg/generated/openapi/zz_generated.openapi.go
generated
@@ -33603,10 +33603,11 @@ func schema_k8sio_api_discovery_v1beta1_EndpointSlice(ref common.ReferenceCallba
|
||||
},
|
||||
"addressType": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name.",
|
||||
Description: "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. This field is immutable after creation. The following address types are currently supported: * IPv4: Represents an IPv4 Address. * IPv6: Represents an IPv6 Address. * FQDN: Represents a Fully Qualified Domain Name.\n\nPossible enum values:\n - `\"FQDN\"` represents a FQDN.\n - `\"IPv4\"` represents an IPv4 Address.\n - `\"IPv6\"` represents an IPv6 Address.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"FQDN", "IPv4", "IPv6"},
|
||||
},
|
||||
},
|
||||
"endpoints": {
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package endpointslice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/discovery"
|
||||
)
|
||||
|
||||
var apiVersions = []string{"v1", "v1beta1"}
|
||||
|
||||
func TestDeclarativeValidate(t *testing.T) {
|
||||
for _, apiVersion := range apiVersions {
|
||||
t.Run(apiVersion, func(t *testing.T) {
|
||||
testDeclarativeValidate(t, apiVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDeclarativeValidate(t *testing.T, apiVersion string) {
|
||||
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
|
||||
APIGroup: "discovery.k8s.io",
|
||||
APIVersion: apiVersion,
|
||||
Resource: "endpointslices",
|
||||
IsResourceRequest: true,
|
||||
Verb: "create",
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
input discovery.EndpointSlice
|
||||
expectedErrs field.ErrorList
|
||||
}{
|
||||
"valid": {
|
||||
input: mkValidEndpointSlice(),
|
||||
},
|
||||
"valid at limit endpoint addresses": {
|
||||
input: mkValidEndpointSlice(tweakAddresses(100)),
|
||||
},
|
||||
"invalid missing endpoint addresses": {
|
||||
input: mkValidEndpointSlice(func(obj *discovery.EndpointSlice) {
|
||||
obj.Endpoints[0].Addresses = nil
|
||||
}),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("endpoints").Index(0).Child("addresses"), ""),
|
||||
},
|
||||
},
|
||||
"invalid too many endpoint addresses": {
|
||||
input: mkValidEndpointSlice(tweakAddresses(101)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("endpoints").Index(0).Child("addresses"), 101, 100).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid missing addressType": {
|
||||
input: mkValidEndpointSlice(tweakAddressType("")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("addressType"), ""),
|
||||
},
|
||||
},
|
||||
"invalid addressType not supported": {
|
||||
input: mkValidEndpointSlice(tweakAddressType("invalid")),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.NotSupported(field.NewPath("addressType"), discovery.AddressType("invalid"), []string{string(discovery.AddressTypeIPv4), string(discovery.AddressTypeIPv6), string(discovery.AddressTypeFQDN)}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeclarativeValidateUpdate(t *testing.T) {
|
||||
for _, apiVersion := range apiVersions {
|
||||
t.Run(apiVersion, func(t *testing.T) {
|
||||
testDeclarativeValidateUpdate(t, apiVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
testCases := map[string]struct {
|
||||
oldObj discovery.EndpointSlice
|
||||
updateObj discovery.EndpointSlice
|
||||
expectedErrs field.ErrorList
|
||||
}{
|
||||
"valid update": {
|
||||
oldObj: mkValidEndpointSlice(),
|
||||
updateObj: mkValidEndpointSlice(),
|
||||
},
|
||||
"valid update at limit endpoint addresses": {
|
||||
oldObj: mkValidEndpointSlice(),
|
||||
updateObj: mkValidEndpointSlice(tweakAddresses(100)),
|
||||
},
|
||||
"invalid update missing addresses": {
|
||||
oldObj: mkValidEndpointSlice(),
|
||||
updateObj: mkValidEndpointSlice(func(obj *discovery.EndpointSlice) {
|
||||
obj.Endpoints[0].Addresses = nil
|
||||
}),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Required(field.NewPath("endpoints").Index(0).Child("addresses"), ""),
|
||||
},
|
||||
},
|
||||
"invalid update too many addresses": {
|
||||
oldObj: mkValidEndpointSlice(),
|
||||
updateObj: mkValidEndpointSlice(tweakAddresses(101)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("endpoints").Index(0).Child("addresses"), 101, 100).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid update addressType immutable": {
|
||||
oldObj: mkValidEndpointSlice(),
|
||||
updateObj: mkValidEndpointSlice(tweakAddressType(discovery.AddressTypeIPv6)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("addressType"), discovery.AddressTypeIPv6, "field is immutable").WithOrigin("immutable"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
|
||||
APIPrefix: "apis",
|
||||
APIGroup: "discovery.k8s.io",
|
||||
APIVersion: apiVersion,
|
||||
Resource: "endpointslices",
|
||||
Name: "valid-endpointslice",
|
||||
IsResourceRequest: true,
|
||||
Verb: "update",
|
||||
})
|
||||
tc.oldObj.ResourceVersion = "1"
|
||||
tc.updateObj.ResourceVersion = "2"
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.updateObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mkValidEndpointSlice(tweaks ...func(obj *discovery.EndpointSlice)) discovery.EndpointSlice {
|
||||
obj := discovery.EndpointSlice{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-endpointslice",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
AddressType: discovery.AddressTypeIPv4,
|
||||
Endpoints: []discovery.Endpoint{
|
||||
{
|
||||
Addresses: []string{"10.1.2.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tweak := range tweaks {
|
||||
tweak(&obj)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
func tweakAddresses(count int) func(*discovery.EndpointSlice) {
|
||||
return func(obj *discovery.EndpointSlice) {
|
||||
addrs := make([]string, count)
|
||||
for i := range addrs {
|
||||
addrs[i] = fmt.Sprintf("10.0.0.%d", i%255)
|
||||
}
|
||||
obj.Endpoints[0].Addresses = addrs
|
||||
}
|
||||
}
|
||||
|
||||
func tweakAddressType(addrType discovery.AddressType) func(*discovery.EndpointSlice) {
|
||||
return func(obj *discovery.EndpointSlice) {
|
||||
obj.AddressType = addrType
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
@@ -91,8 +93,8 @@ func (endpointSliceStrategy) PrepareForUpdate(ctx context.Context, obj, old runt
|
||||
// Validate validates a new EndpointSlice.
|
||||
func (endpointSliceStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
endpointSlice := obj.(*discovery.EndpointSlice)
|
||||
err := validation.ValidateEndpointSliceCreate(endpointSlice)
|
||||
return err
|
||||
allErrs := validation.ValidateEndpointSliceCreate(endpointSlice)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, endpointSlice, nil, allErrs, operation.Create)
|
||||
}
|
||||
|
||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||
@@ -120,7 +122,8 @@ func (endpointSliceStrategy) AllowCreateOnUpdate() bool {
|
||||
func (endpointSliceStrategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList {
|
||||
newEPS := new.(*discovery.EndpointSlice)
|
||||
oldEPS := old.(*discovery.EndpointSlice)
|
||||
return validation.ValidateEndpointSliceUpdate(newEPS, oldEPS)
|
||||
allErrs := validation.ValidateEndpointSliceUpdate(newEPS, oldEPS)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newEPS, oldEPS, allErrs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
||||
@@ -38,6 +38,9 @@ message Endpoint {
|
||||
// controller will always have exactly 1 address. No semantics are defined for
|
||||
// additional addresses beyond the first, and kube-proxy does not look at them.
|
||||
// +listType=set
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:maxItems=100
|
||||
repeated string addresses = 1;
|
||||
|
||||
// conditions contains information about the current status of the endpoint.
|
||||
@@ -181,6 +184,9 @@ message EndpointSlice {
|
||||
// The EndpointSlice controller only generates, and kube-proxy only processes,
|
||||
// slices of addressType "IPv4" and "IPv6". No semantics are defined for
|
||||
// the "FQDN" type.
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:immutable
|
||||
optional string addressType = 4;
|
||||
|
||||
// endpoints is a list of unique endpoints in this slice. Each slice may
|
||||
|
||||
@@ -48,6 +48,9 @@ type EndpointSlice struct {
|
||||
// The EndpointSlice controller only generates, and kube-proxy only processes,
|
||||
// slices of addressType "IPv4" and "IPv6". No semantics are defined for
|
||||
// the "FQDN" type.
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:immutable
|
||||
AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"`
|
||||
|
||||
// endpoints is a list of unique endpoints in this slice. Each slice may
|
||||
@@ -69,6 +72,7 @@ type EndpointSlice struct {
|
||||
|
||||
// AddressType represents the type of address referred to by an endpoint.
|
||||
// +enum
|
||||
// +k8s:enum
|
||||
type AddressType string
|
||||
|
||||
const (
|
||||
@@ -91,6 +95,9 @@ type Endpoint struct {
|
||||
// controller will always have exactly 1 address. No semantics are defined for
|
||||
// additional addresses beyond the first, and kube-proxy does not look at them.
|
||||
// +listType=set
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:maxItems=100
|
||||
Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"`
|
||||
|
||||
// conditions contains information about the current status of the endpoint.
|
||||
|
||||
@@ -38,6 +38,9 @@ message Endpoint {
|
||||
// 100. These are all assumed to be fungible and clients may choose to only
|
||||
// use the first element. Refer to: https://issue.k8s.io/106267
|
||||
// +listType=set
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:maxItems=100
|
||||
repeated string addresses = 1;
|
||||
|
||||
// conditions contains information about the current status of the endpoint.
|
||||
@@ -167,6 +170,9 @@ message EndpointSlice {
|
||||
// * IPv4: Represents an IPv4 Address.
|
||||
// * IPv6: Represents an IPv6 Address.
|
||||
// * FQDN: Represents a Fully Qualified Domain Name.
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:immutable
|
||||
optional string addressType = 4;
|
||||
|
||||
// endpoints is a list of unique endpoints in this slice. Each slice may
|
||||
|
||||
@@ -45,6 +45,9 @@ type EndpointSlice struct {
|
||||
// * IPv4: Represents an IPv4 Address.
|
||||
// * IPv6: Represents an IPv6 Address.
|
||||
// * FQDN: Represents a Fully Qualified Domain Name.
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:immutable
|
||||
AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"`
|
||||
|
||||
// endpoints is a list of unique endpoints in this slice. Each slice may
|
||||
@@ -63,6 +66,8 @@ type EndpointSlice struct {
|
||||
}
|
||||
|
||||
// AddressType represents the type of address referred to by an endpoint.
|
||||
// +enum
|
||||
// +k8s:enum
|
||||
type AddressType string
|
||||
|
||||
const (
|
||||
@@ -85,6 +90,9 @@ type Endpoint struct {
|
||||
// 100. These are all assumed to be fungible and clients may choose to only
|
||||
// use the first element. Refer to: https://issue.k8s.io/106267
|
||||
// +listType=set
|
||||
// +required
|
||||
// +k8s:required
|
||||
// +k8s:maxItems=100
|
||||
Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"`
|
||||
|
||||
// conditions contains information about the current status of the endpoint.
|
||||
|
||||
Reference in New Issue
Block a user