package destination import ( "reflect" "sort" "testing" pb "github.com/linkerd/linkerd2-proxy-api/go/destination" "github.com/linkerd/linkerd2-proxy-api/go/net" "github.com/linkerd/linkerd2/controller/api/destination/watcher" "github.com/linkerd/linkerd2/pkg/addr" "github.com/linkerd/linkerd2/pkg/k8s" logging "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( normalPod = watcher.Address{ IP: "1.1.1.1", Port: 1, Pod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod1", Namespace: "ns", Annotations: map[string]string{ k8s.IdentityModeAnnotation: k8s.IdentityModeDefault, }, Labels: map[string]string{ k8s.ControllerNSLabel: "linkerd", k8s.ProxyDeploymentLabel: "deployment-name", }, }, Spec: corev1.PodSpec{ ServiceAccountName: "serviceaccount-name", }, }, OwnerKind: "replicationcontroller", OwnerName: "rc-name", } tlsOptionalPod = watcher.Address{ IP: "1.1.1.2", Port: 2, Pod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod2", Namespace: "ns", Annotations: map[string]string{ k8s.IdentityModeAnnotation: "optional", }, Labels: map[string]string{ k8s.ControllerNSLabel: "linkerd", k8s.ProxyDeploymentLabel: "deployment-name", }, }, }, } otherMeshPod = watcher.Address{ IP: "1.1.1.3", Port: 3, Pod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod3", Namespace: "ns", Annotations: map[string]string{ k8s.IdentityModeAnnotation: k8s.IdentityModeDefault, }, Labels: map[string]string{ k8s.ControllerNSLabel: "other-linkerd-namespace", k8s.ProxyDeploymentLabel: "deployment-name", }, }, }, } tlsDisabledPod = watcher.Address{ IP: "1.1.1.4", Port: 4, Pod: &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "pod4", Namespace: "ns", Annotations: map[string]string{ k8s.IdentityModeAnnotation: k8s.IdentityModeDisabled, }, Labels: map[string]string{ k8s.ControllerNSLabel: "linkerd", k8s.ProxyDeploymentLabel: "deployment-name", }, }, }, } ) func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) { mockGetServer := &mockDestinationGetServer{updatesReceived: []*pb.Update{}} translator := newEndpointTranslator( "linkerd", "trust.domain", false, watcher.ServiceID{Name: "service-name", Namespace: "service-ns"}, mockGetServer, logging.WithField("test", t.Name), ) return mockGetServer, translator } func TestEndpointTranslator(t *testing.T) { t.Run("Sends one update for add and another for remove", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(normalPod)) translator.Remove(mkPodSet(tlsOptionalPod)) expectedNumUpdates := 2 actualNumUpdates := len(mockGetServer.updatesReceived) if actualNumUpdates != expectedNumUpdates { t.Fatalf("Expecting [%d] updates, got [%d]. Updates: %v", expectedNumUpdates, actualNumUpdates, mockGetServer.updatesReceived) } }) t.Run("Sends addresses as removed or added", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(normalPod, tlsOptionalPod)) translator.Remove(mkPodSet(tlsDisabledPod)) addressesAdded := mockGetServer.updatesReceived[0].GetAdd().Addrs actualNumberOfAdded := len(addressesAdded) expectedNumberOfAdded := 2 if actualNumberOfAdded != expectedNumberOfAdded { t.Fatalf("Expecting [%d] addresses to be added, got [%d]: %v", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded) } addressesRemoved := mockGetServer.updatesReceived[1].GetRemove().Addrs actualNumberOfRemoved := len(addressesRemoved) expectedNumberOfRemoved := 1 if actualNumberOfRemoved != expectedNumberOfRemoved { t.Fatalf("Expecting [%d] addresses to be removed, got [%d]: %v", expectedNumberOfRemoved, actualNumberOfRemoved, addressesRemoved) } sort.Slice(addressesAdded, func(i, j int) bool { return addressesAdded[i].GetAddr().Port < addressesAdded[j].GetAddr().Port }) checkAddressAndWeight(t, addressesAdded[0], normalPod) checkAddressAndWeight(t, addressesAdded[1], tlsOptionalPod) checkAddress(t, addressesRemoved[0], tlsDisabledPod) }) t.Run("Sends metric labels with added addresses", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(normalPod)) actualGlobalMetricLabels := mockGetServer.updatesReceived[0].GetAdd().MetricLabels expectedGlobalMetricLabels := map[string]string{"namespace": "service-ns", "service": "service-name"} if !reflect.DeepEqual(actualGlobalMetricLabels, expectedGlobalMetricLabels) { t.Fatalf("Expected global metric labels sent to be [%v] but was [%v]", expectedGlobalMetricLabels, actualGlobalMetricLabels) } actualAddedAddress1MetricLabels := mockGetServer.updatesReceived[0].GetAdd().Addrs[0].MetricLabels expectedAddedAddress1MetricLabels := map[string]string{ "pod": "pod1", "replicationcontroller": "rc-name", "serviceaccount": "serviceaccount-name", "control_plane_ns": "linkerd", } if !reflect.DeepEqual(actualAddedAddress1MetricLabels, expectedAddedAddress1MetricLabels) { t.Fatalf("Expected global metric labels sent to be [%v] but was [%v]", expectedAddedAddress1MetricLabels, actualAddedAddress1MetricLabels) } }) t.Run("Sends TlsIdentity when enabled", func(t *testing.T) { expectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{ Name: "serviceaccount-name.ns.serviceaccount.identity.linkerd.trust.domain", } mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(normalPod)) addrs := mockGetServer.updatesReceived[0].GetAdd().GetAddrs() if len(addrs) != 1 { t.Fatalf("Expected [1] address returned, got %v", addrs) } actualTLSIdentity := addrs[0].GetTlsIdentity().GetDnsLikeIdentity() if !reflect.DeepEqual(actualTLSIdentity, expectedTLSIdentity) { t.Fatalf("Expected TlsIdentity to be [%v] but was [%v]", expectedTLSIdentity, actualTLSIdentity) } }) t.Run("Does not send TlsIdentity for non-default identity-modes", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(tlsOptionalPod)) addrs := mockGetServer.updatesReceived[0].GetAdd().GetAddrs() if len(addrs) != 1 { t.Fatalf("Expected [1] address returned, got %v", addrs) } if addrs[0].TlsIdentity != nil { t.Fatalf("Expected no TlsIdentity to be sent, but got [%v]", addrs[0].TlsIdentity) } }) t.Run("Does not send TlsIdentity for other meshes", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(otherMeshPod)) addrs := mockGetServer.updatesReceived[0].GetAdd().GetAddrs() if len(addrs) != 1 { t.Fatalf("Expected [1] address returned, got %v", addrs) } if addrs[0].TlsIdentity != nil { t.Fatalf("Expected no TlsIdentity to be sent, but got [%v]", addrs[0].TlsIdentity) } }) t.Run("Does not send TlsIdentity when not enabled", func(t *testing.T) { mockGetServer, translator := makeEndpointTranslator(t) translator.Add(mkPodSet(tlsDisabledPod)) addrs := mockGetServer.updatesReceived[0].GetAdd().GetAddrs() if len(addrs) != 1 { t.Fatalf("Expected [1] address returned, got %v", addrs) } if addrs[0].TlsIdentity != nil { t.Fatalf("Expected no TlsIdentity to be sent, but got [%v]", addrs[0].TlsIdentity) } }) } func mkPodSet(pods ...watcher.Address) watcher.PodSet { set := make(watcher.PodSet) for _, p := range pods { id := watcher.PodID{Name: p.Pod.Name, Namespace: p.Pod.Namespace} set[id] = p } return set } func checkAddressAndWeight(t *testing.T, actual *pb.WeightedAddr, expected watcher.Address) { checkAddress(t, actual.GetAddr(), expected) if actual.GetWeight() != defaultWeight { t.Fatalf("Expected weight [%+v] but got [%+v]", defaultWeight, actual.GetWeight()) } } func checkAddress(t *testing.T, actual *net.TcpAddress, expected watcher.Address) { expectedAddr, err := addr.ParseProxyIPV4(expected.IP) expectedTCP := net.TcpAddress{ Ip: expectedAddr, Port: expected.Port, } if err != nil { t.Fatalf("Failed to parse expected IP [%s]: %s", expected.IP, err) } if !reflect.DeepEqual(*actual, expectedTCP) { t.Fatalf("Expected address [%+v] but got [%+v]", expectedTCP, *actual) } }