
In addition to actually updating their data from the provided list of changes, EndpointsMap.Update() and ServicePortMap.Update() return a struct with some information about things that changed because of that update (eg services with stale conntrack entries). For some reason, they were also returning information about HealthCheckNodePorts, but they were returning *static* information based on the current (post-Update) state of the map, not information about what had *changed* in the update. Since this doesn't match how the other data in the struct is used (and since there's no reason to have the data only be returned when you call Update() anyway) , split it out.
836 lines
29 KiB
Go
836 lines
29 KiB
Go
/*
|
|
Copyright 2017 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 proxy
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
netutils "k8s.io/utils/net"
|
|
)
|
|
|
|
const testHostname = "test-hostname"
|
|
|
|
func makeTestServiceInfo(clusterIP string, port int, protocol string, healthcheckNodePort int, svcInfoFuncs ...func(*BaseServicePortInfo)) *BaseServicePortInfo {
|
|
bsvcPortInfo := &BaseServicePortInfo{
|
|
clusterIP: netutils.ParseIPSloppy(clusterIP),
|
|
port: port,
|
|
protocol: v1.Protocol(protocol),
|
|
}
|
|
if healthcheckNodePort != 0 {
|
|
bsvcPortInfo.healthCheckNodePort = healthcheckNodePort
|
|
}
|
|
for _, svcInfoFunc := range svcInfoFuncs {
|
|
svcInfoFunc(bsvcPortInfo)
|
|
}
|
|
return bsvcPortInfo
|
|
}
|
|
|
|
func makeTestService(namespace, name string, svcFunc func(*v1.Service)) *v1.Service {
|
|
svc := &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: v1.ServiceSpec{},
|
|
Status: v1.ServiceStatus{},
|
|
}
|
|
svcFunc(svc)
|
|
return svc
|
|
}
|
|
|
|
func addTestPort(array []v1.ServicePort, name string, protocol v1.Protocol, port, nodeport int32, targetPort int) []v1.ServicePort {
|
|
svcPort := v1.ServicePort{
|
|
Name: name,
|
|
Protocol: protocol,
|
|
Port: port,
|
|
NodePort: nodeport,
|
|
TargetPort: intstr.FromInt(targetPort),
|
|
}
|
|
return append(array, svcPort)
|
|
}
|
|
|
|
func makeNSN(namespace, name string) types.NamespacedName {
|
|
return types.NamespacedName{Namespace: namespace, Name: name}
|
|
}
|
|
|
|
func makeServicePortName(ns, name, port string, protocol v1.Protocol) ServicePortName {
|
|
return ServicePortName{
|
|
NamespacedName: makeNSN(ns, name),
|
|
Port: port,
|
|
Protocol: protocol,
|
|
}
|
|
}
|
|
|
|
func TestServiceToServiceMap(t *testing.T) {
|
|
testClusterIPv4 := "10.0.0.1"
|
|
testExternalIPv4 := "8.8.8.8"
|
|
testSourceRangeIPv4 := "0.0.0.0/1"
|
|
testClusterIPv6 := "2001:db8:85a3:0:0:8a2e:370:7334"
|
|
testExternalIPv6 := "2001:db8:85a3:0:0:8a2e:370:7335"
|
|
testSourceRangeIPv6 := "2001:db8::/32"
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
service *v1.Service
|
|
expected map[ServicePortName]*BaseServicePortInfo
|
|
ipFamily v1.IPFamily
|
|
}{
|
|
{
|
|
desc: "nothing",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: nil,
|
|
expected: map[ServicePortName]*BaseServicePortInfo{},
|
|
},
|
|
{
|
|
desc: "headless service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "headless", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = v1.ClusterIPNone
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "rpc", "UDP", 1234, 0, 0)
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{},
|
|
},
|
|
{
|
|
desc: "headless sctp service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "headless", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = v1.ClusterIPNone
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sip", "SCTP", 7777, 0, 0)
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{},
|
|
},
|
|
{
|
|
desc: "headless service without port",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "headless-without-port", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = v1.ClusterIPNone
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{},
|
|
},
|
|
{
|
|
desc: "cluster ip service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "cluster-ip", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = "172.16.55.4"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p1", "UDP", 1234, 4321, 0)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p2", "UDP", 1235, 5321, 0)
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("ns2", "cluster-ip", "p1", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.4", 1234, "UDP", 0),
|
|
makeServicePortName("ns2", "cluster-ip", "p2", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.4", 1235, "UDP", 0),
|
|
},
|
|
},
|
|
{
|
|
desc: "nodeport service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "node-port", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeNodePort
|
|
svc.Spec.ClusterIP = "172.16.55.10"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port1", "UDP", 345, 678, 0)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port2", "TCP", 344, 677, 0)
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("ns2", "node-port", "port1", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.10", 345, "UDP", 0),
|
|
makeServicePortName("ns2", "node-port", "port2", v1.ProtocolTCP): makeTestServiceInfo("172.16.55.10", 344, "TCP", 0),
|
|
},
|
|
},
|
|
{
|
|
desc: "load balancer service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns1", "load-balancer", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
|
svc.Spec.ClusterIP = "172.16.55.11"
|
|
svc.Spec.LoadBalancerIP = "5.6.7.8"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port3", "UDP", 8675, 30061, 7000)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port4", "UDP", 8676, 30062, 7001)
|
|
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.4"}}
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("ns1", "load-balancer", "port3", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.11", 8675, "UDP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.4"}}
|
|
}),
|
|
makeServicePortName("ns1", "load-balancer", "port4", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.11", 8676, "UDP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.4"}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "load balancer service with only local traffic policy",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns1", "only-local-load-balancer", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
|
svc.Spec.ClusterIP = "172.16.55.12"
|
|
svc.Spec.LoadBalancerIP = "5.6.7.8"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "portx", "UDP", 8677, 30063, 7002)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "porty", "UDP", 8678, 30064, 7003)
|
|
svc.Status.LoadBalancer.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.3"}}
|
|
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyLocal
|
|
svc.Spec.HealthCheckNodePort = 345
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("ns1", "only-local-load-balancer", "portx", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.12", 8677, "UDP", 345, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.3"}}
|
|
}),
|
|
makeServicePortName("ns1", "only-local-load-balancer", "porty", v1.ProtocolUDP): makeTestServiceInfo("172.16.55.12", 8678, "UDP", 345, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: "10.1.2.3"}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "external name service",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: makeTestService("ns2", "external-name", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeExternalName
|
|
svc.Spec.ClusterIP = "172.16.55.4" // Should be ignored
|
|
svc.Spec.ExternalName = "foo2.bar.com"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "portz", "UDP", 1235, 5321, 0)
|
|
}),
|
|
expected: map[ServicePortName]*BaseServicePortInfo{},
|
|
},
|
|
{
|
|
desc: "service with ipv6 clusterIP under ipv4 mode, service should be filtered",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "invalidIPv6InIPV4Mode",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv6,
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "service with ipv4 clusterIP under ipv6 mode, service should be filtered",
|
|
ipFamily: v1.IPv6Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "invalidIPv4InIPV6Mode",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv4,
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "service with ipv4 configurations under ipv4 mode",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "validIPv4",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv4,
|
|
ExternalIPs: []string{testExternalIPv4},
|
|
LoadBalancerSourceRanges: []string{testSourceRangeIPv4},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("test", "validIPv4", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.externalIPs = []string{testExternalIPv4}
|
|
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4}
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv4}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "service with ipv6 configurations under ipv6 mode",
|
|
ipFamily: v1.IPv6Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "validIPv6",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv6,
|
|
ExternalIPs: []string{testExternalIPv6},
|
|
LoadBalancerSourceRanges: []string{testSourceRangeIPv6},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("test", "validIPv6", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.externalIPs = []string{testExternalIPv6}
|
|
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6}
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv6}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "service with both ipv4 and ipv6 configurations under ipv4 mode, ipv6 fields should be filtered",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "filterIPv6InIPV4Mode",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv4,
|
|
ExternalIPs: []string{testExternalIPv4, testExternalIPv6},
|
|
LoadBalancerSourceRanges: []string{testSourceRangeIPv4, testSourceRangeIPv6},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("test", "filterIPv6InIPV4Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.externalIPs = []string{testExternalIPv4}
|
|
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4}
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv4}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "service with both ipv4 and ipv6 configurations under ipv6 mode, ipv4 fields should be filtered",
|
|
ipFamily: v1.IPv6Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "filterIPv4InIPV6Mode",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv6,
|
|
ExternalIPs: []string{testExternalIPv4, testExternalIPv6},
|
|
LoadBalancerSourceRanges: []string{testSourceRangeIPv4, testSourceRangeIPv6},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: testExternalIPv4},
|
|
{IP: testExternalIPv6},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("test", "filterIPv4InIPV6Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.externalIPs = []string{testExternalIPv6}
|
|
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6}
|
|
bsvcPortInfo.loadBalancerStatus.Ingress = []v1.LoadBalancerIngress{{IP: testExternalIPv6}}
|
|
}),
|
|
},
|
|
},
|
|
{
|
|
desc: "service with extra space in LoadBalancerSourceRanges",
|
|
ipFamily: v1.IPv4Protocol,
|
|
|
|
service: &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "extra-space",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIPv4,
|
|
LoadBalancerSourceRanges: []string{" 10.1.2.0/28"},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "testPort",
|
|
Port: int32(12345),
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: map[ServicePortName]*BaseServicePortInfo{
|
|
makeServicePortName("test", "extra-space", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
|
|
bsvcPortInfo.loadBalancerSourceRanges = []string{"10.1.2.0/28"}
|
|
}),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
svcTracker := NewServiceChangeTracker(nil, tc.ipFamily, nil, nil)
|
|
// outputs
|
|
newServices := svcTracker.serviceToServiceMap(tc.service)
|
|
|
|
if len(newServices) != len(tc.expected) {
|
|
t.Fatalf("expected %d new, got %d: %v", len(tc.expected), len(newServices), spew.Sdump(newServices))
|
|
}
|
|
for svcKey, expectedInfo := range tc.expected {
|
|
svcInfo, exists := newServices[svcKey].(*BaseServicePortInfo)
|
|
if !exists {
|
|
t.Fatalf("[%s] expected to find key %s", tc.desc, svcKey)
|
|
}
|
|
|
|
if !svcInfo.clusterIP.Equal(expectedInfo.clusterIP) ||
|
|
svcInfo.port != expectedInfo.port ||
|
|
svcInfo.protocol != expectedInfo.protocol ||
|
|
svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort ||
|
|
!sets.NewString(svcInfo.externalIPs...).Equal(sets.NewString(expectedInfo.externalIPs...)) ||
|
|
!sets.NewString(svcInfo.loadBalancerSourceRanges...).Equal(sets.NewString(expectedInfo.loadBalancerSourceRanges...)) ||
|
|
!reflect.DeepEqual(svcInfo.loadBalancerStatus, expectedInfo.loadBalancerStatus) {
|
|
t.Errorf("[%s] expected new[%v]to be %v, got %v", tc.desc, svcKey, expectedInfo, *svcInfo)
|
|
}
|
|
for svcKey, expectedInfo := range tc.expected {
|
|
svcInfo, _ := newServices[svcKey].(*BaseServicePortInfo)
|
|
if !svcInfo.clusterIP.Equal(expectedInfo.clusterIP) ||
|
|
svcInfo.port != expectedInfo.port ||
|
|
svcInfo.protocol != expectedInfo.protocol ||
|
|
svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort ||
|
|
!sets.NewString(svcInfo.externalIPs...).Equal(sets.NewString(expectedInfo.externalIPs...)) ||
|
|
!sets.NewString(svcInfo.loadBalancerSourceRanges...).Equal(sets.NewString(expectedInfo.loadBalancerSourceRanges...)) ||
|
|
!reflect.DeepEqual(svcInfo.loadBalancerStatus, expectedInfo.loadBalancerStatus) {
|
|
t.Errorf("expected new[%v]to be %v, got %v", svcKey, expectedInfo, *svcInfo)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type FakeProxier struct {
|
|
endpointsChanges *EndpointChangeTracker
|
|
serviceChanges *ServiceChangeTracker
|
|
svcPortMap ServicePortMap
|
|
endpointsMap EndpointsMap
|
|
hostname string
|
|
}
|
|
|
|
func newFakeProxier(ipFamily v1.IPFamily, t time.Time) *FakeProxier {
|
|
return &FakeProxier{
|
|
svcPortMap: make(ServicePortMap),
|
|
serviceChanges: NewServiceChangeTracker(nil, ipFamily, nil, nil),
|
|
endpointsMap: make(EndpointsMap),
|
|
endpointsChanges: &EndpointChangeTracker{
|
|
lastChangeTriggerTimes: make(map[types.NamespacedName][]time.Time),
|
|
trackerStartTime: t,
|
|
processEndpointsMapChange: nil,
|
|
endpointSliceCache: NewEndpointSliceCache(testHostname, ipFamily, nil, nil),
|
|
},
|
|
}
|
|
}
|
|
|
|
func makeServiceMap(fake *FakeProxier, allServices ...*v1.Service) {
|
|
for i := range allServices {
|
|
fake.addService(allServices[i])
|
|
}
|
|
}
|
|
|
|
func (fake *FakeProxier) addService(service *v1.Service) {
|
|
fake.serviceChanges.Update(nil, service)
|
|
}
|
|
|
|
func (fake *FakeProxier) updateService(oldService *v1.Service, service *v1.Service) {
|
|
fake.serviceChanges.Update(oldService, service)
|
|
}
|
|
|
|
func (fake *FakeProxier) deleteService(service *v1.Service) {
|
|
fake.serviceChanges.Update(service, nil)
|
|
}
|
|
|
|
func TestServiceMapUpdateHeadless(t *testing.T) {
|
|
fp := newFakeProxier(v1.IPv4Protocol, time.Time{})
|
|
|
|
makeServiceMap(fp,
|
|
makeTestService("ns2", "headless", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = v1.ClusterIPNone
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "rpc", "UDP", 1234, 0, 0)
|
|
}),
|
|
makeTestService("ns2", "headless-without-port", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = v1.ClusterIPNone
|
|
}),
|
|
)
|
|
|
|
// Headless service should be ignored
|
|
pending := fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 0 {
|
|
t.Errorf("expected 0 pending service changes, got %d", pending.Len())
|
|
}
|
|
result := fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 0 {
|
|
t.Errorf("expected service map length 0, got %d", len(fp.svcPortMap))
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
t.Errorf("expected stale UDP services length 0, got %d", len(result.UDPStaleClusterIP))
|
|
}
|
|
|
|
// No proxied services, so no healthchecks
|
|
healthCheckNodePorts := fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 0 {
|
|
t.Errorf("expected healthcheck ports length 0, got %d", len(healthCheckNodePorts))
|
|
}
|
|
}
|
|
|
|
func TestUpdateServiceTypeExternalName(t *testing.T) {
|
|
fp := newFakeProxier(v1.IPv4Protocol, time.Time{})
|
|
|
|
makeServiceMap(fp,
|
|
makeTestService("ns2", "external-name", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeExternalName
|
|
svc.Spec.ClusterIP = "172.16.55.4" // Should be ignored
|
|
svc.Spec.ExternalName = "foo2.bar.com"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "blah", "UDP", 1235, 5321, 0)
|
|
}),
|
|
)
|
|
|
|
pending := fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 0 {
|
|
t.Errorf("expected 0 pending service changes, got %d", pending.Len())
|
|
}
|
|
result := fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 0 {
|
|
t.Errorf("expected service map length 0, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
t.Errorf("expected stale UDP services length 0, got %v", result.UDPStaleClusterIP)
|
|
}
|
|
|
|
// No proxied services, so no healthchecks
|
|
healthCheckNodePorts := fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 0 {
|
|
t.Errorf("expected healthcheck ports length 0, got %v", healthCheckNodePorts)
|
|
}
|
|
}
|
|
|
|
func TestBuildServiceMapAddRemove(t *testing.T) {
|
|
fp := newFakeProxier(v1.IPv4Protocol, time.Time{})
|
|
|
|
services := []*v1.Service{
|
|
makeTestService("ns2", "cluster-ip", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = "172.16.55.4"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port1", "UDP", 1234, 4321, 0)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port2", "UDP", 1235, 5321, 0)
|
|
}),
|
|
makeTestService("ns2", "node-port", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeNodePort
|
|
svc.Spec.ClusterIP = "172.16.55.10"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port1", "UDP", 345, 678, 0)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "port2", "TCP", 344, 677, 0)
|
|
}),
|
|
makeTestService("ns1", "load-balancer", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
|
svc.Spec.ClusterIP = "172.16.55.11"
|
|
svc.Spec.LoadBalancerIP = "5.6.7.8"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar", "UDP", 8675, 30061, 7000)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8676, 30062, 7001)
|
|
svc.Status.LoadBalancer = v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: "10.1.2.4"},
|
|
},
|
|
}
|
|
}),
|
|
makeTestService("ns1", "only-local-load-balancer", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
|
svc.Spec.ClusterIP = "172.16.55.12"
|
|
svc.Spec.LoadBalancerIP = "5.6.7.8"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar2", "UDP", 8677, 30063, 7002)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8678, 30064, 7003)
|
|
svc.Status.LoadBalancer = v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: "10.1.2.3"},
|
|
},
|
|
}
|
|
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyLocal
|
|
svc.Spec.HealthCheckNodePort = 345
|
|
}),
|
|
}
|
|
|
|
for i := range services {
|
|
fp.addService(services[i])
|
|
}
|
|
|
|
pending := fp.serviceChanges.PendingChanges()
|
|
for i := range services {
|
|
name := services[i].Namespace + "/" + services[i].Name
|
|
if !pending.Has(name) {
|
|
t.Errorf("expected pending change for %q", name)
|
|
}
|
|
}
|
|
if pending.Len() != len(services) {
|
|
t.Errorf("expected %d pending service changes, got %d", len(services), pending.Len())
|
|
}
|
|
|
|
result := fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 8 {
|
|
t.Errorf("expected service map length 2, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
// Services only added, so nothing stale yet
|
|
t.Errorf("expected stale UDP services length 0, got %d", len(result.UDPStaleClusterIP))
|
|
}
|
|
|
|
// The only-local-loadbalancer ones get added
|
|
healthCheckNodePorts := fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 1 {
|
|
t.Errorf("expected 1 healthcheck port, got %v", healthCheckNodePorts)
|
|
} else {
|
|
nsn := makeNSN("ns1", "only-local-load-balancer")
|
|
if port, found := healthCheckNodePorts[nsn]; !found || port != 345 {
|
|
t.Errorf("expected healthcheck port [%q]=345: got %v", nsn, healthCheckNodePorts)
|
|
}
|
|
}
|
|
|
|
// Remove some stuff
|
|
// oneService is a modification of services[0] with removed first port.
|
|
oneService := makeTestService("ns2", "cluster-ip", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = "172.16.55.4"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p2", "UDP", 1235, 5321, 0)
|
|
})
|
|
|
|
fp.updateService(services[0], oneService)
|
|
fp.deleteService(services[1])
|
|
fp.deleteService(services[2])
|
|
fp.deleteService(services[3])
|
|
|
|
pending = fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 4 {
|
|
t.Errorf("expected 4 pending service changes, got %d", pending.Len())
|
|
}
|
|
result = fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 1 {
|
|
t.Errorf("expected service map length 1, got %v", fp.svcPortMap)
|
|
}
|
|
|
|
healthCheckNodePorts = fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 0 {
|
|
t.Errorf("expected 0 healthcheck ports, got %v", healthCheckNodePorts)
|
|
}
|
|
|
|
// All services but one were deleted. While you'd expect only the ClusterIPs
|
|
// from the three deleted services here, we still have the ClusterIP for
|
|
// the not-deleted service, because one of it's ServicePorts was deleted.
|
|
expectedStaleUDPServices := []string{"172.16.55.10", "172.16.55.4", "172.16.55.11", "172.16.55.12"}
|
|
if len(result.UDPStaleClusterIP) != len(expectedStaleUDPServices) {
|
|
t.Errorf("expected stale UDP services length %d, got %v", len(expectedStaleUDPServices), result.UDPStaleClusterIP.UnsortedList())
|
|
}
|
|
for _, ip := range expectedStaleUDPServices {
|
|
if !result.UDPStaleClusterIP.Has(ip) {
|
|
t.Errorf("expected stale UDP service service %s", ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildServiceMapServiceUpdate(t *testing.T) {
|
|
fp := newFakeProxier(v1.IPv4Protocol, time.Time{})
|
|
|
|
servicev1 := makeTestService("ns1", "svc1", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeClusterIP
|
|
svc.Spec.ClusterIP = "172.16.55.4"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p1", "UDP", 1234, 4321, 0)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p2", "TCP", 1235, 5321, 0)
|
|
})
|
|
servicev2 := makeTestService("ns1", "svc1", func(svc *v1.Service) {
|
|
svc.Spec.Type = v1.ServiceTypeLoadBalancer
|
|
svc.Spec.ClusterIP = "172.16.55.4"
|
|
svc.Spec.LoadBalancerIP = "5.6.7.8"
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p1", "UDP", 1234, 4321, 7002)
|
|
svc.Spec.Ports = addTestPort(svc.Spec.Ports, "p2", "TCP", 1235, 5321, 7003)
|
|
svc.Status.LoadBalancer = v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: "10.1.2.3"},
|
|
},
|
|
}
|
|
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyLocal
|
|
svc.Spec.HealthCheckNodePort = 345
|
|
})
|
|
|
|
fp.addService(servicev1)
|
|
|
|
pending := fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 1 {
|
|
t.Errorf("expected 1 pending service change, got %d", pending.Len())
|
|
}
|
|
result := fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 2 {
|
|
t.Errorf("expected service map length 2, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
// Services only added, so nothing stale yet
|
|
t.Errorf("expected stale UDP services length 0, got %d", len(result.UDPStaleClusterIP))
|
|
}
|
|
|
|
healthCheckNodePorts := fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 0 {
|
|
t.Errorf("expected healthcheck ports length 0, got %v", healthCheckNodePorts)
|
|
}
|
|
|
|
// Change service to load-balancer
|
|
fp.updateService(servicev1, servicev2)
|
|
pending = fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 1 {
|
|
t.Errorf("expected 1 pending service change, got %d", pending.Len())
|
|
}
|
|
result = fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 2 {
|
|
t.Errorf("expected service map length 2, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
t.Errorf("expected stale UDP services length 0, got %v", result.UDPStaleClusterIP.UnsortedList())
|
|
}
|
|
|
|
healthCheckNodePorts = fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 1 {
|
|
t.Errorf("expected healthcheck ports length 1, got %v", healthCheckNodePorts)
|
|
}
|
|
|
|
// No change; make sure the service map stays the same and there are
|
|
// no health-check changes
|
|
fp.updateService(servicev2, servicev2)
|
|
pending = fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 0 {
|
|
t.Errorf("expected 0 pending service changes, got %d", pending.Len())
|
|
}
|
|
result = fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 2 {
|
|
t.Errorf("expected service map length 2, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
t.Errorf("expected stale UDP services length 0, got %v", result.UDPStaleClusterIP.UnsortedList())
|
|
}
|
|
|
|
healthCheckNodePorts = fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 1 {
|
|
t.Errorf("expected healthcheck ports length 1, got %v", healthCheckNodePorts)
|
|
}
|
|
|
|
// And back to ClusterIP
|
|
fp.updateService(servicev2, servicev1)
|
|
pending = fp.serviceChanges.PendingChanges()
|
|
if pending.Len() != 1 {
|
|
t.Errorf("expected 1 pending service change, got %d", pending.Len())
|
|
}
|
|
result = fp.svcPortMap.Update(fp.serviceChanges)
|
|
if len(fp.svcPortMap) != 2 {
|
|
t.Errorf("expected service map length 2, got %v", fp.svcPortMap)
|
|
}
|
|
if len(result.UDPStaleClusterIP) != 0 {
|
|
// Services only added, so nothing stale yet
|
|
t.Errorf("expected stale UDP services length 0, got %d", len(result.UDPStaleClusterIP))
|
|
}
|
|
|
|
healthCheckNodePorts = fp.svcPortMap.HealthCheckNodePorts()
|
|
if len(healthCheckNodePorts) != 0 {
|
|
t.Errorf("expected healthcheck ports length 0, got %v", healthCheckNodePorts)
|
|
}
|
|
}
|