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  }