1 package api
2
3 import (
4 "context"
5 "fmt"
6 "sort"
7
8 "github.com/go-test/deep"
9 "github.com/linkerd/linkerd2/controller/k8s"
10 "github.com/linkerd/linkerd2/pkg/prometheus"
11 pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
12 "github.com/prometheus/common/model"
13 "google.golang.org/grpc"
14 )
15
16
17 type MockAPIClient struct {
18 ErrorToReturn error
19 ListPodsResponseToReturn *pb.ListPodsResponse
20 ListServicesResponseToReturn *pb.ListServicesResponse
21 StatSummaryResponseToReturn *pb.StatSummaryResponse
22 AuthzResponseToReturn *pb.AuthzResponse
23 GatewaysResponseToReturn *pb.GatewaysResponse
24 TopRoutesResponseToReturn *pb.TopRoutesResponse
25 EdgesResponseToReturn *pb.EdgesResponse
26 SelfCheckResponseToReturn *pb.SelfCheckResponse
27 }
28
29
30 func (c *MockAPIClient) StatSummary(ctx context.Context, in *pb.StatSummaryRequest, opts ...grpc.CallOption) (*pb.StatSummaryResponse, error) {
31 return c.StatSummaryResponseToReturn, c.ErrorToReturn
32 }
33
34
35 func (c *MockAPIClient) Authz(ctx context.Context, in *pb.AuthzRequest, opts ...grpc.CallOption) (*pb.AuthzResponse, error) {
36 return c.AuthzResponseToReturn, c.ErrorToReturn
37 }
38
39
40 func (c *MockAPIClient) Gateways(ctx context.Context, in *pb.GatewaysRequest, opts ...grpc.CallOption) (*pb.GatewaysResponse, error) {
41 return c.GatewaysResponseToReturn, c.ErrorToReturn
42 }
43
44
45 func (c *MockAPIClient) TopRoutes(ctx context.Context, in *pb.TopRoutesRequest, opts ...grpc.CallOption) (*pb.TopRoutesResponse, error) {
46 return c.TopRoutesResponseToReturn, c.ErrorToReturn
47 }
48
49
50 func (c *MockAPIClient) Edges(ctx context.Context, in *pb.EdgesRequest, opts ...grpc.CallOption) (*pb.EdgesResponse, error) {
51 return c.EdgesResponseToReturn, c.ErrorToReturn
52 }
53
54
55 func (c *MockAPIClient) ListPods(ctx context.Context, in *pb.ListPodsRequest, opts ...grpc.CallOption) (*pb.ListPodsResponse, error) {
56 return c.ListPodsResponseToReturn, c.ErrorToReturn
57 }
58
59
60 func (c *MockAPIClient) ListServices(ctx context.Context, in *pb.ListServicesRequest, opts ...grpc.CallOption) (*pb.ListServicesResponse, error) {
61 return c.ListServicesResponseToReturn, c.ErrorToReturn
62 }
63
64
65 func (c *MockAPIClient) SelfCheck(ctx context.Context, in *pb.SelfCheckRequest, _ ...grpc.CallOption) (*pb.SelfCheckResponse, error) {
66 return c.SelfCheckResponseToReturn, c.ErrorToReturn
67 }
68
69
70
71 type PodCounts struct {
72 Status string
73 MeshedPods uint64
74 RunningPods uint64
75 FailedPods uint64
76 Errors map[string]*pb.PodErrors
77 }
78
79
80
81 func GenStatSummaryResponse(resName, resType string, resNs []string, counts *PodCounts, basicStats bool, tcpStats bool) *pb.StatSummaryResponse {
82 rows := []*pb.StatTable_PodGroup_Row{}
83 for _, ns := range resNs {
84 statTableRow := &pb.StatTable_PodGroup_Row{
85 Resource: &pb.Resource{
86 Namespace: ns,
87 Type: resType,
88 Name: resName,
89 },
90 TimeWindow: "1m",
91 }
92
93 if basicStats {
94 statTableRow.Stats = &pb.BasicStats{
95 SuccessCount: 123,
96 FailureCount: 0,
97 LatencyMsP50: 123,
98 LatencyMsP95: 123,
99 LatencyMsP99: 123,
100 }
101 }
102
103 if tcpStats {
104 statTableRow.TcpStats = &pb.TcpStats{
105 OpenConnections: 123,
106 ReadBytesTotal: 123,
107 WriteBytesTotal: 123,
108 }
109 }
110
111 if counts != nil {
112 statTableRow.MeshedPodCount = counts.MeshedPods
113 statTableRow.RunningPodCount = counts.RunningPods
114 statTableRow.FailedPodCount = counts.FailedPods
115 statTableRow.Status = counts.Status
116 statTableRow.ErrorsByPod = counts.Errors
117 }
118
119 rows = append(rows, statTableRow)
120 }
121
122 resp := &pb.StatSummaryResponse{
123 Response: &pb.StatSummaryResponse_Ok_{
124 Ok: &pb.StatSummaryResponse_Ok{
125 StatTables: []*pb.StatTable{
126 {
127 Table: &pb.StatTable_PodGroup_{
128 PodGroup: &pb.StatTable_PodGroup{
129 Rows: rows,
130 },
131 },
132 },
133 },
134 },
135 },
136 }
137
138 return resp
139 }
140
141 type mockEdgeRow struct {
142 resourceType string
143 src string
144 dst string
145 srcNamespace string
146 dstNamespace string
147 clientID string
148 serverID string
149 msg string
150 }
151
152
153 var emojivotoEdgeRows = []*mockEdgeRow{
154 {
155 resourceType: "deployment",
156 src: "web",
157 dst: "voting",
158 srcNamespace: "emojivoto",
159 dstNamespace: "emojivoto",
160 clientID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
161 serverID: "voting.emojivoto.serviceaccount.identity.linkerd.cluster.local",
162 msg: "",
163 },
164 {
165 resourceType: "deployment",
166 src: "vote-bot",
167 dst: "web",
168 srcNamespace: "emojivoto",
169 dstNamespace: "emojivoto",
170 clientID: "default.emojivoto.serviceaccount.identity.linkerd.cluster.local",
171 serverID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
172 msg: "",
173 },
174 {
175 resourceType: "deployment",
176 src: "web",
177 dst: "emoji",
178 srcNamespace: "emojivoto",
179 dstNamespace: "emojivoto",
180 clientID: "web.emojivoto.serviceaccount.identity.linkerd.cluster.local",
181 serverID: "emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local",
182 msg: "",
183 },
184 }
185
186
187 var linkerdEdgeRows = []*mockEdgeRow{
188 {
189 resourceType: "deployment",
190 src: "linkerd-identity",
191 dst: "linkerd-prometheus",
192 srcNamespace: "linkerd",
193 dstNamespace: "linkerd",
194 clientID: "linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local",
195 serverID: "linkerd-prometheus.linkerd.serviceaccount.identity.linkerd.cluster.local",
196 msg: "",
197 },
198 }
199
200
201
202 func GenEdgesResponse(resourceType string, edgeRowNamespace string) *pb.EdgesResponse {
203 edgeRows := emojivotoEdgeRows
204
205 if edgeRowNamespace == "linkerd" {
206 edgeRows = linkerdEdgeRows
207 } else if edgeRowNamespace == "all" {
208
209 edgeRows = append(edgeRows, linkerdEdgeRows...)
210 }
211
212 edges := []*pb.Edge{}
213 for _, row := range edgeRows {
214 edge := &pb.Edge{
215 Src: &pb.Resource{
216 Name: row.src,
217 Namespace: row.srcNamespace,
218 Type: row.resourceType,
219 },
220 Dst: &pb.Resource{
221 Name: row.dst,
222 Namespace: row.dstNamespace,
223 Type: row.resourceType,
224 },
225 ClientId: row.clientID,
226 ServerId: row.serverID,
227 NoIdentityMsg: row.msg,
228 }
229 edges = append(edges, edge)
230 }
231
232
233 edges = sortEdgeRows(edges)
234
235 resp := &pb.EdgesResponse{
236 Response: &pb.EdgesResponse_Ok_{
237 Ok: &pb.EdgesResponse_Ok{
238 Edges: edges,
239 },
240 },
241 }
242 return resp
243 }
244
245
246 func GenTopRoutesResponse(routes []string, counts []uint64, outbound bool, authority string) *pb.TopRoutesResponse {
247 rows := []*pb.RouteTable_Row{}
248 for i, route := range routes {
249 row := &pb.RouteTable_Row{
250 Route: route,
251 Authority: authority,
252 Stats: &pb.BasicStats{
253 SuccessCount: counts[i],
254 FailureCount: 0,
255 LatencyMsP50: 123,
256 LatencyMsP95: 123,
257 LatencyMsP99: 123,
258 },
259 TimeWindow: "1m",
260 }
261 if outbound {
262 row.Stats.ActualSuccessCount = counts[i]
263 }
264 rows = append(rows, row)
265 }
266 defaultRow := &pb.RouteTable_Row{
267 Route: "[DEFAULT]",
268 Authority: authority,
269 Stats: &pb.BasicStats{
270 SuccessCount: counts[len(counts)-1],
271 FailureCount: 0,
272 LatencyMsP50: 123,
273 LatencyMsP95: 123,
274 LatencyMsP99: 123,
275 },
276 TimeWindow: "1m",
277 }
278 if outbound {
279 defaultRow.Stats.ActualSuccessCount = counts[len(counts)-1]
280 }
281 rows = append(rows, defaultRow)
282
283 resp := &pb.TopRoutesResponse{
284 Response: &pb.TopRoutesResponse_Ok_{
285 Ok: &pb.TopRoutesResponse_Ok{
286 Routes: []*pb.RouteTable{
287 {
288 Rows: rows,
289 Resource: "deploy/foobar",
290 },
291 },
292 },
293 },
294 }
295
296 return resp
297 }
298
299 type expectedStatRPC struct {
300 err error
301 k8sConfigs []string
302 mockPromResponse model.Value
303 expectedPrometheusQueries []string
304 }
305
306 func newMockGrpcServer(exp expectedStatRPC) (*prometheus.MockProm, *grpcServer, error) {
307 k8sAPI, err := k8s.NewFakeAPI(exp.k8sConfigs...)
308 if err != nil {
309 return nil, nil, err
310 }
311
312 mockProm := &prometheus.MockProm{Res: exp.mockPromResponse}
313 fakeGrpcServer := &grpcServer{
314 prometheusAPI: mockProm,
315 k8sAPI: k8sAPI,
316 controllerNamespace: "linkerd",
317 clusterDomain: "cluster.local",
318 ignoredNamespaces: []string{},
319 }
320
321 k8sAPI.Sync(nil)
322
323 return mockProm, fakeGrpcServer, nil
324 }
325
326 func (exp expectedStatRPC) verifyPromQueries(mockProm *prometheus.MockProm) error {
327
328 if exp.expectedPrometheusQueries != nil {
329 sort.Strings(exp.expectedPrometheusQueries)
330 sort.Strings(mockProm.QueriesExecuted)
331
332
333 if len(exp.expectedPrometheusQueries) == 0 && len(mockProm.QueriesExecuted) == 0 {
334 return nil
335 }
336
337 if diff := deep.Equal(exp.expectedPrometheusQueries, mockProm.QueriesExecuted); diff != nil {
338 return fmt.Errorf("Prometheus queries incorrect: %+v", diff)
339 }
340 }
341 return nil
342 }
343
View as plain text