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:
Kubernetes Prow Robot
2026-01-10 07:27:38 +05:30
committed by GitHub
13 changed files with 526 additions and 10 deletions

View File

@@ -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

View File

@@ -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

View 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
}

View File

@@ -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

View 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
}

View File

@@ -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

View File

@@ -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": {

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.