mirror of https://github.com/linkerd/linkerd2.git
277 lines
8.5 KiB
Go
277 lines
8.5 KiB
Go
package public
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
destinationPb "github.com/linkerd/linkerd2-proxy-api/go/destination"
|
|
healcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
|
|
configPb "github.com/linkerd/linkerd2/controller/gen/config"
|
|
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
|
)
|
|
|
|
type mockServer struct {
|
|
LastRequestReceived proto.Message
|
|
ResponseToReturn proto.Message
|
|
DestinationStreamsToReturn []*destinationPb.Update
|
|
ErrorToReturn error
|
|
}
|
|
|
|
type mockGrpcServer struct {
|
|
mockServer
|
|
DestinationStreamsToReturn []*destinationPb.Update
|
|
}
|
|
|
|
func (m *mockGrpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest) (*pb.StatSummaryResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.StatSummaryResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) TopRoutes(ctx context.Context, req *pb.TopRoutesRequest) (*pb.TopRoutesResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.TopRoutesResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) Edges(ctx context.Context, req *pb.EdgesRequest) (*pb.EdgesResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.EdgesResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) Version(ctx context.Context, req *pb.Empty) (*pb.VersionInfo, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.VersionInfo), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) ListPods(ctx context.Context, req *pb.ListPodsRequest) (*pb.ListPodsResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.ListPodsResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) ListServices(ctx context.Context, req *pb.ListServicesRequest) (*pb.ListServicesResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*pb.ListServicesResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) SelfCheck(ctx context.Context, req *healcheckPb.SelfCheckRequest) (*healcheckPb.SelfCheckResponse, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*healcheckPb.SelfCheckResponse), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) Config(ctx context.Context, req *pb.Empty) (*configPb.All, error) {
|
|
m.LastRequestReceived = req
|
|
return m.ResponseToReturn.(*configPb.All), m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) Tap(req *pb.TapRequest, tapServer pb.Api_TapServer) error {
|
|
m.LastRequestReceived = req
|
|
if m.ErrorToReturn != nil {
|
|
// Not implemented in public API. Instead, use tap APIServer.
|
|
return errors.New("Not implemented")
|
|
}
|
|
|
|
return m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) TapByResource(req *pb.TapByResourceRequest, tapServer pb.Api_TapByResourceServer) error {
|
|
m.LastRequestReceived = req
|
|
if m.ErrorToReturn != nil {
|
|
// Not implemented in public API. Instead, use tap APIServer.
|
|
return errors.New("Not implemented")
|
|
}
|
|
|
|
return m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) Get(req *destinationPb.GetDestination, destinationServer destinationPb.Destination_GetServer) error {
|
|
m.LastRequestReceived = req
|
|
if m.ErrorToReturn == nil {
|
|
for _, msg := range m.DestinationStreamsToReturn {
|
|
destinationServer.Send(msg)
|
|
}
|
|
}
|
|
|
|
return m.ErrorToReturn
|
|
}
|
|
|
|
func (m *mockGrpcServer) GetProfile(_ *destinationPb.GetDestination, _ destinationPb.Destination_GetProfileServer) error {
|
|
// Not implemented in the Public API. Instead, the proxies should reach the Destination gRPC server directly.
|
|
return errors.New("Not implemented")
|
|
}
|
|
|
|
type grpcCallTestCase struct {
|
|
expectedRequest proto.Message
|
|
expectedResponse proto.Message
|
|
functionCall func() (proto.Message, error)
|
|
}
|
|
|
|
func TestServer(t *testing.T) {
|
|
t.Run("Delegates all non-streaming RPC messages to the underlying grpc server", func(t *testing.T) {
|
|
mockGrpcServer, client := getServerClient(t)
|
|
|
|
listPodsReq := &pb.ListPodsRequest{}
|
|
testListPods := grpcCallTestCase{
|
|
expectedRequest: listPodsReq,
|
|
expectedResponse: &pb.ListPodsResponse{
|
|
Pods: []*pb.Pod{
|
|
{Status: "ok-ish"},
|
|
},
|
|
},
|
|
functionCall: func() (proto.Message, error) { return client.ListPods(context.TODO(), listPodsReq) },
|
|
}
|
|
|
|
statSummaryReq := &pb.StatSummaryRequest{}
|
|
testStatSummary := grpcCallTestCase{
|
|
expectedRequest: statSummaryReq,
|
|
expectedResponse: &pb.StatSummaryResponse{},
|
|
functionCall: func() (proto.Message, error) { return client.StatSummary(context.TODO(), statSummaryReq) },
|
|
}
|
|
|
|
versionReq := &pb.Empty{}
|
|
testVersion := grpcCallTestCase{
|
|
expectedRequest: versionReq,
|
|
expectedResponse: &pb.VersionInfo{
|
|
BuildDate: "02/21/1983",
|
|
},
|
|
functionCall: func() (proto.Message, error) { return client.Version(context.TODO(), versionReq) },
|
|
}
|
|
|
|
for _, testCase := range []grpcCallTestCase{testListPods, testStatSummary, testVersion} {
|
|
assertCallWasForwarded(t, &mockGrpcServer.mockServer, testCase.expectedRequest, testCase.expectedResponse, testCase.functionCall)
|
|
}
|
|
})
|
|
|
|
t.Run("Delegates all streaming Destination RPC messages to the underlying grpc server", func(t *testing.T) {
|
|
mockGrpcServer, client := getServerClient(t)
|
|
|
|
expectedDestinationGetResponses := []*destinationPb.Update{
|
|
{
|
|
Update: &destinationPb.Update_Add{
|
|
Add: BuildAddrSet(
|
|
AuthorityEndpoints{
|
|
Namespace: "emojivoto",
|
|
ServiceID: "emoji-svc",
|
|
Pods: []PodDetails{
|
|
{
|
|
Name: "emoji-6bf9f47bd5-jjcrl",
|
|
IP: 16909060,
|
|
Port: 8080,
|
|
},
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
{
|
|
Update: &destinationPb.Update_Add{
|
|
Add: BuildAddrSet(
|
|
AuthorityEndpoints{
|
|
Namespace: "emojivoto",
|
|
ServiceID: "voting-svc",
|
|
Pods: []PodDetails{
|
|
{
|
|
Name: "voting-7bf9f47bd5-jjdrl",
|
|
IP: 84281096,
|
|
Port: 8080,
|
|
},
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
}
|
|
mockGrpcServer.DestinationStreamsToReturn = expectedDestinationGetResponses
|
|
mockGrpcServer.ErrorToReturn = nil
|
|
|
|
destinationGetClient, err := client.Get(context.TODO(), &destinationPb.GetDestination{})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
for _, expectedDestinationGetEvent := range expectedDestinationGetResponses {
|
|
actualDestinationGetEvent, err := destinationGetClient.Recv()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if !proto.Equal(actualDestinationGetEvent, expectedDestinationGetEvent) {
|
|
t.Fatalf("Expecting destination.get event to be [%v], but was [%v]", expectedDestinationGetEvent, actualDestinationGetEvent)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Handles Tap route errors before opening keep-alive response", func(t *testing.T) {
|
|
mockGrpcServer, client := getServerClient(t)
|
|
|
|
mockGrpcServer.ErrorToReturn = errors.New("expected error")
|
|
|
|
_, err := client.Tap(context.TODO(), &pb.TapRequest{})
|
|
if err == nil {
|
|
t.Fatalf("Expecting error, got nothing")
|
|
}
|
|
})
|
|
|
|
t.Run("Handles TapByResource route errors before opening keep-alive response", func(t *testing.T) {
|
|
mockGrpcServer, client := getServerClient(t)
|
|
|
|
mockGrpcServer.ErrorToReturn = errors.New("expected error")
|
|
|
|
_, err := client.TapByResource(context.TODO(), &pb.TapByResourceRequest{})
|
|
if err == nil {
|
|
t.Fatalf("Expecting error, got nothing")
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
func getServerClient(t *testing.T) (*mockGrpcServer, APIClient) {
|
|
mockGrpcServer := &mockGrpcServer{}
|
|
|
|
listener, err := net.Listen("tcp", "localhost:0")
|
|
if err != nil {
|
|
t.Fatalf("Could not start listener: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
handler := &handler{
|
|
grpcServer: mockGrpcServer,
|
|
}
|
|
err := http.Serve(listener, handler)
|
|
if err != nil {
|
|
t.Fatalf("Could not start server: %v", err)
|
|
}
|
|
}()
|
|
|
|
client, err := NewInternalClient("linkerd", listener.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
return mockGrpcServer, client
|
|
}
|
|
|
|
func assertCallWasForwarded(t *testing.T, mockServer *mockServer, expectedRequest proto.Message, expectedResponse proto.Message, functionCall func() (proto.Message, error)) {
|
|
mockServer.ErrorToReturn = nil
|
|
mockServer.ResponseToReturn = expectedResponse
|
|
actualResponse, err := functionCall()
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
actualRequest := mockServer.LastRequestReceived
|
|
if !proto.Equal(actualRequest, expectedRequest) {
|
|
t.Fatalf("Expecting server call to return [%v], but got [%v]", expectedRequest, actualRequest)
|
|
}
|
|
if !proto.Equal(actualResponse, expectedResponse) {
|
|
t.Fatalf("Expecting server call to return [%v], but got [%v]", expectedResponse, actualResponse)
|
|
}
|
|
|
|
mockServer.ErrorToReturn = errors.New("expected")
|
|
_, err = functionCall()
|
|
if err == nil {
|
|
t.Fatalf("Expecting error, got nothing")
|
|
}
|
|
}
|