github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/chunk/purger/tombstones_test.go (about)

     1  package purger
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/prometheus/common/model"
    10  	"github.com/prometheus/prometheus/promql/parser"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestTombstonesLoader(t *testing.T) {
    16  	deleteRequestSelectors := []string{"foo"}
    17  	metric, err := parser.ParseMetric(deleteRequestSelectors[0])
    18  	require.NoError(t, err)
    19  
    20  	for _, tc := range []struct {
    21  		name                   string
    22  		deleteRequestIntervals []model.Interval
    23  		queryForInterval       model.Interval
    24  		expectedIntervals      []model.Interval
    25  	}{
    26  		{
    27  			name:             "no delete requests",
    28  			queryForInterval: model.Interval{End: modelTimeDay},
    29  		},
    30  		{
    31  			name: "query out of range of delete requests",
    32  			deleteRequestIntervals: []model.Interval{
    33  				{End: modelTimeDay},
    34  			},
    35  			queryForInterval: model.Interval{Start: modelTimeDay.Add(time.Hour), End: modelTimeDay * 2},
    36  		},
    37  		{
    38  			name: "no overlap but disjoint deleted intervals",
    39  			deleteRequestIntervals: []model.Interval{
    40  				{End: modelTimeDay},
    41  				{Start: modelTimeDay.Add(time.Hour), End: modelTimeDay.Add(2 * time.Hour)},
    42  			},
    43  			queryForInterval: model.Interval{End: modelTimeDay.Add(2 * time.Hour)},
    44  			expectedIntervals: []model.Interval{
    45  				{End: modelTimeDay},
    46  				{Start: modelTimeDay.Add(time.Hour), End: modelTimeDay.Add(2 * time.Hour)},
    47  			},
    48  		},
    49  		{
    50  			name: "no overlap but continuous deleted intervals",
    51  			deleteRequestIntervals: []model.Interval{
    52  				{End: modelTimeDay},
    53  				{Start: modelTimeDay, End: modelTimeDay.Add(2 * time.Hour)},
    54  			},
    55  			queryForInterval: model.Interval{End: modelTimeDay.Add(2 * time.Hour)},
    56  			expectedIntervals: []model.Interval{
    57  				{End: modelTimeDay.Add(2 * time.Hour)},
    58  			},
    59  		},
    60  		{
    61  			name: "some overlap in deleted intervals",
    62  			deleteRequestIntervals: []model.Interval{
    63  				{End: modelTimeDay},
    64  				{Start: modelTimeDay.Add(-time.Hour), End: modelTimeDay.Add(2 * time.Hour)},
    65  			},
    66  			queryForInterval: model.Interval{End: modelTimeDay.Add(2 * time.Hour)},
    67  			expectedIntervals: []model.Interval{
    68  				{End: modelTimeDay.Add(2 * time.Hour)},
    69  			},
    70  		},
    71  		{
    72  			name: "complete overlap in deleted intervals",
    73  			deleteRequestIntervals: []model.Interval{
    74  				{End: modelTimeDay},
    75  				{End: modelTimeDay},
    76  			},
    77  			queryForInterval: model.Interval{End: modelTimeDay.Add(2 * time.Hour)},
    78  			expectedIntervals: []model.Interval{
    79  				{End: modelTimeDay},
    80  			},
    81  		},
    82  		{
    83  			name: "mix of overlaps in deleted intervals",
    84  			deleteRequestIntervals: []model.Interval{
    85  				{End: modelTimeDay},
    86  				{End: modelTimeDay},
    87  				{Start: modelTimeDay.Add(time.Hour), End: modelTimeDay.Add(2 * time.Hour)},
    88  				{Start: modelTimeDay.Add(2 * time.Hour), End: modelTimeDay.Add(24 * time.Hour)},
    89  				{Start: modelTimeDay.Add(23 * time.Hour), End: modelTimeDay * 3},
    90  			},
    91  			queryForInterval: model.Interval{End: modelTimeDay * 10},
    92  			expectedIntervals: []model.Interval{
    93  				{End: modelTimeDay},
    94  				{Start: modelTimeDay.Add(time.Hour), End: modelTimeDay * 3},
    95  			},
    96  		},
    97  	} {
    98  		t.Run(tc.name, func(t *testing.T) {
    99  			deleteStore := setupTestDeleteStore(t)
   100  			tombstonesLoader := NewTombstonesLoader(deleteStore, nil)
   101  
   102  			// add delete requests
   103  			for _, interval := range tc.deleteRequestIntervals {
   104  				err := deleteStore.AddDeleteRequest(context.Background(), userID, interval.Start, interval.End, deleteRequestSelectors)
   105  				require.NoError(t, err)
   106  			}
   107  
   108  			// get all delete requests for user
   109  			tombstonesAnalyzer, err := tombstonesLoader.GetPendingTombstones(userID)
   110  			require.NoError(t, err)
   111  
   112  			// verify whether number of delete requests is same as what we added
   113  			require.Equal(t, len(tc.deleteRequestIntervals), tombstonesAnalyzer.Len())
   114  
   115  			// if we are expecting to get deleted intervals then HasTombstonesForInterval should return true else false
   116  			expectedHasTombstonesForInterval := true
   117  			if len(tc.expectedIntervals) == 0 {
   118  				expectedHasTombstonesForInterval = false
   119  			}
   120  
   121  			hasTombstonesForInterval := tombstonesAnalyzer.HasTombstonesForInterval(tc.queryForInterval.Start, tc.queryForInterval.End)
   122  			require.Equal(t, expectedHasTombstonesForInterval, hasTombstonesForInterval)
   123  
   124  			// get deleted intervals
   125  			intervals := tombstonesAnalyzer.GetDeletedIntervals(metric, tc.queryForInterval.Start, tc.queryForInterval.End)
   126  			require.Equal(t, len(tc.expectedIntervals), len(intervals))
   127  
   128  			// verify whether we got expected intervals back
   129  			for i, interval := range intervals {
   130  				require.Equal(t, tc.expectedIntervals[i].Start, interval.Start)
   131  				require.Equal(t, tc.expectedIntervals[i].End, interval.End)
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  func TestTombstonesLoader_GetCacheGenNumber(t *testing.T) {
   138  	s := &store{
   139  		numbers: map[string]*cacheGenNumbers{
   140  			"tenant-a": {
   141  				results: "1000",
   142  				store:   "2050",
   143  			},
   144  			"tenant-b": {
   145  				results: "1050",
   146  				store:   "2000",
   147  			},
   148  			"tenant-c": {
   149  				results: "",
   150  				store:   "",
   151  			},
   152  			"tenant-d": {
   153  				results: "results-c",
   154  				store:   "store-c",
   155  			},
   156  		},
   157  	}
   158  	tombstonesLoader := NewTombstonesLoader(s, nil)
   159  
   160  	for _, tc := range []struct {
   161  		name                          string
   162  		expectedResultsCacheGenNumber string
   163  		expectedStoreCacheGenNumber   string
   164  		tenantIDs                     []string
   165  	}{
   166  		{
   167  			name:                          "single tenant with numeric values",
   168  			tenantIDs:                     []string{"tenant-a"},
   169  			expectedResultsCacheGenNumber: "1000",
   170  			expectedStoreCacheGenNumber:   "2050",
   171  		},
   172  		{
   173  			name:                          "single tenant with non-numeric values",
   174  			tenantIDs:                     []string{"tenant-d"},
   175  			expectedResultsCacheGenNumber: "results-c",
   176  			expectedStoreCacheGenNumber:   "store-c",
   177  		},
   178  		{
   179  			name:                          "multiple tenants with numeric values",
   180  			tenantIDs:                     []string{"tenant-a", "tenant-b"},
   181  			expectedResultsCacheGenNumber: "1050",
   182  			expectedStoreCacheGenNumber:   "2050",
   183  		},
   184  		{
   185  			name:                          "multiple tenants with numeric and non-numeric values",
   186  			tenantIDs:                     []string{"tenant-d", "tenant-c", "tenant-b", "tenant-a"},
   187  			expectedResultsCacheGenNumber: "1050",
   188  			expectedStoreCacheGenNumber:   "2050",
   189  		},
   190  		{
   191  			name: "no tenants", // not really an expected call, edge case check to avoid any panics
   192  		},
   193  	} {
   194  		t.Run(tc.name, func(t *testing.T) {
   195  			assert.Equal(t, tc.expectedResultsCacheGenNumber, tombstonesLoader.GetResultsCacheGenNumber(tc.tenantIDs))
   196  			assert.Equal(t, tc.expectedStoreCacheGenNumber, tombstonesLoader.GetStoreCacheGenNumber(tc.tenantIDs))
   197  		})
   198  	}
   199  }
   200  
   201  func TestTombstonesReloadDoesntDeadlockOnFailure(t *testing.T) {
   202  	s := &store{}
   203  	tombstonesLoader := NewTombstonesLoader(s, nil)
   204  	tombstonesLoader.getCacheGenNumbers("test")
   205  
   206  	s.err = errors.New("error")
   207  	require.NotNil(t, tombstonesLoader.reloadTombstones())
   208  
   209  	s.err = nil
   210  	require.NotNil(t, tombstonesLoader.getCacheGenNumbers("test2"))
   211  }
   212  
   213  type store struct {
   214  	numbers map[string]*cacheGenNumbers
   215  	err     error
   216  }
   217  
   218  func (f *store) getCacheGenerationNumbers(ctx context.Context, user string) (*cacheGenNumbers, error) {
   219  	if f.numbers != nil {
   220  		number, ok := f.numbers[user]
   221  		if ok {
   222  			return number, nil
   223  		}
   224  	}
   225  	return &cacheGenNumbers{}, f.err
   226  }
   227  
   228  func (f *store) GetPendingDeleteRequestsForUser(ctx context.Context, id string) ([]DeleteRequest, error) {
   229  	return nil, nil
   230  }