
Add an interface between CleanStaleEntries and the lower-level conntrack helpers (ClearEntriesForIP, etc), and a fake implementation of that interface, so that we can explicitly test CleanStaleEntries's logic. Remove some comments from conntrack.go that were explaining the functions' callers rather than explaining the functions themselves (and which were redundant with other comments in the callers anyway).
264 lines
7.6 KiB
Go
264 lines
7.6 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
/*
|
|
Copyright 2023 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 conntrack
|
|
|
|
import (
|
|
"net"
|
|
"reflect"
|
|
"testing"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/kubernetes/pkg/proxy"
|
|
)
|
|
|
|
const (
|
|
testClusterIP = "172.30.1.1"
|
|
testExternalIP = "192.168.99.100"
|
|
testLoadBalancerIP = "1.2.3.4"
|
|
|
|
testEndpointIP = "10.240.0.4"
|
|
|
|
testPort = 53
|
|
testNodePort = 5353
|
|
testEndpointPort = "5300"
|
|
)
|
|
|
|
func TestCleanStaleEntries(t *testing.T) {
|
|
// We need to construct a proxy.ServicePortMap to pass to CleanStaleEntries.
|
|
// ServicePortMap is just map[string]proxy.ServicePort, but there are no public
|
|
// constructors for any implementation of proxy.ServicePort, so we have to either
|
|
// provide our own implementation of that interface, or else use a
|
|
// proxy.ServiceChangeTracker to construct them and fill in the map for us.
|
|
|
|
sct := proxy.NewServiceChangeTracker(nil, v1.IPv4Protocol, nil, nil)
|
|
svc := &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "cleanup-test",
|
|
Namespace: "test",
|
|
},
|
|
Spec: v1.ServiceSpec{
|
|
ClusterIP: testClusterIP,
|
|
ExternalIPs: []string{testExternalIP},
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
Name: "dns-tcp",
|
|
Port: testPort,
|
|
Protocol: v1.ProtocolTCP,
|
|
},
|
|
{
|
|
Name: "dns-udp",
|
|
Port: testPort,
|
|
NodePort: testNodePort,
|
|
Protocol: v1.ProtocolUDP,
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{{
|
|
IP: testLoadBalancerIP,
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
sct.Update(nil, svc)
|
|
|
|
svcPortMap := make(proxy.ServicePortMap)
|
|
_ = svcPortMap.Update(sct)
|
|
|
|
// (At this point we are done with sct, and in particular, we don't use sct to
|
|
// construct UpdateServiceMapResults, because pkg/proxy already has its own tests
|
|
// for that. Also, svcPortMap is read-only from this point on.)
|
|
|
|
tcpPortName := proxy.ServicePortName{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: svc.Namespace,
|
|
Name: svc.Name,
|
|
},
|
|
Port: svc.Spec.Ports[0].Name,
|
|
Protocol: svc.Spec.Ports[0].Protocol,
|
|
}
|
|
|
|
udpPortName := proxy.ServicePortName{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: svc.Namespace,
|
|
Name: svc.Name,
|
|
},
|
|
Port: svc.Spec.Ports[1].Name,
|
|
Protocol: svc.Spec.Ports[1].Protocol,
|
|
}
|
|
|
|
unknownPortName := udpPortName
|
|
unknownPortName.Namespace = "unknown"
|
|
|
|
// Sanity-check to make sure we constructed the map correctly
|
|
if len(svcPortMap) != 2 {
|
|
t.Fatalf("expected svcPortMap to have 2 entries, got %+v", svcPortMap)
|
|
}
|
|
servicePort := svcPortMap[tcpPortName]
|
|
if servicePort == nil || servicePort.String() != "172.30.1.1:53/TCP" {
|
|
t.Fatalf("expected svcPortMap[%q] to be \"172.30.1.1:53/TCP\", got %q", tcpPortName.String(), servicePort.String())
|
|
}
|
|
servicePort = svcPortMap[udpPortName]
|
|
if servicePort == nil || servicePort.String() != "172.30.1.1:53/UDP" {
|
|
t.Fatalf("expected svcPortMap[%q] to be \"172.30.1.1:53/UDP\", got %q", udpPortName.String(), servicePort.String())
|
|
}
|
|
|
|
testCases := []struct {
|
|
description string
|
|
|
|
serviceUpdates proxy.UpdateServiceMapResult
|
|
endpointsUpdates proxy.UpdateEndpointsMapResult
|
|
|
|
result FakeInterface
|
|
}{
|
|
{
|
|
description: "DeletedUDPClusterIPs clears entries for given clusterIPs (only)",
|
|
|
|
serviceUpdates: proxy.UpdateServiceMapResult{
|
|
// Note: this isn't testClusterIP; it's the IP of some
|
|
// unknown (because deleted) service.
|
|
DeletedUDPClusterIPs: sets.New("172.30.99.99"),
|
|
},
|
|
endpointsUpdates: proxy.UpdateEndpointsMapResult{},
|
|
|
|
result: FakeInterface{
|
|
ClearedIPs: sets.New("172.30.99.99"),
|
|
|
|
ClearedPorts: sets.New[int](),
|
|
ClearedNATs: map[string]string{},
|
|
ClearedPortNATs: map[int]string{},
|
|
},
|
|
},
|
|
{
|
|
description: "DeletedUDPEndpoints clears NAT entries for all IPs and NodePorts",
|
|
|
|
serviceUpdates: proxy.UpdateServiceMapResult{
|
|
DeletedUDPClusterIPs: sets.New[string](),
|
|
},
|
|
endpointsUpdates: proxy.UpdateEndpointsMapResult{
|
|
DeletedUDPEndpoints: []proxy.ServiceEndpoint{{
|
|
Endpoint: net.JoinHostPort(testEndpointIP, testEndpointPort),
|
|
ServicePortName: udpPortName,
|
|
}},
|
|
},
|
|
|
|
result: FakeInterface{
|
|
ClearedIPs: sets.New[string](),
|
|
ClearedPorts: sets.New[int](),
|
|
|
|
ClearedNATs: map[string]string{
|
|
testClusterIP: testEndpointIP,
|
|
testExternalIP: testEndpointIP,
|
|
testLoadBalancerIP: testEndpointIP,
|
|
},
|
|
ClearedPortNATs: map[int]string{
|
|
testNodePort: testEndpointIP,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
description: "NewlyActiveUDPServices clears entries for all IPs and NodePorts",
|
|
|
|
serviceUpdates: proxy.UpdateServiceMapResult{
|
|
DeletedUDPClusterIPs: sets.New[string](),
|
|
},
|
|
endpointsUpdates: proxy.UpdateEndpointsMapResult{
|
|
DeletedUDPEndpoints: []proxy.ServiceEndpoint{},
|
|
NewlyActiveUDPServices: []proxy.ServicePortName{
|
|
udpPortName,
|
|
},
|
|
},
|
|
|
|
result: FakeInterface{
|
|
ClearedIPs: sets.New(testClusterIP, testExternalIP, testLoadBalancerIP),
|
|
ClearedPorts: sets.New(testNodePort),
|
|
|
|
ClearedNATs: map[string]string{},
|
|
ClearedPortNATs: map[int]string{},
|
|
},
|
|
},
|
|
|
|
{
|
|
description: "DeletedUDPEndpoints for unknown Service has no effect",
|
|
|
|
serviceUpdates: proxy.UpdateServiceMapResult{
|
|
DeletedUDPClusterIPs: sets.New[string](),
|
|
},
|
|
endpointsUpdates: proxy.UpdateEndpointsMapResult{
|
|
DeletedUDPEndpoints: []proxy.ServiceEndpoint{{
|
|
Endpoint: "10.240.0.4:80",
|
|
ServicePortName: unknownPortName,
|
|
}},
|
|
NewlyActiveUDPServices: []proxy.ServicePortName{},
|
|
},
|
|
|
|
result: FakeInterface{
|
|
ClearedIPs: sets.New[string](),
|
|
ClearedPorts: sets.New[int](),
|
|
ClearedNATs: map[string]string{},
|
|
ClearedPortNATs: map[int]string{},
|
|
},
|
|
},
|
|
{
|
|
description: "NewlyActiveUDPServices for unknown Service has no effect",
|
|
|
|
serviceUpdates: proxy.UpdateServiceMapResult{
|
|
DeletedUDPClusterIPs: sets.New[string](),
|
|
},
|
|
endpointsUpdates: proxy.UpdateEndpointsMapResult{
|
|
DeletedUDPEndpoints: []proxy.ServiceEndpoint{},
|
|
NewlyActiveUDPServices: []proxy.ServicePortName{
|
|
unknownPortName,
|
|
},
|
|
},
|
|
|
|
result: FakeInterface{
|
|
ClearedIPs: sets.New[string](),
|
|
ClearedPorts: sets.New[int](),
|
|
ClearedNATs: map[string]string{},
|
|
ClearedPortNATs: map[int]string{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
fake := NewFake()
|
|
cleanStaleEntries(fake, false, svcPortMap, tc.serviceUpdates, tc.endpointsUpdates)
|
|
if !fake.ClearedIPs.Equal(tc.result.ClearedIPs) {
|
|
t.Errorf("Expected ClearedIPs=%v, got %v", tc.result.ClearedIPs, fake.ClearedIPs)
|
|
}
|
|
if !fake.ClearedPorts.Equal(tc.result.ClearedPorts) {
|
|
t.Errorf("Expected ClearedPorts=%v, got %v", tc.result.ClearedPorts, fake.ClearedPorts)
|
|
}
|
|
if !reflect.DeepEqual(fake.ClearedNATs, tc.result.ClearedNATs) {
|
|
t.Errorf("Expected ClearedNATs=%v, got %v", tc.result.ClearedNATs, fake.ClearedNATs)
|
|
}
|
|
if !reflect.DeepEqual(fake.ClearedPortNATs, tc.result.ClearedPortNATs) {
|
|
t.Errorf("Expected ClearedPortNATs=%v, got %v", tc.result.ClearedPortNATs, fake.ClearedPortNATs)
|
|
}
|
|
})
|
|
}
|
|
}
|