mirror of https://github.com/linkerd/linkerd2.git
Add unit test for edges API endpoint (#3306)
Fixes #3052. Adds a unit test for the edges API endpoint. To maintain a consistent order for testing, the returned rows in api/public/edges.go are now sorted.
This commit is contained in:
parent
653ec8c5b7
commit
089836842a
|
|
@ -7,107 +7,38 @@ import (
|
|||
)
|
||||
|
||||
type edgesParamsExp struct {
|
||||
options *edgesOptions
|
||||
resSrc []string
|
||||
resSrcNamespace []string
|
||||
resDst []string
|
||||
resDstNamespace []string
|
||||
resClient []string
|
||||
resServer []string
|
||||
resMsg []string
|
||||
resourceType string
|
||||
file string
|
||||
options *edgesOptions
|
||||
resourceType string
|
||||
file string
|
||||
}
|
||||
|
||||
func TestEdges(t *testing.T) {
|
||||
// response content for SRC, DST, SRC_NS, DST_NS, CLIENT_ID, SERVER_ID and MSG
|
||||
var (
|
||||
resSrc = []string{
|
||||
"web",
|
||||
"vote-bot",
|
||||
"web",
|
||||
"linkerd-controller",
|
||||
}
|
||||
resDst = []string{
|
||||
"voting",
|
||||
"web",
|
||||
"emoji",
|
||||
"linkerd-prometheus",
|
||||
}
|
||||
resSrcNamespace = []string{
|
||||
"emojivoto",
|
||||
"emojivoto",
|
||||
"emojivoto",
|
||||
"linkerd",
|
||||
}
|
||||
resDstNamespace = []string{
|
||||
"emojivoto",
|
||||
"emojivoto",
|
||||
"emojivoto",
|
||||
"linkerd",
|
||||
}
|
||||
resClient = []string{
|
||||
"web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"default.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"linkerd-controller.linkerd.identity.linkerd.cluster.local",
|
||||
}
|
||||
resServer = []string{
|
||||
"voting.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
"linkerd-prometheus.linkerd.identity.linkerd.cluster.local",
|
||||
}
|
||||
resMsg = []string{"", "", "", ""}
|
||||
)
|
||||
|
||||
options := newEdgesOptions()
|
||||
options.outputFormat = tableOutput
|
||||
options.allNamespaces = true
|
||||
t.Run("Returns edges", func(t *testing.T) {
|
||||
testEdgesCall(edgesParamsExp{
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
resSrc: resSrc,
|
||||
resSrcNamespace: resSrcNamespace,
|
||||
resDst: resDst,
|
||||
resDstNamespace: resDstNamespace,
|
||||
resClient: resClient,
|
||||
resServer: resServer,
|
||||
resMsg: resMsg,
|
||||
file: "edges_one_output.golden",
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
file: "edges_one_output.golden",
|
||||
}, t)
|
||||
})
|
||||
|
||||
options.outputFormat = jsonOutput
|
||||
t.Run("Returns edges (json)", func(t *testing.T) {
|
||||
testEdgesCall(edgesParamsExp{
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
resSrc: resSrc,
|
||||
resSrcNamespace: resSrcNamespace,
|
||||
resDst: resDst,
|
||||
resDstNamespace: resDstNamespace,
|
||||
resClient: resClient,
|
||||
resServer: resServer,
|
||||
resMsg: resMsg,
|
||||
file: "edges_one_output_json.golden",
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
file: "edges_one_output_json.golden",
|
||||
}, t)
|
||||
})
|
||||
|
||||
t.Run("Returns edges (wide)", func(t *testing.T) {
|
||||
options.outputFormat = wideOutput
|
||||
testEdgesCall(edgesParamsExp{
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
resSrc: resSrc,
|
||||
resSrcNamespace: resSrcNamespace,
|
||||
resDst: resDst,
|
||||
resDstNamespace: resDstNamespace,
|
||||
resClient: resClient,
|
||||
resServer: resServer,
|
||||
resMsg: resMsg,
|
||||
file: "edges_wide_output.golden",
|
||||
options: options,
|
||||
resourceType: "deployment",
|
||||
file: "edges_wide_output.golden",
|
||||
}, t)
|
||||
})
|
||||
|
||||
|
|
@ -169,7 +100,7 @@ func TestEdges(t *testing.T) {
|
|||
|
||||
func testEdgesCall(exp edgesParamsExp, t *testing.T) {
|
||||
mockClient := &public.MockAPIClient{}
|
||||
response := public.GenEdgesResponse(exp.resourceType, exp.resSrc, exp.resDst, exp.resSrcNamespace, exp.resDstNamespace, exp.resClient, exp.resServer, exp.resMsg)
|
||||
response := public.GenEdgesResponse(exp.resourceType, "all")
|
||||
|
||||
mockClient.EdgesResponseToReturn = &response
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
||||
|
|
@ -189,5 +190,17 @@ func processEdgeMetrics(inbound, outbound model.Vector, resourceType, selectedNa
|
|||
}
|
||||
}
|
||||
|
||||
// sort rows before returning in order to have a consistent order for tests
|
||||
edges = sortEdgeRows(edges)
|
||||
|
||||
return edges
|
||||
}
|
||||
|
||||
func sortEdgeRows(rows []*pb.Edge) []*pb.Edge {
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
keyI := rows[i].GetSrc().GetNamespace() + rows[i].GetDst().GetNamespace() + rows[i].GetSrc().GetName() + rows[i].GetDst().GetName()
|
||||
keyJ := rows[j].GetSrc().GetNamespace() + rows[j].GetDst().GetNamespace() + rows[j].GetSrc().GetName() + rows[j].GetDst().GetName()
|
||||
return keyI < keyJ
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "github.com/linkerd/linkerd2/controller/gen/public"
|
||||
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
clientIDLabel = model.LabelName("client_id")
|
||||
serverIDLabel = model.LabelName("server_id")
|
||||
resourceLabel = model.LabelName("deployment")
|
||||
)
|
||||
|
||||
type edgesExpected struct {
|
||||
expectedStatRPC
|
||||
req pb.EdgesRequest // the request we would like to test
|
||||
expectedResponse pb.EdgesResponse // the edges response we expect
|
||||
}
|
||||
|
||||
func genInboundPromSample(resourceNamespace, resourceName, clientID string) *model.Sample {
|
||||
return &model.Sample{
|
||||
Metric: model.Metric{
|
||||
resourceLabel: model.LabelValue(resourceName),
|
||||
namespaceLabel: model.LabelValue(resourceNamespace),
|
||||
clientIDLabel: model.LabelValue(clientID),
|
||||
},
|
||||
Value: 123,
|
||||
Timestamp: 456,
|
||||
}
|
||||
}
|
||||
|
||||
func genOutboundPromSample(resourceNamespace, resourceName, resourceNameDst, resourceNamespaceDst, serverID string) *model.Sample {
|
||||
dstResourceLabel := "dst_" + resourceLabel
|
||||
|
||||
return &model.Sample{
|
||||
Metric: model.Metric{
|
||||
resourceLabel: model.LabelValue(resourceName),
|
||||
namespaceLabel: model.LabelValue(resourceNamespace),
|
||||
dstNamespaceLabel: model.LabelValue(resourceNamespaceDst),
|
||||
dstResourceLabel: model.LabelValue(resourceNameDst),
|
||||
serverIDLabel: model.LabelValue(serverID),
|
||||
},
|
||||
Value: 123,
|
||||
Timestamp: 456,
|
||||
}
|
||||
}
|
||||
|
||||
func testEdges(t *testing.T, expectations []edgesExpected) {
|
||||
for _, exp := range expectations {
|
||||
mockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating mock grpc server: %s", err)
|
||||
}
|
||||
|
||||
rsp, err := fakeGrpcServer.Edges(context.TODO(), &exp.req)
|
||||
if err != exp.err {
|
||||
t.Fatalf("Expected error: %s, Got: %s", exp.err, err)
|
||||
}
|
||||
|
||||
err = exp.verifyPromQueries(mockProm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rspEdgeRows := rsp.GetOk().Edges
|
||||
|
||||
if len(rspEdgeRows) != len(exp.expectedResponse.GetOk().Edges) {
|
||||
t.Fatalf(
|
||||
"Expected [%d] edge rows, got [%d].\nExpected:\n%s\nGot:\n%s",
|
||||
len(exp.expectedResponse.GetOk().Edges),
|
||||
len(rspEdgeRows),
|
||||
exp.expectedResponse.GetOk().Edges,
|
||||
rspEdgeRows,
|
||||
)
|
||||
}
|
||||
|
||||
for i, st := range rspEdgeRows {
|
||||
expected := exp.expectedResponse.GetOk().Edges[i]
|
||||
if !proto.Equal(st, expected) {
|
||||
t.Fatalf("Expected: %+v\n Got: %+v\n", expected, st)
|
||||
}
|
||||
}
|
||||
|
||||
if !proto.Equal(exp.expectedResponse.GetOk(), rsp.GetOk()) {
|
||||
t.Fatalf("Expected edgesOkResp: %+v\n Got: %+v", &exp.expectedResponse, rsp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEdges(t *testing.T) {
|
||||
mockPromResponse := model.Vector{
|
||||
genInboundPromSample("emojivoto", "emoji", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genInboundPromSample("emojivoto", "voting", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genInboundPromSample("emojivoto", "web", "default.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genInboundPromSample("linkerd", "linkerd-prometheus", "linkerd-controller.linkerd.identity.linkerd.cluster.local"),
|
||||
|
||||
genOutboundPromSample("emojivoto", "web", "emoji", "emojivoto", "emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genOutboundPromSample("emojivoto", "web", "voting", "emojivoto", "voting.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genOutboundPromSample("emojivoto", "vote-bot", "web", "emojivoto", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
|
||||
genOutboundPromSample("linkerd", "linkerd-controller", "linkerd-prometheus", "linkerd", "linkerd-prometheus.linkerd.identity.linkerd.cluster.local"),
|
||||
}
|
||||
|
||||
t.Run("Successfully returns edges for resource type Deployment and namespace emojivoto", func(t *testing.T) {
|
||||
expectations := []edgesExpected{
|
||||
{
|
||||
expectedStatRPC: expectedStatRPC{
|
||||
err: nil,
|
||||
mockPromResponse: mockPromResponse,
|
||||
},
|
||||
req: pb.EdgesRequest{
|
||||
Selector: &pb.ResourceSelection{
|
||||
Resource: &pb.Resource{
|
||||
Namespace: "emojivoto",
|
||||
Type: pkgK8s.Deployment,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResponse: GenEdgesResponse("deployment", "emojivoto"),
|
||||
}}
|
||||
|
||||
testEdges(t, expectations)
|
||||
})
|
||||
|
||||
t.Run("Successfully returns edges for resource type Deployment and namespace linkerd", func(t *testing.T) {
|
||||
expectations := []edgesExpected{
|
||||
{
|
||||
expectedStatRPC: expectedStatRPC{
|
||||
err: nil,
|
||||
mockPromResponse: mockPromResponse,
|
||||
},
|
||||
req: pb.EdgesRequest{
|
||||
Selector: &pb.ResourceSelection{
|
||||
Resource: &pb.Resource{
|
||||
Namespace: "linkerd",
|
||||
Type: pkgK8s.Deployment,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResponse: GenEdgesResponse("deployment", "linkerd"),
|
||||
}}
|
||||
|
||||
testEdges(t, expectations)
|
||||
})
|
||||
|
||||
t.Run("Successfully returns edges for resource type Deployment and all namespaces", func(t *testing.T) {
|
||||
expectations := []edgesExpected{
|
||||
{
|
||||
expectedStatRPC: expectedStatRPC{
|
||||
err: nil,
|
||||
mockPromResponse: mockPromResponse,
|
||||
},
|
||||
req: pb.EdgesRequest{
|
||||
Selector: &pb.ResourceSelection{
|
||||
Resource: &pb.Resource{
|
||||
Type: pkgK8s.Deployment,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResponse: GenEdgesResponse("deployment", "all"),
|
||||
}}
|
||||
|
||||
testEdges(t, expectations)
|
||||
})
|
||||
}
|
||||
|
|
@ -371,29 +371,100 @@ func GenStatTsResponse(resName, resType string, resNs []string, basicStats bool,
|
|||
return resp
|
||||
}
|
||||
|
||||
// GenEdgesResponse generates a mock Public API StatSummaryResponse
|
||||
type mockEdgeRow struct {
|
||||
resourceType string
|
||||
src string
|
||||
dst string
|
||||
srcNamespace string
|
||||
dstNamespace string
|
||||
clientID string
|
||||
serverID string
|
||||
msg string
|
||||
}
|
||||
|
||||
// a slice of edge rows to generate mock results
|
||||
var emojivotoEdgeRows = []*mockEdgeRow{
|
||||
{
|
||||
resourceType: "deployment",
|
||||
src: "web",
|
||||
dst: "voting",
|
||||
srcNamespace: "emojivoto",
|
||||
dstNamespace: "emojivoto",
|
||||
clientID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
serverID: "voting.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
resourceType: "deployment",
|
||||
src: "vote-bot",
|
||||
dst: "web",
|
||||
srcNamespace: "emojivoto",
|
||||
dstNamespace: "emojivoto",
|
||||
clientID: "default.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
serverID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
resourceType: "deployment",
|
||||
src: "web",
|
||||
dst: "emoji",
|
||||
srcNamespace: "emojivoto",
|
||||
dstNamespace: "emojivoto",
|
||||
clientID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
serverID: "emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local",
|
||||
msg: "",
|
||||
},
|
||||
}
|
||||
|
||||
// a slice of edge rows to generate mock results
|
||||
var linkerdEdgeRows = []*mockEdgeRow{
|
||||
{
|
||||
resourceType: "deployment",
|
||||
src: "linkerd-controller",
|
||||
dst: "linkerd-prometheus",
|
||||
srcNamespace: "linkerd",
|
||||
dstNamespace: "linkerd",
|
||||
clientID: "linkerd-controller.linkerd.identity.linkerd.cluster.local",
|
||||
serverID: "linkerd-prometheus.linkerd.identity.linkerd.cluster.local",
|
||||
msg: "",
|
||||
},
|
||||
}
|
||||
|
||||
// GenEdgesResponse generates a mock Public API EdgesResponse
|
||||
// object.
|
||||
func GenEdgesResponse(resourceType string, resSrc, resDst, resSrcNamespace, resDstNamespace, resClient, resServer, msg []string) pb.EdgesResponse {
|
||||
func GenEdgesResponse(resourceType string, edgeRowNamespace string) pb.EdgesResponse {
|
||||
edgeRows := emojivotoEdgeRows
|
||||
|
||||
if edgeRowNamespace == "linkerd" {
|
||||
edgeRows = linkerdEdgeRows
|
||||
} else if edgeRowNamespace == "all" {
|
||||
// combine emojivotoEdgeRows and linkerdEdgeRows
|
||||
edgeRows = append(edgeRows, linkerdEdgeRows...)
|
||||
}
|
||||
|
||||
edges := []*pb.Edge{}
|
||||
for i := range resSrc {
|
||||
for _, row := range edgeRows {
|
||||
edge := &pb.Edge{
|
||||
Src: &pb.Resource{
|
||||
Name: resSrc[i],
|
||||
Namespace: resSrcNamespace[i],
|
||||
Type: resourceType,
|
||||
Name: row.src,
|
||||
Namespace: row.srcNamespace,
|
||||
Type: row.resourceType,
|
||||
},
|
||||
Dst: &pb.Resource{
|
||||
Name: resDst[i],
|
||||
Namespace: resDstNamespace[i],
|
||||
Type: resourceType,
|
||||
Name: row.dst,
|
||||
Namespace: row.dstNamespace,
|
||||
Type: row.resourceType,
|
||||
},
|
||||
ClientId: resClient[i],
|
||||
ServerId: resServer[i],
|
||||
NoIdentityMsg: msg[i],
|
||||
ClientId: row.clientID,
|
||||
ServerId: row.serverID,
|
||||
NoIdentityMsg: row.msg,
|
||||
}
|
||||
edges = append(edges, edge)
|
||||
}
|
||||
|
||||
// sorting to retain consistent order for tests
|
||||
edges = sortEdgeRows(edges)
|
||||
|
||||
resp := pb.EdgesResponse{
|
||||
Response: &pb.EdgesResponse_Ok_{
|
||||
Ok: &pb.EdgesResponse_Ok{
|
||||
|
|
|
|||
Loading…
Reference in New Issue