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 }