github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/indexshipper/compactor/testutil.go (about) 1 package compactor 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/go-kit/log" 17 "github.com/klauspost/compress/gzip" 18 "github.com/prometheus/prometheus/model/labels" 19 "github.com/stretchr/testify/require" 20 21 "github.com/grafana/loki/pkg/storage/chunk" 22 "github.com/grafana/loki/pkg/storage/chunk/client/util" 23 "github.com/grafana/loki/pkg/storage/config" 24 "github.com/grafana/loki/pkg/storage/stores/indexshipper/compactor/retention" 25 "github.com/grafana/loki/pkg/storage/stores/indexshipper/index" 26 "github.com/grafana/loki/pkg/storage/stores/shipper/testutil" 27 ) 28 29 const ( 30 multiTenantIndexPrefix = "mti" 31 sharedIndexPrefix = "si" 32 ) 33 34 type IndexFileConfig struct { 35 CompressFile bool 36 } 37 38 type IndexRecords struct { 39 Start, NumRecords int 40 } 41 42 func compressFile(t *testing.T, filepath string) { 43 t.Helper() 44 uncompressedFile, err := os.Open(filepath) 45 require.NoError(t, err) 46 47 compressedFile, err := os.Create(fmt.Sprintf("%s.gz", filepath)) 48 require.NoError(t, err) 49 50 compressedWriter := gzip.NewWriter(compressedFile) 51 52 _, err = io.Copy(compressedWriter, uncompressedFile) 53 require.NoError(t, err) 54 55 require.NoError(t, compressedWriter.Close()) 56 require.NoError(t, uncompressedFile.Close()) 57 require.NoError(t, compressedFile.Close()) 58 require.NoError(t, os.Remove(filepath)) 59 } 60 61 type IndexesConfig struct { 62 NumUnCompactedFiles, NumCompactedFiles int 63 } 64 65 func (c IndexesConfig) String() string { 66 return fmt.Sprintf("Common Indexes - UCIFs: %d, CIFs: %d", c.NumUnCompactedFiles, c.NumCompactedFiles) 67 } 68 69 type PerUserIndexesConfig struct { 70 IndexesConfig 71 NumUsers int 72 } 73 74 func (c PerUserIndexesConfig) String() string { 75 return fmt.Sprintf("Per User Indexes - UCIFs: %d, CIFs: %d, Users: %d", c.NumUnCompactedFiles, c.NumCompactedFiles, c.NumUsers) 76 } 77 78 func SetupTable(t *testing.T, path string, commonDBsConfig IndexesConfig, perUserDBsConfig PerUserIndexesConfig) { 79 require.NoError(t, util.EnsureDirectory(path)) 80 commonIndexes, perUserIndexes := buildFilesContent(commonDBsConfig, perUserDBsConfig) 81 82 idx := 0 83 for filename, content := range commonIndexes { 84 filePath := filepath.Join(path, strings.TrimSuffix(filename, ".gz")) 85 require.NoError(t, ioutil.WriteFile(filePath, []byte(content), 0777)) 86 if strings.HasSuffix(filename, ".gz") { 87 compressFile(t, filePath) 88 } 89 idx++ 90 } 91 92 for userID, files := range perUserIndexes { 93 require.NoError(t, util.EnsureDirectory(filepath.Join(path, userID))) 94 for filename, content := range files { 95 filePath := filepath.Join(path, userID, strings.TrimSuffix(filename, ".gz")) 96 require.NoError(t, ioutil.WriteFile(filePath, []byte(content), 0777)) 97 if strings.HasSuffix(filename, ".gz") { 98 compressFile(t, filePath) 99 } 100 } 101 } 102 } 103 104 func buildFilesContent(commonDBsConfig IndexesConfig, perUserDBsConfig PerUserIndexesConfig) (map[string]string, map[string]map[string]string) { 105 // filename -> content 106 commonIndexes := map[string]string{} 107 // userID -> filename -> content 108 perUserIndexes := map[string]map[string]string{} 109 110 for i := 0; i < commonDBsConfig.NumUnCompactedFiles; i++ { 111 fileName := fmt.Sprintf("%s-%d", sharedIndexPrefix, i) 112 if i%2 == 0 { 113 fileName += ".gz" 114 } 115 commonIndexes[fileName] = fmt.Sprint(i) 116 } 117 118 for i := 0; i < commonDBsConfig.NumCompactedFiles; i++ { 119 commonIndexes[fmt.Sprintf("compactor-%d.gz", i)] = fmt.Sprint(i) 120 } 121 122 for i := 0; i < perUserDBsConfig.NumUnCompactedFiles; i++ { 123 fileName := fmt.Sprintf("%s-%d", multiTenantIndexPrefix, i) 124 if i%2 == 0 { 125 fileName += ".gz" 126 } 127 commonIndexes[fileName] = "" 128 for j := 0; j < perUserDBsConfig.NumUsers; j = j + 1 { 129 commonIndexes[fileName] = commonIndexes[fileName] + BuildUserID(j) + "\n" 130 } 131 } 132 133 for i := 0; i < perUserDBsConfig.NumCompactedFiles; i++ { 134 for j := 0; j < perUserDBsConfig.NumUsers; j++ { 135 userID := BuildUserID(j) 136 if i == 0 { 137 perUserIndexes[userID] = map[string]string{} 138 } 139 perUserIndexes[userID][fmt.Sprintf("compactor-%d.gz", i)] = fmt.Sprint(i) 140 } 141 } 142 143 return commonIndexes, perUserIndexes 144 } 145 146 func BuildUserID(id int) string { 147 return fmt.Sprintf("user-%d", id) 148 } 149 150 type compactedIndex struct { 151 indexFile *os.File 152 } 153 154 func openCompactedIndex(path string) (*compactedIndex, error) { 155 idxFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) 156 if err != nil { 157 return nil, err 158 } 159 160 return &compactedIndex{indexFile: idxFile}, nil 161 } 162 163 func (c compactedIndex) ForEachChunk(_ context.Context, _ retention.ChunkEntryCallback) error { 164 return nil 165 } 166 167 func (c compactedIndex) IndexChunk(_ chunk.Chunk) (bool, error) { 168 return true, nil 169 } 170 171 func (c compactedIndex) CleanupSeries(_ []byte, _ labels.Labels) error { 172 return nil 173 } 174 175 func (c compactedIndex) Cleanup() { 176 _ = c.indexFile.Close() 177 } 178 179 func (c compactedIndex) ToIndexFile() (index.Index, error) { 180 return c, nil 181 } 182 183 func (c compactedIndex) Name() string { 184 return fmt.Sprintf("compactor-%d", time.Now().Unix()) 185 } 186 187 func (c compactedIndex) Path() string { 188 return c.indexFile.Name() 189 } 190 191 func (c compactedIndex) Close() error { 192 return c.indexFile.Close() 193 } 194 195 func (c compactedIndex) Reader() (io.ReadSeeker, error) { 196 _, err := c.indexFile.Seek(0, 0) 197 if err != nil { 198 return nil, err 199 } 200 return c.indexFile, nil 201 } 202 203 type testIndexCompactor struct{} 204 205 func newTestIndexCompactor() testIndexCompactor { 206 return testIndexCompactor{} 207 } 208 209 func (i testIndexCompactor) NewTableCompactor(ctx context.Context, commonIndexSet IndexSet, existingUserIndexSet map[string]IndexSet, makeEmptyUserIndexSetFunc MakeEmptyUserIndexSetFunc, periodConfig config.PeriodConfig) TableCompactor { 210 return newTestTableCompactor(ctx, commonIndexSet, existingUserIndexSet, makeEmptyUserIndexSetFunc, periodConfig) 211 } 212 213 type tableCompactor struct { 214 ctx context.Context 215 commonIndexSet IndexSet 216 existingUserIndexSet map[string]IndexSet 217 makeEmptyUserIndexSetFunc MakeEmptyUserIndexSetFunc 218 periodConfig config.PeriodConfig 219 } 220 221 func newTestTableCompactor(ctx context.Context, commonIndexSet IndexSet, existingUserIndexSet map[string]IndexSet, makeEmptyUserIndexSetFunc MakeEmptyUserIndexSetFunc, periodConfig config.PeriodConfig) tableCompactor { 222 return tableCompactor{ 223 ctx: ctx, 224 commonIndexSet: commonIndexSet, 225 existingUserIndexSet: existingUserIndexSet, 226 makeEmptyUserIndexSetFunc: makeEmptyUserIndexSetFunc, 227 periodConfig: periodConfig, 228 } 229 } 230 231 func (t tableCompactor) CompactTable() error { 232 sourceFiles := t.commonIndexSet.ListSourceFiles() 233 perUserIndexes := map[string]CompactedIndex{} 234 235 var commonCompactedIndex CompactedIndex 236 237 if len(sourceFiles) > 1 || (len(sourceFiles) == 1 && !strings.Contains(sourceFiles[0].Name, "compactor")) { 238 multiTenantIndexFilesCount := 0 239 240 for _, sourceIndex := range t.commonIndexSet.ListSourceFiles() { 241 if strings.HasPrefix(sourceIndex.Name, multiTenantIndexPrefix) { 242 multiTenantIndexFilesCount++ 243 } 244 245 srcFilePath, err := t.commonIndexSet.GetSourceFile(sourceIndex) 246 if err != nil { 247 return err 248 } 249 250 if strings.HasPrefix(sourceIndex.Name, multiTenantIndexPrefix) { 251 srcFile, err := os.Open(srcFilePath) 252 if err != nil { 253 return err 254 } 255 256 scanner := bufio.NewScanner(srcFile) 257 for scanner.Scan() { 258 userID := scanner.Text() 259 userIndex, ok := perUserIndexes[userID] 260 if ok { 261 _, err := userIndex.(*compactedIndex).indexFile.WriteString(sourceIndex.Name) 262 if err != nil { 263 return err 264 } 265 continue 266 } 267 268 userIdxSet, ok := t.existingUserIndexSet[userID] 269 if !ok { 270 userIdxSet, err = t.makeEmptyUserIndexSetFunc(userID) 271 if err != nil { 272 return err 273 } 274 t.existingUserIndexSet[userID] = userIdxSet 275 } 276 277 userIndex, err = openCompactedIndex(filepath.Join(userIdxSet.GetWorkingDir(), fmt.Sprintf("compactor-%d", time.Now().Unix()))) 278 if err != nil { 279 return err 280 } 281 282 perUserIndexes[userID] = userIndex 283 284 for _, idx := range userIdxSet.ListSourceFiles() { 285 _, err := userIndex.(*compactedIndex).indexFile.WriteString(idx.Name) 286 if err != nil { 287 return err 288 } 289 } 290 291 _, err := userIndex.(*compactedIndex).indexFile.WriteString(sourceIndex.Name) 292 if err != nil { 293 return err 294 } 295 } 296 297 if err := srcFile.Close(); err != nil { 298 return err 299 } 300 } else { 301 if commonCompactedIndex == nil { 302 commonCompactedIndex, err = openCompactedIndex(filepath.Join(t.commonIndexSet.GetWorkingDir(), fmt.Sprintf("compactor-%d", time.Now().Unix()))) 303 if err != nil { 304 return err 305 } 306 } 307 _, err := commonCompactedIndex.(*compactedIndex).indexFile.WriteString(sourceIndex.Name) 308 if err != nil { 309 return err 310 } 311 } 312 } 313 314 if err := t.commonIndexSet.SetCompactedIndex(commonCompactedIndex, true); err != nil { 315 return err 316 } 317 } 318 319 for userID, idxSet := range t.existingUserIndexSet { 320 if _, ok := perUserIndexes[userID]; ok || len(idxSet.ListSourceFiles()) <= 1 { 321 continue 322 } 323 324 var err error 325 perUserIndexes[userID], err = openCompactedIndex(filepath.Join(idxSet.GetWorkingDir(), fmt.Sprintf("compactor-%d", time.Now().Unix()))) 326 if err != nil { 327 return err 328 } 329 for _, srcFile := range idxSet.ListSourceFiles() { 330 _, err := perUserIndexes[userID].(*compactedIndex).indexFile.WriteString(srcFile.Name) 331 if err != nil { 332 return err 333 } 334 } 335 } 336 337 for userID, userIndex := range perUserIndexes { 338 if err := t.existingUserIndexSet[userID].SetCompactedIndex(userIndex, true); err != nil { 339 return err 340 } 341 } 342 343 return nil 344 } 345 346 func (i testIndexCompactor) OpenCompactedIndexFile(_ context.Context, path, _, _, _ string, _ config.PeriodConfig, _ log.Logger) (CompactedIndex, error) { 347 return openCompactedIndex(path) 348 } 349 350 func verifyCompactedIndexTable(t *testing.T, commonDBsConfig IndexesConfig, perUserDBsConfig PerUserIndexesConfig, tablePathInStorage string) { 351 commonIndexes, perUserIndexes := buildFilesContent(commonDBsConfig, perUserDBsConfig) 352 filesInfo, err := ioutil.ReadDir(tablePathInStorage) 353 require.NoError(t, err) 354 355 files, folders := []string{}, []string{} 356 for _, fileInfo := range filesInfo { 357 if fileInfo.IsDir() { 358 folders = append(folders, fileInfo.Name()) 359 } else { 360 files = append(files, fileInfo.Name()) 361 } 362 } 363 364 expectedCommonIndexContent := []string{} 365 expectedUserIndexContent := map[string][]string{} 366 367 for userID := range perUserIndexes { 368 for fileName := range perUserIndexes[userID] { 369 if perUserDBsConfig.NumCompactedFiles == 1 && perUserDBsConfig.NumUnCompactedFiles == 0 { 370 expectedUserIndexContent[userID] = append(expectedUserIndexContent[userID], "0") 371 } else { 372 expectedUserIndexContent[userID] = append(expectedUserIndexContent[userID], fileName) 373 } 374 } 375 } 376 377 if len(commonIndexes) == 0 { 378 require.Equal(t, 0, len(files)) 379 } else if len(commonIndexes) == 1 && commonDBsConfig.NumCompactedFiles == 1 { 380 expectedCommonIndexContent = append(expectedCommonIndexContent, "0") 381 } else { 382 for fileName, content := range commonIndexes { 383 if strings.HasPrefix(fileName, multiTenantIndexPrefix) { 384 scanner := bufio.NewScanner(strings.NewReader(content)) 385 for scanner.Scan() { 386 expectedUserIndexContent[scanner.Text()] = append(expectedUserIndexContent[scanner.Text()], fileName) 387 } 388 } else { 389 expectedCommonIndexContent = append(expectedCommonIndexContent, fileName) 390 } 391 } 392 } 393 394 if len(expectedCommonIndexContent) == 0 { 395 require.Len(t, files, 0, fmt.Sprintf("%v", commonIndexes)) 396 } else { 397 require.Len(t, files, 1, fmt.Sprintf("%v", commonIndexes)) 398 sort.Strings(expectedCommonIndexContent) 399 require.Equal(t, strings.Join(expectedCommonIndexContent, ""), string(readFile(t, filepath.Join(tablePathInStorage, files[0])))) 400 } 401 402 require.Len(t, folders, len(expectedUserIndexContent), fmt.Sprintf("%v", commonIndexes)) 403 for _, userID := range folders { 404 filesInfo, err := ioutil.ReadDir(filepath.Join(tablePathInStorage, userID)) 405 require.NoError(t, err) 406 require.Len(t, filesInfo, 1) 407 require.False(t, filesInfo[0].IsDir()) 408 sort.Strings(expectedUserIndexContent[userID]) 409 require.Equal(t, strings.Join(expectedUserIndexContent[userID], ""), string(readFile(t, filepath.Join(tablePathInStorage, userID, filesInfo[0].Name())))) 410 } 411 } 412 413 func readFile(t *testing.T, path string) []byte { 414 if strings.HasSuffix(path, ".gz") { 415 tempDir := t.TempDir() 416 decompressedFilePath := filepath.Join(tempDir, "decompressed") 417 testutil.DecompressFile(t, path, decompressedFilePath) 418 path = decompressedFilePath 419 } 420 421 fileContent, err := ioutil.ReadFile(path) 422 require.NoError(t, err) 423 424 return fileContent 425 }