github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/multi_tenant_querier_test.go (about)

     1  package querier
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  	"unicode"
    11  
    12  	"github.com/go-kit/log"
    13  	"github.com/prometheus/prometheus/model/labels"
    14  	"github.com/stretchr/testify/mock"
    15  	"github.com/stretchr/testify/require"
    16  	"github.com/weaveworks/common/user"
    17  
    18  	"github.com/grafana/dskit/tenant"
    19  
    20  	"github.com/grafana/loki/pkg/iter"
    21  	"github.com/grafana/loki/pkg/logproto"
    22  	"github.com/grafana/loki/pkg/logql"
    23  	"github.com/grafana/loki/pkg/logql/syntax"
    24  )
    25  
    26  func TestMultiTenantQuerier_SelectLogs(t *testing.T) {
    27  	tenant.WithDefaultResolver(tenant.NewMultiResolver())
    28  
    29  	for _, tc := range []struct {
    30  		desc      string
    31  		orgID     string
    32  		selector  string
    33  		expLabels []string
    34  		expLines  []string
    35  	}{
    36  		{
    37  			"two tenants",
    38  			"1|2",
    39  			`{type="test"}`,
    40  			[]string{
    41  				`{__tenant_id__="1", type="test"}`,
    42  				`{__tenant_id__="1", type="test"}`,
    43  				`{__tenant_id__="2", type="test"}`,
    44  				`{__tenant_id__="2", type="test"}`,
    45  			},
    46  			[]string{"line 1", "line 2", "line 1", "line 2"},
    47  		},
    48  		{
    49  			"two tenants with selector",
    50  			"1|2",
    51  			`{type="test", __tenant_id__="1"}`,
    52  			[]string{
    53  				`{__tenant_id__="1", type="test"}`,
    54  				`{__tenant_id__="1", type="test"}`,
    55  			},
    56  			[]string{"line 1", "line 2", "line 1", "line 2"},
    57  		},
    58  		{
    59  			"two tenants with selector and pipeline filter",
    60  			"1|2",
    61  			`{type="test", __tenant_id__!="2"} | logfmt | some_lable="foobar"`,
    62  			[]string{
    63  				`{__tenant_id__="1", type="test"}`,
    64  				`{__tenant_id__="1", type="test"}`,
    65  			},
    66  			[]string{"line 1", "line 2", "line 1", "line 2"},
    67  		},
    68  		{
    69  			"one tenant",
    70  			"1",
    71  			`{type="test"}`,
    72  			[]string{
    73  				`{type="test"}`,
    74  				`{type="test"}`,
    75  			},
    76  			[]string{"line 1", "line 2"},
    77  		},
    78  	} {
    79  		t.Run(tc.desc, func(t *testing.T) {
    80  			querier := newQuerierMock()
    81  			querier.On("SelectLogs", mock.Anything, mock.Anything).Return(func() iter.EntryIterator { return mockStreamIterator(1, 2) }, nil)
    82  
    83  			multiTenantQuerier := NewMultiTenantQuerier(querier, log.NewNopLogger())
    84  
    85  			ctx := user.InjectOrgID(context.Background(), tc.orgID)
    86  			params := logql.SelectLogParams{QueryRequest: &logproto.QueryRequest{
    87  				Selector:  tc.selector,
    88  				Direction: logproto.BACKWARD,
    89  				Limit:     0,
    90  				Shards:    nil,
    91  				Start:     time.Unix(0, 1),
    92  				End:       time.Unix(0, time.Now().UnixNano()),
    93  			}}
    94  			iter, err := multiTenantQuerier.SelectLogs(ctx, params)
    95  			require.NoError(t, err)
    96  
    97  			entriesCount := 0
    98  			for iter.Next() {
    99  				require.Equal(t, tc.expLabels[entriesCount], iter.Labels())
   100  				require.Equal(t, tc.expLines[entriesCount], iter.Entry().Line)
   101  				entriesCount++
   102  			}
   103  			require.Equalf(t, len(tc.expLabels), entriesCount, "Expected %d entries but got %d", len(tc.expLabels), entriesCount)
   104  		})
   105  	}
   106  }
   107  
   108  func TestMultiTenantQuerier_SelectSamples(t *testing.T) {
   109  	tenant.WithDefaultResolver(tenant.NewMultiResolver())
   110  
   111  	for _, tc := range []struct {
   112  		desc      string
   113  		orgID     string
   114  		selector  string
   115  		expLabels []string
   116  	}{
   117  		{
   118  			"two tenants",
   119  			"1|2",
   120  			`count_over_time({foo="bar"}[1m]) > 10`,
   121  			[]string{
   122  				`{__tenant_id__="1", app="foo"}`,
   123  				`{__tenant_id__="2", app="foo"}`,
   124  				`{__tenant_id__="2", app="bar"}`,
   125  				`{__tenant_id__="1", app="bar"}`,
   126  				`{__tenant_id__="1", app="foo"}`,
   127  				`{__tenant_id__="2", app="foo"}`,
   128  				`{__tenant_id__="2", app="bar"}`,
   129  				`{__tenant_id__="1", app="bar"}`,
   130  			},
   131  		},
   132  		{
   133  			"two tenants with selector",
   134  			"1|2",
   135  			`count_over_time({foo="bar", __tenant_id__="1"}[1m]) > 10`,
   136  			[]string{
   137  				`{__tenant_id__="1", app="foo"}`,
   138  				`{__tenant_id__="1", app="bar"}`,
   139  				`{__tenant_id__="1", app="foo"}`,
   140  				`{__tenant_id__="1", app="bar"}`,
   141  			},
   142  		},
   143  		{
   144  			"one tenant",
   145  			"1",
   146  			`count_over_time({foo="bar"}[1m]) > 10`,
   147  			[]string{
   148  				`{app="foo"}`,
   149  				`{app="bar"}`,
   150  				`{app="foo"}`,
   151  				`{app="bar"}`,
   152  			},
   153  		},
   154  	} {
   155  		t.Run(tc.desc, func(t *testing.T) {
   156  			querier := newQuerierMock()
   157  			querier.On("SelectSamples", mock.Anything, mock.Anything).Return(func() iter.SampleIterator { return newSampleIterator() }, nil)
   158  
   159  			multiTenantQuerier := NewMultiTenantQuerier(querier, log.NewNopLogger())
   160  
   161  			ctx := user.InjectOrgID(context.Background(), tc.orgID)
   162  			params := logql.SelectSampleParams{SampleQueryRequest: &logproto.SampleQueryRequest{
   163  				Selector: tc.selector,
   164  			}}
   165  			iter, err := multiTenantQuerier.SelectSamples(ctx, params)
   166  			require.NoError(t, err)
   167  
   168  			received := make([]string, 0, len(tc.expLabels))
   169  			for iter.Next() {
   170  				received = append(received, iter.Labels())
   171  			}
   172  			require.ElementsMatch(t, tc.expLabels, received)
   173  		})
   174  	}
   175  }
   176  
   177  func TestMultiTenantQuerier_TenantFilter(t *testing.T) {
   178  	for _, tc := range []struct {
   179  		selector string
   180  		expected string
   181  	}{
   182  		{
   183  			`count_over_time({foo="bar", __tenant_id__="1"}[1m]) > 10`,
   184  			`(count_over_time({foo="bar"}[1m]) > 10)`,
   185  		},
   186  		{
   187  			`topk(2, count_over_time({app="foo", __tenant_id__="1"}[3m]))`,
   188  			`topk(2, count_over_time({app="foo"}[3m]))`,
   189  		},
   190  	} {
   191  		t.Run(tc.selector, func(t *testing.T) {
   192  			params := logql.SelectSampleParams{SampleQueryRequest: &logproto.SampleQueryRequest{
   193  				Selector: tc.selector,
   194  			}}
   195  			_, updatedSelector, err := removeTenantSelector(params, []string{})
   196  			require.NoError(t, err)
   197  			require.Equal(t, removeWhiteSpace(tc.expected), removeWhiteSpace(updatedSelector.String()))
   198  		})
   199  	}
   200  }
   201  
   202  var samples = []logproto.Sample{
   203  	{Timestamp: time.Unix(2, 0).UnixNano(), Hash: 1, Value: 1.},
   204  	{Timestamp: time.Unix(5, 0).UnixNano(), Hash: 2, Value: 1.},
   205  }
   206  
   207  var (
   208  	labelFoo, _ = syntax.ParseLabels("{app=\"foo\"}")
   209  	labelBar, _ = syntax.ParseLabels("{app=\"bar\"}")
   210  )
   211  
   212  func newSampleIterator() iter.SampleIterator {
   213  	return iter.NewSortSampleIterator([]iter.SampleIterator{
   214  		iter.NewSeriesIterator(logproto.Series{
   215  			Labels:     labelFoo.String(),
   216  			Samples:    samples,
   217  			StreamHash: labelFoo.Hash(),
   218  		}),
   219  		iter.NewSeriesIterator(logproto.Series{
   220  			Labels:     labelBar.String(),
   221  			Samples:    samples,
   222  			StreamHash: labelBar.Hash(),
   223  		}),
   224  	})
   225  }
   226  
   227  func BenchmarkTenantEntryIteratorLabels(b *testing.B) {
   228  	it := newMockEntryIterator(12)
   229  	tenantIter := NewTenantEntryIterator(it, "tenant_1")
   230  
   231  	b.ResetTimer()
   232  	b.ReportAllocs()
   233  
   234  	for n := 0; n < b.N; n++ {
   235  		tenantIter.Labels()
   236  	}
   237  }
   238  
   239  type mockEntryIterator struct {
   240  	labels string
   241  }
   242  
   243  func newMockEntryIterator(numLabels int) mockEntryIterator {
   244  	builder := labels.NewBuilder(nil)
   245  	for i := 1; i <= numLabels; i++ {
   246  		builder.Set(fmt.Sprintf("label_%d", i), strconv.Itoa(i))
   247  	}
   248  	return mockEntryIterator{labels: builder.Labels().String()}
   249  }
   250  
   251  func (it mockEntryIterator) Labels() string {
   252  	return it.labels
   253  }
   254  
   255  func (it mockEntryIterator) Entry() logproto.Entry {
   256  	return logproto.Entry{}
   257  }
   258  
   259  func (it mockEntryIterator) Next() bool {
   260  	return true
   261  }
   262  
   263  func (it mockEntryIterator) StreamHash() uint64 {
   264  	return 0
   265  }
   266  
   267  func (it mockEntryIterator) Error() error {
   268  	return nil
   269  }
   270  
   271  func (it mockEntryIterator) Close() error {
   272  	return nil
   273  }
   274  
   275  func TestMultiTenantQuerier_Label(t *testing.T) {
   276  	start := time.Unix(0, 0)
   277  	end := time.Unix(10, 0)
   278  
   279  	mockLabelRequest := func(name string) *logproto.LabelRequest {
   280  		return &logproto.LabelRequest{
   281  			Name:   name,
   282  			Values: name != "",
   283  			Start:  &start,
   284  			End:    &end,
   285  		}
   286  	}
   287  
   288  	tenant.WithDefaultResolver(tenant.NewMultiResolver())
   289  
   290  	for _, tc := range []struct {
   291  		desc           string
   292  		name           string
   293  		orgID          string
   294  		expectedLabels []string
   295  	}{
   296  		{
   297  			desc:           "test label request for multiple tenants",
   298  			name:           "test",
   299  			orgID:          "1|2",
   300  			expectedLabels: []string{"test"},
   301  		},
   302  		{
   303  			desc:           "test label request for a single tenant",
   304  			name:           "test",
   305  			orgID:          "1",
   306  			expectedLabels: []string{"test"},
   307  		},
   308  		{
   309  			desc:           "defaultTenantLabel label request for multiple tenants",
   310  			name:           defaultTenantLabel,
   311  			orgID:          "1|2",
   312  			expectedLabels: []string{"1", "2"},
   313  		},
   314  		{
   315  			desc:           "defaultTenantLabel label request for a single tenant",
   316  			name:           defaultTenantLabel,
   317  			orgID:          "1",
   318  			expectedLabels: []string{"1"},
   319  		},
   320  		{
   321  			desc:           "label names for multiple tenants",
   322  			name:           "",
   323  			orgID:          "1|2",
   324  			expectedLabels: []string{defaultTenantLabel, "test"},
   325  		},
   326  		{
   327  			desc:           "label names for a single tenant",
   328  			name:           "",
   329  			orgID:          "1",
   330  			expectedLabels: []string{"test"},
   331  		},
   332  	} {
   333  		t.Run(tc.desc, func(t *testing.T) {
   334  			querier := newQuerierMock()
   335  			querier.On("Label", mock.Anything, mock.Anything).Return(mockLabelResponse([]string{"test"}), nil)
   336  			multiTenantQuerier := NewMultiTenantQuerier(querier, log.NewNopLogger())
   337  			ctx := user.InjectOrgID(context.Background(), tc.orgID)
   338  
   339  			resp, err := multiTenantQuerier.Label(ctx, mockLabelRequest(tc.name))
   340  			require.NoError(t, err)
   341  			require.Equal(t, tc.expectedLabels, resp.GetValues())
   342  		})
   343  	}
   344  }
   345  
   346  func TestMultiTenantQuerierSeries(t *testing.T) {
   347  	tenant.WithDefaultResolver(tenant.NewMultiResolver())
   348  
   349  	for _, tc := range []struct {
   350  		desc           string
   351  		orgID          string
   352  		expectedSeries []logproto.SeriesIdentifier
   353  	}{
   354  		{
   355  			desc:  "two tenantIDs",
   356  			orgID: "1|2",
   357  			expectedSeries: []logproto.SeriesIdentifier{
   358  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "2"}},
   359  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "3"}},
   360  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "4"}},
   361  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "5"}},
   362  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "2"}},
   363  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "3"}},
   364  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "4"}},
   365  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "5"}},
   366  			},
   367  		},
   368  		{
   369  			desc:  "three tenantIDs",
   370  			orgID: "1|2|3",
   371  			expectedSeries: []logproto.SeriesIdentifier{
   372  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "2"}},
   373  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "3"}},
   374  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "4"}},
   375  				{Labels: map[string]string{"__tenant_id__": "1", "a": "1", "b": "5"}},
   376  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "2"}},
   377  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "3"}},
   378  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "4"}},
   379  				{Labels: map[string]string{"__tenant_id__": "2", "a": "1", "b": "5"}},
   380  				{Labels: map[string]string{"__tenant_id__": "3", "a": "1", "b": "2"}},
   381  				{Labels: map[string]string{"__tenant_id__": "3", "a": "1", "b": "3"}},
   382  				{Labels: map[string]string{"__tenant_id__": "3", "a": "1", "b": "4"}},
   383  				{Labels: map[string]string{"__tenant_id__": "3", "a": "1", "b": "5"}},
   384  			},
   385  		},
   386  		{
   387  			desc:  "single tenantID; behaves like a normal `Series` call",
   388  			orgID: "2",
   389  			expectedSeries: []logproto.SeriesIdentifier{
   390  				{Labels: map[string]string{"a": "1", "b": "2"}},
   391  				{Labels: map[string]string{"a": "1", "b": "3"}},
   392  				{Labels: map[string]string{"a": "1", "b": "4"}},
   393  				{Labels: map[string]string{"a": "1", "b": "5"}},
   394  			},
   395  		},
   396  	} {
   397  		t.Run(tc.desc, func(t *testing.T) {
   398  			querier := newQuerierMock()
   399  			querier.On("Series", mock.Anything, mock.Anything).Return(func() *logproto.SeriesResponse { return mockSeriesResponse() }, nil)
   400  			multiTenantQuerier := NewMultiTenantQuerier(querier, log.NewNopLogger())
   401  			ctx := user.InjectOrgID(context.Background(), tc.orgID)
   402  
   403  			resp, err := multiTenantQuerier.Series(ctx, mockSeriesRequest())
   404  			require.NoError(t, err)
   405  			require.Equal(t, tc.expectedSeries, resp.GetSeries())
   406  		})
   407  	}
   408  }
   409  
   410  func mockSeriesRequest() *logproto.SeriesRequest {
   411  	return &logproto.SeriesRequest{
   412  		Start: time.Unix(0, 0),
   413  		End:   time.Unix(10, 0),
   414  	}
   415  }
   416  
   417  func mockSeriesResponse() *logproto.SeriesResponse {
   418  	return &logproto.SeriesResponse{
   419  		Series: []logproto.SeriesIdentifier{
   420  			{
   421  				Labels: map[string]string{"a": "1", "b": "2"},
   422  			},
   423  			{
   424  				Labels: map[string]string{"a": "1", "b": "3"},
   425  			},
   426  			{
   427  				Labels: map[string]string{"a": "1", "b": "4"},
   428  			},
   429  			{
   430  				Labels: map[string]string{"a": "1", "b": "5"},
   431  			},
   432  		},
   433  	}
   434  }
   435  
   436  func removeWhiteSpace(s string) string {
   437  	return strings.Map(func(r rune) rune {
   438  		if r == ' ' || unicode.IsSpace(r) {
   439  			return -1
   440  		}
   441  		return r
   442  	}, s)
   443  }
   444  
   445  func TestSliceToSet(t *testing.T) {
   446  	for _, tc := range []struct {
   447  		desc     string
   448  		slice    []string
   449  		expected map[string]struct{}
   450  	}{
   451  		{
   452  			desc:     "empty slice",
   453  			slice:    []string{},
   454  			expected: map[string]struct{}{},
   455  		},
   456  		{
   457  			desc:     "single element",
   458  			slice:    []string{"a"},
   459  			expected: map[string]struct{}{"a": {}},
   460  		},
   461  		{
   462  			desc:     "multiple elements",
   463  			slice:    []string{"a", "b", "c"},
   464  			expected: map[string]struct{}{"a": {}, "b": {}, "c": {}},
   465  		},
   466  	} {
   467  		t.Run(tc.desc, func(t *testing.T) {
   468  			actual := sliceToSet(tc.slice)
   469  			require.Equal(t, tc.expected, actual)
   470  		})
   471  	}
   472  }