github.com/thanos-io/thanos@v0.32.5/pkg/tenancy/tenancy_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package tenancy_test
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"google.golang.org/grpc"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/metadata"
    14  	"google.golang.org/grpc/status"
    15  
    16  	"github.com/efficientgo/core/testutil"
    17  	"github.com/thanos-io/thanos/pkg/component"
    18  	"github.com/thanos-io/thanos/pkg/store"
    19  	"github.com/thanos-io/thanos/pkg/store/storepb"
    20  	"github.com/thanos-io/thanos/pkg/tenancy"
    21  
    22  	"github.com/pkg/errors"
    23  
    24  	storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil"
    25  )
    26  
    27  // mockedStoreAPI is test gRPC store API client.
    28  type mockedStoreAPI struct {
    29  	RespSeries      []*storepb.SeriesResponse
    30  	RespLabelValues *storepb.LabelValuesResponse
    31  	RespLabelNames  *storepb.LabelNamesResponse
    32  	RespError       error
    33  	RespDuration    time.Duration
    34  	// Index of series in store to slow response.
    35  	SlowSeriesIndex int
    36  
    37  	LastSeriesReq      *storepb.SeriesRequest
    38  	LastLabelValuesReq *storepb.LabelValuesRequest
    39  	LastLabelNamesReq  *storepb.LabelNamesRequest
    40  
    41  	t *testing.T
    42  }
    43  
    44  // storeSeriesServer is test gRPC storeAPI series server.
    45  type storeSeriesServer struct {
    46  	storepb.Store_SeriesServer
    47  
    48  	ctx context.Context
    49  }
    50  
    51  func (s *storeSeriesServer) Context() context.Context {
    52  	return s.ctx
    53  }
    54  
    55  const testTenant = "test-tenant"
    56  
    57  func getAndAssertTenant(ctx context.Context, t *testing.T) {
    58  	md, ok := metadata.FromOutgoingContext(ctx)
    59  	if !ok || len(md.Get(tenancy.DefaultTenantHeader)) == 0 {
    60  		testutil.Ok(t, errors.Errorf("could not get tenant from grpc metadata, using default: %s", tenancy.DefaultTenantHeader))
    61  	}
    62  	tenant := md.Get(tenancy.DefaultTenantHeader)[0]
    63  	testutil.Assert(t, tenant == testTenant)
    64  }
    65  
    66  func (s *mockedStoreAPI) Info(context.Context, *storepb.InfoRequest, ...grpc.CallOption) (*storepb.InfoResponse, error) {
    67  	return nil, status.Error(codes.Unimplemented, "not implemented")
    68  }
    69  
    70  func (s *mockedStoreAPI) Series(ctx context.Context, req *storepb.SeriesRequest, _ ...grpc.CallOption) (storepb.Store_SeriesClient, error) {
    71  	getAndAssertTenant(ctx, s.t)
    72  
    73  	return &storetestutil.StoreSeriesClient{Ctx: ctx, RespSet: s.RespSeries, RespDur: s.RespDuration, SlowSeriesIndex: s.SlowSeriesIndex}, s.RespError
    74  }
    75  
    76  func (s *mockedStoreAPI) LabelNames(ctx context.Context, req *storepb.LabelNamesRequest, _ ...grpc.CallOption) (*storepb.LabelNamesResponse, error) {
    77  	getAndAssertTenant(ctx, s.t)
    78  
    79  	return s.RespLabelNames, s.RespError
    80  }
    81  
    82  func (s *mockedStoreAPI) LabelValues(ctx context.Context, req *storepb.LabelValuesRequest, _ ...grpc.CallOption) (*storepb.LabelValuesResponse, error) {
    83  	getAndAssertTenant(ctx, s.t)
    84  
    85  	return s.RespLabelValues, s.RespError
    86  }
    87  
    88  func TestTenantFromGRPC(t *testing.T) {
    89  	t.Run("tenant-present", func(t *testing.T) {
    90  
    91  		ctx := context.Background()
    92  		md := metadata.New(map[string]string{tenancy.DefaultTenantHeader: testTenant})
    93  		ctx = metadata.NewIncomingContext(ctx, md)
    94  
    95  		tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(ctx)
    96  		testutil.Equals(t, true, foundTenant)
    97  		testutil.Assert(t, tenant == testTenant)
    98  	})
    99  	t.Run("no-tenant", func(t *testing.T) {
   100  
   101  		ctx := context.Background()
   102  
   103  		tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(ctx)
   104  		testutil.Equals(t, false, foundTenant)
   105  		testutil.Assert(t, tenant == tenancy.DefaultTenant)
   106  	})
   107  }
   108  
   109  func TestTenantProxyPassing(t *testing.T) {
   110  	// When a querier requests info from a store
   111  	// the tenant information is passed from the query api
   112  	// to the proxy via context. This test ensures
   113  	// proxy store correct places the tenant into the
   114  	// outgoing grpc metadata
   115  	t.Run("tenant-via-context", func(t *testing.T) {
   116  
   117  		ctx, cancel := context.WithCancel(context.Background())
   118  		defer cancel()
   119  		ctx = context.WithValue(ctx, tenancy.TenantKey, testTenant)
   120  
   121  		mockedStore := &mockedStoreAPI{
   122  			RespLabelValues: &storepb.LabelValuesResponse{
   123  				Values:   []string{"1", "2"},
   124  				Warnings: []string{"warning"},
   125  			},
   126  			RespLabelNames: &storepb.LabelNamesResponse{
   127  				Names: []string{"a", "b"},
   128  			},
   129  			t: t,
   130  		}
   131  
   132  		cls := []store.Client{
   133  			&storetestutil.TestClient{StoreClient: mockedStore},
   134  		}
   135  
   136  		q := store.NewProxyStore(nil,
   137  			nil,
   138  			func() []store.Client { return cls },
   139  			component.Query,
   140  			nil, 0*time.Second, store.EagerRetrieval,
   141  		)
   142  		// We assert directly in the mocked store apis LabelValues/LabelNames/Series funcs
   143  		_, _ = q.LabelValues(ctx, &storepb.LabelValuesRequest{})
   144  		_, _ = q.LabelNames(ctx, &storepb.LabelNamesRequest{})
   145  
   146  		seriesMatchers := []storepb.LabelMatcher{
   147  			{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
   148  		}
   149  
   150  		_ = q.Series(&storepb.SeriesRequest{Matchers: seriesMatchers}, &storeSeriesServer{ctx: ctx})
   151  	})
   152  
   153  	// In the case of nested queriers, the 2nd querier
   154  	// will get the tenant via grpc metadata from the 1st querier.
   155  	// This test ensures that the proxy store of the 2nd querier
   156  	// correctly places the tenant information in the outgoing
   157  	// grpc metadata to be sent to its stores.
   158  	t.Run("tenant-via-grpc", func(t *testing.T) {
   159  
   160  		ctx, cancel := context.WithCancel(context.Background())
   161  		defer cancel()
   162  
   163  		md := metadata.New(map[string]string{tenancy.DefaultTenantHeader: testTenant})
   164  		ctx = metadata.NewIncomingContext(ctx, md)
   165  
   166  		mockedStore := &mockedStoreAPI{
   167  			RespLabelValues: &storepb.LabelValuesResponse{
   168  				Values:   []string{"1", "2"},
   169  				Warnings: []string{"warning"},
   170  			},
   171  			RespLabelNames: &storepb.LabelNamesResponse{
   172  				Names: []string{"a", "b"},
   173  			},
   174  			t: t,
   175  		}
   176  
   177  		cls := []store.Client{
   178  			&storetestutil.TestClient{StoreClient: mockedStore},
   179  		}
   180  
   181  		q := store.NewProxyStore(nil,
   182  			nil,
   183  			func() []store.Client { return cls },
   184  			component.Query,
   185  			nil, 0*time.Second, store.EagerRetrieval,
   186  		)
   187  
   188  		// We assert directly in the mocked store apis LabelValues/LabelNames/Series funcs
   189  		_, _ = q.LabelValues(ctx, &storepb.LabelValuesRequest{})
   190  		_, _ = q.LabelNames(ctx, &storepb.LabelNamesRequest{})
   191  
   192  		seriesMatchers := []storepb.LabelMatcher{
   193  			{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
   194  		}
   195  
   196  		_ = q.Series(&storepb.SeriesRequest{Matchers: seriesMatchers}, &storeSeriesServer{ctx: ctx})
   197  	})
   198  }