Files
kubernetes/pkg/apis/discovery/validation/validation_test.go
Rob Scott fe54e1f386 Fixing EndpointSlice port validation
This updates EndpointSlice port validation to mirror the validation
already in use for Service and Endpoint ports. This is required to
ensure all valid Service ports can be mapped directly to EndpointSlice
ports.
2019-10-30 12:32:23 -07:00

514 lines
15 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"fmt"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/discovery"
utilpointer "k8s.io/utils/pointer"
)
func TestValidateEndpointSlice(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{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"good-fqdns": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeFQDN),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: []string{"foo.example.com", "example.com", "example.com.", "hyphens-are-good.example.com"},
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"all-protocols": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("tcp"),
Protocol: protocolPtr(api.ProtocolTCP),
}, {
Name: utilpointer.StringPtr("udp"),
Protocol: protocolPtr(api.ProtocolUDP),
}, {
Name: utilpointer.StringPtr("sctp"),
Protocol: protocolPtr(api.ProtocolSCTP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"empty-port-name": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr(""),
Protocol: protocolPtr(api.ProtocolTCP),
}, {
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
}},
},
},
"long-port-name": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr(strings.Repeat("a", 63)),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
}},
},
},
"empty-ports-and-endpoints": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{},
Endpoints: []discovery.Endpoint{},
},
},
"max-endpoints": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: generatePorts(1),
Endpoints: generateEndpoints(maxEndpoints),
},
},
"max-ports": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: generatePorts(maxPorts),
},
},
"max-addresses": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(maxAddresses),
}},
},
},
"max-topology-keys": {
expectedErrors: 0,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Topology: generateTopology(maxTopologyLabels),
}},
},
},
// expected failures
"duplicate-port-name": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr(""),
Protocol: protocolPtr(api.ProtocolTCP),
}, {
Name: utilpointer.StringPtr(""),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{},
},
},
"bad-port-name-caps": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("aCapital"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{},
},
},
"bad-port-name-chars": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("almost_valid"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{},
},
},
"bad-port-name-length": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr(strings.Repeat("a", 64)),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{},
},
},
"invalid-port-protocol": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.Protocol("foo")),
}},
},
},
"too-many-ports": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: generatePorts(maxPorts + 1),
},
},
"too-many-endpoints": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: generatePorts(1),
Endpoints: generateEndpoints(maxEndpoints + 1),
},
},
"no-endpoint-addresses": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(0),
}},
},
},
"too-many-addresses": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(maxAddresses + 1),
}},
},
},
"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),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Topology: map[string]string{"--INVALID": "example"},
}},
},
},
"too-many-topology-keys": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Topology: generateTopology(maxTopologyLabels + 1),
}},
},
},
"bad-hostname": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hostname: utilpointer.StringPtr("--INVALID"),
}},
},
},
"bad-meta": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Name: "*&^",
Namespace: "foo",
},
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: generateIPAddresses(1),
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"bad-ip": {
expectedErrors: 1,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: []string{"123.456.789.012"},
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"bad-fqdns": {
expectedErrors: 4,
endpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeFQDN),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr("http"),
Protocol: protocolPtr(api.ProtocolTCP),
}},
Endpoints: []discovery.Endpoint{{
Addresses: []string{"foo.*", "FOO.example.com", "underscores_are_bad.example.com", "*.example.com"},
Hostname: utilpointer.StringPtr("valid-123"),
}},
},
},
"empty-everything": {
expectedErrors: 3,
endpointSlice: &discovery.EndpointSlice{
AddressType: addressTypePtr(""),
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
errs := ValidateEndpointSlice(testCase.endpointSlice)
if len(errs) != testCase.expectedErrors {
t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
}
})
}
}
func TestValidateEndpointSliceUpdate(t *testing.T) {
standardMeta := metav1.ObjectMeta{Name: "es1", Namespace: "test"}
testCases := map[string]struct {
expectedErrors int
newEndpointSlice *discovery.EndpointSlice
oldEndpointSlice *discovery.EndpointSlice
}{
"valid and identical slices": {
newEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
},
oldEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
},
expectedErrors: 0,
},
"valid and identical slices with different address types": {
newEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
},
oldEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressType("other")),
},
expectedErrors: 1,
},
"invalid slices with valid address types": {
newEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
Ports: []discovery.EndpointPort{{
Name: utilpointer.StringPtr(""),
Protocol: protocolPtr(api.Protocol("invalid")),
}},
},
oldEndpointSlice: &discovery.EndpointSlice{
ObjectMeta: standardMeta,
AddressType: addressTypePtr(discovery.AddressTypeIP),
},
expectedErrors: 1,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
errs := ValidateEndpointSliceUpdate(testCase.newEndpointSlice, testCase.oldEndpointSlice)
if len(errs) != testCase.expectedErrors {
t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
}
})
}
}
// Test helpers
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++ {
ports = append(ports, discovery.EndpointPort{
Name: utilpointer.StringPtr(fmt.Sprintf("http-%d", i)),
Protocol: protocolPtr(api.ProtocolTCP),
})
}
return ports
}
func generateEndpoints(n int) []discovery.Endpoint {
endpoints := []discovery.Endpoint{}
for i := 0; i < n; i++ {
endpoints = append(endpoints, discovery.Endpoint{
Addresses: []string{fmt.Sprintf("10.1.2.%d", i%255)},
})
}
return endpoints
}
func generateIPAddresses(n int) []string {
addresses := []string{}
for i := 0; i < n; i++ {
addresses = append(addresses, fmt.Sprintf("10.1.2.%d", i%255))
}
return addresses
}
func generateTopology(n int) map[string]string {
topology := map[string]string{}
for i := 0; i < n; i++ {
topology[fmt.Sprintf("topology-%d", i)] = "example"
}
return topology
}