github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/compactor/compactor_test.go (about)

     1  package compactor
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/rand"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/go-kit/log"
    21  	"github.com/grafana/dskit/concurrency"
    22  	"github.com/grafana/dskit/flagext"
    23  	"github.com/grafana/dskit/kv/consul"
    24  	"github.com/grafana/dskit/services"
    25  	"github.com/oklog/ulid"
    26  	"github.com/pkg/errors"
    27  	"github.com/prometheus/client_golang/prometheus"
    28  	prom_testutil "github.com/prometheus/client_golang/prometheus/testutil"
    29  	"github.com/prometheus/prometheus/pkg/labels"
    30  	"github.com/prometheus/prometheus/tsdb"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/mock"
    33  	"github.com/stretchr/testify/require"
    34  	"github.com/thanos-io/thanos/pkg/block/metadata"
    35  	"github.com/thanos-io/thanos/pkg/compact"
    36  	"github.com/thanos-io/thanos/pkg/objstore"
    37  	"gopkg.in/yaml.v2"
    38  
    39  	"github.com/grafana/dskit/ring"
    40  
    41  	"github.com/cortexproject/cortex/pkg/storage/bucket"
    42  	cortex_tsdb "github.com/cortexproject/cortex/pkg/storage/tsdb"
    43  	cortex_testutil "github.com/cortexproject/cortex/pkg/util/test"
    44  	"github.com/cortexproject/cortex/pkg/util/validation"
    45  )
    46  
    47  func TestConfig_ShouldSupportYamlConfig(t *testing.T) {
    48  	yamlCfg := `
    49  block_ranges: [2h, 48h]
    50  consistency_delay: 1h
    51  block_sync_concurrency: 123
    52  data_dir: /tmp
    53  compaction_interval: 15m
    54  compaction_retries: 123
    55  `
    56  
    57  	cfg := Config{}
    58  	flagext.DefaultValues(&cfg)
    59  	assert.NoError(t, yaml.Unmarshal([]byte(yamlCfg), &cfg))
    60  	assert.Equal(t, cortex_tsdb.DurationList{2 * time.Hour, 48 * time.Hour}, cfg.BlockRanges)
    61  	assert.Equal(t, time.Hour, cfg.ConsistencyDelay)
    62  	assert.Equal(t, 123, cfg.BlockSyncConcurrency)
    63  	assert.Equal(t, "/tmp", cfg.DataDir)
    64  	assert.Equal(t, 15*time.Minute, cfg.CompactionInterval)
    65  	assert.Equal(t, 123, cfg.CompactionRetries)
    66  }
    67  
    68  func TestConfig_ShouldSupportCliFlags(t *testing.T) {
    69  	fs := flag.NewFlagSet("", flag.PanicOnError)
    70  	cfg := Config{}
    71  	cfg.RegisterFlags(fs)
    72  	require.NoError(t, fs.Parse([]string{
    73  		"-compactor.block-ranges=2h,48h",
    74  		"-compactor.consistency-delay=1h",
    75  		"-compactor.block-sync-concurrency=123",
    76  		"-compactor.data-dir=/tmp",
    77  		"-compactor.compaction-interval=15m",
    78  		"-compactor.compaction-retries=123",
    79  	}))
    80  
    81  	assert.Equal(t, cortex_tsdb.DurationList{2 * time.Hour, 48 * time.Hour}, cfg.BlockRanges)
    82  	assert.Equal(t, time.Hour, cfg.ConsistencyDelay)
    83  	assert.Equal(t, 123, cfg.BlockSyncConcurrency)
    84  	assert.Equal(t, "/tmp", cfg.DataDir)
    85  	assert.Equal(t, 15*time.Minute, cfg.CompactionInterval)
    86  	assert.Equal(t, 123, cfg.CompactionRetries)
    87  }
    88  
    89  func TestConfig_Validate(t *testing.T) {
    90  	tests := map[string]struct {
    91  		setup    func(cfg *Config)
    92  		expected string
    93  	}{
    94  		"should pass with the default config": {
    95  			setup:    func(cfg *Config) {},
    96  			expected: "",
    97  		},
    98  		"should pass with only 1 block range period": {
    99  			setup: func(cfg *Config) {
   100  				cfg.BlockRanges = cortex_tsdb.DurationList{time.Hour}
   101  			},
   102  			expected: "",
   103  		},
   104  		"should fail with non divisible block range periods": {
   105  			setup: func(cfg *Config) {
   106  				cfg.BlockRanges = cortex_tsdb.DurationList{2 * time.Hour, 12 * time.Hour, 24 * time.Hour, 30 * time.Hour}
   107  			},
   108  			expected: errors.Errorf(errInvalidBlockRanges, 30*time.Hour, 24*time.Hour).Error(),
   109  		},
   110  	}
   111  
   112  	for testName, testData := range tests {
   113  		t.Run(testName, func(t *testing.T) {
   114  			cfg := &Config{}
   115  			flagext.DefaultValues(cfg)
   116  			testData.setup(cfg)
   117  
   118  			if actualErr := cfg.Validate(); testData.expected != "" {
   119  				assert.EqualError(t, actualErr, testData.expected)
   120  			} else {
   121  				assert.NoError(t, actualErr)
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  func TestCompactor_ShouldDoNothingOnNoUserBlocks(t *testing.T) {
   128  	t.Parallel()
   129  
   130  	// No user blocks stored in the bucket.
   131  	bucketClient := &bucket.ClientMock{}
   132  	bucketClient.MockIter("", []string{}, nil)
   133  	cfg := prepareConfig()
   134  	c, _, _, logs, registry := prepare(t, cfg, bucketClient)
   135  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   136  
   137  	// Wait until a run has completed.
   138  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   139  		return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   140  	})
   141  
   142  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   143  
   144  	assert.Equal(t, prom_testutil.ToFloat64(c.compactionRunInterval), cfg.CompactionInterval.Seconds())
   145  
   146  	assert.Equal(t, []string{
   147  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   148  		`level=info component=cleaner msg="successfully completed blocks cleanup and maintenance"`,
   149  		`level=info component=compactor msg="discovering users from bucket"`,
   150  		`level=info component=compactor msg="discovered users from bucket" users=0`,
   151  	}, strings.Split(strings.TrimSpace(logs.String()), "\n"))
   152  
   153  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   154  		# TYPE cortex_compactor_runs_started_total counter
   155  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   156  		cortex_compactor_runs_started_total 1
   157  
   158  		# TYPE cortex_compactor_runs_completed_total counter
   159  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   160  		cortex_compactor_runs_completed_total 1
   161  
   162  		# TYPE cortex_compactor_runs_failed_total counter
   163  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   164  		cortex_compactor_runs_failed_total 0
   165  
   166  		# HELP cortex_compactor_garbage_collected_blocks_total Total number of blocks marked for deletion by compactor.
   167  		# TYPE cortex_compactor_garbage_collected_blocks_total counter
   168  		cortex_compactor_garbage_collected_blocks_total 0
   169  
   170  		# HELP cortex_compactor_garbage_collection_duration_seconds Time it took to perform garbage collection iteration.
   171  		# TYPE cortex_compactor_garbage_collection_duration_seconds histogram
   172  		cortex_compactor_garbage_collection_duration_seconds_bucket{le="+Inf"} 0
   173  		cortex_compactor_garbage_collection_duration_seconds_sum 0
   174  		cortex_compactor_garbage_collection_duration_seconds_count 0
   175  
   176  		# HELP cortex_compactor_garbage_collection_failures_total Total number of failed garbage collection operations.
   177  		# TYPE cortex_compactor_garbage_collection_failures_total counter
   178  		cortex_compactor_garbage_collection_failures_total 0
   179  
   180  		# HELP cortex_compactor_garbage_collection_total Total number of garbage collection operations.
   181  		# TYPE cortex_compactor_garbage_collection_total counter
   182  		cortex_compactor_garbage_collection_total 0
   183  
   184  		# HELP cortex_compactor_meta_sync_consistency_delay_seconds Configured consistency delay in seconds.
   185  		# TYPE cortex_compactor_meta_sync_consistency_delay_seconds gauge
   186  		cortex_compactor_meta_sync_consistency_delay_seconds 0
   187  
   188  		# HELP cortex_compactor_meta_sync_duration_seconds Duration of the blocks metadata synchronization in seconds.
   189  		# TYPE cortex_compactor_meta_sync_duration_seconds histogram
   190  		cortex_compactor_meta_sync_duration_seconds_bucket{le="+Inf"} 0
   191  		cortex_compactor_meta_sync_duration_seconds_sum 0
   192  		cortex_compactor_meta_sync_duration_seconds_count 0
   193  
   194  		# HELP cortex_compactor_meta_sync_failures_total Total blocks metadata synchronization failures.
   195  		# TYPE cortex_compactor_meta_sync_failures_total counter
   196  		cortex_compactor_meta_sync_failures_total 0
   197  
   198  		# HELP cortex_compactor_meta_syncs_total Total blocks metadata synchronization attempts.
   199  		# TYPE cortex_compactor_meta_syncs_total counter
   200  		cortex_compactor_meta_syncs_total 0
   201  
   202  		# HELP cortex_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction.
   203  		# TYPE cortex_compactor_group_compaction_runs_completed_total counter
   204  		cortex_compactor_group_compaction_runs_completed_total 0
   205  
   206  		# HELP cortex_compactor_group_compaction_runs_started_total Total number of group compaction attempts.
   207  		# TYPE cortex_compactor_group_compaction_runs_started_total counter
   208  		cortex_compactor_group_compaction_runs_started_total 0
   209  
   210  		# HELP cortex_compactor_group_compactions_failures_total Total number of failed group compactions.
   211  		# TYPE cortex_compactor_group_compactions_failures_total counter
   212  		cortex_compactor_group_compactions_failures_total 0
   213  
   214  		# HELP cortex_compactor_group_compactions_total Total number of group compaction attempts that resulted in a new block.
   215  		# TYPE cortex_compactor_group_compactions_total counter
   216  		cortex_compactor_group_compactions_total 0
   217  
   218  		# HELP cortex_compactor_group_vertical_compactions_total Total number of group compaction attempts that resulted in a new block based on overlapping blocks.
   219  		# TYPE cortex_compactor_group_vertical_compactions_total counter
   220  		cortex_compactor_group_vertical_compactions_total 0
   221  
   222  		# TYPE cortex_compactor_block_cleanup_failures_total counter
   223  		# HELP cortex_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted.
   224  		cortex_compactor_block_cleanup_failures_total 0
   225  
   226  		# HELP cortex_compactor_blocks_cleaned_total Total number of blocks deleted.
   227  		# TYPE cortex_compactor_blocks_cleaned_total counter
   228  		cortex_compactor_blocks_cleaned_total 0
   229  
   230  		# HELP cortex_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor.
   231  		# TYPE cortex_compactor_blocks_marked_for_deletion_total counter
   232  		cortex_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0
   233  		cortex_compactor_blocks_marked_for_deletion_total{reason="retention"} 0
   234  
   235  		# TYPE cortex_compactor_block_cleanup_started_total counter
   236  		# HELP cortex_compactor_block_cleanup_started_total Total number of blocks cleanup runs started.
   237  		cortex_compactor_block_cleanup_started_total 1
   238  
   239  		# TYPE cortex_compactor_block_cleanup_completed_total counter
   240  		# HELP cortex_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed.
   241  		cortex_compactor_block_cleanup_completed_total 1
   242  
   243  		# TYPE cortex_compactor_block_cleanup_failed_total counter
   244  		# HELP cortex_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed.
   245  		cortex_compactor_block_cleanup_failed_total 0
   246  	`),
   247  		"cortex_compactor_runs_started_total",
   248  		"cortex_compactor_runs_completed_total",
   249  		"cortex_compactor_runs_failed_total",
   250  		"cortex_compactor_garbage_collected_blocks_total",
   251  		"cortex_compactor_garbage_collection_duration_seconds",
   252  		"cortex_compactor_garbage_collection_failures_total",
   253  		"cortex_compactor_garbage_collection_total",
   254  		"cortex_compactor_meta_sync_consistency_delay_seconds",
   255  		"cortex_compactor_meta_sync_duration_seconds",
   256  		"cortex_compactor_meta_sync_failures_total",
   257  		"cortex_compactor_meta_syncs_total",
   258  		"cortex_compactor_group_compaction_runs_completed_total",
   259  		"cortex_compactor_group_compaction_runs_started_total",
   260  		"cortex_compactor_group_compactions_failures_total",
   261  		"cortex_compactor_group_compactions_total",
   262  		"cortex_compactor_group_vertical_compactions_total",
   263  		"cortex_compactor_block_cleanup_failures_total",
   264  		"cortex_compactor_blocks_cleaned_total",
   265  		"cortex_compactor_blocks_marked_for_deletion_total",
   266  		"cortex_compactor_block_cleanup_started_total",
   267  		"cortex_compactor_block_cleanup_completed_total",
   268  		"cortex_compactor_block_cleanup_failed_total",
   269  	))
   270  }
   271  
   272  func TestCompactor_ShouldRetryCompactionOnFailureWhileDiscoveringUsersFromBucket(t *testing.T) {
   273  	t.Parallel()
   274  
   275  	// Fail to iterate over the bucket while discovering users.
   276  	bucketClient := &bucket.ClientMock{}
   277  	bucketClient.MockIter("", nil, errors.New("failed to iterate the bucket"))
   278  
   279  	c, _, _, logs, registry := prepare(t, prepareConfig(), bucketClient)
   280  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   281  
   282  	// Wait until all retry attempts have completed.
   283  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   284  		return prom_testutil.ToFloat64(c.compactionRunsFailed)
   285  	})
   286  
   287  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   288  
   289  	// Ensure the bucket iteration has been retried the configured number of times.
   290  	bucketClient.AssertNumberOfCalls(t, "Iter", 1+3)
   291  
   292  	assert.Equal(t, []string{
   293  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   294  		`level=error component=cleaner msg="failed to run blocks cleanup and maintenance" err="failed to discover users from bucket: failed to iterate the bucket"`,
   295  		`level=info component=compactor msg="discovering users from bucket"`,
   296  		`level=error component=compactor msg="failed to discover users from bucket" err="failed to iterate the bucket"`,
   297  	}, strings.Split(strings.TrimSpace(logs.String()), "\n"))
   298  
   299  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   300  		# TYPE cortex_compactor_runs_started_total counter
   301  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   302  		cortex_compactor_runs_started_total 1
   303  
   304  		# TYPE cortex_compactor_runs_completed_total counter
   305  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   306  		cortex_compactor_runs_completed_total 0
   307  
   308  		# TYPE cortex_compactor_runs_failed_total counter
   309  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   310  		cortex_compactor_runs_failed_total 1
   311  
   312  		# HELP cortex_compactor_garbage_collected_blocks_total Total number of blocks marked for deletion by compactor.
   313  		# TYPE cortex_compactor_garbage_collected_blocks_total counter
   314  		cortex_compactor_garbage_collected_blocks_total 0
   315  
   316  		# HELP cortex_compactor_garbage_collection_duration_seconds Time it took to perform garbage collection iteration.
   317  		# TYPE cortex_compactor_garbage_collection_duration_seconds histogram
   318  		cortex_compactor_garbage_collection_duration_seconds_bucket{le="+Inf"} 0
   319  		cortex_compactor_garbage_collection_duration_seconds_sum 0
   320  		cortex_compactor_garbage_collection_duration_seconds_count 0
   321  
   322  		# HELP cortex_compactor_garbage_collection_failures_total Total number of failed garbage collection operations.
   323  		# TYPE cortex_compactor_garbage_collection_failures_total counter
   324  		cortex_compactor_garbage_collection_failures_total 0
   325  
   326  		# HELP cortex_compactor_garbage_collection_total Total number of garbage collection operations.
   327  		# TYPE cortex_compactor_garbage_collection_total counter
   328  		cortex_compactor_garbage_collection_total 0
   329  
   330  		# HELP cortex_compactor_meta_sync_consistency_delay_seconds Configured consistency delay in seconds.
   331  		# TYPE cortex_compactor_meta_sync_consistency_delay_seconds gauge
   332  		cortex_compactor_meta_sync_consistency_delay_seconds 0
   333  
   334  		# HELP cortex_compactor_meta_sync_duration_seconds Duration of the blocks metadata synchronization in seconds.
   335  		# TYPE cortex_compactor_meta_sync_duration_seconds histogram
   336  		cortex_compactor_meta_sync_duration_seconds_bucket{le="+Inf"} 0
   337  		cortex_compactor_meta_sync_duration_seconds_sum 0
   338  		cortex_compactor_meta_sync_duration_seconds_count 0
   339  
   340  		# HELP cortex_compactor_meta_sync_failures_total Total blocks metadata synchronization failures.
   341  		# TYPE cortex_compactor_meta_sync_failures_total counter
   342  		cortex_compactor_meta_sync_failures_total 0
   343  
   344  		# HELP cortex_compactor_meta_syncs_total Total blocks metadata synchronization attempts.
   345  		# TYPE cortex_compactor_meta_syncs_total counter
   346  		cortex_compactor_meta_syncs_total 0
   347  
   348  		# HELP cortex_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction.
   349  		# TYPE cortex_compactor_group_compaction_runs_completed_total counter
   350  		cortex_compactor_group_compaction_runs_completed_total 0
   351  
   352  		# HELP cortex_compactor_group_compaction_runs_started_total Total number of group compaction attempts.
   353  		# TYPE cortex_compactor_group_compaction_runs_started_total counter
   354  		cortex_compactor_group_compaction_runs_started_total 0
   355  
   356  		# HELP cortex_compactor_group_compactions_failures_total Total number of failed group compactions.
   357  		# TYPE cortex_compactor_group_compactions_failures_total counter
   358  		cortex_compactor_group_compactions_failures_total 0
   359  
   360  		# HELP cortex_compactor_group_compactions_total Total number of group compaction attempts that resulted in a new block.
   361  		# TYPE cortex_compactor_group_compactions_total counter
   362  		cortex_compactor_group_compactions_total 0
   363  
   364  		# HELP cortex_compactor_group_vertical_compactions_total Total number of group compaction attempts that resulted in a new block based on overlapping blocks.
   365  		# TYPE cortex_compactor_group_vertical_compactions_total counter
   366  		cortex_compactor_group_vertical_compactions_total 0
   367  
   368  		# TYPE cortex_compactor_block_cleanup_failures_total counter
   369  		# HELP cortex_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted.
   370  		cortex_compactor_block_cleanup_failures_total 0
   371  
   372  		# HELP cortex_compactor_blocks_cleaned_total Total number of blocks deleted.
   373  		# TYPE cortex_compactor_blocks_cleaned_total counter
   374  		cortex_compactor_blocks_cleaned_total 0
   375  
   376  		# HELP cortex_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor.
   377  		# TYPE cortex_compactor_blocks_marked_for_deletion_total counter
   378  		cortex_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0
   379  		cortex_compactor_blocks_marked_for_deletion_total{reason="retention"} 0
   380  
   381  		# TYPE cortex_compactor_block_cleanup_started_total counter
   382  		# HELP cortex_compactor_block_cleanup_started_total Total number of blocks cleanup runs started.
   383  		cortex_compactor_block_cleanup_started_total 1
   384  
   385  		# TYPE cortex_compactor_block_cleanup_completed_total counter
   386  		# HELP cortex_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed.
   387  		cortex_compactor_block_cleanup_completed_total 0
   388  
   389  		# TYPE cortex_compactor_block_cleanup_failed_total counter
   390  		# HELP cortex_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed.
   391  		cortex_compactor_block_cleanup_failed_total 1
   392  	`),
   393  		"cortex_compactor_runs_started_total",
   394  		"cortex_compactor_runs_completed_total",
   395  		"cortex_compactor_runs_failed_total",
   396  		"cortex_compactor_garbage_collected_blocks_total",
   397  		"cortex_compactor_garbage_collection_duration_seconds",
   398  		"cortex_compactor_garbage_collection_failures_total",
   399  		"cortex_compactor_garbage_collection_total",
   400  		"cortex_compactor_meta_sync_consistency_delay_seconds",
   401  		"cortex_compactor_meta_sync_duration_seconds",
   402  		"cortex_compactor_meta_sync_failures_total",
   403  		"cortex_compactor_meta_syncs_total",
   404  		"cortex_compactor_group_compaction_runs_completed_total",
   405  		"cortex_compactor_group_compaction_runs_started_total",
   406  		"cortex_compactor_group_compactions_failures_total",
   407  		"cortex_compactor_group_compactions_total",
   408  		"cortex_compactor_group_vertical_compactions_total",
   409  		"cortex_compactor_block_cleanup_failures_total",
   410  		"cortex_compactor_blocks_cleaned_total",
   411  		"cortex_compactor_blocks_marked_for_deletion_total",
   412  		"cortex_compactor_block_cleanup_started_total",
   413  		"cortex_compactor_block_cleanup_completed_total",
   414  		"cortex_compactor_block_cleanup_failed_total",
   415  	))
   416  }
   417  
   418  func TestCompactor_ShouldIncrementCompactionErrorIfFailedToCompactASingleTenant(t *testing.T) {
   419  	t.Parallel()
   420  
   421  	userID := "test-user"
   422  	bucketClient := &bucket.ClientMock{}
   423  	bucketClient.MockIter("", []string{userID}, nil)
   424  	bucketClient.MockIter(userID+"/", []string{userID + "/01DTVP434PA9VFXSW2JKB3392D"}, nil)
   425  	bucketClient.MockIter(userID+"/markers/", nil, nil)
   426  	bucketClient.MockExists(path.Join(userID, cortex_tsdb.TenantDeletionMarkPath), false, nil)
   427  	bucketClient.MockGet(userID+"/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   428  	bucketClient.MockGet(userID+"/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil)
   429  	bucketClient.MockGet(userID+"/bucket-index.json.gz", "", nil)
   430  	bucketClient.MockUpload(userID+"/bucket-index.json.gz", nil)
   431  
   432  	c, _, tsdbPlannerMock, _, registry := prepare(t, prepareConfig(), bucketClient)
   433  	tsdbPlannerMock.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, errors.New("Failed to plan"))
   434  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   435  
   436  	// Wait until all retry attempts have completed.
   437  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   438  		return prom_testutil.ToFloat64(c.compactionRunsFailed)
   439  	})
   440  
   441  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   442  
   443  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   444  		# TYPE cortex_compactor_runs_started_total counter
   445  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   446  		cortex_compactor_runs_started_total 1
   447  
   448  		# TYPE cortex_compactor_runs_completed_total counter
   449  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   450  		cortex_compactor_runs_completed_total 0
   451  
   452  		# TYPE cortex_compactor_runs_failed_total counter
   453  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   454  		cortex_compactor_runs_failed_total 1
   455  	`),
   456  		"cortex_compactor_runs_started_total",
   457  		"cortex_compactor_runs_completed_total",
   458  		"cortex_compactor_runs_failed_total",
   459  	))
   460  }
   461  
   462  func TestCompactor_ShouldIterateOverUsersAndRunCompaction(t *testing.T) {
   463  	t.Parallel()
   464  
   465  	// Mock the bucket to contain two users, each one with one block.
   466  	bucketClient := &bucket.ClientMock{}
   467  	bucketClient.MockIter("", []string{"user-1", "user-2"}, nil)
   468  	bucketClient.MockExists(path.Join("user-1", cortex_tsdb.TenantDeletionMarkPath), false, nil)
   469  	bucketClient.MockExists(path.Join("user-2", cortex_tsdb.TenantDeletionMarkPath), false, nil)
   470  	bucketClient.MockIter("user-1/", []string{"user-1/01DTVP434PA9VFXSW2JKB3392D"}, nil)
   471  	bucketClient.MockIter("user-2/", []string{"user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil)
   472  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   473  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil)
   474  	bucketClient.MockGet("user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil)
   475  	bucketClient.MockGet("user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil)
   476  	bucketClient.MockGet("user-1/bucket-index.json.gz", "", nil)
   477  	bucketClient.MockGet("user-2/bucket-index.json.gz", "", nil)
   478  	bucketClient.MockIter("user-1/markers/", nil, nil)
   479  	bucketClient.MockIter("user-2/markers/", nil, nil)
   480  	bucketClient.MockUpload("user-1/bucket-index.json.gz", nil)
   481  	bucketClient.MockUpload("user-2/bucket-index.json.gz", nil)
   482  
   483  	c, _, tsdbPlanner, logs, registry := prepare(t, prepareConfig(), bucketClient)
   484  
   485  	// Mock the planner as if there's no compaction to do,
   486  	// in order to simplify tests (all in all, we just want to
   487  	// test our logic and not TSDB compactor which we expect to
   488  	// be already tested).
   489  	tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, nil)
   490  
   491  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   492  
   493  	// Wait until a run has completed.
   494  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   495  		return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   496  	})
   497  
   498  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   499  
   500  	// Ensure a plan has been executed for the blocks of each user.
   501  	tsdbPlanner.AssertNumberOfCalls(t, "Plan", 2)
   502  
   503  	assert.ElementsMatch(t, []string{
   504  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   505  		`level=info component=cleaner org_id=user-1 msg="started blocks cleanup and maintenance"`,
   506  		`level=info component=cleaner org_id=user-1 msg="completed blocks cleanup and maintenance"`,
   507  		`level=info component=cleaner org_id=user-2 msg="started blocks cleanup and maintenance"`,
   508  		`level=info component=cleaner org_id=user-2 msg="completed blocks cleanup and maintenance"`,
   509  		`level=info component=cleaner msg="successfully completed blocks cleanup and maintenance"`,
   510  		`level=info component=compactor msg="discovering users from bucket"`,
   511  		`level=info component=compactor msg="discovered users from bucket" users=2`,
   512  		`level=info component=compactor msg="starting compaction of user blocks" user=user-1`,
   513  		`level=info component=compactor org_id=user-1 msg="start sync of metas"`,
   514  		`level=info component=compactor org_id=user-1 msg="start of GC"`,
   515  		`level=info component=compactor org_id=user-1 msg="start of compactions"`,
   516  		`level=info component=compactor org_id=user-1 msg="compaction iterations done"`,
   517  		`level=info component=compactor msg="successfully compacted user blocks" user=user-1`,
   518  		`level=info component=compactor msg="starting compaction of user blocks" user=user-2`,
   519  		`level=info component=compactor org_id=user-2 msg="start sync of metas"`,
   520  		`level=info component=compactor org_id=user-2 msg="start of GC"`,
   521  		`level=info component=compactor org_id=user-2 msg="start of compactions"`,
   522  		`level=info component=compactor org_id=user-2 msg="compaction iterations done"`,
   523  		`level=info component=compactor msg="successfully compacted user blocks" user=user-2`,
   524  	}, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
   525  
   526  	// Instead of testing for shipper metrics, we only check our metrics here.
   527  	// Real shipper metrics are too variable to embed into a test.
   528  	testedMetrics := []string{
   529  		"cortex_compactor_runs_started_total", "cortex_compactor_runs_completed_total", "cortex_compactor_runs_failed_total",
   530  		"cortex_compactor_blocks_cleaned_total", "cortex_compactor_block_cleanup_failures_total", "cortex_compactor_blocks_marked_for_deletion_total",
   531  		"cortex_compactor_block_cleanup_started_total", "cortex_compactor_block_cleanup_completed_total", "cortex_compactor_block_cleanup_failed_total",
   532  	}
   533  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   534  		# TYPE cortex_compactor_runs_started_total counter
   535  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   536  		cortex_compactor_runs_started_total 1
   537  
   538  		# TYPE cortex_compactor_runs_completed_total counter
   539  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   540  		cortex_compactor_runs_completed_total 1
   541  
   542  		# TYPE cortex_compactor_runs_failed_total counter
   543  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   544  		cortex_compactor_runs_failed_total 0
   545  
   546  		# TYPE cortex_compactor_block_cleanup_failures_total counter
   547  		# HELP cortex_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted.
   548  		cortex_compactor_block_cleanup_failures_total 0
   549  
   550  		# HELP cortex_compactor_blocks_cleaned_total Total number of blocks deleted.
   551  		# TYPE cortex_compactor_blocks_cleaned_total counter
   552  		cortex_compactor_blocks_cleaned_total 0
   553  
   554  		# HELP cortex_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor.
   555  		# TYPE cortex_compactor_blocks_marked_for_deletion_total counter
   556  		cortex_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0
   557  		cortex_compactor_blocks_marked_for_deletion_total{reason="retention"} 0
   558  
   559  		# TYPE cortex_compactor_block_cleanup_started_total counter
   560  		# HELP cortex_compactor_block_cleanup_started_total Total number of blocks cleanup runs started.
   561  		cortex_compactor_block_cleanup_started_total 1
   562  
   563  		# TYPE cortex_compactor_block_cleanup_completed_total counter
   564  		# HELP cortex_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed.
   565  		cortex_compactor_block_cleanup_completed_total 1
   566  
   567  		# TYPE cortex_compactor_block_cleanup_failed_total counter
   568  		# HELP cortex_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed.
   569  		cortex_compactor_block_cleanup_failed_total 0
   570  	`), testedMetrics...))
   571  }
   572  
   573  func TestCompactor_ShouldNotCompactBlocksMarkedForDeletion(t *testing.T) {
   574  	t.Parallel()
   575  
   576  	cfg := prepareConfig()
   577  	cfg.DeletionDelay = 10 * time.Minute // Delete block after 10 minutes
   578  
   579  	// Mock the bucket to contain two users, each one with one block.
   580  	bucketClient := &bucket.ClientMock{}
   581  	bucketClient.MockIter("", []string{"user-1"}, nil)
   582  	bucketClient.MockIter("user-1/", []string{"user-1/01DTVP434PA9VFXSW2JKB3392D", "user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil)
   583  	bucketClient.MockExists(path.Join("user-1", cortex_tsdb.TenantDeletionMarkPath), false, nil)
   584  
   585  	// Block that has just been marked for deletion. It will not be deleted just yet, and it also will not be compacted.
   586  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   587  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", mockDeletionMarkJSON("01DTVP434PA9VFXSW2JKB3392D", time.Now()), nil)
   588  	bucketClient.MockGet("user-1/markers/01DTVP434PA9VFXSW2JKB3392D-deletion-mark.json", mockDeletionMarkJSON("01DTVP434PA9VFXSW2JKB3392D", time.Now()), nil)
   589  
   590  	// This block will be deleted by cleaner.
   591  	bucketClient.MockGet("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil)
   592  	bucketClient.MockGet("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", mockDeletionMarkJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ", time.Now().Add(-cfg.DeletionDelay)), nil)
   593  	bucketClient.MockGet("user-1/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json", mockDeletionMarkJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ", time.Now().Add(-cfg.DeletionDelay)), nil)
   594  
   595  	bucketClient.MockIter("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ", []string{
   596  		"user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json",
   597  		"user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json",
   598  	}, nil)
   599  
   600  	bucketClient.MockIter("user-1/markers/", []string{
   601  		"user-1/markers/01DTVP434PA9VFXSW2JKB3392D-deletion-mark.json",
   602  		"user-1/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json",
   603  	}, nil)
   604  
   605  	bucketClient.MockDelete("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", nil)
   606  	bucketClient.MockDelete("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", nil)
   607  	bucketClient.MockDelete("user-1/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json", nil)
   608  	bucketClient.MockDelete("user-1/01DTW0ZCPDDNV4BV83Q2SV4QAZ", nil)
   609  	bucketClient.MockGet("user-1/bucket-index.json.gz", "", nil)
   610  	bucketClient.MockUpload("user-1/bucket-index.json.gz", nil)
   611  
   612  	c, _, tsdbPlanner, logs, registry := prepare(t, cfg, bucketClient)
   613  
   614  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   615  
   616  	// Wait until a run has completed.
   617  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   618  		return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   619  	})
   620  
   621  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   622  
   623  	// Since both blocks are marked for deletion, none of them are going to be compacted.
   624  	tsdbPlanner.AssertNumberOfCalls(t, "Plan", 0)
   625  
   626  	assert.ElementsMatch(t, []string{
   627  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   628  		`level=info component=cleaner org_id=user-1 msg="started blocks cleanup and maintenance"`,
   629  		`level=debug component=cleaner org_id=user-1 msg="deleted file" file=01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json bucket=mock`,
   630  		`level=debug component=cleaner org_id=user-1 msg="deleted file" file=01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json bucket=mock`,
   631  		`level=info component=cleaner org_id=user-1 msg="deleted block marked for deletion" block=01DTW0ZCPDDNV4BV83Q2SV4QAZ`,
   632  		`level=info component=cleaner org_id=user-1 msg="completed blocks cleanup and maintenance"`,
   633  		`level=info component=cleaner msg="successfully completed blocks cleanup and maintenance"`,
   634  		`level=info component=compactor msg="discovering users from bucket"`,
   635  		`level=info component=compactor msg="discovered users from bucket" users=1`,
   636  		`level=info component=compactor msg="starting compaction of user blocks" user=user-1`,
   637  		`level=info component=compactor org_id=user-1 msg="start sync of metas"`,
   638  		`level=info component=compactor org_id=user-1 msg="start of GC"`,
   639  		`level=info component=compactor org_id=user-1 msg="start of compactions"`,
   640  		`level=info component=compactor org_id=user-1 msg="compaction iterations done"`,
   641  		`level=info component=compactor msg="successfully compacted user blocks" user=user-1`,
   642  	}, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
   643  
   644  	// Instead of testing for shipper metrics, we only check our metrics here.
   645  	// Real shipper metrics are too variable to embed into a test.
   646  	testedMetrics := []string{
   647  		"cortex_compactor_runs_started_total", "cortex_compactor_runs_completed_total", "cortex_compactor_runs_failed_total",
   648  		"cortex_compactor_blocks_cleaned_total", "cortex_compactor_block_cleanup_failures_total", "cortex_compactor_blocks_marked_for_deletion_total",
   649  		"cortex_compactor_block_cleanup_started_total", "cortex_compactor_block_cleanup_completed_total", "cortex_compactor_block_cleanup_failed_total",
   650  	}
   651  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   652  		# TYPE cortex_compactor_runs_started_total counter
   653  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   654  		cortex_compactor_runs_started_total 1
   655  
   656  		# TYPE cortex_compactor_runs_completed_total counter
   657  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   658  		cortex_compactor_runs_completed_total 1
   659  
   660  		# TYPE cortex_compactor_runs_failed_total counter
   661  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   662  		cortex_compactor_runs_failed_total 0
   663  
   664  		# TYPE cortex_compactor_block_cleanup_failures_total counter
   665  		# HELP cortex_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted.
   666  		cortex_compactor_block_cleanup_failures_total 0
   667  
   668  		# HELP cortex_compactor_blocks_cleaned_total Total number of blocks deleted.
   669  		# TYPE cortex_compactor_blocks_cleaned_total counter
   670  		cortex_compactor_blocks_cleaned_total 1
   671  
   672  		# HELP cortex_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor.
   673  		# TYPE cortex_compactor_blocks_marked_for_deletion_total counter
   674  		cortex_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0
   675  		cortex_compactor_blocks_marked_for_deletion_total{reason="retention"} 0
   676  
   677  		# TYPE cortex_compactor_block_cleanup_started_total counter
   678  		# HELP cortex_compactor_block_cleanup_started_total Total number of blocks cleanup runs started.
   679  		cortex_compactor_block_cleanup_started_total 1
   680  
   681  		# TYPE cortex_compactor_block_cleanup_completed_total counter
   682  		# HELP cortex_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed.
   683  		cortex_compactor_block_cleanup_completed_total 1
   684  
   685  		# TYPE cortex_compactor_block_cleanup_failed_total counter
   686  		# HELP cortex_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed.
   687  		cortex_compactor_block_cleanup_failed_total 0
   688  	`), testedMetrics...))
   689  }
   690  
   691  func TestCompactor_ShouldNotCompactBlocksForUsersMarkedForDeletion(t *testing.T) {
   692  	t.Parallel()
   693  
   694  	cfg := prepareConfig()
   695  	cfg.DeletionDelay = 10 * time.Minute      // Delete block after 10 minutes
   696  	cfg.TenantCleanupDelay = 10 * time.Minute // To make sure it's not 0.
   697  
   698  	// Mock the bucket to contain two users, each one with one block.
   699  	bucketClient := &bucket.ClientMock{}
   700  	bucketClient.MockIter("", []string{"user-1"}, nil)
   701  	bucketClient.MockIter("user-1/", []string{"user-1/01DTVP434PA9VFXSW2JKB3392D"}, nil)
   702  	bucketClient.MockGet(path.Join("user-1", cortex_tsdb.TenantDeletionMarkPath), `{"deletion_time": 1}`, nil)
   703  	bucketClient.MockUpload(path.Join("user-1", cortex_tsdb.TenantDeletionMarkPath), nil)
   704  
   705  	bucketClient.MockIter("user-1/01DTVP434PA9VFXSW2JKB3392D", []string{"user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", "user-1/01DTVP434PA9VFXSW2JKB3392D/index"}, nil)
   706  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   707  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/index", "some index content", nil)
   708  	bucketClient.MockExists("user-1/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", false, nil)
   709  
   710  	bucketClient.MockDelete("user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", nil)
   711  	bucketClient.MockDelete("user-1/01DTVP434PA9VFXSW2JKB3392D/index", nil)
   712  	bucketClient.MockDelete("user-1/bucket-index.json.gz", nil)
   713  
   714  	c, _, tsdbPlanner, logs, registry := prepare(t, cfg, bucketClient)
   715  
   716  	// Mock the planner as if there's no compaction to do,
   717  	// in order to simplify tests (all in all, we just want to
   718  	// test our logic and not TSDB compactor which we expect to
   719  	// be already tested).
   720  	tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, nil)
   721  
   722  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   723  
   724  	// Wait until a run has completed.
   725  	cortex_testutil.Poll(t, time.Second, 1.0, func() interface{} {
   726  		return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   727  	})
   728  
   729  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   730  
   731  	// No user is compacted, single user we have is marked for deletion.
   732  	tsdbPlanner.AssertNumberOfCalls(t, "Plan", 0)
   733  
   734  	assert.ElementsMatch(t, []string{
   735  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   736  		`level=info component=cleaner org_id=user-1 msg="deleting blocks for tenant marked for deletion"`,
   737  		`level=debug component=cleaner org_id=user-1 msg="deleted file" file=01DTVP434PA9VFXSW2JKB3392D/meta.json bucket=mock`,
   738  		`level=debug component=cleaner org_id=user-1 msg="deleted file" file=01DTVP434PA9VFXSW2JKB3392D/index bucket=mock`,
   739  		`level=info component=cleaner org_id=user-1 msg="deleted block" block=01DTVP434PA9VFXSW2JKB3392D`,
   740  		`level=info component=cleaner org_id=user-1 msg="deleted blocks for tenant marked for deletion" deletedBlocks=1`,
   741  		`level=info component=cleaner org_id=user-1 msg="updating finished time in tenant deletion mark"`,
   742  		`level=info component=cleaner msg="successfully completed blocks cleanup and maintenance"`,
   743  		`level=info component=compactor msg="discovering users from bucket"`,
   744  		`level=info component=compactor msg="discovered users from bucket" users=1`,
   745  		`level=debug component=compactor msg="skipping user because it is marked for deletion" user=user-1`,
   746  	}, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
   747  
   748  	// Instead of testing for shipper metrics, we only check our metrics here.
   749  	// Real shipper metrics are too variable to embed into a test.
   750  	testedMetrics := []string{
   751  		"cortex_compactor_runs_started_total", "cortex_compactor_runs_completed_total", "cortex_compactor_runs_failed_total",
   752  		"cortex_compactor_blocks_cleaned_total", "cortex_compactor_block_cleanup_failures_total", "cortex_compactor_blocks_marked_for_deletion_total",
   753  		"cortex_compactor_block_cleanup_started_total", "cortex_compactor_block_cleanup_completed_total", "cortex_compactor_block_cleanup_failed_total",
   754  		"cortex_bucket_blocks_count", "cortex_bucket_blocks_marked_for_deletion_count", "cortex_bucket_index_last_successful_update_timestamp_seconds",
   755  	}
   756  	assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(`
   757  		# TYPE cortex_compactor_runs_started_total counter
   758  		# HELP cortex_compactor_runs_started_total Total number of compaction runs started.
   759  		cortex_compactor_runs_started_total 1
   760  
   761  		# TYPE cortex_compactor_runs_completed_total counter
   762  		# HELP cortex_compactor_runs_completed_total Total number of compaction runs successfully completed.
   763  		cortex_compactor_runs_completed_total 1
   764  
   765  		# TYPE cortex_compactor_runs_failed_total counter
   766  		# HELP cortex_compactor_runs_failed_total Total number of compaction runs failed.
   767  		cortex_compactor_runs_failed_total 0
   768  
   769  		# TYPE cortex_compactor_block_cleanup_failures_total counter
   770  		# HELP cortex_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted.
   771  		cortex_compactor_block_cleanup_failures_total 0
   772  
   773  		# HELP cortex_compactor_blocks_cleaned_total Total number of blocks deleted.
   774  		# TYPE cortex_compactor_blocks_cleaned_total counter
   775  		cortex_compactor_blocks_cleaned_total 1
   776  
   777  		# HELP cortex_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor.
   778  		# TYPE cortex_compactor_blocks_marked_for_deletion_total counter
   779  		cortex_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0
   780  		cortex_compactor_blocks_marked_for_deletion_total{reason="retention"} 0
   781  
   782  		# TYPE cortex_compactor_block_cleanup_started_total counter
   783  		# HELP cortex_compactor_block_cleanup_started_total Total number of blocks cleanup runs started.
   784  		cortex_compactor_block_cleanup_started_total 1
   785  
   786  		# TYPE cortex_compactor_block_cleanup_completed_total counter
   787  		# HELP cortex_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed.
   788  		cortex_compactor_block_cleanup_completed_total 1
   789  
   790  		# TYPE cortex_compactor_block_cleanup_failed_total counter
   791  		# HELP cortex_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed.
   792  		cortex_compactor_block_cleanup_failed_total 0
   793  	`), testedMetrics...))
   794  }
   795  
   796  func TestCompactor_ShouldCompactAllUsersOnShardingEnabledButOnlyOneInstanceRunning(t *testing.T) {
   797  	t.Parallel()
   798  
   799  	// Mock the bucket to contain two users, each one with one block.
   800  	bucketClient := &bucket.ClientMock{}
   801  	bucketClient.MockIter("", []string{"user-1", "user-2"}, nil)
   802  	bucketClient.MockExists(path.Join("user-1", cortex_tsdb.TenantDeletionMarkPath), false, nil)
   803  	bucketClient.MockExists(path.Join("user-2", cortex_tsdb.TenantDeletionMarkPath), false, nil)
   804  	bucketClient.MockIter("user-1/", []string{"user-1/01DTVP434PA9VFXSW2JKB3392D"}, nil)
   805  	bucketClient.MockIter("user-2/", []string{"user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil)
   806  	bucketClient.MockIter("user-1/markers/", nil, nil)
   807  	bucketClient.MockIter("user-2/markers/", nil, nil)
   808  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   809  	bucketClient.MockGet("user-1/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil)
   810  	bucketClient.MockGet("user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil)
   811  	bucketClient.MockGet("user-2/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil)
   812  	bucketClient.MockGet("user-1/bucket-index.json.gz", "", nil)
   813  	bucketClient.MockGet("user-2/bucket-index.json.gz", "", nil)
   814  	bucketClient.MockUpload("user-1/bucket-index.json.gz", nil)
   815  	bucketClient.MockUpload("user-2/bucket-index.json.gz", nil)
   816  
   817  	ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil)
   818  	t.Cleanup(func() { assert.NoError(t, closer.Close()) })
   819  
   820  	cfg := prepareConfig()
   821  	cfg.ShardingEnabled = true
   822  	cfg.ShardingRing.InstanceID = "compactor-1"
   823  	cfg.ShardingRing.InstanceAddr = "1.2.3.4"
   824  	cfg.ShardingRing.KVStore.Mock = ringStore
   825  
   826  	c, _, tsdbPlanner, logs, _ := prepare(t, cfg, bucketClient)
   827  
   828  	// Mock the planner as if there's no compaction to do,
   829  	// in order to simplify tests (all in all, we just want to
   830  	// test our logic and not TSDB compactor which we expect to
   831  	// be already tested).
   832  	tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, nil)
   833  
   834  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   835  
   836  	// Wait until a run has completed.
   837  	cortex_testutil.Poll(t, 5*time.Second, 1.0, func() interface{} {
   838  		return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   839  	})
   840  
   841  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
   842  
   843  	// Ensure a plan has been executed for the blocks of each user.
   844  	tsdbPlanner.AssertNumberOfCalls(t, "Plan", 2)
   845  
   846  	assert.ElementsMatch(t, []string{
   847  		`level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`,
   848  		`level=info component=compactor msg="compactor is ACTIVE in the ring"`,
   849  		`level=info component=cleaner msg="started blocks cleanup and maintenance"`,
   850  		`level=info component=cleaner org_id=user-1 msg="started blocks cleanup and maintenance"`,
   851  		`level=info component=cleaner org_id=user-1 msg="completed blocks cleanup and maintenance"`,
   852  		`level=info component=cleaner org_id=user-2 msg="started blocks cleanup and maintenance"`,
   853  		`level=info component=cleaner org_id=user-2 msg="completed blocks cleanup and maintenance"`,
   854  		`level=info component=cleaner msg="successfully completed blocks cleanup and maintenance"`,
   855  		`level=info component=compactor msg="discovering users from bucket"`,
   856  		`level=info component=compactor msg="discovered users from bucket" users=2`,
   857  		`level=info component=compactor msg="starting compaction of user blocks" user=user-1`,
   858  		`level=info component=compactor org_id=user-1 msg="start sync of metas"`,
   859  		`level=info component=compactor org_id=user-1 msg="start of GC"`,
   860  		`level=info component=compactor org_id=user-1 msg="start of compactions"`,
   861  		`level=info component=compactor org_id=user-1 msg="compaction iterations done"`,
   862  		`level=info component=compactor msg="successfully compacted user blocks" user=user-1`,
   863  		`level=info component=compactor msg="starting compaction of user blocks" user=user-2`,
   864  		`level=info component=compactor org_id=user-2 msg="start sync of metas"`,
   865  		`level=info component=compactor org_id=user-2 msg="start of GC"`,
   866  		`level=info component=compactor org_id=user-2 msg="start of compactions"`,
   867  		`level=info component=compactor org_id=user-2 msg="compaction iterations done"`,
   868  		`level=info component=compactor msg="successfully compacted user blocks" user=user-2`,
   869  	}, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
   870  }
   871  
   872  func TestCompactor_ShouldCompactOnlyUsersOwnedByTheInstanceOnShardingEnabledAndMultipleInstancesRunning(t *testing.T) {
   873  	t.Parallel()
   874  
   875  	numUsers := 100
   876  
   877  	// Setup user IDs
   878  	userIDs := make([]string, 0, numUsers)
   879  	for i := 1; i <= numUsers; i++ {
   880  		userIDs = append(userIDs, fmt.Sprintf("user-%d", i))
   881  	}
   882  
   883  	// Mock the bucket to contain all users, each one with one block.
   884  	bucketClient := &bucket.ClientMock{}
   885  	bucketClient.MockIter("", userIDs, nil)
   886  	for _, userID := range userIDs {
   887  		bucketClient.MockIter(userID+"/", []string{userID + "/01DTVP434PA9VFXSW2JKB3392D"}, nil)
   888  		bucketClient.MockIter(userID+"/markers/", nil, nil)
   889  		bucketClient.MockExists(path.Join(userID, cortex_tsdb.TenantDeletionMarkPath), false, nil)
   890  		bucketClient.MockGet(userID+"/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil)
   891  		bucketClient.MockGet(userID+"/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil)
   892  		bucketClient.MockGet(userID+"/bucket-index.json.gz", "", nil)
   893  		bucketClient.MockUpload(userID+"/bucket-index.json.gz", nil)
   894  	}
   895  
   896  	// Create a shared KV Store
   897  	kvstore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil)
   898  	t.Cleanup(func() { assert.NoError(t, closer.Close()) })
   899  
   900  	// Create two compactors
   901  	var compactors []*Compactor
   902  	var logs []*concurrency.SyncBuffer
   903  
   904  	for i := 1; i <= 2; i++ {
   905  		cfg := prepareConfig()
   906  		cfg.ShardingEnabled = true
   907  		cfg.ShardingRing.InstanceID = fmt.Sprintf("compactor-%d", i)
   908  		cfg.ShardingRing.InstanceAddr = fmt.Sprintf("127.0.0.%d", i)
   909  		cfg.ShardingRing.WaitStabilityMinDuration = 3 * time.Second
   910  		cfg.ShardingRing.WaitStabilityMaxDuration = 10 * time.Second
   911  		cfg.ShardingRing.KVStore.Mock = kvstore
   912  
   913  		c, _, tsdbPlanner, l, _ := prepare(t, cfg, bucketClient)
   914  		defer services.StopAndAwaitTerminated(context.Background(), c) //nolint:errcheck
   915  
   916  		compactors = append(compactors, c)
   917  		logs = append(logs, l)
   918  
   919  		// Mock the planner as if there's no compaction to do,
   920  		// in order to simplify tests (all in all, we just want to
   921  		// test our logic and not TSDB compactor which we expect to
   922  		// be already tested).
   923  		tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, nil)
   924  	}
   925  
   926  	// Start all compactors
   927  	for _, c := range compactors {
   928  		require.NoError(t, services.StartAndAwaitRunning(context.Background(), c))
   929  	}
   930  
   931  	// Wait until a run has been completed on each compactor
   932  	for _, c := range compactors {
   933  		cortex_testutil.Poll(t, 10*time.Second, 1.0, func() interface{} {
   934  			return prom_testutil.ToFloat64(c.compactionRunsCompleted)
   935  		})
   936  	}
   937  
   938  	// Ensure that each user has been compacted by the correct instance
   939  	for _, userID := range userIDs {
   940  		_, l, err := findCompactorByUserID(compactors, logs, userID)
   941  		require.NoError(t, err)
   942  		assert.Contains(t, l.String(), fmt.Sprintf(`level=info component=compactor msg="successfully compacted user blocks" user=%s`, userID))
   943  	}
   944  }
   945  
   946  func createTSDBBlock(t *testing.T, bkt objstore.Bucket, userID string, minT, maxT int64, externalLabels map[string]string) ulid.ULID {
   947  	// Create a temporary dir for TSDB.
   948  	tempDir, err := ioutil.TempDir(os.TempDir(), "tsdb")
   949  	require.NoError(t, err)
   950  	defer os.RemoveAll(tempDir) //nolint:errcheck
   951  
   952  	// Create a temporary dir for the snapshot.
   953  	snapshotDir, err := ioutil.TempDir(os.TempDir(), "snapshot")
   954  	require.NoError(t, err)
   955  	defer os.RemoveAll(snapshotDir) //nolint:errcheck
   956  
   957  	// Create a new TSDB.
   958  	db, err := tsdb.Open(tempDir, nil, nil, &tsdb.Options{
   959  		MinBlockDuration:  int64(2 * 60 * 60 * 1000), // 2h period
   960  		MaxBlockDuration:  int64(2 * 60 * 60 * 1000), // 2h period
   961  		RetentionDuration: int64(15 * 86400 * 1000),  // 15 days
   962  	}, nil)
   963  	require.NoError(t, err)
   964  
   965  	db.DisableCompactions()
   966  
   967  	// Append a sample at the beginning and one at the end of the time range.
   968  	for i, ts := range []int64{minT, maxT - 1} {
   969  		lbls := labels.Labels{labels.Label{Name: "series_id", Value: strconv.Itoa(i)}}
   970  
   971  		app := db.Appender(context.Background())
   972  		_, err := app.Append(0, lbls, ts, float64(i))
   973  		require.NoError(t, err)
   974  
   975  		err = app.Commit()
   976  		require.NoError(t, err)
   977  	}
   978  
   979  	require.NoError(t, db.Compact())
   980  	require.NoError(t, db.Snapshot(snapshotDir, true))
   981  
   982  	// Look for the created block (we expect one).
   983  	entries, err := ioutil.ReadDir(snapshotDir)
   984  	require.NoError(t, err)
   985  	require.Len(t, entries, 1)
   986  	require.True(t, entries[0].IsDir())
   987  
   988  	blockID, err := ulid.Parse(entries[0].Name())
   989  	require.NoError(t, err)
   990  
   991  	// Inject Thanos external labels to the block.
   992  	meta := metadata.Thanos{
   993  		Labels: externalLabels,
   994  		Source: "test",
   995  	}
   996  	_, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(snapshotDir, blockID.String()), meta, nil)
   997  	require.NoError(t, err)
   998  
   999  	// Copy the block files to the bucket.
  1000  	srcRoot := filepath.Join(snapshotDir, blockID.String())
  1001  	require.NoError(t, filepath.Walk(srcRoot, func(file string, info os.FileInfo, err error) error {
  1002  		if err != nil {
  1003  			return err
  1004  		}
  1005  		if info.IsDir() {
  1006  			return nil
  1007  		}
  1008  
  1009  		// Read the file content in memory.
  1010  		content, err := ioutil.ReadFile(file)
  1011  		if err != nil {
  1012  			return err
  1013  		}
  1014  
  1015  		// Upload it to the bucket.
  1016  		relPath, err := filepath.Rel(srcRoot, file)
  1017  		if err != nil {
  1018  			return err
  1019  		}
  1020  
  1021  		return bkt.Upload(context.Background(), path.Join(userID, blockID.String(), relPath), bytes.NewReader(content))
  1022  	}))
  1023  
  1024  	return blockID
  1025  }
  1026  
  1027  func createDeletionMark(t *testing.T, bkt objstore.Bucket, userID string, blockID ulid.ULID, deletionTime time.Time) {
  1028  	content := mockDeletionMarkJSON(blockID.String(), deletionTime)
  1029  	blockPath := path.Join(userID, blockID.String())
  1030  	markPath := path.Join(blockPath, metadata.DeletionMarkFilename)
  1031  
  1032  	require.NoError(t, bkt.Upload(context.Background(), markPath, strings.NewReader(content)))
  1033  }
  1034  
  1035  func findCompactorByUserID(compactors []*Compactor, logs []*concurrency.SyncBuffer, userID string) (*Compactor, *concurrency.SyncBuffer, error) {
  1036  	var compactor *Compactor
  1037  	var log *concurrency.SyncBuffer
  1038  
  1039  	for i, c := range compactors {
  1040  		owned, err := c.ownUser(userID)
  1041  		if err != nil {
  1042  			return nil, nil, err
  1043  		}
  1044  
  1045  		// Ensure the user is not owned by multiple compactors
  1046  		if owned && compactor != nil {
  1047  			return nil, nil, fmt.Errorf("user %s owned by multiple compactors", userID)
  1048  		}
  1049  		if owned {
  1050  			compactor = c
  1051  			log = logs[i]
  1052  		}
  1053  	}
  1054  
  1055  	// Return an error if we've not been able to find it
  1056  	if compactor == nil {
  1057  		return nil, nil, fmt.Errorf("user %s not owned by any compactor", userID)
  1058  	}
  1059  
  1060  	return compactor, log, nil
  1061  }
  1062  
  1063  func removeIgnoredLogs(input []string) []string {
  1064  	ignoredLogStringsMap := map[string]struct{}{
  1065  		// Since we moved to the component logger from the global logger for the ring in dskit these lines are now expected but are just ring setup information.
  1066  		`level=info component=compactor msg="ring doesn't exist in KV store yet"`:                                                                                 {},
  1067  		`level=info component=compactor msg="not loading tokens from file, tokens file path is empty"`:                                                            {},
  1068  		`level=info component=compactor msg="instance not found in ring, adding with no tokens" ring=compactor`:                                                   {},
  1069  		`level=debug component=compactor msg="JoinAfter expired" ring=compactor`:                                                                                  {},
  1070  		`level=info component=compactor msg="auto-joining cluster after timeout" ring=compactor`:                                                                  {},
  1071  		`level=info component=compactor msg="lifecycler loop() exited gracefully" ring=compactor`:                                                                 {},
  1072  		`level=info component=compactor msg="changing instance state from" old_state=ACTIVE new_state=LEAVING ring=compactor`:                                     {},
  1073  		`level=error component=compactor msg="failed to set state to LEAVING" ring=compactor err="Changing instance state from LEAVING -> LEAVING is disallowed"`: {},
  1074  		`level=error component=compactor msg="failed to set state to LEAVING" ring=compactor err="Changing instance state from JOINING -> LEAVING is disallowed"`: {},
  1075  		`level=debug component=compactor msg="unregistering instance from ring" ring=compactor`:                                                                   {},
  1076  		`level=info component=compactor msg="instance removed from the KV store" ring=compactor`:                                                                  {},
  1077  		`level=info component=compactor msg="observing tokens before going ACTIVE" ring=compactor`:                                                                {},
  1078  	}
  1079  
  1080  	out := make([]string, 0, len(input))
  1081  	durationRe := regexp.MustCompile(`\s?duration=\S+`)
  1082  
  1083  	for i := 0; i < len(input); i++ {
  1084  		log := input[i]
  1085  		if strings.Contains(log, "block.MetaFetcher") || strings.Contains(log, "block.BaseFetcher") {
  1086  			continue
  1087  		}
  1088  
  1089  		if _, exists := ignoredLogStringsMap[log]; exists {
  1090  			continue
  1091  		}
  1092  
  1093  		// Remove any duration from logs.
  1094  		log = durationRe.ReplaceAllString(log, "")
  1095  
  1096  		out = append(out, log)
  1097  	}
  1098  
  1099  	return out
  1100  }
  1101  
  1102  func prepareConfig() Config {
  1103  	compactorCfg := Config{}
  1104  	flagext.DefaultValues(&compactorCfg)
  1105  
  1106  	compactorCfg.retryMinBackoff = 0
  1107  	compactorCfg.retryMaxBackoff = 0
  1108  
  1109  	// The migration is tested in a dedicated test.
  1110  	compactorCfg.BlockDeletionMarksMigrationEnabled = false
  1111  
  1112  	// Do not wait for ring stability by default, in order to speed up tests.
  1113  	compactorCfg.ShardingRing.WaitStabilityMinDuration = 0
  1114  	compactorCfg.ShardingRing.WaitStabilityMaxDuration = 0
  1115  
  1116  	// Set lower timeout for waiting on compactor to become ACTIVE in the ring for unit tests
  1117  	compactorCfg.ShardingRing.WaitActiveInstanceTimeout = 5 * time.Second
  1118  
  1119  	return compactorCfg
  1120  }
  1121  
  1122  func prepare(t *testing.T, compactorCfg Config, bucketClient objstore.Bucket) (*Compactor, *tsdbCompactorMock, *tsdbPlannerMock, *concurrency.SyncBuffer, prometheus.Gatherer) {
  1123  	storageCfg := cortex_tsdb.BlocksStorageConfig{}
  1124  	flagext.DefaultValues(&storageCfg)
  1125  
  1126  	// Create a temporary directory for compactor data.
  1127  	dataDir, err := ioutil.TempDir(os.TempDir(), "compactor-test")
  1128  	require.NoError(t, err)
  1129  
  1130  	compactorCfg.DataDir = dataDir
  1131  	t.Cleanup(func() {
  1132  		require.NoError(t, os.RemoveAll(dataDir))
  1133  	})
  1134  
  1135  	tsdbCompactor := &tsdbCompactorMock{}
  1136  	tsdbPlanner := &tsdbPlannerMock{}
  1137  	logs := &concurrency.SyncBuffer{}
  1138  	logger := log.NewLogfmtLogger(logs)
  1139  	registry := prometheus.NewRegistry()
  1140  
  1141  	var limits validation.Limits
  1142  	flagext.DefaultValues(&limits)
  1143  	overrides, err := validation.NewOverrides(limits, nil)
  1144  	require.NoError(t, err)
  1145  
  1146  	bucketClientFactory := func(ctx context.Context) (objstore.Bucket, error) {
  1147  		return bucketClient, nil
  1148  	}
  1149  
  1150  	blocksCompactorFactory := func(ctx context.Context, cfg Config, logger log.Logger, reg prometheus.Registerer) (compact.Compactor, compact.Planner, error) {
  1151  		return tsdbCompactor, tsdbPlanner, nil
  1152  	}
  1153  
  1154  	c, err := newCompactor(compactorCfg, storageCfg, overrides, logger, registry, bucketClientFactory, DefaultBlocksGrouperFactory, blocksCompactorFactory)
  1155  	require.NoError(t, err)
  1156  
  1157  	return c, tsdbCompactor, tsdbPlanner, logs, registry
  1158  }
  1159  
  1160  type tsdbCompactorMock struct {
  1161  	mock.Mock
  1162  }
  1163  
  1164  func (m *tsdbCompactorMock) Plan(dir string) ([]string, error) {
  1165  	args := m.Called(dir)
  1166  	return args.Get(0).([]string), args.Error(1)
  1167  }
  1168  
  1169  func (m *tsdbCompactorMock) Write(dest string, b tsdb.BlockReader, mint, maxt int64, parent *tsdb.BlockMeta) (ulid.ULID, error) {
  1170  	args := m.Called(dest, b, mint, maxt, parent)
  1171  	return args.Get(0).(ulid.ULID), args.Error(1)
  1172  }
  1173  
  1174  func (m *tsdbCompactorMock) Compact(dest string, dirs []string, open []*tsdb.Block) (ulid.ULID, error) {
  1175  	args := m.Called(dest, dirs, open)
  1176  	return args.Get(0).(ulid.ULID), args.Error(1)
  1177  }
  1178  
  1179  type tsdbPlannerMock struct {
  1180  	mock.Mock
  1181  }
  1182  
  1183  func (m *tsdbPlannerMock) Plan(ctx context.Context, metasByMinTime []*metadata.Meta) ([]*metadata.Meta, error) {
  1184  	args := m.Called(ctx, metasByMinTime)
  1185  	return args.Get(0).([]*metadata.Meta), args.Error(1)
  1186  }
  1187  
  1188  func mockBlockMetaJSON(id string) string {
  1189  	meta := tsdb.BlockMeta{
  1190  		Version: 1,
  1191  		ULID:    ulid.MustParse(id),
  1192  		MinTime: 1574776800000,
  1193  		MaxTime: 1574784000000,
  1194  		Compaction: tsdb.BlockMetaCompaction{
  1195  			Level:   1,
  1196  			Sources: []ulid.ULID{ulid.MustParse(id)},
  1197  		},
  1198  	}
  1199  
  1200  	content, err := json.Marshal(meta)
  1201  	if err != nil {
  1202  		panic("failed to marshal mocked block meta")
  1203  	}
  1204  
  1205  	return string(content)
  1206  }
  1207  
  1208  func mockDeletionMarkJSON(id string, deletionTime time.Time) string {
  1209  	meta := metadata.DeletionMark{
  1210  		Version:      metadata.DeletionMarkVersion1,
  1211  		ID:           ulid.MustParse(id),
  1212  		DeletionTime: deletionTime.Unix(),
  1213  	}
  1214  
  1215  	content, err := json.Marshal(meta)
  1216  	if err != nil {
  1217  		panic("failed to marshal mocked block meta")
  1218  	}
  1219  
  1220  	return string(content)
  1221  }
  1222  
  1223  func TestCompactor_DeleteLocalSyncFiles(t *testing.T) {
  1224  	numUsers := 10
  1225  
  1226  	// Setup user IDs
  1227  	userIDs := make([]string, 0, numUsers)
  1228  	for i := 1; i <= numUsers; i++ {
  1229  		userIDs = append(userIDs, fmt.Sprintf("user-%d", i))
  1230  	}
  1231  
  1232  	inmem := objstore.NewInMemBucket()
  1233  	for _, userID := range userIDs {
  1234  		id, err := ulid.New(ulid.Now(), rand.Reader)
  1235  		require.NoError(t, err)
  1236  		require.NoError(t, inmem.Upload(context.Background(), userID+"/"+id.String()+"/meta.json", strings.NewReader(mockBlockMetaJSON(id.String()))))
  1237  	}
  1238  
  1239  	// Create a shared KV Store
  1240  	kvstore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil)
  1241  	t.Cleanup(func() { assert.NoError(t, closer.Close()) })
  1242  
  1243  	// Create two compactors
  1244  	var compactors []*Compactor
  1245  
  1246  	for i := 1; i <= 2; i++ {
  1247  		cfg := prepareConfig()
  1248  		cfg.CompactionInterval = 10 * time.Minute // We will only call compaction manually.
  1249  
  1250  		cfg.ShardingEnabled = true
  1251  		cfg.ShardingRing.InstanceID = fmt.Sprintf("compactor-%d", i)
  1252  		cfg.ShardingRing.InstanceAddr = fmt.Sprintf("127.0.0.%d", i)
  1253  		cfg.ShardingRing.WaitStabilityMinDuration = 3 * time.Second
  1254  		cfg.ShardingRing.WaitStabilityMaxDuration = 10 * time.Second
  1255  		cfg.ShardingRing.KVStore.Mock = kvstore
  1256  
  1257  		// Each compactor will get its own temp dir for storing local files.
  1258  		c, _, tsdbPlanner, _, _ := prepare(t, cfg, inmem)
  1259  		t.Cleanup(func() {
  1260  			require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c))
  1261  		})
  1262  
  1263  		compactors = append(compactors, c)
  1264  
  1265  		// Mock the planner as if there's no compaction to do,
  1266  		// in order to simplify tests (all in all, we just want to
  1267  		// test our logic and not TSDB compactor which we expect to
  1268  		// be already tested).
  1269  		tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*metadata.Meta{}, nil)
  1270  	}
  1271  
  1272  	require.Equal(t, 2, len(compactors))
  1273  	c1 := compactors[0]
  1274  	c2 := compactors[1]
  1275  
  1276  	// Start first compactor
  1277  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c1))
  1278  
  1279  	// Wait until a run has been completed on first compactor. This happens as soon as compactor starts.
  1280  	cortex_testutil.Poll(t, 10*time.Second, 1.0, func() interface{} {
  1281  		return prom_testutil.ToFloat64(c1.compactionRunsCompleted)
  1282  	})
  1283  
  1284  	require.NoError(t, os.Mkdir(c1.metaSyncDirForUser("new-user"), 0600))
  1285  
  1286  	// Verify that first compactor has synced all the users, plus there is one extra we have just created.
  1287  	require.Equal(t, numUsers+1, len(c1.listTenantsWithMetaSyncDirectories()))
  1288  
  1289  	// Now start second compactor, and wait until it runs compaction.
  1290  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), c2))
  1291  	cortex_testutil.Poll(t, 10*time.Second, 1.0, func() interface{} {
  1292  		return prom_testutil.ToFloat64(c2.compactionRunsCompleted)
  1293  	})
  1294  
  1295  	// Let's check how many users second compactor has.
  1296  	c2Users := len(c2.listTenantsWithMetaSyncDirectories())
  1297  	require.NotZero(t, c2Users)
  1298  
  1299  	// Force new compaction cycle on first compactor. It will run the cleanup of un-owned users at the end of compaction cycle.
  1300  	c1.compactUsers(context.Background())
  1301  	c1Users := len(c1.listTenantsWithMetaSyncDirectories())
  1302  
  1303  	// Now compactor 1 should have cleaned old sync files.
  1304  	require.NotEqual(t, numUsers, c1Users)
  1305  	require.Equal(t, numUsers, c1Users+c2Users)
  1306  }
  1307  
  1308  func TestCompactor_ShouldFailCompactionOnTimeout(t *testing.T) {
  1309  	t.Parallel()
  1310  
  1311  	// Mock the bucket
  1312  	bucketClient := &bucket.ClientMock{}
  1313  	bucketClient.MockIter("", []string{}, nil)
  1314  
  1315  	ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil)
  1316  	t.Cleanup(func() { assert.NoError(t, closer.Close()) })
  1317  
  1318  	cfg := prepareConfig()
  1319  	cfg.ShardingEnabled = true
  1320  	cfg.ShardingRing.InstanceID = "compactor-1"
  1321  	cfg.ShardingRing.InstanceAddr = "1.2.3.4"
  1322  	cfg.ShardingRing.KVStore.Mock = ringStore
  1323  
  1324  	// Set ObservePeriod to longer than the timeout period to mock a timeout while waiting on ring to become ACTIVE
  1325  	cfg.ShardingRing.ObservePeriod = time.Second * 10
  1326  
  1327  	c, _, _, logs, _ := prepare(t, cfg, bucketClient)
  1328  
  1329  	// Try to start the compactor with a bad consul kv-store. The
  1330  	err := services.StartAndAwaitRunning(context.Background(), c)
  1331  
  1332  	// Assert that the compactor timed out
  1333  	assert.Equal(t, context.DeadlineExceeded, err)
  1334  
  1335  	assert.ElementsMatch(t, []string{
  1336  		`level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`,
  1337  		`level=error component=compactor msg="compactor failed to become ACTIVE in the ring" err="context deadline exceeded"`,
  1338  	}, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n")))
  1339  }