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 }