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 }