mirror of https://github.com/linkerd/linkerd2.git
452 lines
14 KiB
Go
452 lines
14 KiB
Go
package destination
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"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"
|
|
pkgk8s "github.com/linkerd/linkerd2/controller/k8s"
|
|
"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",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
remoteGatewayWithNoTLS = watcher.Address{
|
|
IP: "1.1.1.1",
|
|
Port: 1,
|
|
}
|
|
|
|
remoteGatewayWithTLS = watcher.Address{
|
|
IP: "1.1.1.2",
|
|
Port: 2,
|
|
Identity: "some-identity",
|
|
}
|
|
|
|
remoteGatewayWithTLSAndAuthOverride = watcher.Address{
|
|
IP: "1.1.1.2",
|
|
Port: 2,
|
|
Identity: "some-identity",
|
|
AuthorityOverride: "some-auth.com:2",
|
|
}
|
|
)
|
|
|
|
func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {
|
|
k8sAPI, err := pkgk8s.NewFakeAPI(`
|
|
apiVersion: v1
|
|
kind: Node
|
|
metadata:
|
|
annotations:
|
|
kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
|
|
node.alpha.kubernetes.io/ttl: "0"
|
|
labels:
|
|
beta.kubernetes.io/arch: amd64
|
|
beta.kubernetes.io/os: linux
|
|
kubernetes.io/arch: amd64
|
|
kubernetes.io/hostname: kind-worker
|
|
kubernetes.io/os: linux
|
|
topology.kubernetes.io/region: west
|
|
topology.kubernetes.io/zone: west-1a
|
|
name: test-123
|
|
`,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewFakeAPI returned an error: %s", err)
|
|
}
|
|
k8sAPI.Sync(nil)
|
|
|
|
mockGetServer := &mockDestinationGetServer{updatesReceived: []*pb.Update{}}
|
|
translator := newEndpointTranslator(
|
|
"linkerd",
|
|
"trust.domain",
|
|
true,
|
|
"service-name.service-ns",
|
|
"test-123",
|
|
map[uint32]struct{}{},
|
|
k8sAPI.Node(),
|
|
mockGetServer,
|
|
logging.WithField("test", t.Name()),
|
|
)
|
|
return mockGetServer, translator
|
|
}
|
|
|
|
func TestEndpointTranslatorForRemoteGateways(t *testing.T) {
|
|
t.Run("Sends one update for add and another for remove", func(t *testing.T) {
|
|
mockGetServer, translator := makeEndpointTranslator(t)
|
|
|
|
translator.Add(mkAddressSetForServices(remoteGatewayWithNoTLS, remoteGatewayWithTLS))
|
|
translator.Remove(mkAddressSetForServices(remoteGatewayWithTLS))
|
|
|
|
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 TlsIdentity when enabled", func(t *testing.T) {
|
|
expectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{
|
|
Name: "some-identity",
|
|
}
|
|
|
|
expectedProtocolHint := &pb.ProtocolHint{
|
|
Protocol: &pb.ProtocolHint_H2_{
|
|
H2: &pb.ProtocolHint_H2{},
|
|
},
|
|
}
|
|
|
|
mockGetServer, translator := makeEndpointTranslator(t)
|
|
|
|
translator.Add(mkAddressSetForServices(remoteGatewayWithTLS))
|
|
|
|
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)
|
|
}
|
|
|
|
actualProtocolHint := addrs[0].GetProtocolHint()
|
|
if !reflect.DeepEqual(actualProtocolHint, expectedProtocolHint) {
|
|
t.Fatalf("Expected ProtocolHint to be [%v] but was [%v]", expectedProtocolHint, actualProtocolHint)
|
|
}
|
|
})
|
|
|
|
t.Run("Sends TlsIdentity and Auth override when present", func(t *testing.T) {
|
|
expectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{
|
|
Name: "some-identity",
|
|
}
|
|
|
|
expectedProtocolHint := &pb.ProtocolHint{
|
|
Protocol: &pb.ProtocolHint_H2_{
|
|
H2: &pb.ProtocolHint_H2{},
|
|
},
|
|
}
|
|
|
|
expectedAuthOverride := &pb.AuthorityOverride{
|
|
AuthorityOverride: "some-auth.com:2",
|
|
}
|
|
|
|
mockGetServer, translator := makeEndpointTranslator(t)
|
|
|
|
translator.Add(mkAddressSetForServices(remoteGatewayWithTLSAndAuthOverride))
|
|
|
|
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)
|
|
}
|
|
|
|
actualProtocolHint := addrs[0].GetProtocolHint()
|
|
if !reflect.DeepEqual(actualProtocolHint, expectedProtocolHint) {
|
|
t.Fatalf("Expected ProtocolHint to be [%v] but was [%v]", expectedProtocolHint, actualProtocolHint)
|
|
}
|
|
|
|
actualAuthOverride := addrs[0].GetAuthorityOverride()
|
|
if !reflect.DeepEqual(actualProtocolHint, expectedProtocolHint) {
|
|
t.Fatalf("Expected AuthOverride to be [%v] but was [%v]", expectedAuthOverride, actualAuthOverride)
|
|
}
|
|
})
|
|
|
|
t.Run("Does not send TlsIdentity when not present", func(t *testing.T) {
|
|
mockGetServer, translator := makeEndpointTranslator(t)
|
|
|
|
translator.Add(mkAddressSetForServices(remoteGatewayWithNoTLS))
|
|
|
|
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)
|
|
}
|
|
if addrs[0].ProtocolHint != nil {
|
|
t.Fatalf("Expected no ProtocolHint to be sent, but got [%v]", addrs[0].TlsIdentity)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
func TestEndpointTranslatorForPods(t *testing.T) {
|
|
t.Run("Sends one update for add and another for remove", func(t *testing.T) {
|
|
mockGetServer, translator := makeEndpointTranslator(t)
|
|
|
|
translator.Add(mkAddressSetForPods(normalPod, tlsOptionalPod))
|
|
translator.Remove(mkAddressSetForPods(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(mkAddressSetForPods(normalPod, tlsOptionalPod, tlsDisabledPod))
|
|
translator.Remove(mkAddressSetForPods(tlsDisabledPod))
|
|
|
|
addressesAdded := mockGetServer.updatesReceived[0].GetAdd().Addrs
|
|
actualNumberOfAdded := len(addressesAdded)
|
|
expectedNumberOfAdded := 3
|
|
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(mkAddressSetForPods(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(mkAddressSetForPods(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(mkAddressSetForPods(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(mkAddressSetForPods(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(mkAddressSetForPods(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 mkAddressSetForServices(gatewayAddresses ...watcher.Address) watcher.AddressSet {
|
|
set := watcher.AddressSet{
|
|
Addresses: make(map[watcher.ServiceID]watcher.Address),
|
|
Labels: map[string]string{"service": "service-name", "namespace": "service-ns"},
|
|
TopologicalPref: []string{},
|
|
}
|
|
for _, a := range gatewayAddresses {
|
|
a := a // pin
|
|
|
|
id := watcher.ServiceID{
|
|
Name: strings.Join([]string{
|
|
a.IP,
|
|
fmt.Sprint(a.Port),
|
|
}, "-"),
|
|
}
|
|
set.Addresses[id] = a
|
|
}
|
|
return set
|
|
}
|
|
|
|
func mkAddressSetForPods(podAddresses ...watcher.Address) watcher.AddressSet {
|
|
set := watcher.AddressSet{
|
|
Addresses: make(map[watcher.PodID]watcher.Address),
|
|
Labels: map[string]string{"service": "service-name", "namespace": "service-ns"},
|
|
TopologicalPref: []string{},
|
|
}
|
|
for _, p := range podAddresses {
|
|
id := watcher.PodID{Name: p.Pod.Name, Namespace: p.Pod.Namespace}
|
|
set.Addresses[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 actual.Ip.GetIpv4() != expectedTCP.Ip.GetIpv4() {
|
|
t.Fatalf("Expected IP [%+v] but got [%+v]", expectedTCP.Ip, actual.Ip)
|
|
}
|
|
if actual.Ip.GetIpv6() != expectedTCP.Ip.GetIpv6() {
|
|
t.Fatalf("Expected IP [%+v] but got [%+v]", expectedTCP.Ip, actual.Ip)
|
|
}
|
|
if actual.Port != expectedTCP.Port {
|
|
t.Fatalf("Expected port [%+v] but got [%+v]", expectedTCP.Port, actual.Port)
|
|
}
|
|
}
|