| //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, 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) |
| } |
| }) |
| } |
| } |