...

Source file src/github.com/linkerd/linkerd2/viz/metrics-api/test_helper.go

Documentation: github.com/linkerd/linkerd2/viz/metrics-api

     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  // MockAPIClient satisfies the metrics-api gRPC interfaces
    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  // StatSummary provides a mock of a metrics-api method.
    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  // Authz provides a mock of a metrics-api method.
    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  // Gateways provides a mock of a metrics-api method.
    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  // TopRoutes provides a mock of a metrics-api method.
    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  // Edges provides a mock of a metrics-api method.
    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  // ListPods provides a mock of a metrics-api method.
    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  // ListServices provides a mock of a metrics-api method.
    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  // SelfCheck provides a mock of a metrics-api method.
    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  // PodCounts is a test helper struct that is used for representing data in a
    70  // StatTable.PodGroup.Row.
    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  // GenStatSummaryResponse generates a mock metrics-api StatSummaryResponse
    80  // object.
    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_{ // https://github.com/golang/protobuf/issues/205
   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  // a slice of edge rows to generate mock results
   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  // a slice of edge rows to generate mock results
   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  // GenEdgesResponse generates a mock metrics-api EdgesResponse
   201  // object.
   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  		// combine emojivotoEdgeRows and linkerdEdgeRows
   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  	// sorting to retain consistent order for tests
   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  // GenTopRoutesResponse generates a mock metrics-api TopRoutesResponse object.
   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    // k8s objects to seed the API
   302  	mockPromResponse          model.Value // mock out a prometheus query response
   303  	expectedPrometheusQueries []string    // queries we expect metrics-api to issue to prometheus
   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  	// if exp.expectedPrometheusQueries is an empty slice we still want to check no queries were executed.
   328  	if exp.expectedPrometheusQueries != nil {
   329  		sort.Strings(exp.expectedPrometheusQueries)
   330  		sort.Strings(mockProm.QueriesExecuted)
   331  
   332  		// because reflect.DeepEqual([]string{}, nil) is false
   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