From de83e0939c472a6964511c54bb4b226db4788dc5 Mon Sep 17 00:00:00 2001 From: Gaurav Ghildiyal Date: Mon, 3 Apr 2023 13:40:10 -0700 Subject: [PATCH 1/2] Cleanup tests for toZoneNetworkEndpointMap --- pkg/neg/syncers/utils_test.go | 167 ++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 79 deletions(-) diff --git a/pkg/neg/syncers/utils_test.go b/pkg/neg/syncers/utils_test.go index 6901ad6b55..d36789572d 100644 --- a/pkg/neg/syncers/utils_test.go +++ b/pkg/neg/syncers/utils_test.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud" "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta" + "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" @@ -441,109 +442,117 @@ func TestEnsureNetworkEndpointGroup(t *testing.T) { } } -func TestToZoneNetworkEndpointMapUtil(t *testing.T) { +func TestToZoneNetworkEndpointMap(t *testing.T) { t.Parallel() zoneGetter := negtypes.NewFakeZoneGetter() podLister := negtypes.NewTestContext().PodInformer.GetIndexer() addPodsToLister(podLister) testCases := []struct { - desc string - portName string - endpointSets map[string]negtypes.NetworkEndpointSet - expectMap negtypes.EndpointPodMap - networkEndpointType negtypes.NetworkEndpointType + desc string + portName string + wantZoneNetworkEndpointMap map[string]negtypes.NetworkEndpointSet + wantNetworkEndpointPodMap negtypes.EndpointPodMap + networkEndpointType negtypes.NetworkEndpointType }{ { - desc: "non exist target port", - portName: "non-exists", - endpointSets: map[string]negtypes.NetworkEndpointSet{}, - expectMap: negtypes.EndpointPodMap{}, - networkEndpointType: negtypes.VmIpPortEndpointType, + desc: "target port does not exist", + portName: "non-exists", + wantZoneNetworkEndpointMap: map[string]negtypes.NetworkEndpointSet{}, + wantNetworkEndpointPodMap: negtypes.EndpointPodMap{}, + networkEndpointType: negtypes.VmIpPortEndpointType, }, { - desc: "target port number", + desc: "default service port name", portName: "", - endpointSets: map[string]negtypes.NetworkEndpointSet{ - negtypes.TestZone1: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.1.1||instance1||80"), - networkEndpointFromEncodedEndpoint("10.100.1.2||instance1||80"), - networkEndpointFromEncodedEndpoint("10.100.1.3||instance1||80"), - networkEndpointFromEncodedEndpoint("10.100.1.4||instance1||80"), - networkEndpointFromEncodedEndpoint("10.100.2.1||instance2||80")), - negtypes.TestZone2: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.3.1||instance3||80")), - }, - expectMap: negtypes.EndpointPodMap{ - networkEndpointFromEncodedEndpoint("10.100.1.1||instance1||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod1"}, - networkEndpointFromEncodedEndpoint("10.100.1.2||instance1||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod2"}, - networkEndpointFromEncodedEndpoint("10.100.2.1||instance2||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod3"}, - networkEndpointFromEncodedEndpoint("10.100.3.1||instance3||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod4"}, - networkEndpointFromEncodedEndpoint("10.100.1.3||instance1||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod5"}, - networkEndpointFromEncodedEndpoint("10.100.1.4||instance1||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod6"}, + wantZoneNetworkEndpointMap: map[string]negtypes.NetworkEndpointSet{ + negtypes.TestZone1: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.1.1", Node: "instance1", Port: "80"}, + {IP: "10.100.1.2", Node: "instance1", Port: "80"}, + {IP: "10.100.1.3", Node: "instance1", Port: "80"}, + {IP: "10.100.1.4", Node: "instance1", Port: "80"}, + {IP: "10.100.2.1", Node: "instance2", Port: "80"}, + }...), + negtypes.TestZone2: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.3.1", Node: "instance3", Port: "80"}, + }...), + }, + wantNetworkEndpointPodMap: negtypes.EndpointPodMap{ + {IP: "10.100.1.1", Node: "instance1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod1"}, + {IP: "10.100.1.2", Node: "instance1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod2"}, + {IP: "10.100.2.1", Node: "instance2", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod3"}, + {IP: "10.100.3.1", Node: "instance3", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod4"}, + {IP: "10.100.1.3", Node: "instance1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod5"}, + {IP: "10.100.1.4", Node: "instance1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod6"}, }, networkEndpointType: negtypes.VmIpPortEndpointType, }, { - desc: "named target port", + desc: "explicitly named service port", portName: testNamedPort, - endpointSets: map[string]negtypes.NetworkEndpointSet{ - negtypes.TestZone1: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.2.2||instance2||81")), - negtypes.TestZone2: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.4.1||instance4||81"), - networkEndpointFromEncodedEndpoint("10.100.3.2||instance3||8081"), - networkEndpointFromEncodedEndpoint("10.100.4.2||instance4||8081"), - networkEndpointFromEncodedEndpoint("10.100.4.3||instance4||81"), - networkEndpointFromEncodedEndpoint("10.100.4.4||instance4||8081")), - }, - expectMap: negtypes.EndpointPodMap{ - networkEndpointFromEncodedEndpoint("10.100.2.2||instance2||81"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod7"}, - networkEndpointFromEncodedEndpoint("10.100.4.1||instance4||81"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod8"}, - networkEndpointFromEncodedEndpoint("10.100.4.3||instance4||81"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod9"}, - networkEndpointFromEncodedEndpoint("10.100.3.2||instance3||8081"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod10"}, - networkEndpointFromEncodedEndpoint("10.100.4.2||instance4||8081"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod11"}, - networkEndpointFromEncodedEndpoint("10.100.4.4||instance4||8081"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod12"}, + wantZoneNetworkEndpointMap: map[string]negtypes.NetworkEndpointSet{ + negtypes.TestZone1: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.2.2", Node: "instance2", Port: "81"}, + }...), + negtypes.TestZone2: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.4.1", Node: "instance4", Port: "81"}, + {IP: "10.100.3.2", Node: "instance3", Port: "8081"}, + {IP: "10.100.4.2", Node: "instance4", Port: "8081"}, + {IP: "10.100.4.3", Node: "instance4", Port: "81"}, + {IP: "10.100.4.4", Node: "instance4", Port: "8081"}, + }...), + }, + wantNetworkEndpointPodMap: negtypes.EndpointPodMap{ + {IP: "10.100.2.2", Node: "instance2", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod7"}, + {IP: "10.100.4.1", Node: "instance4", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod8"}, + {IP: "10.100.4.3", Node: "instance4", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod9"}, + {IP: "10.100.3.2", Node: "instance3", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod10"}, + {IP: "10.100.4.2", Node: "instance4", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod11"}, + {IP: "10.100.4.4", Node: "instance4", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod12"}, }, networkEndpointType: negtypes.VmIpPortEndpointType, }, { - desc: "Non-GCP network endpoints", + desc: "non GCP network endpoints", portName: "", - endpointSets: map[string]negtypes.NetworkEndpointSet{ - negtypes.TestZone1: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.1.1||||80"), - networkEndpointFromEncodedEndpoint("10.100.1.2||||80"), - networkEndpointFromEncodedEndpoint("10.100.1.3||||80"), - networkEndpointFromEncodedEndpoint("10.100.1.4||||80"), - networkEndpointFromEncodedEndpoint("10.100.2.1||||80")), - negtypes.TestZone2: negtypes.NewNetworkEndpointSet( - networkEndpointFromEncodedEndpoint("10.100.3.1||||80")), - }, - expectMap: negtypes.EndpointPodMap{ - networkEndpointFromEncodedEndpoint("10.100.1.1||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod1"}, - networkEndpointFromEncodedEndpoint("10.100.1.2||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod2"}, - networkEndpointFromEncodedEndpoint("10.100.2.1||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod3"}, - networkEndpointFromEncodedEndpoint("10.100.3.1||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod4"}, - networkEndpointFromEncodedEndpoint("10.100.1.3||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod5"}, - networkEndpointFromEncodedEndpoint("10.100.1.4||||80"): types.NamespacedName{Namespace: testServiceNamespace, Name: "pod6"}, + wantZoneNetworkEndpointMap: map[string]negtypes.NetworkEndpointSet{ + negtypes.TestZone1: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.1.1", Port: "80"}, + {IP: "10.100.1.2", Port: "80"}, + {IP: "10.100.1.3", Port: "80"}, + {IP: "10.100.1.4", Port: "80"}, + {IP: "10.100.2.1", Port: "80"}, + }...), + negtypes.TestZone2: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.3.1", Port: "80"}, + }...), + }, + wantNetworkEndpointPodMap: negtypes.EndpointPodMap{ + {IP: "10.100.1.1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod1"}, + {IP: "10.100.1.2", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod2"}, + {IP: "10.100.2.1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod3"}, + {IP: "10.100.3.1", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod4"}, + {IP: "10.100.1.3", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod5"}, + {IP: "10.100.1.4", Port: "80"}: {Namespace: testServiceNamespace, Name: "pod6"}, }, networkEndpointType: negtypes.NonGCPPrivateEndpointType, }, } for _, tc := range testCases { - result, err := toZoneNetworkEndpointMap(negtypes.EndpointsDataFromEndpointSlices(getDefaultEndpointSlices()), zoneGetter, podLister, tc.portName, tc.networkEndpointType) - if err != nil { - t.Errorf("For case %q, expect nil error, but got %v.", tc.desc, err) - } - - if !reflect.DeepEqual(result.NetworkEndpointSet, tc.endpointSets) { - t.Errorf("For case %q, expecting endpoint set %v, but got %v.", tc.desc, tc.endpointSets, result.NetworkEndpointSet) - } + t.Run(tc.desc, func(t *testing.T) { + gotResult, err := toZoneNetworkEndpointMap(negtypes.EndpointsDataFromEndpointSlices(getDefaultEndpointSlices()), zoneGetter, podLister, tc.portName, tc.networkEndpointType) + if err != nil { + t.Errorf("toZoneNetworkEndpointMap() = err %v, want no error", err) + } - if !reflect.DeepEqual(result.EndpointPodMap, tc.expectMap) { - t.Errorf("For case %q, expecting endpoint map %v, but got %v.", tc.desc, tc.expectMap, result.EndpointPodMap) - } + zoneNetworkEndpointMap, networkEndpointPodMap := gotResult.NetworkEndpointSet, gotResult.EndpointPodMap + if diff := cmp.Diff(tc.wantZoneNetworkEndpointMap, zoneNetworkEndpointMap); diff != "" { + t.Errorf("toZoneNetworkEndpointMap() returned unexpected diff for zoneNetworkEndpointMap (-want +got):\n%s", diff) + } + if diff := cmp.Diff(tc.wantNetworkEndpointPodMap, networkEndpointPodMap); diff != "" { + t.Errorf("toZoneNetworkEndpointMap() returned unexpected diff for networkEndpointPodMap (-want +got):\n%s", diff) + } + }) } } @@ -1858,7 +1867,7 @@ func getTestEndpointSlices(name, namespace string) []*discovery.EndpointSlice { AddressType: discovery.AddressTypeIPv6, Endpoints: []discovery.Endpoint{ { - Addresses: []string{"aa:aa"}, + Addresses: []string{"a:b::1"}, NodeName: &instance3, TargetRef: &v1.ObjectReference{ Namespace: namespace, @@ -1866,7 +1875,7 @@ func getTestEndpointSlices(name, namespace string) []*discovery.EndpointSlice { }, }, { - Addresses: []string{"aa:ab"}, + Addresses: []string{"a:b::2"}, NodeName: &instance4, TargetRef: &v1.ObjectReference{ Namespace: namespace, @@ -1874,7 +1883,7 @@ func getTestEndpointSlices(name, namespace string) []*discovery.EndpointSlice { }, }, { - Addresses: []string{"aa:ac"}, + Addresses: []string{"a:b::3"}, NodeName: &instance4, TargetRef: &v1.ObjectReference{ Namespace: namespace, From 956f352b0eff6a3993f0dc332e1e52b7a47d7e7d Mon Sep 17 00:00:00 2001 From: Gaurav Ghildiyal Date: Mon, 3 Apr 2023 13:40:43 -0700 Subject: [PATCH 2/2] Add IPv6 address when calculating target endpoints within L7EndpointsCalculator --- pkg/neg/syncers/endpoints_calculator.go | 2 +- pkg/neg/syncers/utils.go | 84 +++++++---- pkg/neg/syncers/utils_test.go | 182 +++++++++++++++++++++++- pkg/neg/types/network_endpoint.go | 1 + 4 files changed, 242 insertions(+), 27 deletions(-) diff --git a/pkg/neg/syncers/endpoints_calculator.go b/pkg/neg/syncers/endpoints_calculator.go index dea6ef8633..00c2fa5ba5 100644 --- a/pkg/neg/syncers/endpoints_calculator.go +++ b/pkg/neg/syncers/endpoints_calculator.go @@ -213,7 +213,7 @@ func (l *L7EndpointsCalculator) Mode() types.EndpointsCalculatorMode { // CalculateEndpoints determines the endpoints in the NEGs based on the current service endpoints and the current NEGs. func (l *L7EndpointsCalculator) CalculateEndpoints(eds []types.EndpointsData, _ map[string]types.NetworkEndpointSet) (map[string]types.NetworkEndpointSet, types.EndpointPodMap, int, error) { - result, err := toZoneNetworkEndpointMap(eds, l.zoneGetter, l.podLister, l.servicePortName, l.networkEndpointType) + result, err := toZoneNetworkEndpointMap(eds, l.zoneGetter, l.podLister, l.servicePortName, l.networkEndpointType, l.enableDualStackNEG) return result.NetworkEndpointSet, result.EndpointPodMap, result.DupCount, err } diff --git a/pkg/neg/syncers/utils.go b/pkg/neg/syncers/utils.go index a17b1f69da..223cba2745 100644 --- a/pkg/neg/syncers/utils.go +++ b/pkg/neg/syncers/utils.go @@ -18,6 +18,7 @@ package syncers import ( "fmt" + "net" "strconv" "strings" "time" @@ -225,15 +226,10 @@ type ZoneNetworkEndpointMapResult struct { } // toZoneNetworkEndpointMap translates addresses in endpoints object into zone and endpoints map, and also return the count for duplicated endpoints -func toZoneNetworkEndpointMap( - eds []negtypes.EndpointsData, - zoneGetter negtypes.ZoneGetter, - podLister cache.Indexer, - servicePortName string, - networkEndpointType negtypes.NetworkEndpointType, -) (ZoneNetworkEndpointMapResult, error) { +func toZoneNetworkEndpointMap(eds []negtypes.EndpointsData, zoneGetter negtypes.ZoneGetter, podLister cache.Indexer, servicePortName string, networkEndpointType negtypes.NetworkEndpointType, enableDualStackNEG bool) (ZoneNetworkEndpointMapResult, error) { zoneNetworkEndpointMap := map[string]negtypes.NetworkEndpointSet{} networkEndpointPodMap := negtypes.EndpointPodMap{} + ipsForPod := ipsForPod(eds) dupCount := 0 if eds == nil { klog.Errorf("Endpoint object is nil") @@ -262,7 +258,7 @@ func toZoneNetworkEndpointMap( foundMatchingPort = true for _, endpointAddress := range ed.Addresses { - if endpointAddress.AddressType != discovery.AddressTypeIPv4 { + if !enableDualStackNEG && endpointAddress.AddressType != discovery.AddressTypeIPv4 { klog.Infof("Skipping non IPv4 address: %q, in endpoint slice %s/%s", endpointAddress.Addresses, ed.Meta.Namespace, ed.Meta.Name) continue } @@ -293,24 +289,28 @@ func toZoneNetworkEndpointMap( zoneNetworkEndpointMap[zone] = negtypes.NewNetworkEndpointSet() } - for _, address := range endpointAddress.Addresses { - networkEndpoint := negtypes.NetworkEndpoint{IP: address, Port: matchPort, Node: *endpointAddress.NodeName} - if networkEndpointType == negtypes.NonGCPPrivateEndpointType { - // Non-GCP network endpoints don't have associated nodes. - networkEndpoint.Node = "" - } - zoneNetworkEndpointMap[zone].Insert(networkEndpoint) - - // if existing name is alphabetically lower than current one, continue and don't replace - if existingPod, contains := networkEndpointPodMap[networkEndpoint]; contains { - dupCount += 1 - if existingPod.Name < endpointAddress.TargetRef.Name { - klog.Infof("Found duplicate endpoints for %q, save the pod information from the alphabetically higher pod", address) - continue - } + podIPs := ipsForPod[types.NamespacedName{Namespace: endpointAddress.TargetRef.Namespace, Name: endpointAddress.TargetRef.Name}] + networkEndpoint := negtypes.NetworkEndpoint{IP: podIPs.IP, Port: matchPort, Node: *endpointAddress.NodeName} + if enableDualStackNEG { + // Convert all addresses to a standard form as per rfc5952 to prevent + // accidental diffs resulting from different formats. + networkEndpoint.IPv6 = parseIPAddress(podIPs.IPv6) + } + if networkEndpointType == negtypes.NonGCPPrivateEndpointType { + // Non-GCP network endpoints don't have associated nodes. + networkEndpoint.Node = "" + } + zoneNetworkEndpointMap[zone].Insert(networkEndpoint) + + // if existing name is alphabetically lower than current one, continue and don't replace + if existingPod, contains := networkEndpointPodMap[networkEndpoint]; contains { + dupCount += 1 + if existingPod.Name < endpointAddress.TargetRef.Name { + klog.Infof("Found duplicate endpoints for %v, save the pod information from the alphabetically higher pod", networkEndpoint) + continue // if existing name is alphabetically lower than current one, continue and don't replace } - networkEndpointPodMap[networkEndpoint] = types.NamespacedName{Namespace: endpointAddress.TargetRef.Namespace, Name: endpointAddress.TargetRef.Name} } + networkEndpointPodMap[networkEndpoint] = types.NamespacedName{Namespace: endpointAddress.TargetRef.Namespace, Name: endpointAddress.TargetRef.Name} } } if !foundMatchingPort { @@ -457,6 +457,32 @@ func validatePod(pod *apiv1.Pod, nodeLister cache.Indexer) bool { return true } +// ipsForPod will return a mapping of pods to their IPv4 and IPv6 addresses. +func ipsForPod(eds []negtypes.EndpointsData) map[types.NamespacedName]negtypes.NetworkEndpoint { + result := make(map[types.NamespacedName]negtypes.NetworkEndpoint) + for _, ed := range eds { + for _, address := range ed.Addresses { + if address.TargetRef == nil || len(address.Addresses) < 1 { + continue + } + podNN := types.NamespacedName{Namespace: address.TargetRef.Namespace, Name: address.TargetRef.Name} + ne := result[podNN] + if address.AddressType == discovery.AddressTypeIPv4 { + // See the discussion in https://issue.k8s.io/106267 regarding why it is + // valid if we just pick the first IP. + ne.IP = address.Addresses[0] + } + if address.AddressType == discovery.AddressTypeIPv6 { + // See the discussion in https://issue.k8s.io/106267 regarding why it is + // valid if we just pick the first IP. + ne.IPv6 = address.Addresses[0] + } + result[podNN] = ne + } + } + return result +} + // retrieveExistingZoneNetworkEndpointMap lists existing network endpoints in the neg and return the zone and endpoints map func retrieveExistingZoneNetworkEndpointMap(negName string, zoneGetter negtypes.ZoneGetter, cloud negtypes.NetworkEndpointGroupCloud, version meta.Version, mode negtypes.EndpointsCalculatorMode) (map[string]negtypes.NetworkEndpointSet, error) { // Include zones that have non-candidate nodes currently. It is possible that NEGs were created in those zones previously and the endpoints now became non-candidates. @@ -525,3 +551,13 @@ func makeEndpointBatch(endpoints negtypes.NetworkEndpointSet, negType negtypes.N } return endpointBatch, nil } + +// parseIPAddress is used to normalize the given IPv4 or IPv6 address. If the +// address is invalid, an empty string is returned. +func parseIPAddress(address string) string { + result := net.ParseIP(address).String() + if result == "" { + return "" + } + return result +} diff --git a/pkg/neg/syncers/utils_test.go b/pkg/neg/syncers/utils_test.go index d36789572d..8bab128aa8 100644 --- a/pkg/neg/syncers/utils_test.go +++ b/pkg/neg/syncers/utils_test.go @@ -453,6 +453,7 @@ func TestToZoneNetworkEndpointMap(t *testing.T) { wantZoneNetworkEndpointMap map[string]negtypes.NetworkEndpointSet wantNetworkEndpointPodMap negtypes.EndpointPodMap networkEndpointType negtypes.NetworkEndpointType + enableDualStackNEG bool }{ { desc: "target port does not exist", @@ -511,6 +512,32 @@ func TestToZoneNetworkEndpointMap(t *testing.T) { }, networkEndpointType: negtypes.VmIpPortEndpointType, }, + { + desc: "dual stack enabled with explicitly named service ports", + portName: testNamedPort, + wantZoneNetworkEndpointMap: map[string]negtypes.NetworkEndpointSet{ + negtypes.TestZone1: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.2.2", Node: "instance2", Port: "81"}, + }...), + negtypes.TestZone2: negtypes.NewNetworkEndpointSet([]negtypes.NetworkEndpoint{ + {IP: "10.100.4.1", Node: "instance4", Port: "81"}, + {IP: "10.100.3.2", IPv6: "a:b::1", Node: "instance3", Port: "8081"}, + {IP: "10.100.4.2", IPv6: "a:b::2", Node: "instance4", Port: "8081"}, + {IP: "10.100.4.3", Node: "instance4", Port: "81"}, + {IP: "10.100.4.4", IPv6: "a:b::3", Node: "instance4", Port: "8081"}, + }...), + }, + wantNetworkEndpointPodMap: negtypes.EndpointPodMap{ + {IP: "10.100.2.2", Node: "instance2", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod7"}, + {IP: "10.100.4.1", Node: "instance4", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod8"}, + {IP: "10.100.4.3", Node: "instance4", Port: "81"}: {Namespace: testServiceNamespace, Name: "pod9"}, + {IP: "10.100.3.2", IPv6: "a:b::1", Node: "instance3", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod10"}, + {IP: "10.100.4.2", IPv6: "a:b::2", Node: "instance4", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod11"}, + {IP: "10.100.4.4", IPv6: "a:b::3", Node: "instance4", Port: "8081"}: {Namespace: testServiceNamespace, Name: "pod12"}, + }, + networkEndpointType: negtypes.VmIpPortEndpointType, + enableDualStackNEG: true, + }, { desc: "non GCP network endpoints", portName: "", @@ -540,7 +567,7 @@ func TestToZoneNetworkEndpointMap(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - gotResult, err := toZoneNetworkEndpointMap(negtypes.EndpointsDataFromEndpointSlices(getDefaultEndpointSlices()), zoneGetter, podLister, tc.portName, tc.networkEndpointType) + gotResult, err := toZoneNetworkEndpointMap(negtypes.EndpointsDataFromEndpointSlices(getDefaultEndpointSlices()), zoneGetter, podLister, tc.portName, tc.networkEndpointType, tc.enableDualStackNEG) if err != nil { t.Errorf("toZoneNetworkEndpointMap() = err %v, want no error", err) } @@ -556,6 +583,117 @@ func TestToZoneNetworkEndpointMap(t *testing.T) { } } +func TestIpsForPod(t *testing.T) { + t.Parallel() + testCases := []struct { + desc string + input []negtypes.EndpointsData + want map[types.NamespacedName]negtypes.NetworkEndpoint + }{ + { + desc: "normal", + input: negtypes.EndpointsDataFromEndpointSlices([]*discovery.EndpointSlice{ + { + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"10.0.0.1"}, + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod1"}, + }, + }, + }, + { + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"10.0.0.2"}, + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod2"}, + }, + }, + }, + { + AddressType: discovery.AddressTypeIPv6, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"a:b::1"}, + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod1"}, + }, + { + Addresses: []string{"a:b::2"}, + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod2"}, + }, + }, + }, + }), + want: map[types.NamespacedName]negtypes.NetworkEndpoint{ + {Namespace: "ns", Name: "pod1"}: {IP: "10.0.0.1", IPv6: "a:b::1"}, + {Namespace: "ns", Name: "pod2"}: {IP: "10.0.0.2", IPv6: "a:b::2"}, + }, + }, + { + desc: "should skip endpoints without any address or target", + input: negtypes.EndpointsDataFromEndpointSlices([]*discovery.EndpointSlice{ + { + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"10.0.0.1"}, + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod1"}, + }, + { + // Endpoint without any address. + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod2"}, + }, + { + // Endpoint without any target. + Addresses: []string{"10.0.0.2"}, + }, + }, + }, + }), + want: map[types.NamespacedName]negtypes.NetworkEndpoint{ + {Namespace: "ns", Name: "pod1"}: {IP: "10.0.0.1"}, + }, + }, + { + desc: "should ignore additional addresses in the same endpoint", + input: negtypes.EndpointsDataFromEndpointSlices([]*discovery.EndpointSlice{ + { + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"10.0.0.1", "10.0.0.2"}, // More than 1 address. + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod1"}, + }, + }, + }, + { + AddressType: discovery.AddressTypeIPv6, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"a:b::1", "a:b::2", "a:b::3"}, // More than 1 address. + TargetRef: &v1.ObjectReference{Namespace: "ns", Name: "pod1"}, + }, + }, + }, + }), + want: map[types.NamespacedName]negtypes.NetworkEndpoint{ + {Namespace: "ns", Name: "pod1"}: {IP: "10.0.0.1", IPv6: "a:b::1"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + got := ipsForPod(tc.input) + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("ipsForPods(tc.input) returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} + func TestRetrieveExistingZoneNetworkEndpointMap(t *testing.T) { zoneGetter := negtypes.NewFakeZoneGetter() negCloud := negtypes.NewFakeNetworkEndpointGroupCloud("test-subnetwork", "test-network") @@ -1547,7 +1685,47 @@ func TestValidatePod(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { if got := validatePod(tc.pod, nodeLister); got != tc.expect { - t.Errorf("validatePod() = %t, expected %t", got, tc.expect) + t.Errorf("validatePod() = %t, expected %t\n", got, tc.expect) + } + }) + } +} + +func TestParseIPAddress(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + input string + want string + }{ + { + desc: "normal IPv4", + input: "10.0.0.1", + want: "10.0.0.1", + }, + { + desc: "normal IPv6", + input: "a::b", + want: "a::b", + }, + { + desc: "empty address", + input: "", + want: "", + }, + { + desc: "invalid IP address", + input: "random string", + want: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + got := parseIPAddress(tc.input) + if got != tc.want { + t.Errorf("parseIPAddress(%v)=%q; want=%q", tc.input, got, tc.want) } }) } diff --git a/pkg/neg/types/network_endpoint.go b/pkg/neg/types/network_endpoint.go index 50f19bd7f9..e92550550d 100644 --- a/pkg/neg/types/network_endpoint.go +++ b/pkg/neg/types/network_endpoint.go @@ -23,6 +23,7 @@ import ( // NetworkEndpoint contains the essential information for each network endpoint in a NEG type NetworkEndpoint struct { IP string + IPv6 string Port string Node string }