diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 0e9f3825f1f..5cc07b43941 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -12025,7 +12025,7 @@ "description": "Endpoint represents a single logical \"backend\" implementing a service.", "properties": { "addresses": { - "description": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack networking where both IPv4 and IPv6 addresses would be included with the IP addressType. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", + "description": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. Consumers must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", "items": { "type": "string" }, @@ -12094,7 +12094,7 @@ "description": "EndpointSlice represents a subset of the endpoints that implement a service. For a given service there may be multiple EndpointSlice objects, selected by labels, which must be joined to produce the full set of endpoints.", "properties": { "addressType": { - "description": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. The following address types are currently supported: * IP: Represents an IP Address. This can include both IPv4 and IPv6\n addresses.\n* FQDN: Represents a Fully Qualified Domain Name. Default is IP", + "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.", "type": "string" }, "apiVersion": { @@ -12127,6 +12127,7 @@ } }, "required": [ + "addressType", "endpoints" ], "type": "object", diff --git a/pkg/apis/discovery/fuzzer/fuzzer.go b/pkg/apis/discovery/fuzzer/fuzzer.go index bb2bdcca017..b69490a7842 100644 --- a/pkg/apis/discovery/fuzzer/fuzzer.go +++ b/pkg/apis/discovery/fuzzer/fuzzer.go @@ -30,11 +30,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { func(obj *discovery.EndpointSlice, c fuzz.Continue) { c.FuzzNoCustom(obj) // fuzz self without calling this function again - // match defaults - if obj.AddressType == nil { - ipAddressType := discovery.AddressTypeIP - obj.AddressType = &ipAddressType - } + addressTypes := []discovery.AddressType{discovery.AddressTypeIPv4, discovery.AddressTypeIPv6, discovery.AddressTypeFQDN} + obj.AddressType = addressTypes[c.Rand.Intn(len(addressTypes))] for i, endpointPort := range obj.Ports { if endpointPort.Name == nil { diff --git a/pkg/apis/discovery/types.go b/pkg/apis/discovery/types.go index dad50f6887b..4d3005c11a3 100644 --- a/pkg/apis/discovery/types.go +++ b/pkg/apis/discovery/types.go @@ -32,13 +32,13 @@ type EndpointSlice struct { // +optional metav1.ObjectMeta // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. The following address - // types are currently supported: - // * IP: Represents an IP Address. This can include both IPv4 and IPv6 - // addresses. + // 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. - // +optional - AddressType *AddressType + AddressType AddressType // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. // +listType=atomic @@ -57,22 +57,27 @@ type EndpointSlice struct { type AddressType string const ( - // AddressTypeIP represents an IP Address. Inclusive of IPv4 and IPv6 - // addresses. + // AddressTypeIP represents an IP Address. + // This address type has been deprecated and has been replaced by the IPv4 + // and IPv6 adddress types. New resources with this address type will be + // considered invalid. This will be fully removed in 1.18. + // +deprecated AddressTypeIP = AddressType("IP") - // AddressTypeFQDN represents a Fully Qualified Domain Name. + // AddressTypeIPv4 represents an IPv4 Address. + AddressTypeIPv4 = AddressType(api.IPv4Protocol) + // AddressTypeIPv6 represents an IPv6 Address. + AddressTypeIPv6 = AddressType(api.IPv6Protocol) + // AddressTypeFQDN represents a FQDN. AddressTypeFQDN = AddressType("FQDN") ) // Endpoint represents a single logical "backend" implementing a service. type Endpoint struct { // addresses of this endpoint. The contents of this field are interpreted - // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack networking where both IPv4 and IPv6 - // addresses would be included with the IP addressType. Consumers (e.g. - // kube-proxy) must handle different types of addresses in the context of - // their own capabilities. This must contain at least one address but no - // more than 100. + // according to the corresponding EndpointSlice addressType field. Consumers + // must handle different types of addresses in the context of their own + // capabilities. This must contain at least one address but no more than + // 100. // +listType=set Addresses []string // conditions contains information about the current status of the endpoint. diff --git a/pkg/apis/discovery/v1alpha1/defaults.go b/pkg/apis/discovery/v1alpha1/defaults.go index a03d63811e7..7efdad3ef44 100644 --- a/pkg/apis/discovery/v1alpha1/defaults.go +++ b/pkg/apis/discovery/v1alpha1/defaults.go @@ -23,21 +23,14 @@ import ( ) var ( - defaultAddressType = discoveryv1alpha1.AddressTypeIP - defaultPortName = "" - defaultProtocol = v1.ProtocolTCP + defaultPortName = "" + defaultProtocol = v1.ProtocolTCP ) func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } -func SetDefaults_EndpointSlice(obj *discoveryv1alpha1.EndpointSlice) { - if obj.AddressType == nil { - obj.AddressType = &defaultAddressType - } -} - func SetDefaults_EndpointPort(obj *discoveryv1alpha1.EndpointPort) { if obj.Name == nil { obj.Name = &defaultPortName diff --git a/pkg/apis/discovery/v1alpha1/defaults_test.go b/pkg/apis/discovery/v1alpha1/defaults_test.go index 9f5b3a582fd..73bcb329196 100644 --- a/pkg/apis/discovery/v1alpha1/defaults_test.go +++ b/pkg/apis/discovery/v1alpha1/defaults_test.go @@ -33,8 +33,6 @@ func TestSetDefaultEndpointPort(t *testing.T) { fooStr := "foo" protoTCP := v1.ProtocolTCP protoUDP := v1.ProtocolUDP - ipAddressType := discovery.AddressTypeIP - otherAddressType := discovery.AddressType("other") tests := map[string]struct { original *discovery.EndpointSlice @@ -45,7 +43,6 @@ func TestSetDefaultEndpointPort(t *testing.T) { Port: utilpointer.Int32Ptr(80), }}}, expected: &discovery.EndpointSlice{ - AddressType: &ipAddressType, Ports: []discovery.EndpointPort{{ Name: &emptyStr, Protocol: &protoTCP, @@ -55,14 +52,12 @@ func TestSetDefaultEndpointPort(t *testing.T) { }, "should not overwrite values with defaults when set": { original: &discovery.EndpointSlice{ - AddressType: &otherAddressType, Ports: []discovery.EndpointPort{{ Name: &fooStr, Protocol: &protoUDP, }}, }, expected: &discovery.EndpointSlice{ - AddressType: &otherAddressType, Ports: []discovery.EndpointPort{{ Name: &fooStr, Protocol: &protoUDP, diff --git a/pkg/apis/discovery/v1alpha1/zz_generated.conversion.go b/pkg/apis/discovery/v1alpha1/zz_generated.conversion.go index a37934bf4b5..236ba256733 100644 --- a/pkg/apis/discovery/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/discovery/v1alpha1/zz_generated.conversion.go @@ -171,7 +171,7 @@ func Convert_discovery_EndpointPort_To_v1alpha1_EndpointPort(in *discovery.Endpo func autoConvert_v1alpha1_EndpointSlice_To_discovery_EndpointSlice(in *v1alpha1.EndpointSlice, out *discovery.EndpointSlice, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - out.AddressType = (*discovery.AddressType)(unsafe.Pointer(in.AddressType)) + out.AddressType = discovery.AddressType(in.AddressType) out.Endpoints = *(*[]discovery.Endpoint)(unsafe.Pointer(&in.Endpoints)) out.Ports = *(*[]discovery.EndpointPort)(unsafe.Pointer(&in.Ports)) return nil @@ -184,7 +184,7 @@ func Convert_v1alpha1_EndpointSlice_To_discovery_EndpointSlice(in *v1alpha1.Endp func autoConvert_discovery_EndpointSlice_To_v1alpha1_EndpointSlice(in *discovery.EndpointSlice, out *v1alpha1.EndpointSlice, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - out.AddressType = (*v1alpha1.AddressType)(unsafe.Pointer(in.AddressType)) + out.AddressType = v1alpha1.AddressType(in.AddressType) out.Endpoints = *(*[]v1alpha1.Endpoint)(unsafe.Pointer(&in.Endpoints)) out.Ports = *(*[]v1alpha1.EndpointPort)(unsafe.Pointer(&in.Ports)) return nil diff --git a/pkg/apis/discovery/v1alpha1/zz_generated.defaults.go b/pkg/apis/discovery/v1alpha1/zz_generated.defaults.go index 1302b11b440..67686962241 100644 --- a/pkg/apis/discovery/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/discovery/v1alpha1/zz_generated.defaults.go @@ -35,7 +35,6 @@ func RegisterDefaults(scheme *runtime.Scheme) error { } func SetObjectDefaults_EndpointSlice(in *v1alpha1.EndpointSlice) { - SetDefaults_EndpointSlice(in) for i := range in.Ports { a := &in.Ports[i] SetDefaults_EndpointPort(a) diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go index cfd1551e754..810f2ca124d 100644 --- a/pkg/apis/discovery/validation/validation.go +++ b/pkg/apis/discovery/validation/validation.go @@ -28,12 +28,23 @@ import ( ) var ( - supportedAddressTypes = sets.NewString(string(discovery.AddressTypeIP), string(discovery.AddressTypeFQDN)) - supportedPortProtocols = sets.NewString(string(api.ProtocolTCP), string(api.ProtocolUDP), string(api.ProtocolSCTP)) - maxTopologyLabels = 16 - maxAddresses = 100 - maxPorts = 100 - maxEndpoints = 1000 + supportedAddressTypes = sets.NewString( + string(discovery.AddressTypeIPv4), + string(discovery.AddressTypeIPv6), + string(discovery.AddressTypeFQDN), + ) + deprecatedAddressTypes = sets.NewString( + string(discovery.AddressTypeIP), + ) + supportedPortProtocols = sets.NewString( + string(api.ProtocolTCP), + string(api.ProtocolUDP), + string(api.ProtocolSCTP), + ) + maxTopologyLabels = 16 + maxAddresses = 100 + maxPorts = 100 + maxEndpoints = 1000 ) // ValidateEndpointSliceName can be used to check whether the given endpoint @@ -42,33 +53,24 @@ var ( var ValidateEndpointSliceName = apimachineryvalidation.NameIsDNSSubdomain // ValidateEndpointSlice validates an EndpointSlice. -func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorList { +func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice, validAddressTypes sets.String) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, ValidateEndpointSliceName, field.NewPath("metadata")) - - // AddressType should have had a default value set at this point, this is - // just a safety check if for some reason that changes or doesn't work. - addrType := discovery.AddressType("") - if endpointSlice.AddressType == nil { - allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) - } else { - addrType = *endpointSlice.AddressType - } - - if !supportedAddressTypes.Has(string(addrType)) { - allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addrType, supportedAddressTypes.List())) - } - - allErrs = append(allErrs, validateEndpoints(endpointSlice.Endpoints, addrType, field.NewPath("endpoints"))...) + allErrs = append(allErrs, validateAddressType(endpointSlice.AddressType, validAddressTypes)...) + allErrs = append(allErrs, validateEndpoints(endpointSlice.Endpoints, endpointSlice.AddressType, field.NewPath("endpoints"))...) allErrs = append(allErrs, validatePorts(endpointSlice.Ports, field.NewPath("ports"))...) return allErrs } +// ValidateEndpointSliceCreate validates an EndpointSlice when it is created. +func ValidateEndpointSliceCreate(endpointSlice *discovery.EndpointSlice) field.ErrorList { + return ValidateEndpointSlice(endpointSlice, supportedAddressTypes) +} + // ValidateEndpointSliceUpdate validates an EndpointSlice when it is updated. func ValidateEndpointSliceUpdate(newEndpointSlice, oldEndpointSlice *discovery.EndpointSlice) field.ErrorList { - allErrs := ValidateEndpointSlice(newEndpointSlice) - - allErrs = append(allErrs, apivalidation.ValidateImmutableField(*newEndpointSlice.AddressType, *oldEndpointSlice.AddressType, field.NewPath("addressType"))...) + allErrs := ValidateEndpointSlice(newEndpointSlice, supportedAddressTypes.Union(deprecatedAddressTypes)) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType"))...) return allErrs } @@ -92,11 +94,17 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres } for i, address := range endpoint.Addresses { + // This validates known address types, unknown types fall through + // and do not get validated. switch addrType { case discovery.AddressTypeIP: for _, msg := range validation.IsValidIP(address) { allErrs = append(allErrs, field.Invalid(addressPath.Index(i), address, msg)) } + case discovery.AddressTypeIPv4: + allErrs = append(allErrs, validation.IsValidIPv4Address(addressPath.Index(i), address)...) + case discovery.AddressTypeIPv6: + allErrs = append(allErrs, validation.IsValidIPv6Address(addressPath.Index(i), address)...) case discovery.AddressTypeFQDN: allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...) } @@ -153,3 +161,15 @@ func validatePorts(endpointPorts []discovery.EndpointPort, fldPath *field.Path) return allErrs } + +func validateAddressType(addressType discovery.AddressType, validAddressTypes sets.String) field.ErrorList { + allErrs := field.ErrorList{} + + if addressType == "" { + allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) + } else if !validAddressTypes.Has(string(addressType)) { + allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, validAddressTypes.List())) + } + + return allErrs +} diff --git a/pkg/apis/discovery/validation/validation_test.go b/pkg/apis/discovery/validation/validation_test.go index 0f7e7853a60..060545f93ab 100644 --- a/pkg/apis/discovery/validation/validation_test.go +++ b/pkg/apis/discovery/validation/validation_test.go @@ -41,7 +41,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -56,7 +56,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeFQDN), + AddressType: discovery.AddressTypeFQDN, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -71,7 +71,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("tcp"), Protocol: protocolPtr(api.ProtocolTCP), @@ -92,7 +92,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("one"), Protocol: protocolPtr(api.ProtocolTCP), @@ -120,7 +120,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr(""), Protocol: protocolPtr(api.ProtocolTCP), @@ -137,7 +137,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr(strings.Repeat("a", 63)), Protocol: protocolPtr(api.ProtocolTCP), @@ -151,7 +151,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{}, Endpoints: []discovery.Endpoint{}, }, @@ -160,7 +160,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: generatePorts(1), Endpoints: generateEndpoints(maxEndpoints), }, @@ -169,7 +169,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: generatePorts(maxPorts), }, }, @@ -177,7 +177,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -191,7 +191,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -208,7 +208,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr(""), Protocol: protocolPtr(api.ProtocolTCP), @@ -223,7 +223,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("aCapital"), Protocol: protocolPtr(api.ProtocolTCP), @@ -235,7 +235,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("almost_valid"), Protocol: protocolPtr(api.ProtocolTCP), @@ -247,7 +247,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr(strings.Repeat("a", 64)), Protocol: protocolPtr(api.ProtocolTCP), @@ -259,7 +259,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.Protocol("foo")), @@ -270,7 +270,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: generatePorts(maxPorts + 1), }, }, @@ -278,7 +278,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: generatePorts(1), Endpoints: generateEndpoints(maxEndpoints + 1), }, @@ -287,7 +287,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -301,7 +301,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -311,25 +311,11 @@ func TestValidateEndpointSlice(t *testing.T) { }}, }, }, - "bad-address-type": { - expectedErrors: 1, - endpointSlice: &discovery.EndpointSlice{ - ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressType("other")), - Ports: []discovery.EndpointPort{{ - Name: utilpointer.StringPtr("http"), - Protocol: protocolPtr(api.ProtocolTCP), - }}, - Endpoints: []discovery.Endpoint{{ - Addresses: generateIPAddresses(1), - }}, - }, - }, "bad-topology-key": { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -344,7 +330,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -359,7 +345,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -377,7 +363,7 @@ func TestValidateEndpointSlice(t *testing.T) { Name: "*&^", Namespace: "foo", }, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -392,7 +378,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -403,11 +389,41 @@ func TestValidateEndpointSlice(t *testing.T) { }}, }, }, + "bad-ipv4": { + expectedErrors: 2, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIPv4, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"123.456.789.012", "2001:4860:4860::8888"}, + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, + "bad-ipv6": { + expectedErrors: 2, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIPv6, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: []string{"123.456.789.012", "2001:4860:4860:defg"}, + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, "bad-fqdns": { expectedErrors: 4, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeFQDN), + AddressType: discovery.AddressTypeFQDN, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -422,7 +438,7 @@ func TestValidateEndpointSlice(t *testing.T) { expectedErrors: 1, endpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Protocol: protocolPtr(api.ProtocolTCP), @@ -436,15 +452,80 @@ func TestValidateEndpointSlice(t *testing.T) { }, "empty-everything": { expectedErrors: 3, + endpointSlice: &discovery.EndpointSlice{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + errs := ValidateEndpointSlice(testCase.endpointSlice, supportedAddressTypes.Union(deprecatedAddressTypes)) + if len(errs) != testCase.expectedErrors { + t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs) + } + }) + } +} + +func TestValidateEndpointSliceCreate(t *testing.T) { + standardMeta := metav1.ObjectMeta{ + Name: "hello", + Namespace: "world", + } + + testCases := map[string]struct { + expectedErrors int + endpointSlice *discovery.EndpointSlice + }{ + "good-slice": { + expectedErrors: 0, endpointSlice: &discovery.EndpointSlice{ - AddressType: addressTypePtr(""), + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIPv4, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: generateIPAddresses(1), + Hostname: utilpointer.StringPtr("valid-123"), + }}, + }, + }, + + // expected failures + "deprecated-address-type": { + expectedErrors: 1, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIP, + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: generateIPAddresses(1), + }}, + }, + }, + "bad-address-type": { + expectedErrors: 1, + endpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressType("other"), + Ports: []discovery.EndpointPort{{ + Name: utilpointer.StringPtr("http"), + Protocol: protocolPtr(api.ProtocolTCP), + }}, + Endpoints: []discovery.Endpoint{{ + Addresses: generateIPAddresses(1), + }}, }, }, } for name, testCase := range testCases { t.Run(name, func(t *testing.T) { - errs := ValidateEndpointSlice(testCase.endpointSlice) + errs := ValidateEndpointSliceCreate(testCase.endpointSlice) if len(errs) != testCase.expectedErrors { t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs) } @@ -463,29 +544,40 @@ func TestValidateEndpointSliceUpdate(t *testing.T) { "valid and identical slices": { newEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv6, }, oldEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIPv6, }, expectedErrors: 0, }, + "deprecated address type": { + expectedErrors: 0, + newEndpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIP, + }, + oldEndpointSlice: &discovery.EndpointSlice{ + ObjectMeta: standardMeta, + AddressType: discovery.AddressTypeIP, + }, + }, "valid and identical slices with different address types": { newEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, }, oldEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressType("other")), + AddressType: discovery.AddressType("other"), }, expectedErrors: 1, }, "invalid slices with valid address types": { newEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr(""), Protocol: protocolPtr(api.Protocol("invalid")), @@ -493,7 +585,7 @@ func TestValidateEndpointSliceUpdate(t *testing.T) { }, oldEndpointSlice: &discovery.EndpointSlice{ ObjectMeta: standardMeta, - AddressType: addressTypePtr(discovery.AddressTypeIP), + AddressType: discovery.AddressTypeIP, }, expectedErrors: 1, }, @@ -515,10 +607,6 @@ func protocolPtr(protocol api.Protocol) *api.Protocol { return &protocol } -func addressTypePtr(addressType discovery.AddressType) *discovery.AddressType { - return &addressType -} - func generatePorts(n int) []discovery.EndpointPort { ports := []discovery.EndpointPort{} for i := 0; i < n; i++ { diff --git a/pkg/apis/discovery/zz_generated.deepcopy.go b/pkg/apis/discovery/zz_generated.deepcopy.go index 4ac7376b96d..483988691a9 100644 --- a/pkg/apis/discovery/zz_generated.deepcopy.go +++ b/pkg/apis/discovery/zz_generated.deepcopy.go @@ -126,11 +126,6 @@ func (in *EndpointSlice) DeepCopyInto(out *EndpointSlice) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.AddressType != nil { - in, out := &in.AddressType, &out.AddressType - *out = new(AddressType) - **out = **in - } if in.Endpoints != nil { in, out := &in.Endpoints, &out.Endpoints *out = make([]Endpoint, len(*in)) diff --git a/pkg/controller/endpointslice/BUILD b/pkg/controller/endpointslice/BUILD index 0d47680a4de..7749558dfb1 100644 --- a/pkg/controller/endpointslice/BUILD +++ b/pkg/controller/endpointslice/BUILD @@ -41,6 +41,7 @@ go_library( "//staging/src/k8s.io/client-go/util/workqueue:go_default_library", "//staging/src/k8s.io/component-base/metrics/prometheus/ratelimiter:go_default_library", "//vendor/k8s.io/klog:go_default_library", + "//vendor/k8s.io/utils/net:go_default_library", ], ) diff --git a/pkg/controller/endpointslice/endpointslice_controller_test.go b/pkg/controller/endpointslice/endpointslice_controller_test.go index 1f253242c92..2d3d69ffb62 100644 --- a/pkg/controller/endpointslice/endpointslice_controller_test.go +++ b/pkg/controller/endpointslice/endpointslice_controller_test.go @@ -198,6 +198,7 @@ func TestSyncServiceEndpointSliceLabelSelection(t *testing.T) { discovery.LabelManagedBy: controllerName, }, }, + AddressType: discovery.AddressTypeIPv4, }, { ObjectMeta: metav1.ObjectMeta{ Name: "matching-2", @@ -207,6 +208,7 @@ func TestSyncServiceEndpointSliceLabelSelection(t *testing.T) { discovery.LabelManagedBy: controllerName, }, }, + AddressType: discovery.AddressTypeIPv4, }, { ObjectMeta: metav1.ObjectMeta{ Name: "partially-matching-1", @@ -215,6 +217,7 @@ func TestSyncServiceEndpointSliceLabelSelection(t *testing.T) { discovery.LabelServiceName: serviceName, }, }, + AddressType: discovery.AddressTypeIPv4, }, { ObjectMeta: metav1.ObjectMeta{ Name: "not-matching-1", @@ -224,6 +227,7 @@ func TestSyncServiceEndpointSliceLabelSelection(t *testing.T) { discovery.LabelManagedBy: controllerName, }, }, + AddressType: discovery.AddressTypeIPv4, }, { ObjectMeta: metav1.ObjectMeta{ Name: "not-matching-2", @@ -233,6 +237,7 @@ func TestSyncServiceEndpointSliceLabelSelection(t *testing.T) { discovery.LabelManagedBy: "something-else", }, }, + AddressType: discovery.AddressTypeIPv4, }} // need to add them to both store and fake clientset @@ -272,16 +277,13 @@ func TestSyncServiceFull(t *testing.T) { client, esController := newController([]string{"node-1"}) namespace := metav1.NamespaceDefault serviceName := "all-the-protocols" + ipv6Family := v1.IPv6Protocol - // pod 1 only uses PodIP status attr pod1 := newPod(1, namespace, true, 0) - pod1.Status.PodIP = "1.2.3.4" - pod1.Status.PodIPs = []v1.PodIP{} + pod1.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.4"}} esController.podStore.Add(pod1) - // pod 2 only uses PodIPs status attr pod2 := newPod(2, namespace, true, 0) - pod2.Status.PodIP = "" pod2.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.5"}, {IP: "1234::5678:0000:0000:9abc:def0"}} esController.podStore.Add(pod2) @@ -300,6 +302,7 @@ func TestSyncServiceFull(t *testing.T) { {Name: "sctp-example", TargetPort: intstr.FromInt(3456), Protocol: v1.ProtocolSCTP}, }, Selector: map[string]string{"foo": "bar"}, + IPFamily: &ipv6Family, }, } esController.serviceStore.Add(service) @@ -318,7 +321,7 @@ func TestSyncServiceFull(t *testing.T) { // ensure all attributes of endpoint slice match expected state slice := sliceList.Items[0] - assert.Len(t, slice.Endpoints, 2, "Expected 2 endpoints in first slice") + assert.Len(t, slice.Endpoints, 1, "Expected 1 endpoints in first slice") assert.Equal(t, slice.Annotations["endpoints.kubernetes.io/last-change-trigger-time"], serviceCreateTime.Format(time.RFC3339Nano)) assert.ElementsMatch(t, []discovery.EndpointPort{{ Name: strPtr("tcp-example"), @@ -336,12 +339,7 @@ func TestSyncServiceFull(t *testing.T) { assert.ElementsMatch(t, []discovery.Endpoint{{ Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, - Addresses: []string{"1.2.3.4"}, - TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: namespace, Name: pod1.Name}, - Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, - }, { - Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, - Addresses: []string{"1.2.3.5", "1234::5678:0000:0000:9abc:def0"}, + Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, TargetRef: &v1.ObjectReference{Kind: "Pod", Namespace: namespace, Name: pod2.Name}, Topology: map[string]string{"kubernetes.io/hostname": "node-1"}, }}, slice.Endpoints) diff --git a/pkg/controller/endpointslice/reconciler.go b/pkg/controller/endpointslice/reconciler.go index 3f19493f1f4..8b8dae4c022 100644 --- a/pkg/controller/endpointslice/reconciler.go +++ b/pkg/controller/endpointslice/reconciler.go @@ -47,7 +47,7 @@ type reconciler struct { // that logic in reconciler type endpointMeta struct { Ports []discovery.EndpointPort `json:"ports" protobuf:"bytes,2,rep,name=ports"` - AddressType *discovery.AddressType `json:"addressType" protobuf:"bytes,3,rep,name=addressType"` + AddressType discovery.AddressType `json:"addressType" protobuf:"bytes,3,rep,name=addressType"` } // reconcile takes a set of pods currently matching a service selector and @@ -55,13 +55,26 @@ type endpointMeta struct { // slices for the given service. It creates, updates, or deletes endpoint slices // to ensure the desired set of pods are represented by endpoint slices. func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, existingSlices []*discovery.EndpointSlice, triggerTime time.Time) error { + addressType := discovery.AddressTypeIPv4 + if service.Spec.IPFamily != nil && *service.Spec.IPFamily == corev1.IPv6Protocol { + addressType = discovery.AddressTypeIPv6 + } + + slicesToCreate := []*discovery.EndpointSlice{} + slicesToUpdate := []*discovery.EndpointSlice{} + slicesToDelete := []*discovery.EndpointSlice{} + // Build data structures for existing state. existingSlicesByPortMap := map[endpointutil.PortMapKey][]*discovery.EndpointSlice{} numExistingEndpoints := 0 for _, existingSlice := range existingSlices { - epHash := endpointutil.NewPortMapKey(existingSlice.Ports) - existingSlicesByPortMap[epHash] = append(existingSlicesByPortMap[epHash], existingSlice) - numExistingEndpoints += len(existingSlice.Endpoints) + if existingSlice.AddressType == addressType { + epHash := endpointutil.NewPortMapKey(existingSlice.Ports) + existingSlicesByPortMap[epHash] = append(existingSlicesByPortMap[epHash], existingSlice) + numExistingEndpoints += len(existingSlice.Endpoints) + } else { + slicesToDelete = append(slicesToDelete, existingSlice) + } } // Build data structures for desired state. @@ -78,10 +91,8 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis } if _, ok := desiredMetaByPortMap[epHash]; !ok { - // TODO: Support multiple backend types - ipAddressType := discovery.AddressTypeIP desiredMetaByPortMap[epHash] = &endpointMeta{ - AddressType: &ipAddressType, + AddressType: addressType, Ports: endpointPorts, } } @@ -90,16 +101,14 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis if err != nil { return err } - endpoint := podToEndpoint(pod, node, service) - desiredEndpointsByPortMap[epHash].Insert(&endpoint) - numDesiredEndpoints++ + if len(endpoint.Addresses) > 0 { + desiredEndpointsByPortMap[epHash].Insert(&endpoint) + numDesiredEndpoints++ + } } } - slicesToCreate := []*discovery.EndpointSlice{} - slicesToUpdate := []*discovery.EndpointSlice{} - sliceNamesToDelete := sets.String{} spMetrics := metrics.NewServicePortCache() totalAdded := 0 totalRemoved := 0 @@ -107,7 +116,7 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis // Determine changes necessary for each group of slices by port map. for portMap, desiredEndpoints := range desiredEndpointsByPortMap { numEndpoints := len(desiredEndpoints) - pmSlicesToCreate, pmSlicesToUpdate, pmSliceNamesToDelete, added, removed := r.reconcileByPortMapping( + pmSlicesToCreate, pmSlicesToUpdate, pmSlicesToDelete, added, removed := r.reconcileByPortMapping( service, existingSlicesByPortMap[portMap], desiredEndpoints, desiredMetaByPortMap[portMap]) totalAdded += added @@ -115,7 +124,7 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis spMetrics.Set(portMap, metrics.EfficiencyInfo{ Endpoints: numEndpoints, - Slices: len(existingSlicesByPortMap[portMap]) + len(pmSlicesToCreate) - len(pmSliceNamesToDelete), + Slices: len(existingSlicesByPortMap[portMap]) + len(pmSlicesToCreate) - len(pmSlicesToDelete), }) if len(pmSlicesToCreate) > 0 { @@ -124,8 +133,8 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis if len(pmSlicesToUpdate) > 0 { slicesToUpdate = append(slicesToUpdate, pmSlicesToUpdate...) } - if pmSliceNamesToDelete.Len() > 0 { - sliceNamesToDelete = sliceNamesToDelete.Union(pmSliceNamesToDelete) + if len(pmSlicesToDelete) > 0 { + slicesToDelete = append(slicesToDelete, pmSlicesToDelete...) } } @@ -134,14 +143,14 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis for portMap, existingSlices := range existingSlicesByPortMap { if _, ok := desiredEndpointsByPortMap[portMap]; !ok { for _, existingSlice := range existingSlices { - sliceNamesToDelete.Insert(existingSlice.Name) + slicesToDelete = append(slicesToDelete, existingSlice) } } } // When no endpoint slices would usually exist, we need to add a placeholder. - if len(existingSlices) == sliceNamesToDelete.Len() && len(slicesToCreate) < 1 { - placeholderSlice := newEndpointSlice(service, &endpointMeta{Ports: []discovery.EndpointPort{}}) + if len(existingSlices) == len(slicesToDelete) && len(slicesToCreate) < 1 { + placeholderSlice := newEndpointSlice(service, &endpointMeta{Ports: []discovery.EndpointPort{}, AddressType: addressType}) slicesToCreate = append(slicesToCreate, placeholderSlice) spMetrics.Set(endpointutil.NewPortMapKey(placeholderSlice.Ports), metrics.EfficiencyInfo{ Endpoints: 0, @@ -155,27 +164,41 @@ func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, exis serviceNN := types.NamespacedName{Name: service.Name, Namespace: service.Namespace} r.metricsCache.UpdateServicePortCache(serviceNN, spMetrics) - return r.finalize(service, slicesToCreate, slicesToUpdate, sliceNamesToDelete, triggerTime) + return r.finalize(service, slicesToCreate, slicesToUpdate, slicesToDelete, triggerTime) } // finalize creates, updates, and deletes slices as specified func (r *reconciler) finalize( service *corev1.Service, slicesToCreate, - slicesToUpdate []*discovery.EndpointSlice, - sliceNamesToDelete sets.String, + slicesToUpdate, + slicesToDelete []*discovery.EndpointSlice, triggerTime time.Time, ) error { errs := []error{} // If there are slices to create and delete, change the creates to updates // of the slices that would otherwise be deleted. - for len(slicesToCreate) > 0 && sliceNamesToDelete.Len() > 0 { - sliceName, _ := sliceNamesToDelete.PopAny() + for i := 0; i < len(slicesToDelete); { + if len(slicesToCreate) == 0 { + break + } + sliceToDelete := slicesToDelete[i] slice := slicesToCreate[len(slicesToCreate)-1] - slicesToCreate = slicesToCreate[:len(slicesToCreate)-1] - slice.Name = sliceName - slicesToUpdate = append(slicesToUpdate, slice) + // Only update EndpointSlices that have the same AddressType as this + // field is considered immutable. Since Services also consider IPFamily + // immutable, the only case where this should matter will be the + // migration from IP to IPv4 and IPv6 AddressTypes, where there's a + // chance EndpointSlices with an IP AddressType would otherwise be + // updated to IPv4 or IPv6 without this check. + if sliceToDelete.AddressType == slice.AddressType { + slice.Name = sliceToDelete.Name + slicesToCreate = slicesToCreate[:len(slicesToCreate)-1] + slicesToUpdate = append(slicesToUpdate, slice) + slicesToDelete = append(slicesToDelete[:i], slicesToDelete[i+1:]...) + } else { + i++ + } } for _, endpointSlice := range slicesToCreate { @@ -202,11 +225,10 @@ func (r *reconciler) finalize( } } - for sliceNamesToDelete.Len() > 0 { - sliceName, _ := sliceNamesToDelete.PopAny() - err := r.client.DiscoveryV1alpha1().EndpointSlices(service.Namespace).Delete(sliceName, &metav1.DeleteOptions{}) + for _, endpointSlice := range slicesToDelete { + err := r.client.DiscoveryV1alpha1().EndpointSlices(service.Namespace).Delete(endpointSlice.Name, &metav1.DeleteOptions{}) if err != nil { - errs = append(errs, fmt.Errorf("Error deleting %s EndpointSlice for Service %s/%s: %v", sliceName, service.Namespace, service.Name, err)) + errs = append(errs, fmt.Errorf("Error deleting %s EndpointSlice for Service %s/%s: %v", endpointSlice.Name, service.Namespace, service.Name, err)) } else { metrics.EndpointSliceChanges.WithLabelValues("delete").Inc() } @@ -229,7 +251,7 @@ func (r *reconciler) reconcileByPortMapping( existingSlices []*discovery.EndpointSlice, desiredSet endpointSet, endpointMeta *endpointMeta, -) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice, sets.String, int, int) { +) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice, []*discovery.EndpointSlice, int, int) { slicesByName := map[string]*discovery.EndpointSlice{} sliceNamesUnchanged := sets.String{} sliceNamesToUpdate := sets.String{} @@ -345,7 +367,13 @@ func (r *reconciler) reconcileByPortMapping( slicesToUpdate = append(slicesToUpdate, slicesByName[sliceName]) } - return slicesToCreate, slicesToUpdate, sliceNamesToDelete, numAdded, numRemoved + // Build slicesToDelete from slice names. + slicesToDelete := []*discovery.EndpointSlice{} + for _, sliceName := range sliceNamesToDelete.UnsortedList() { + slicesToDelete = append(slicesToDelete, slicesByName[sliceName]) + } + + return slicesToCreate, slicesToUpdate, slicesToDelete, numAdded, numRemoved } func (r *reconciler) deleteService(namespace, name string) { diff --git a/pkg/controller/endpointslice/reconciler_test.go b/pkg/controller/endpointslice/reconciler_test.go index 40c48a9238c..babbc02a9bb 100644 --- a/pkg/controller/endpointslice/reconciler_test.go +++ b/pkg/controller/endpointslice/reconciler_test.go @@ -18,6 +18,8 @@ package endpointslice import ( "fmt" + "reflect" + "strings" "testing" "time" @@ -65,11 +67,14 @@ func TestReconcileEmpty(t *testing.T) { // Given a single pod matching a service selector and no existing endpoint slices, // a slice should be created func TestReconcile1Pod(t *testing.T) { - client := newClientset() - setupMetrics() namespace := "test" - svc, _ := newServiceAndEndpointMeta("foo", namespace) + ipv6Family := corev1.IPv6Protocol + svcv4, _ := newServiceAndEndpointMeta("foo", namespace) + svcv6, _ := newServiceAndEndpointMeta("foo", namespace) + svcv6.Spec.IPFamily = &ipv6Family + pod1 := newPod(1, namespace, true, 1) + pod1.Status.PodIPs = []corev1.PodIP{{IP: "1.2.3.4"}, {IP: "1234::5678:0000:0000:9abc:def0"}} pod1.Spec.Hostname = "example-hostname" node1 := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ @@ -81,33 +86,92 @@ func TestReconcile1Pod(t *testing.T) { }, } - triggerTime := time.Now() - r := newReconciler(client, []*corev1.Node{node1}, defaultMaxEndpointsPerSlice) - reconcileHelper(t, r, &svc, []*corev1.Pod{pod1}, []*discovery.EndpointSlice{}, triggerTime) - assert.Len(t, client.Actions(), 1, "Expected 1 additional clientset action") + testCases := map[string]struct { + service corev1.Service + expectedAddressType discovery.AddressType + expectedEndpoint discovery.Endpoint + }{ + "ipv4": { + service: svcv4, + expectedAddressType: discovery.AddressTypeIPv4, + expectedEndpoint: discovery.Endpoint{ + Addresses: []string{"1.2.3.4"}, + Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, + Topology: map[string]string{ + "kubernetes.io/hostname": "node-1", + "topology.kubernetes.io/zone": "us-central1-a", + "topology.kubernetes.io/region": "us-central1", + }, + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Namespace: namespace, + Name: "pod1", + }, + }, + }, + "ipv6": { + service: svcv6, + expectedAddressType: discovery.AddressTypeIPv6, + expectedEndpoint: discovery.Endpoint{ + Addresses: []string{"1234::5678:0000:0000:9abc:def0"}, + Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, + Topology: map[string]string{ + "kubernetes.io/hostname": "node-1", + "topology.kubernetes.io/zone": "us-central1-a", + "topology.kubernetes.io/region": "us-central1", + }, + TargetRef: &corev1.ObjectReference{ + Kind: "Pod", + Namespace: namespace, + Name: "pod1", + }, + }, + }, + } - slices := fetchEndpointSlices(t, client, namespace) - assert.Len(t, slices, 1, "Expected 1 endpoint slices") - assert.Regexp(t, "^"+svc.Name, slices[0].Name) - assert.Equal(t, svc.Name, slices[0].Labels[discovery.LabelServiceName]) - assert.Equal(t, slices[0].Annotations, map[string]string{ - "endpoints.kubernetes.io/last-change-trigger-time": triggerTime.Format(time.RFC3339Nano), - }) - assert.EqualValues(t, []discovery.Endpoint{{ - Addresses: []string{"1.2.3.5"}, - Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, - Topology: map[string]string{ - "kubernetes.io/hostname": "node-1", - "topology.kubernetes.io/zone": "us-central1-a", - "topology.kubernetes.io/region": "us-central1", - }, - TargetRef: &corev1.ObjectReference{ - Kind: "Pod", - Namespace: namespace, - Name: "pod1", - }, - }}, slices[0].Endpoints) - expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 1, addedPerSync: 1, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0}) + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + client := newClientset() + setupMetrics() + triggerTime := time.Now() + r := newReconciler(client, []*corev1.Node{node1}, defaultMaxEndpointsPerSlice) + reconcileHelper(t, r, &testCase.service, []*corev1.Pod{pod1}, []*discovery.EndpointSlice{}, triggerTime) + + if len(client.Actions()) != 1 { + t.Errorf("Expected 1 clientset action, got %d", len(client.Actions())) + } + + slices := fetchEndpointSlices(t, client, namespace) + + if len(slices) != 1 { + t.Fatalf("Expected 1 EndpointSlice, got %d", len(slices)) + } + + slice := slices[0] + if !strings.HasPrefix(slice.Name, testCase.service.Name) { + t.Errorf("Expected EndpointSlice name to start with %s, got %s", testCase.service.Name, slice.Name) + } + + if slice.Labels[discovery.LabelServiceName] != testCase.service.Name { + t.Errorf("Expected EndpointSlice to have label set with %s value, got %s", testCase.service.Name, slice.Labels[discovery.LabelServiceName]) + } + + if slice.Annotations[corev1.EndpointsLastChangeTriggerTime] != triggerTime.Format(time.RFC3339Nano) { + t.Errorf("Expected EndpointSlice trigger time annotation to be %s, got %s", triggerTime.Format(time.RFC3339Nano), slice.Annotations[corev1.EndpointsLastChangeTriggerTime]) + } + + if len(slice.Endpoints) != 1 { + t.Fatalf("Expected 1 Endpoint, got %d", len(slice.Endpoints)) + } + + endpoint := slice.Endpoints[0] + if !reflect.DeepEqual(endpoint, testCase.expectedEndpoint) { + t.Errorf("Expected endpoint: %+v, got: %+v", testCase.expectedEndpoint, endpoint) + } + + expectMetrics(t, expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 1, addedPerSync: 1, removedPerSync: 0, numCreated: 1, numUpdated: 0, numDeleted: 0}) + }) + } } // given an existing endpoint slice and no pods matching the service, the existing @@ -437,6 +501,56 @@ func TestReconcileEndpointSlicesUpdatePacking(t *testing.T) { expectUnorderedSlicesWithLengths(t, fetchEndpointSlices(t, client, namespace), []int{95, 20}) } +// In this test, we want to verify that old EndpointSlices with a deprecated IP +// address type will be replaced with a newer IPv4 type. +func TestReconcileEndpointSlicesReplaceDeprecated(t *testing.T) { + client := newClientset() + setupMetrics() + namespace := "test" + + svc, endpointMeta := newServiceAndEndpointMeta("foo", namespace) + endpointMeta.AddressType = discovery.AddressTypeIP + + existingSlices := []*discovery.EndpointSlice{} + pods := []*corev1.Pod{} + + slice1 := newEmptyEndpointSlice(1, namespace, endpointMeta, svc) + for i := 0; i < 80; i++ { + pod := newPod(i, namespace, true, 1) + slice1.Endpoints = append(slice1.Endpoints, podToEndpoint(pod, &corev1.Node{}, &corev1.Service{Spec: corev1.ServiceSpec{}})) + pods = append(pods, pod) + } + existingSlices = append(existingSlices, slice1) + + slice2 := newEmptyEndpointSlice(2, namespace, endpointMeta, svc) + for i := 100; i < 150; i++ { + pod := newPod(i, namespace, true, 1) + slice2.Endpoints = append(slice2.Endpoints, podToEndpoint(pod, &corev1.Node{}, &corev1.Service{Spec: corev1.ServiceSpec{}})) + pods = append(pods, pod) + } + existingSlices = append(existingSlices, slice2) + + createEndpointSlices(t, client, namespace, existingSlices) + + r := newReconciler(client, []*corev1.Node{{ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}}, defaultMaxEndpointsPerSlice) + reconcileHelper(t, r, &svc, pods, existingSlices, time.Now()) + + // ensure that both original endpoint slices have been deleted + expectActions(t, client.Actions(), 2, "delete", "endpointslices") + + endpointSlices := fetchEndpointSlices(t, client, namespace) + + // since this involved replacing both EndpointSlices, the result should be + // perfectly packed. + expectUnorderedSlicesWithLengths(t, endpointSlices, []int{100, 30}) + + for _, endpointSlice := range endpointSlices { + if endpointSlice.AddressType != discovery.AddressTypeIPv4 { + t.Errorf("Expected address type to be IPv4, got %s", endpointSlice.AddressType) + } + } +} + // Named ports can map to different port numbers on different pods. // This test ensures that EndpointSlices are grouped correctly in that case. func TestReconcileEndpointSlicesNamedPorts(t *testing.T) { @@ -489,7 +603,6 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) { // generate data structures for expected slice ports and address types protoTCP := corev1.ProtocolTCP - ipAddressType := discovery.AddressTypeIP expectedSlices := []discovery.EndpointSlice{} for i := range fetchedSlices { expectedSlices = append(expectedSlices, discovery.EndpointSlice{ @@ -498,7 +611,7 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) { Protocol: &protoTCP, Port: utilpointer.Int32Ptr(int32(8080 + i)), }}, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, }) } diff --git a/pkg/controller/endpointslice/utils.go b/pkg/controller/endpointslice/utils.go index bfc05eb53ac..e1e4b670e59 100644 --- a/pkg/controller/endpointslice/utils.go +++ b/pkg/controller/endpointslice/utils.go @@ -31,6 +31,7 @@ import ( api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/discovery/validation" endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint" + utilnet "k8s.io/utils/net" ) // podEndpointChanged returns true if the results of podToEndpoint are different @@ -70,7 +71,7 @@ func podToEndpoint(pod *corev1.Pod, node *corev1.Node, service *corev1.Service) ready := service.Spec.PublishNotReadyAddresses || podutil.IsPodReady(pod) ep := discovery.Endpoint{ - Addresses: getEndpointAddresses(pod.Status), + Addresses: getEndpointAddresses(pod.Status, service.Spec.IPFamily), Conditions: discovery.EndpointConditions{ Ready: &ready, }, @@ -124,16 +125,18 @@ func getEndpointPorts(service *corev1.Service, pod *corev1.Pod) []discovery.Endp } // getEndpointAddresses returns a list of addresses generated from a pod status. -func getEndpointAddresses(podStatus corev1.PodStatus) []string { - if len(podStatus.PodIPs) > 1 { - addresss := []string{} - for _, podIP := range podStatus.PodIPs { - addresss = append(addresss, podIP.IP) +func getEndpointAddresses(podStatus corev1.PodStatus, ipFamily *corev1.IPFamily) []string { + isIPv6Family := ipFamily != nil && *ipFamily == corev1.IPv6Protocol + addresses := []string{} + + for _, podIP := range podStatus.PodIPs { + isIPv6PodIP := utilnet.IsIPv6String(podIP.IP) + if isIPv6Family == isIPv6PodIP { + addresses = append(addresses, podIP.IP) } - return addresss } - return []string{podStatus.PodIP} + return addresses } // endpointsEqualBeyondHash returns true if endpoints have equal attributes diff --git a/pkg/controller/endpointslice/utils_test.go b/pkg/controller/endpointslice/utils_test.go index e7aa82c088b..ad0cb3ba69d 100644 --- a/pkg/controller/endpointslice/utils_test.go +++ b/pkg/controller/endpointslice/utils_test.go @@ -38,12 +38,12 @@ import ( ) func TestNewEndpointSlice(t *testing.T) { - ipAddressType := discovery.AddressTypeIP + ipAddressType := discovery.AddressTypeIPv4 portName := "foo" protocol := v1.ProtocolTCP endpointMeta := endpointMeta{ Ports: []discovery.EndpointPort{{Name: &portName, Protocol: &protocol}}, - AddressType: &ipAddressType, + AddressType: ipAddressType, } service := v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}, @@ -204,7 +204,7 @@ func TestPodToEndpoint(t *testing.T) { node: node1, svc: &svc, expectedEndpoint: discovery.Endpoint{ - Addresses: []string{"1.2.3.4", "1234::5678:0000:0000:9abc:def0"}, + Addresses: []string{"1.2.3.4"}, Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, Topology: map[string]string{ "kubernetes.io/hostname": "node-1", @@ -283,11 +283,11 @@ func TestPodChangedWithPodEndpointChanged(t *testing.T) { } newPod.ObjectMeta.ResourceVersion = oldPod.ObjectMeta.ResourceVersion - newPod.Status.PodIP = "1.2.3.1" + newPod.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.1"}} if !podChangedHelper(oldPod, newPod, podEndpointChanged) { t.Errorf("Expected pod to be changed with pod IP address change") } - newPod.Status.PodIP = oldPod.Status.PodIP + newPod.Status.PodIPs = oldPod.Status.PodIPs newPod.ObjectMeta.Name = "wrong-name" if !podChangedHelper(oldPod, newPod, podEndpointChanged) { @@ -333,6 +333,9 @@ func newPod(n int, namespace string, ready bool, nPorts int) *v1.Pod { }, Status: v1.PodStatus{ PodIP: fmt.Sprintf("1.2.3.%d", 4+n), + PodIPs: []v1.PodIP{{ + IP: fmt.Sprintf("1.2.3.%d", 4+n), + }}, Conditions: []v1.PodCondition{ { Type: v1.PodReady, @@ -381,10 +384,10 @@ func newServiceAndEndpointMeta(name, namespace string) (v1.Service, endpointMeta }, } - ipAddressType := discovery.AddressTypeIP + addressType := discovery.AddressTypeIPv4 protocol := v1.ProtocolTCP endpointMeta := endpointMeta{ - AddressType: &ipAddressType, + AddressType: addressType, Ports: []discovery.EndpointPort{{Name: &name, Port: &portNum, Protocol: &protocol}}, } diff --git a/pkg/master/reconcilers/BUILD b/pkg/master/reconcilers/BUILD index 1dd0d753966..b6cec8280fc 100644 --- a/pkg/master/reconcilers/BUILD +++ b/pkg/master/reconcilers/BUILD @@ -27,6 +27,7 @@ go_library( "//staging/src/k8s.io/client-go/kubernetes/typed/discovery/v1alpha1:go_default_library", "//staging/src/k8s.io/client-go/util/retry:go_default_library", "//vendor/k8s.io/klog:go_default_library", + "//vendor/k8s.io/utils/net:go_default_library", ], ) diff --git a/pkg/master/reconcilers/endpointsadapter.go b/pkg/master/reconcilers/endpointsadapter.go index 1b317443f83..deed9ff2e75 100644 --- a/pkg/master/reconcilers/endpointsadapter.go +++ b/pkg/master/reconcilers/endpointsadapter.go @@ -24,6 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" discoveryclient "k8s.io/client-go/kubernetes/typed/discovery/v1alpha1" + utilnet "k8s.io/utils/net" ) // EndpointsAdapter provides a simple interface for reading and writing both @@ -95,6 +96,16 @@ func (adapter *EndpointsAdapter) EnsureEndpointSliceFromEndpoints(namespace stri return err } + // required for transition from IP to IPv4 address type. + if currentEndpointSlice.AddressType != endpointSlice.AddressType { + err = adapter.endpointSliceClient.EndpointSlices(namespace).Delete(endpointSlice.Name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + _, err = adapter.endpointSliceClient.EndpointSlices(namespace).Create(endpointSlice) + return err + } + if apiequality.Semantic.DeepEqual(currentEndpointSlice.Endpoints, endpointSlice.Endpoints) && apiequality.Semantic.DeepEqual(currentEndpointSlice.Ports, endpointSlice.Ports) && apiequality.Semantic.DeepEqual(currentEndpointSlice.Labels, endpointSlice.Labels) { @@ -112,8 +123,9 @@ func endpointSliceFromEndpoints(endpoints *corev1.Endpoints) *discovery.Endpoint endpointSlice.Name = endpoints.Name endpointSlice.Labels = map[string]string{discovery.LabelServiceName: endpoints.Name} - ipAddressType := discovery.AddressTypeIP - endpointSlice.AddressType = &ipAddressType + // TODO: Add support for IPv6 addresses here (and in the rest of + // EndpointsAdapter). + endpointSlice.AddressType = discovery.AddressTypeIPv4 if len(endpoints.Subsets) > 0 { subset := endpoints.Subsets[0] @@ -124,17 +136,28 @@ func endpointSliceFromEndpoints(endpoints *corev1.Endpoints) *discovery.Endpoint Protocol: &subset.Ports[i].Protocol, }) } - for _, address := range subset.Addresses { - endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpointFromAddress(address, true)) - } - for _, address := range subset.NotReadyAddresses { - endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpointFromAddress(address, false)) - } + endpointSlice.Endpoints = append(endpointSlice.Endpoints, getEndpointsFromAddresses(subset.Addresses, endpointSlice.AddressType, true)...) + endpointSlice.Endpoints = append(endpointSlice.Endpoints, getEndpointsFromAddresses(subset.NotReadyAddresses, endpointSlice.AddressType, false)...) } return endpointSlice } +// getEndpointsFromAddresses returns a list of Endpoints from addresses that +// match the provided address type. +func getEndpointsFromAddresses(addresses []corev1.EndpointAddress, addressType discovery.AddressType, ready bool) []discovery.Endpoint { + endpoints := []discovery.Endpoint{} + isIPv6AddressType := addressType == discovery.AddressTypeIPv6 + + for _, address := range addresses { + if utilnet.IsIPv6String(address.IP) == isIPv6AddressType { + endpoints = append(endpoints, endpointFromAddress(address, ready)) + } + } + + return endpoints +} + // endpointFromAddress generates an Endpoint from an EndpointAddress resource. func endpointFromAddress(address corev1.EndpointAddress, ready bool) discovery.Endpoint { topology := map[string]string{} diff --git a/pkg/master/reconcilers/endpointsadapter_test.go b/pkg/master/reconcilers/endpointsadapter_test.go index 1a833055049..a418d2bd125 100644 --- a/pkg/master/reconcilers/endpointsadapter_test.go +++ b/pkg/master/reconcilers/endpointsadapter_test.go @@ -105,6 +105,11 @@ func TestEndpointsAdapterGet(t *testing.T) { func TestEndpointsAdapterCreate(t *testing.T) { endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"}) + // even if an Endpoints resource includes an IPv6 address, it should not be + // included in the corresponding EndpointSlice. + endpoints2, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6", "1234::5678:0000:0000:9abc:def0"}) + _, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6"}) + testCases := map[string]struct { endpointSlicesEnabled bool expectedError error @@ -124,6 +129,15 @@ func TestEndpointsAdapterCreate(t *testing.T) { namespaceParam: endpoints1.Namespace, endpointsParam: endpoints1, }, + "single-endpoint-with-ipv6": { + endpointSlicesEnabled: true, + expectedError: nil, + expectedEndpoints: endpoints2, + expectedEndpointSlice: epSlice2, + endpoints: []*corev1.Endpoints{}, + namespaceParam: endpoints2.Namespace, + endpointsParam: endpoints2, + }, "single-endpoint-no-slices": { endpointSlicesEnabled: false, expectedError: nil, @@ -199,6 +213,13 @@ func TestEndpointsAdapterUpdate(t *testing.T) { endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"}) endpoints3, _ := generateEndpointsAndSlice("bar", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"}) + // ensure that EndpointSlice with deprecated IP address type is replaced + // with one that has an IPv4 address type. + endpoints4, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) + _, epSlice4IP := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) + epSlice4IP.AddressType = discovery.AddressTypeIP + _, epSlice4IPv4 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) + testCases := map[string]struct { endpointSlicesEnabled bool expectedError error @@ -218,6 +239,16 @@ func TestEndpointsAdapterUpdate(t *testing.T) { namespaceParam: "testing", endpointsParam: endpoints1, }, + "existing-endpointslice-replaced-with-updated-ipv4-address-type": { + endpointSlicesEnabled: true, + expectedError: nil, + expectedEndpoints: endpoints4, + expectedEndpointSlice: epSlice4IPv4, + endpoints: []*corev1.Endpoints{endpoints4}, + endpointSlices: []*discovery.EndpointSlice{epSlice4IP}, + namespaceParam: "testing", + endpointsParam: endpoints4, + }, "add-ports-and-ips": { endpointSlicesEnabled: true, expectedError: nil, @@ -291,9 +322,8 @@ func TestEndpointsAdapterUpdate(t *testing.T) { func generateEndpointsAndSlice(name, namespace string, ports []int, addresses []string) (*corev1.Endpoints, *discovery.EndpointSlice) { objectMeta := metav1.ObjectMeta{Name: name, Namespace: namespace} trueBool := true - addressTypeIP := discovery.AddressTypeIP - epSlice := &discovery.EndpointSlice{ObjectMeta: objectMeta, AddressType: &addressTypeIP} + epSlice := &discovery.EndpointSlice{ObjectMeta: objectMeta, AddressType: discovery.AddressTypeIPv4} epSlice.Labels = map[string]string{discovery.LabelServiceName: name} subset := corev1.EndpointSubset{} diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 6b96b57d942..d244d40bcb1 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -1173,11 +1173,7 @@ func printEndpointSlice(obj *discovery.EndpointSlice, options printers.GenerateO row := metav1beta1.TableRow{ Object: runtime.RawExtension{Object: obj}, } - addressType := "" - if obj.AddressType != nil { - addressType = string(*obj.AddressType) - } - row.Cells = append(row.Cells, obj.Name, addressType, formatDiscoveryPorts(obj.Ports), formatDiscoveryEndpoints(obj.Endpoints), translateTimestampSince(obj.CreationTimestamp)) + row.Cells = append(row.Cells, obj.Name, string(obj.AddressType), formatDiscoveryPorts(obj.Ports), formatDiscoveryEndpoints(obj.Endpoints), translateTimestampSince(obj.CreationTimestamp)) return []metav1beta1.TableRow{row}, nil } diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 5d33a05c38f..ed6be0fc8ae 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -4736,7 +4736,6 @@ func TestPrintEndpoint(t *testing.T) { } func TestPrintEndpointSlice(t *testing.T) { - ipAddressType := discovery.AddressTypeIP tcpProtocol := api.ProtocolTCP tests := []struct { @@ -4749,7 +4748,7 @@ func TestPrintEndpointSlice(t *testing.T) { Name: "abcslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), @@ -4760,14 +4759,14 @@ func TestPrintEndpointSlice(t *testing.T) { }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age - expected: []metav1beta1.TableRow{{Cells: []interface{}{"abcslice.123", "IP", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}}, + expected: []metav1beta1.TableRow{{Cells: []interface{}{"abcslice.123", "IPv4", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}}, }, { endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "longerslicename.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv6, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), @@ -4784,14 +4783,14 @@ func TestPrintEndpointSlice(t *testing.T) { }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age - expected: []metav1beta1.TableRow{{Cells: []interface{}{"longerslicename.123", "IP", "80,443", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, + expected: []metav1beta1.TableRow{{Cells: []interface{}{"longerslicename.123", "IPv6", "80,443", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, }, { endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "multiportslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), @@ -4816,7 +4815,7 @@ func TestPrintEndpointSlice(t *testing.T) { }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age - expected: []metav1beta1.TableRow{{Cells: []interface{}{"multiportslice.123", "IP", "80,443,3000 + 1 more...", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, + expected: []metav1beta1.TableRow{{Cells: []interface{}{"multiportslice.123", "IPv4", "80,443,3000 + 1 more...", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, }, } diff --git a/pkg/proxy/endpointslicecache_test.go b/pkg/proxy/endpointslicecache_test.go index 655eab08c69..bfd38c8690c 100644 --- a/pkg/proxy/endpointslicecache_test.go +++ b/pkg/proxy/endpointslicecache_test.go @@ -336,7 +336,6 @@ func TestEsInfoChanged(t *testing.T) { } func generateEndpointSliceWithOffset(serviceName, namespace string, sliceNum, offset, numEndpoints, unreadyMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice { - ipAddressType := discovery.AddressTypeIP tcpProtocol := v1.ProtocolTCP endpointSlice := &discovery.EndpointSlice{ @@ -346,7 +345,7 @@ func generateEndpointSliceWithOffset(serviceName, namespace string, sliceNum, of Labels: map[string]string{discovery.LabelServiceName: serviceName}, }, Ports: []discovery.EndpointPort{}, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, Endpoints: []discovery.Endpoint{}, } diff --git a/pkg/proxy/iptables/proxier_test.go b/pkg/proxy/iptables/proxier_test.go index 02f483f09e9..b2cc8d30571 100644 --- a/pkg/proxy/iptables/proxier_test.go +++ b/pkg/proxy/iptables/proxier_test.go @@ -2390,7 +2390,6 @@ COMMIT }, }) - ipAddressType := discovery.AddressTypeIP tcpProtocol := v1.ProtocolTCP endpointSlice := &discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ @@ -2403,7 +2402,7 @@ COMMIT Port: utilpointer.Int32Ptr(80), Protocol: &tcpProtocol, }}, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, Endpoints: []discovery.Endpoint{{ Addresses: []string{"10.0.1.1"}, Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, diff --git a/pkg/proxy/ipvs/proxier_test.go b/pkg/proxy/ipvs/proxier_test.go index b6268eddd0a..322bc9e0040 100644 --- a/pkg/proxy/ipvs/proxier_test.go +++ b/pkg/proxy/ipvs/proxier_test.go @@ -3713,7 +3713,6 @@ func TestEndpointSliceE2E(t *testing.T) { }) // Add initial endpoint slice - ipAddressType := discovery.AddressTypeIP tcpProtocol := v1.ProtocolTCP endpointSlice := &discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ @@ -3726,7 +3725,7 @@ func TestEndpointSliceE2E(t *testing.T) { Port: utilpointer.Int32Ptr(80), Protocol: &tcpProtocol, }}, - AddressType: &ipAddressType, + AddressType: discovery.AddressTypeIPv4, Endpoints: []discovery.Endpoint{{ Addresses: []string{"10.0.1.1"}, Conditions: discovery.EndpointConditions{Ready: utilpointer.BoolPtr(true)}, diff --git a/pkg/registry/discovery/endpointslice/strategy.go b/pkg/registry/discovery/endpointslice/strategy.go index 098942d0092..ee50a67063a 100644 --- a/pkg/registry/discovery/endpointslice/strategy.go +++ b/pkg/registry/discovery/endpointslice/strategy.go @@ -72,7 +72,7 @@ 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.ValidateEndpointSlice(endpointSlice) + err := validation.ValidateEndpointSliceCreate(endpointSlice) return err } diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/generated.pb.go b/staging/src/k8s.io/api/discovery/v1alpha1/generated.pb.go index 917e2e4141a..fa4d3ac5042 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/generated.pb.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/generated.pb.go @@ -200,54 +200,54 @@ func init() { } var fileDescriptor_772f83c5b34e07a5 = []byte{ - // 744 bytes of a gzipped FileDescriptorProto + // 746 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4b, 0x6f, 0xd3, 0x4a, 0x14, 0x8e, 0x9b, 0x5a, 0xb2, 0x27, 0x8d, 0x6e, 0x3b, 0xba, 0x8b, 0x28, 0xf7, 0x5e, 0x3b, 0xca, - 0x5d, 0x10, 0xa9, 0x30, 0x26, 0x15, 0x45, 0x15, 0x6c, 0xa8, 0x51, 0x79, 0x48, 0x3c, 0xc2, 0xd0, - 0x05, 0x42, 0x2c, 0x98, 0xd8, 0x53, 0xc7, 0x24, 0xf1, 0x58, 0xf6, 0x24, 0x52, 0x76, 0xfc, 0x04, - 0x7e, 0x10, 0x4b, 0x84, 0xba, 0xec, 0xb2, 0x2b, 0x43, 0xcd, 0xbf, 0xe8, 0x0a, 0xcd, 0xf8, 0x95, - 0x12, 0x1e, 0xd9, 0xcd, 0x7c, 0x73, 0xbe, 0xef, 0x9c, 0xf3, 0xcd, 0x39, 0xe0, 0xc1, 0xf8, 0x20, - 0x46, 0x3e, 0xb3, 0xc6, 0xb3, 0x21, 0x8d, 0x02, 0xca, 0x69, 0x6c, 0xcd, 0x69, 0xe0, 0xb2, 0xc8, - 0xca, 0x1f, 0x48, 0xe8, 0x5b, 0xae, 0x1f, 0x3b, 0x6c, 0x4e, 0xa3, 0x85, 0x35, 0xef, 0x93, 0x49, - 0x38, 0x22, 0x7d, 0xcb, 0xa3, 0x01, 0x8d, 0x08, 0xa7, 0x2e, 0x0a, 0x23, 0xc6, 0x19, 0xfc, 0x2f, - 0x0b, 0x47, 0x24, 0xf4, 0x51, 0x19, 0x8e, 0x8a, 0xf0, 0xf6, 0x0d, 0xcf, 0xe7, 0xa3, 0xd9, 0x10, - 0x39, 0x6c, 0x6a, 0x79, 0xcc, 0x63, 0x96, 0x64, 0x0d, 0x67, 0x27, 0xf2, 0x26, 0x2f, 0xf2, 0x94, - 0xa9, 0xb5, 0xbb, 0x4b, 0xc9, 0x1d, 0x16, 0x51, 0x6b, 0xbe, 0x92, 0xb1, 0x7d, 0xab, 0x8a, 0x99, - 0x12, 0x67, 0xe4, 0x07, 0xa2, 0xbe, 0x70, 0xec, 0x09, 0x20, 0xb6, 0xa6, 0x94, 0x93, 0x9f, 0xb1, - 0xac, 0x5f, 0xb1, 0xa2, 0x59, 0xc0, 0xfd, 0x29, 0x5d, 0x21, 0xdc, 0xfe, 0x13, 0x21, 0x76, 0x46, - 0x74, 0x4a, 0x7e, 0xe4, 0x75, 0x3f, 0xd6, 0x81, 0x76, 0x14, 0xb8, 0x21, 0xf3, 0x03, 0x0e, 0x77, - 0x81, 0x4e, 0x5c, 0x37, 0xa2, 0x71, 0x4c, 0xe3, 0x96, 0xd2, 0xa9, 0xf7, 0x74, 0xbb, 0x99, 0x26, - 0xa6, 0x7e, 0x58, 0x80, 0xb8, 0x7a, 0x87, 0x14, 0x00, 0x87, 0x05, 0xae, 0xcf, 0x7d, 0x16, 0xc4, - 0xad, 0x8d, 0x8e, 0xd2, 0x6b, 0xec, 0xf5, 0xd1, 0x6f, 0xfd, 0x45, 0x45, 0xa6, 0xfb, 0x25, 0xd1, - 0x86, 0xa7, 0x89, 0x59, 0x4b, 0x13, 0x13, 0x54, 0x18, 0x5e, 0x12, 0x86, 0x3d, 0xa0, 0x8d, 0x58, - 0xcc, 0x03, 0x32, 0xa5, 0xad, 0x7a, 0x47, 0xe9, 0xe9, 0xf6, 0x56, 0x9a, 0x98, 0xda, 0xa3, 0x1c, - 0xc3, 0xe5, 0x2b, 0x1c, 0x00, 0x9d, 0x93, 0xc8, 0xa3, 0x1c, 0xd3, 0x93, 0xd6, 0xa6, 0xac, 0xe7, - 0xff, 0xe5, 0x7a, 0xc4, 0x0f, 0xa1, 0x79, 0x1f, 0x3d, 0x1f, 0xbe, 0xa3, 0x8e, 0x08, 0xa2, 0x11, - 0x0d, 0x1c, 0x9a, 0xb5, 0x78, 0x5c, 0x30, 0x71, 0x25, 0x02, 0x1d, 0xa0, 0x71, 0x16, 0xb2, 0x09, - 0xf3, 0x16, 0x2d, 0xb5, 0x53, 0xef, 0x35, 0xf6, 0xf6, 0xd7, 0x6c, 0x10, 0x1d, 0xe7, 0xbc, 0xa3, - 0x80, 0x47, 0x0b, 0x7b, 0x3b, 0x6f, 0x52, 0x2b, 0x60, 0x5c, 0x0a, 0xb7, 0xef, 0x82, 0xe6, 0x95, - 0x60, 0xb8, 0x0d, 0xea, 0x63, 0xba, 0x68, 0x29, 0xa2, 0x59, 0x2c, 0x8e, 0xf0, 0x6f, 0xa0, 0xce, - 0xc9, 0x64, 0x46, 0xa5, 0xcb, 0x3a, 0xce, 0x2e, 0x77, 0x36, 0x0e, 0x94, 0xee, 0x3e, 0x80, 0xab, - 0x9e, 0x42, 0x13, 0xa8, 0x11, 0x25, 0x6e, 0xa6, 0xa1, 0xd9, 0x7a, 0x9a, 0x98, 0x2a, 0x16, 0x00, - 0xce, 0xf0, 0xee, 0x67, 0x05, 0x6c, 0x15, 0xbc, 0x01, 0x8b, 0x38, 0xfc, 0x17, 0x6c, 0x4a, 0x87, - 0x65, 0x52, 0x5b, 0x4b, 0x13, 0x73, 0xf3, 0x99, 0x70, 0x57, 0xa2, 0xf0, 0x21, 0xd0, 0xe4, 0xb4, - 0x38, 0x6c, 0x92, 0x95, 0x60, 0xef, 0x8a, 0x66, 0x06, 0x39, 0x76, 0x99, 0x98, 0xff, 0xac, 0x6e, - 0x02, 0x2a, 0x9e, 0x71, 0x49, 0x16, 0x69, 0x42, 0x16, 0x71, 0xf9, 0x91, 0x6a, 0x96, 0x46, 0xa4, - 0xc7, 0x12, 0x85, 0x7d, 0xd0, 0x20, 0x61, 0x58, 0xd0, 0xe4, 0x17, 0xea, 0xf6, 0x5f, 0x69, 0x62, - 0x36, 0x0e, 0x2b, 0x18, 0x2f, 0xc7, 0x74, 0xbf, 0x6c, 0x80, 0x66, 0xd1, 0xc8, 0xcb, 0x89, 0xef, - 0x50, 0xf8, 0x16, 0x68, 0x62, 0xa9, 0x5c, 0xc2, 0x89, 0xec, 0xa6, 0xb1, 0x77, 0x73, 0xe9, 0xcf, - 0xca, 0xdd, 0x40, 0xe1, 0xd8, 0x13, 0x40, 0x8c, 0x44, 0x74, 0x35, 0x16, 0x4f, 0x29, 0x27, 0xd5, - 0x4c, 0x56, 0x18, 0x2e, 0x55, 0xe1, 0x3d, 0xd0, 0xc8, 0xb7, 0xe0, 0x78, 0x11, 0xd2, 0xbc, 0x4c, - 0x43, 0x96, 0x59, 0xc1, 0x97, 0x57, 0xaf, 0x78, 0x99, 0x02, 0x5f, 0x01, 0x9d, 0xe6, 0x45, 0x8b, - 0xcd, 0x11, 0x83, 0x75, 0x6d, 0xcd, 0xc1, 0xb2, 0x77, 0xf2, 0xda, 0xf4, 0x02, 0x89, 0x71, 0x25, - 0x06, 0x07, 0x40, 0x15, 0x56, 0xc6, 0xad, 0xba, 0x54, 0xdd, 0x5d, 0x53, 0x55, 0x7c, 0x82, 0xdd, - 0xcc, 0x95, 0x55, 0x71, 0x8b, 0x71, 0x26, 0xd4, 0xfd, 0xa4, 0x80, 0x9d, 0x2b, 0x0e, 0x3f, 0xf1, - 0x63, 0x0e, 0xdf, 0xac, 0xb8, 0x8c, 0xd6, 0x73, 0x59, 0xb0, 0xa5, 0xc7, 0xe5, 0x4a, 0x14, 0xc8, - 0x92, 0xc3, 0x2f, 0x80, 0xea, 0x73, 0x3a, 0x2d, 0xbc, 0xb9, 0xbe, 0x66, 0x17, 0xb2, 0xbc, 0xaa, - 0x8d, 0xc7, 0x42, 0x02, 0x67, 0x4a, 0x36, 0x3a, 0xbd, 0x30, 0x6a, 0x67, 0x17, 0x46, 0xed, 0xfc, - 0xc2, 0xa8, 0xbd, 0x4f, 0x0d, 0xe5, 0x34, 0x35, 0x94, 0xb3, 0xd4, 0x50, 0xce, 0x53, 0x43, 0xf9, - 0x9a, 0x1a, 0xca, 0x87, 0x6f, 0x46, 0xed, 0xb5, 0x56, 0x68, 0x7e, 0x0f, 0x00, 0x00, 0xff, 0xff, - 0x99, 0xbb, 0x72, 0xd7, 0x71, 0x06, 0x00, 0x00, + 0x5d, 0x10, 0xa9, 0x30, 0x26, 0x15, 0x45, 0x15, 0xac, 0x6a, 0x28, 0x0f, 0x89, 0x47, 0x18, 0xba, + 0x40, 0x88, 0x05, 0x13, 0x7b, 0xea, 0x98, 0x24, 0x1e, 0xcb, 0x9e, 0x44, 0xca, 0x8e, 0x9f, 0xc0, + 0x0f, 0x62, 0x89, 0x50, 0x97, 0x5d, 0x76, 0x65, 0x51, 0xf7, 0x5f, 0x74, 0x85, 0x66, 0xfc, 0x4a, + 0x09, 0x8f, 0xec, 0x66, 0xbe, 0x39, 0xdf, 0x77, 0xce, 0xf9, 0xe6, 0x1c, 0xf0, 0x68, 0x7c, 0x10, + 0x23, 0x9f, 0x59, 0xe3, 0xd9, 0x90, 0x46, 0x01, 0xe5, 0x34, 0xb6, 0xe6, 0x34, 0x70, 0x59, 0x64, + 0xe5, 0x0f, 0x24, 0xf4, 0x2d, 0xd7, 0x8f, 0x1d, 0x36, 0xa7, 0xd1, 0xc2, 0x9a, 0xf7, 0xc9, 0x24, + 0x1c, 0x91, 0xbe, 0xe5, 0xd1, 0x80, 0x46, 0x84, 0x53, 0x17, 0x85, 0x11, 0xe3, 0x0c, 0xfe, 0x97, + 0x85, 0x23, 0x12, 0xfa, 0xa8, 0x0c, 0x47, 0x45, 0x78, 0xfb, 0x96, 0xe7, 0xf3, 0xd1, 0x6c, 0x88, + 0x1c, 0x36, 0xb5, 0x3c, 0xe6, 0x31, 0x4b, 0xb2, 0x86, 0xb3, 0x13, 0x79, 0x93, 0x17, 0x79, 0xca, + 0xd4, 0xda, 0xdd, 0xa5, 0xe4, 0x0e, 0x8b, 0xa8, 0x35, 0x5f, 0xc9, 0xd8, 0xbe, 0x53, 0xc5, 0x4c, + 0x89, 0x33, 0xf2, 0x03, 0x51, 0x5f, 0x38, 0xf6, 0x04, 0x10, 0x5b, 0x53, 0xca, 0xc9, 0xcf, 0x58, + 0xd6, 0xaf, 0x58, 0xd1, 0x2c, 0xe0, 0xfe, 0x94, 0xae, 0x10, 0xee, 0xfe, 0x89, 0x10, 0x3b, 0x23, + 0x3a, 0x25, 0x3f, 0xf2, 0xba, 0x9f, 0xeb, 0x40, 0x3b, 0x0a, 0xdc, 0x90, 0xf9, 0x01, 0x87, 0xbb, + 0x40, 0x27, 0xae, 0x1b, 0xd1, 0x38, 0xa6, 0x71, 0x4b, 0xe9, 0xd4, 0x7b, 0xba, 0xdd, 0x4c, 0x13, + 0x53, 0x3f, 0x2c, 0x40, 0x5c, 0xbd, 0x43, 0x0a, 0x80, 0xc3, 0x02, 0xd7, 0xe7, 0x3e, 0x0b, 0xe2, + 0xd6, 0x46, 0x47, 0xe9, 0x35, 0xf6, 0xfa, 0xe8, 0xb7, 0xfe, 0xa2, 0x22, 0xd3, 0x83, 0x92, 0x68, + 0xc3, 0xd3, 0xc4, 0xac, 0xa5, 0x89, 0x09, 0x2a, 0x0c, 0x2f, 0x09, 0xc3, 0x1e, 0xd0, 0x46, 0x2c, + 0xe6, 0x01, 0x99, 0xd2, 0x56, 0xbd, 0xa3, 0xf4, 0x74, 0x7b, 0x2b, 0x4d, 0x4c, 0xed, 0x49, 0x8e, + 0xe1, 0xf2, 0x15, 0x0e, 0x80, 0xce, 0x49, 0xe4, 0x51, 0x8e, 0xe9, 0x49, 0x6b, 0x53, 0xd6, 0xf3, + 0xff, 0x72, 0x3d, 0xe2, 0x87, 0xd0, 0xbc, 0x8f, 0x5e, 0x0e, 0x3f, 0x50, 0x47, 0x04, 0xd1, 0x88, + 0x06, 0x0e, 0xcd, 0x5a, 0x3c, 0x2e, 0x98, 0xb8, 0x12, 0x81, 0x0e, 0xd0, 0x38, 0x0b, 0xd9, 0x84, + 0x79, 0x8b, 0x96, 0xda, 0xa9, 0xf7, 0x1a, 0x7b, 0xfb, 0x6b, 0x36, 0x88, 0x8e, 0x73, 0xde, 0x51, + 0xc0, 0xa3, 0x85, 0xbd, 0x9d, 0x37, 0xa9, 0x15, 0x30, 0x2e, 0x85, 0xdb, 0xf7, 0x41, 0xf3, 0x5a, + 0x30, 0xdc, 0x06, 0xf5, 0x31, 0x5d, 0xb4, 0x14, 0xd1, 0x2c, 0x16, 0x47, 0xf8, 0x37, 0x50, 0xe7, + 0x64, 0x32, 0xa3, 0xd2, 0x65, 0x1d, 0x67, 0x97, 0x7b, 0x1b, 0x07, 0x4a, 0x77, 0x1f, 0xc0, 0x55, + 0x4f, 0xa1, 0x09, 0xd4, 0x88, 0x12, 0x37, 0xd3, 0xd0, 0x6c, 0x3d, 0x4d, 0x4c, 0x15, 0x0b, 0x00, + 0x67, 0x78, 0xf7, 0xab, 0x02, 0xb6, 0x0a, 0xde, 0x80, 0x45, 0x1c, 0xfe, 0x0b, 0x36, 0xa5, 0xc3, + 0x32, 0xa9, 0xad, 0xa5, 0x89, 0xb9, 0xf9, 0x42, 0xb8, 0x2b, 0x51, 0xf8, 0x18, 0x68, 0x72, 0x5a, + 0x1c, 0x36, 0xc9, 0x4a, 0xb0, 0x77, 0x45, 0x33, 0x83, 0x1c, 0xbb, 0x4a, 0xcc, 0x7f, 0x56, 0x37, + 0x01, 0x15, 0xcf, 0xb8, 0x24, 0x8b, 0x34, 0x21, 0x8b, 0xb8, 0xfc, 0x48, 0x35, 0x4b, 0x23, 0xd2, + 0x63, 0x89, 0xc2, 0x3e, 0x68, 0x90, 0x30, 0x2c, 0x68, 0xf2, 0x0b, 0x75, 0xfb, 0xaf, 0x34, 0x31, + 0x1b, 0x87, 0x15, 0x8c, 0x97, 0x63, 0xba, 0x97, 0x1b, 0xa0, 0x59, 0x34, 0xf2, 0x7a, 0xe2, 0x3b, + 0x14, 0xbe, 0x07, 0x9a, 0x58, 0x2a, 0x97, 0x70, 0x22, 0xbb, 0x69, 0xec, 0xdd, 0x5e, 0xfa, 0xb3, + 0x72, 0x37, 0x50, 0x38, 0xf6, 0x04, 0x10, 0x23, 0x11, 0x5d, 0x8d, 0xc5, 0x73, 0xca, 0x49, 0x35, + 0x93, 0x15, 0x86, 0x4b, 0x55, 0xf8, 0x10, 0x34, 0xf2, 0x2d, 0x38, 0x5e, 0x84, 0x34, 0x2f, 0xb3, + 0x9b, 0x53, 0x1a, 0x87, 0xd5, 0xd3, 0xd5, 0xf5, 0x2b, 0x5e, 0xa6, 0xc1, 0x37, 0x40, 0xa7, 0x79, + 0xe1, 0x62, 0x7b, 0xc4, 0x70, 0xdd, 0x58, 0x73, 0xb8, 0xec, 0x9d, 0x3c, 0x99, 0x5e, 0x20, 0x31, + 0xae, 0xc4, 0xe0, 0x00, 0xa8, 0xc2, 0xce, 0xb8, 0x55, 0x97, 0xaa, 0xbb, 0x6b, 0xaa, 0x8a, 0x8f, + 0xb0, 0x9b, 0xb9, 0xb2, 0x2a, 0x6e, 0x31, 0xce, 0x84, 0xba, 0x5f, 0x14, 0xb0, 0x73, 0xcd, 0xe5, + 0x67, 0x7e, 0xcc, 0xe1, 0xbb, 0x15, 0xa7, 0xd1, 0x7a, 0x4e, 0x0b, 0xb6, 0xf4, 0xb9, 0x5c, 0x8b, + 0x02, 0x59, 0x72, 0xf9, 0x15, 0x50, 0x7d, 0x4e, 0xa7, 0x85, 0x37, 0x37, 0xd7, 0xec, 0x42, 0x96, + 0x57, 0xb5, 0xf1, 0x54, 0x48, 0xe0, 0x4c, 0xc9, 0x46, 0xa7, 0x17, 0x46, 0xed, 0xec, 0xc2, 0xa8, + 0x9d, 0x5f, 0x18, 0xb5, 0x8f, 0xa9, 0xa1, 0x9c, 0xa6, 0x86, 0x72, 0x96, 0x1a, 0xca, 0x79, 0x6a, + 0x28, 0xdf, 0x52, 0x43, 0xf9, 0x74, 0x69, 0xd4, 0xde, 0x6a, 0x85, 0xe6, 0xf7, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x65, 0x85, 0x5a, 0x9b, 0x75, 0x06, 0x00, 0x00, } func (m *Endpoint) Marshal() (dAtA []byte, err error) { @@ -437,13 +437,11 @@ func (m *EndpointSlice) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.AddressType != nil { - i -= len(*m.AddressType) - copy(dAtA[i:], *m.AddressType) - i = encodeVarintGenerated(dAtA, i, uint64(len(*m.AddressType))) - i-- - dAtA[i] = 0x22 - } + i -= len(m.AddressType) + copy(dAtA[i:], m.AddressType) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AddressType))) + i-- + dAtA[i] = 0x22 if len(m.Ports) > 0 { for iNdEx := len(m.Ports) - 1; iNdEx >= 0; iNdEx-- { { @@ -632,10 +630,8 @@ func (m *EndpointSlice) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) } } - if m.AddressType != nil { - l = len(*m.AddressType) - n += 1 + l + sovGenerated(uint64(l)) - } + l = len(m.AddressType) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -727,7 +723,7 @@ func (this *EndpointSlice) String() string { `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v11.ObjectMeta", 1), `&`, ``, 1) + `,`, `Endpoints:` + repeatedStringForEndpoints + `,`, `Ports:` + repeatedStringForPorts + `,`, - `AddressType:` + valueToStringGenerated(this.AddressType) + `,`, + `AddressType:` + fmt.Sprintf("%v", this.AddressType) + `,`, `}`, }, "") return s @@ -1476,8 +1472,7 @@ func (m *EndpointSlice) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - s := AddressType(dAtA[iNdEx:postIndex]) - m.AddressType = &s + m.AddressType = AddressType(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto b/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto index 69093655e05..62074e7a7b4 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1alpha1/generated.proto @@ -32,12 +32,10 @@ option go_package = "v1alpha1"; // Endpoint represents a single logical "backend" implementing a service. message Endpoint { // addresses of this endpoint. The contents of this field are interpreted - // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack networking where both IPv4 and IPv6 - // addresses would be included with the IP addressType. Consumers (e.g. - // kube-proxy) must handle different types of addresses in the context of - // their own capabilities. This must contain at least one address but no - // more than 100. + // according to the corresponding EndpointSlice addressType field. Consumers + // must handle different types of addresses in the context of their own + // capabilities. This must contain at least one address but no more than + // 100. // +listType=set repeated string addresses = 1; @@ -123,13 +121,12 @@ message EndpointSlice { optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. The following address - // types are currently supported: - // * IP: Represents an IP Address. This can include both IPv4 and IPv6 - // addresses. + // 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. - // Default is IP - // +optional optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/types.go b/staging/src/k8s.io/api/discovery/v1alpha1/types.go index 4452dd4f614..fff30b5c7a0 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/types.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/types.go @@ -33,14 +33,13 @@ type EndpointSlice struct { // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // addressType specifies the type of address carried by this EndpointSlice. - // All addresses in this slice must be the same type. The following address - // types are currently supported: - // * IP: Represents an IP Address. This can include both IPv4 and IPv6 - // addresses. + // 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. - // Default is IP - // +optional - AddressType *AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` + AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may // include a maximum of 1000 endpoints. // +listType=atomic @@ -59,22 +58,27 @@ type EndpointSlice struct { type AddressType string const ( - // AddressTypeIP represents an IP Address. Inclusive of IPv4 and IPv6 - // addresses. + // AddressTypeIP represents an IP Address. + // This address type has been deprecated and has been replaced by the IPv4 + // and IPv6 adddress types. New resources with this address type will be + // considered invalid. This will be fully removed in 1.18. + // +deprecated AddressTypeIP = AddressType("IP") - // AddressTypeFQDN represents a Fully Qualified Domain Name. + // AddressTypeIPv4 represents an IPv4 Address. + AddressTypeIPv4 = AddressType(v1.IPv4Protocol) + // AddressTypeIPv6 represents an IPv6 Address. + AddressTypeIPv6 = AddressType(v1.IPv6Protocol) + // AddressTypeFQDN represents a FQDN. AddressTypeFQDN = AddressType("FQDN") ) // Endpoint represents a single logical "backend" implementing a service. type Endpoint struct { // addresses of this endpoint. The contents of this field are interpreted - // according to the corresponding EndpointSlice addressType field. This - // allows for cases like dual-stack networking where both IPv4 and IPv6 - // addresses would be included with the IP addressType. Consumers (e.g. - // kube-proxy) must handle different types of addresses in the context of - // their own capabilities. This must contain at least one address but no - // more than 100. + // according to the corresponding EndpointSlice addressType field. Consumers + // must handle different types of addresses in the context of their own + // capabilities. This must contain at least one address but no more than + // 100. // +listType=set Addresses []string `json:"addresses" protobuf:"bytes,1,rep,name=addresses"` // conditions contains information about the current status of the endpoint. diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go index 226f9528cc1..1ba2d60d4a8 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/types_swagger_doc_generated.go @@ -29,7 +29,7 @@ package v1alpha1 // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_Endpoint = map[string]string{ "": "Endpoint represents a single logical \"backend\" implementing a service.", - "addresses": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. This allows for cases like dual-stack networking where both IPv4 and IPv6 addresses would be included with the IP addressType. Consumers (e.g. kube-proxy) must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", + "addresses": "addresses of this endpoint. The contents of this field are interpreted according to the corresponding EndpointSlice addressType field. Consumers must handle different types of addresses in the context of their own capabilities. This must contain at least one address but no more than 100.", "conditions": "conditions contains information about the current status of the endpoint.", "hostname": "hostname of this endpoint. This field may be used by consumers of endpoints to distinguish endpoints from each other (e.g. in DNS names). Multiple endpoints which use the same hostname should be considered fungible (e.g. multiple A values in DNS). Must pass DNS Label (RFC 1123) validation.", "targetRef": "targetRef is a reference to a Kubernetes object that represents this endpoint.", @@ -64,7 +64,7 @@ func (EndpointPort) SwaggerDoc() map[string]string { var map_EndpointSlice = map[string]string{ "": "EndpointSlice represents a subset of the endpoints that implement a service. For a given service there may be multiple EndpointSlice objects, selected by labels, which must be joined to produce the full set of endpoints.", "metadata": "Standard object's metadata.", - "addressType": "addressType specifies the type of address carried by this EndpointSlice. All addresses in this slice must be the same type. The following address types are currently supported: * IP: Represents an IP Address. This can include both IPv4 and IPv6\n addresses.\n* FQDN: Represents a Fully Qualified Domain Name. Default is IP", + "addressType": "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.", "endpoints": "endpoints is a list of unique endpoints in this slice. Each slice may include a maximum of 1000 endpoints.", "ports": "ports specifies the list of network ports exposed by each endpoint in this slice. Each port must have a unique name. When ports is empty, it indicates that there are no defined ports. When a port is defined with a nil port value, it indicates \"all ports\". Each slice may include a maximum of 100 ports.", } diff --git a/staging/src/k8s.io/api/discovery/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/discovery/v1alpha1/zz_generated.deepcopy.go index de1eae5563c..c72f64acfd0 100644 --- a/staging/src/k8s.io/api/discovery/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/discovery/v1alpha1/zz_generated.deepcopy.go @@ -126,11 +126,6 @@ func (in *EndpointSlice) DeepCopyInto(out *EndpointSlice) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.AddressType != nil { - in, out := &in.AddressType, &out.AddressType - *out = new(AddressType) - **out = **in - } if in.Endpoints != nil { in, out := &in.Endpoints, &out.Endpoints *out = make([]Endpoint, len(*in)) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go index a336c2872d9..8e1907c2a69 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go @@ -309,6 +309,26 @@ func IsValidIP(value string) []string { return nil } +// IsValidIPv4Address tests that the argument is a valid IPv4 address. +func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList { + var allErrors field.ErrorList + ip := net.ParseIP(value) + if ip == nil || ip.To4() == nil { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address")) + } + return allErrors +} + +// IsValidIPv6Address tests that the argument is a valid IPv6 address. +func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList { + var allErrors field.ErrorList + ip := net.ParseIP(value) + if ip == nil || ip.To4() != nil { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address")) + } + return allErrors +} + const percentFmt string = "[0-9]+%" const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'" diff --git a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go index 4c02cba1d0a..48ccfa2663b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/validation/validation_test.go @@ -355,6 +355,75 @@ func TestIsValidIP(t *testing.T) { } } +func TestIsValidIPv4Address(t *testing.T) { + goodValues := []string{ + "1.1.1.1", + "1.1.1.01", + "255.0.0.1", + "1.0.0.0", + "0.0.0.0", + } + for _, val := range goodValues { + if msgs := IsValidIPv4Address(field.NewPath(""), val); len(msgs) != 0 { + t.Errorf("expected %q to be valid IPv4 address: %v", val, msgs) + } + } + + badValues := []string{ + "[2001:db8:0:1]:80", + "myhost.mydomain", + "-1.0.0.0", + "[2001:db8:0:1]", + "a", + "2001:4860:4860::8888", + "::fff:1.1.1.1", + "::1", + "2a00:79e0:2:0:f1c3:e797:93c1:df80", + "::", + } + for _, val := range badValues { + if msgs := IsValidIPv4Address(field.NewPath(""), val); len(msgs) == 0 { + t.Errorf("expected %q to be invalid IPv4 address", val) + } + } +} + +func TestIsValidIPv6Address(t *testing.T) { + goodValues := []string{ + "2001:4860:4860::8888", + "2a00:79e0:2:0:f1c3:e797:93c1:df80", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "::fff:1.1.1.1", + "::1", + "::", + } + + for _, val := range goodValues { + if msgs := IsValidIPv6Address(field.NewPath(""), val); len(msgs) != 0 { + t.Errorf("expected %q to be valid IPv6 address: %v", val, msgs) + } + } + + badValues := []string{ + "1.1.1.1", + "1.1.1.01", + "255.0.0.1", + "1.0.0.0", + "0.0.0.0", + "[2001:db8:0:1]:80", + "myhost.mydomain", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334:2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "-1.0.0.0", + "[2001:db8:0:1]", + "a", + } + for _, val := range badValues { + if msgs := IsValidIPv6Address(field.NewPath(""), val); len(msgs) == 0 { + t.Errorf("expected %q to be invalid IPv6 address", val) + } + } +} + func TestIsHTTPHeaderName(t *testing.T) { goodValues := []string{ // Common ones diff --git a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go index 315ac7426ff..8155aa1f670 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe.go @@ -2644,11 +2644,7 @@ func describeEndpointSlice(eps *discoveryv1alpha1.EndpointSlice, events *corev1. printLabelsMultiline(w, "Labels", eps.Labels) printAnnotationsMultiline(w, "Annotations", eps.Annotations) - addressType := "" - if eps.AddressType != nil { - addressType = string(*eps.AddressType) - } - w.Write(LEVEL_0, "AddressType:\t%s\n", addressType) + w.Write(LEVEL_0, "AddressType:\t%s\n", string(eps.AddressType)) if len(eps.Ports) == 0 { w.Write(LEVEL_0, "Ports: \n") diff --git a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go index 5236f4f2889..48c43f586ce 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/versioned/describe_test.go @@ -3516,7 +3516,6 @@ func TestDescribeStatefulSet(t *testing.T) { } func TestDescribeEndpointSlice(t *testing.T) { - addressTypeIP := discoveryv1alpha1.AddressTypeIP protocolTCP := corev1.ProtocolTCP port80 := int32(80) @@ -3525,7 +3524,7 @@ func TestDescribeEndpointSlice(t *testing.T) { Name: "foo.123", Namespace: "bar", }, - AddressType: &addressTypeIP, + AddressType: discoveryv1alpha1.AddressTypeIPv4, Endpoints: []discoveryv1alpha1.Endpoint{ { Addresses: []string{"1.2.3.4", "1.2.3.5"}, @@ -3564,7 +3563,7 @@ func TestDescribeEndpointSlice(t *testing.T) { Namespace: bar Labels: Annotations: -AddressType: IP +AddressType: IPv4 Ports: Name Port Protocol ---- ---- -------- diff --git a/test/integration/apiserver/admissionwebhook/admission_test.go b/test/integration/apiserver/admissionwebhook/admission_test.go index 93ba1ce409e..688dd6e3fff 100644 --- a/test/integration/apiserver/admissionwebhook/admission_test.go +++ b/test/integration/apiserver/admissionwebhook/admission_test.go @@ -1390,9 +1390,6 @@ func getStubObj(gvr schema.GroupVersionResource, resource metav1.APIResource) (* func createOrGetResource(client dynamic.Interface, gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) { stubObj, err := getStubObj(gvr, resource) - if gvr.Group == "discovery.k8s.io" { - fmt.Printf("stubObj =====> %v\n", stubObj) - } if err != nil { return nil, err } diff --git a/test/integration/etcd/data.go b/test/integration/etcd/data.go index 12ed41195f8..197df20eb56 100644 --- a/test/integration/etcd/data.go +++ b/test/integration/etcd/data.go @@ -239,7 +239,7 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes // k8s.io/kubernetes/pkg/apis/discovery/v1alpha1 gvr("discovery.k8s.io", "v1alpha1", "endpointslices"): { - Stub: `{"metadata": {"name": "slice1"}, "protocol": "TCP", "ports": [], "endpoints": []}`, + Stub: `{"metadata": {"name": "slice1"}, "addressType": "IPv4", "protocol": "TCP", "ports": [], "endpoints": []}`, ExpectedEtcdPath: "/registry/endpointslices/" + namespace + "/slice1", }, // --