package util import ( "testing" "github.com/go-test/deep" "github.com/linkerd/linkerd2/pkg/k8s" pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestBuildStatSummaryRequest(t *testing.T) { t.Run("Maps Kubernetes friendly names to canonical names", func(t *testing.T) { expectations := map[string]string{ "deployments": k8s.Deployment, "deployment": k8s.Deployment, "deploy": k8s.Deployment, "pods": k8s.Pod, "pod": k8s.Pod, "po": k8s.Pod, } for friendly, canonical := range expectations { statSummaryRequest, err := BuildStatSummaryRequest( StatsSummaryRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ ResourceType: friendly, }, }, ) if err != nil { t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, err) } if statSummaryRequest.Selector.Resource.Type != canonical { t.Fatalf("Unexpected resource type from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, statSummaryRequest.Selector.Resource.Type) } } }) t.Run("Parses valid time windows", func(t *testing.T) { expectations := []string{ "1m", "60s", "1m", } for _, timeWindow := range expectations { statSummaryRequest, err := BuildStatSummaryRequest( StatsSummaryRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ TimeWindow: timeWindow, ResourceType: k8s.Deployment, }, }, ) if err != nil { t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]", timeWindow, err) } if statSummaryRequest.TimeWindow != timeWindow { t.Fatalf("Unexpected TimeWindow from BuildStatSummaryRequest [%s => %s]", timeWindow, statSummaryRequest.TimeWindow) } } }) t.Run("Rejects invalid time windows", func(t *testing.T) { expectations := map[string]string{ "1": "time: missing unit in duration \"1\"", "s": "time: invalid duration \"s\"", } for timeWindow, msg := range expectations { _, err := BuildStatSummaryRequest( StatsSummaryRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ TimeWindow: timeWindow, }, }, ) if err == nil { t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg) } if err.Error() != msg { t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err) } } }) t.Run("Rejects invalid Kubernetes resource types", func(t *testing.T) { expectations := map[string]string{ "foo": "cannot find Kubernetes canonical name from friendly name [foo]", "": "cannot find Kubernetes canonical name from friendly name []", } for input, msg := range expectations { _, err := BuildStatSummaryRequest( StatsSummaryRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ ResourceType: input, }, }, ) if err == nil { t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", input, msg) } if err.Error() != msg { t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", input, msg, err) } } }) } func TestBuildTopRoutesRequest(t *testing.T) { t.Run("Parses valid time windows", func(t *testing.T) { expectations := []string{ "1m", "60s", "1m", } for _, timeWindow := range expectations { topRoutesRequest, err := BuildTopRoutesRequest( TopRoutesRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ TimeWindow: timeWindow, ResourceType: k8s.Deployment, }, }, ) if err != nil { t.Fatalf("Unexpected error from BuildTopRoutesRequest [%s => %s]", timeWindow, err) } if topRoutesRequest.TimeWindow != timeWindow { t.Fatalf("Unexpected TimeWindow from BuildTopRoutesRequest [%s => %s]", timeWindow, topRoutesRequest.TimeWindow) } } }) t.Run("Rejects invalid time windows", func(t *testing.T) { expectations := map[string]string{ "1": "time: missing unit in duration \"1\"", "s": "time: invalid duration \"s\"", } for timeWindow, msg := range expectations { _, err := BuildTopRoutesRequest( TopRoutesRequestParams{ StatsBaseRequestParams: StatsBaseRequestParams{ TimeWindow: timeWindow, ResourceType: k8s.Deployment, }, }, ) if err == nil { t.Fatalf("BuildTopRoutesRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg) } if err.Error() != msg { t.Fatalf("BuildTopRoutesRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err) } } }) } func TestK8sPodToPublicPod(t *testing.T) { type podExp struct { k8sPod corev1.Pod ownerKind string ownerName string publicPod *pb.Pod } t.Run("Returns expected pods", func(t *testing.T) { expectations := []podExp{ { k8sPod: corev1.Pod{}, publicPod: &pb.Pod{ Name: "/", }, }, { k8sPod: corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns", Name: "name", ResourceVersion: "resource-version", Labels: map[string]string{ k8s.ControllerComponentLabel: "controller-component", k8s.ControllerNSLabel: "controller-ns", }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: k8s.ProxyContainerName, Image: "linkerd-proxy:test-version", }, }, }, Status: corev1.PodStatus{ PodIP: "pod-ip", Phase: "status", ContainerStatuses: []corev1.ContainerStatus{ { Name: k8s.ProxyContainerName, Ready: true, }, }, }, }, ownerKind: k8s.Deployment, ownerName: "owner-name", publicPod: &pb.Pod{ Name: "ns/name", Owner: &pb.Pod_Deployment{Deployment: "ns/owner-name"}, ResourceVersion: "resource-version", ControlPlane: true, ControllerNamespace: "controller-ns", Status: "status", ProxyReady: true, ProxyVersion: "test-version", PodIP: "pod-ip", }, }, { k8sPod: corev1.Pod{ Status: corev1.PodStatus{ Phase: "Failed", Reason: "Evicted", ContainerStatuses: []corev1.ContainerStatus{ { Name: k8s.ProxyContainerName, Ready: true, }, }, }, }, ownerName: "owner-name", publicPod: &pb.Pod{ Name: "/", Status: "Evicted", ProxyReady: true, }, }, } for _, exp := range expectations { res := K8sPodToPublicPod(exp.k8sPod, exp.ownerKind, exp.ownerName) if diff := deep.Equal(exp.publicPod, res); diff != nil { t.Errorf("%v", diff) } } }) }