mirror of https://github.com/linkerd/linkerd2.git
370 lines
10 KiB
Go
370 lines
10 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/ptypes/duration"
|
|
"github.com/linkerd/linkerd2/controller/api/public"
|
|
"github.com/linkerd/linkerd2/controller/api/util"
|
|
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
|
"github.com/linkerd/linkerd2/pkg/addr"
|
|
"github.com/linkerd/linkerd2/pkg/k8s"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
func busyTest(t *testing.T, wide bool) {
|
|
resourceType := k8s.Pod
|
|
targetName := "pod-666"
|
|
params := util.TapRequestParams{
|
|
Resource: resourceType + "/" + targetName,
|
|
Scheme: "https",
|
|
Method: "GET",
|
|
Authority: "localhost",
|
|
Path: "/some/path",
|
|
}
|
|
|
|
req, err := util.BuildTapByResourceRequest(params)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
event1 := createEvent(
|
|
&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_RequestInit_{
|
|
RequestInit: &pb.TapEvent_Http_RequestInit{
|
|
Id: &pb.TapEvent_Http_StreamId{
|
|
Base: 1,
|
|
},
|
|
Authority: params.Authority,
|
|
Path: params.Path,
|
|
},
|
|
},
|
|
},
|
|
map[string]string{
|
|
"pod": "my-pod",
|
|
"tls": "true",
|
|
},
|
|
)
|
|
event2 := createEvent(
|
|
&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseEnd_{
|
|
ResponseEnd: &pb.TapEvent_Http_ResponseEnd{
|
|
Id: &pb.TapEvent_Http_StreamId{
|
|
Base: 1,
|
|
},
|
|
Eos: &pb.Eos{
|
|
End: &pb.Eos_GrpcStatusCode{GrpcStatusCode: 666},
|
|
},
|
|
SinceRequestInit: &duration.Duration{
|
|
Seconds: 10,
|
|
},
|
|
SinceResponseInit: &duration.Duration{
|
|
Seconds: 100,
|
|
},
|
|
ResponseBytes: 1337,
|
|
},
|
|
},
|
|
},
|
|
map[string]string{},
|
|
)
|
|
mockAPIClient := &public.MockAPIClient{}
|
|
mockAPIClient.APITapByResourceClientToReturn = &public.MockAPITapByResourceClient{
|
|
TapEventsToReturn: []pb.TapEvent{event1, event2},
|
|
}
|
|
|
|
writer := bytes.NewBufferString("")
|
|
err = requestTapByResourceFromAPI(writer, mockAPIClient, req, wide)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var goldenFilePath string
|
|
if wide {
|
|
goldenFilePath = "testdata/tap_busy_output_wide.golden"
|
|
} else {
|
|
goldenFilePath = "testdata/tap_busy_output.golden"
|
|
}
|
|
|
|
goldenFileBytes, err := ioutil.ReadFile(goldenFilePath)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectedContent := string(goldenFileBytes)
|
|
output := writer.String()
|
|
if expectedContent != output {
|
|
t.Fatalf("Expected function to render:\n%s\bbut got:\n%s", expectedContent, output)
|
|
}
|
|
}
|
|
|
|
func TestRequestTapByResourceFromAPI(t *testing.T) {
|
|
t.Run("Should render busy response if everything went well", func(t *testing.T) {
|
|
busyTest(t, false)
|
|
})
|
|
|
|
t.Run("Should render wide busy response if everything went well", func(t *testing.T) {
|
|
busyTest(t, true)
|
|
})
|
|
|
|
t.Run("Should render empty response if no events returned", func(t *testing.T) {
|
|
resourceType := k8s.Pod
|
|
targetName := "pod-666"
|
|
params := util.TapRequestParams{
|
|
Resource: resourceType + "/" + targetName,
|
|
Scheme: "https",
|
|
Method: "GET",
|
|
Authority: "localhost",
|
|
Path: "/some/path",
|
|
}
|
|
|
|
req, err := util.BuildTapByResourceRequest(params)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
mockAPIClient := &public.MockAPIClient{}
|
|
mockAPIClient.APITapByResourceClientToReturn = &public.MockAPITapByResourceClient{
|
|
TapEventsToReturn: []pb.TapEvent{},
|
|
}
|
|
|
|
writer := bytes.NewBufferString("")
|
|
err = requestTapByResourceFromAPI(writer, mockAPIClient, req, false)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
goldenFileBytes, err := ioutil.ReadFile("testdata/tap_empty_output.golden")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
expectedContent := string(goldenFileBytes)
|
|
output := writer.String()
|
|
if expectedContent != output {
|
|
t.Fatalf("Expected function to render:\n%s\bbut got:\n%s", expectedContent, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Should return error if stream returned error", func(t *testing.T) {
|
|
t.SkipNow()
|
|
resourceType := k8s.Pod
|
|
targetName := "pod-666"
|
|
params := util.TapRequestParams{
|
|
Resource: resourceType + "/" + targetName,
|
|
Scheme: "https",
|
|
Method: "GET",
|
|
Authority: "localhost",
|
|
Path: "/some/path",
|
|
}
|
|
|
|
req, err := util.BuildTapByResourceRequest(params)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
mockAPIClient := &public.MockAPIClient{}
|
|
mockAPIClient.APITapByResourceClientToReturn = &public.MockAPITapByResourceClient{
|
|
ErrorsToReturn: []error{errors.New("expected")},
|
|
}
|
|
|
|
writer := bytes.NewBufferString("")
|
|
err = requestTapByResourceFromAPI(writer, mockAPIClient, req, false)
|
|
if err == nil {
|
|
t.Fatalf("Expecting error, got nothing but output [%s]", writer.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestEventToString(t *testing.T) {
|
|
toTapEvent := func(httpEvent *pb.TapEvent_Http) *pb.TapEvent {
|
|
streamID := &pb.TapEvent_Http_StreamId{
|
|
Base: 7,
|
|
Stream: 8,
|
|
}
|
|
|
|
switch httpEvent.Event.(type) {
|
|
case *pb.TapEvent_Http_RequestInit_:
|
|
httpEvent.GetRequestInit().Id = streamID
|
|
case *pb.TapEvent_Http_ResponseInit_:
|
|
httpEvent.GetResponseInit().Id = streamID
|
|
case *pb.TapEvent_Http_ResponseEnd_:
|
|
httpEvent.GetResponseEnd().Id = streamID
|
|
}
|
|
|
|
return &pb.TapEvent{
|
|
ProxyDirection: pb.TapEvent_OUTBOUND,
|
|
Source: &pb.TcpAddress{
|
|
Ip: addr.PublicIPV4(1, 2, 3, 4),
|
|
Port: 5555,
|
|
},
|
|
Destination: &pb.TcpAddress{
|
|
Ip: addr.PublicIPV4(2, 3, 4, 5),
|
|
Port: 6666,
|
|
},
|
|
Event: &pb.TapEvent_Http_{Http: httpEvent},
|
|
}
|
|
}
|
|
|
|
t.Run("Converts HTTP request init event to string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_RequestInit_{
|
|
RequestInit: &pb.TapEvent_Http_RequestInit{
|
|
Method: &pb.HttpMethod{
|
|
Type: &pb.HttpMethod_Registered_{
|
|
Registered: pb.HttpMethod_POST,
|
|
},
|
|
},
|
|
Scheme: &pb.Scheme{
|
|
Type: &pb.Scheme_Registered_{
|
|
Registered: pb.Scheme_HTTPS,
|
|
},
|
|
},
|
|
Authority: "hello.default:7777",
|
|
Path: "/hello.v1.HelloService/Hello",
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "req id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= :method=POST :authority=hello.default:7777 :path=/hello.v1.HelloService/Hello"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Converts HTTP response init event to string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseInit_{
|
|
ResponseInit: &pb.TapEvent_Http_ResponseInit{
|
|
SinceRequestInit: &duration.Duration{Nanos: 999000},
|
|
HttpStatus: http.StatusOK,
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "rsp id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= :status=200 latency=999µs"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Converts gRPC response end event to string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseEnd_{
|
|
ResponseEnd: &pb.TapEvent_Http_ResponseEnd{
|
|
SinceRequestInit: &duration.Duration{Nanos: 999000},
|
|
SinceResponseInit: &duration.Duration{Nanos: 888000},
|
|
ResponseBytes: 111,
|
|
Eos: &pb.Eos{
|
|
End: &pb.Eos_GrpcStatusCode{GrpcStatusCode: uint32(codes.OK)},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= grpc-status=OK duration=888µs response-length=111B"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Converts HTTP response end event with reset error code to string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseEnd_{
|
|
ResponseEnd: &pb.TapEvent_Http_ResponseEnd{
|
|
SinceRequestInit: &duration.Duration{Nanos: 999000},
|
|
SinceResponseInit: &duration.Duration{Nanos: 888000},
|
|
ResponseBytes: 111,
|
|
Eos: &pb.Eos{
|
|
End: &pb.Eos_ResetErrorCode{ResetErrorCode: 123},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= reset-error=123 duration=888µs response-length=111B"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Converts HTTP response end event with empty EOS context string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseEnd_{
|
|
ResponseEnd: &pb.TapEvent_Http_ResponseEnd{
|
|
SinceRequestInit: &duration.Duration{Nanos: 999000},
|
|
SinceResponseInit: &duration.Duration{Nanos: 888000},
|
|
ResponseBytes: 111,
|
|
Eos: &pb.Eos{},
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= duration=888µs response-length=111B"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Converts HTTP response end event without EOS context string", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{
|
|
Event: &pb.TapEvent_Http_ResponseEnd_{
|
|
ResponseEnd: &pb.TapEvent_Http_ResponseEnd{
|
|
SinceRequestInit: &duration.Duration{Nanos: 999000},
|
|
SinceResponseInit: &duration.Duration{Nanos: 888000},
|
|
ResponseBytes: 111,
|
|
},
|
|
},
|
|
})
|
|
|
|
expectedOutput := "end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= duration=888µs response-length=111B"
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
|
|
t.Run("Handles unknown event types", func(t *testing.T) {
|
|
event := toTapEvent(&pb.TapEvent_Http{})
|
|
|
|
expectedOutput := "unknown proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls="
|
|
output := renderTapEvent(event, "")
|
|
if output != expectedOutput {
|
|
t.Fatalf("Expecting command output to be [%s], got [%s]", expectedOutput, output)
|
|
}
|
|
})
|
|
}
|
|
|
|
func createEvent(eventHTTP *pb.TapEvent_Http, dstMeta map[string]string) pb.TapEvent {
|
|
event := pb.TapEvent{
|
|
ProxyDirection: pb.TapEvent_OUTBOUND,
|
|
Source: &pb.TcpAddress{
|
|
Ip: &pb.IPAddress{
|
|
Ip: &pb.IPAddress_Ipv4{
|
|
Ipv4: uint32(1),
|
|
},
|
|
},
|
|
},
|
|
Destination: &pb.TcpAddress{
|
|
Ip: &pb.IPAddress{
|
|
Ip: &pb.IPAddress_Ipv4{
|
|
Ipv4: uint32(9),
|
|
},
|
|
},
|
|
},
|
|
Event: &pb.TapEvent_Http_{
|
|
Http: eventHTTP,
|
|
},
|
|
DestinationMeta: &pb.TapEvent_EndpointMeta{
|
|
Labels: dstMeta,
|
|
},
|
|
}
|
|
return event
|
|
}
|