Skip to content

Commit 2cc4540

Browse files
committed
add unit tests
Signed-off-by: Yi Jin <[email protected]>
1 parent c3c1e59 commit 2cc4540

File tree

2 files changed

+222
-12
lines changed

2 files changed

+222
-12
lines changed

pkg/compact/retention.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
)
2323

2424
const (
25+
// tenantRetentionRegex is the regex pattern for parsing tenant retention.
26+
// valid format is `<tenant>:(<yyyy-mm-dd>|<duration>d)` where <duration> > 0.
2527
tenantRetentionRegex = `^([\w-]+):((\d{4}-\d{2}-\d{2})|(\d+d))$`
2628
)
2729

@@ -80,14 +82,18 @@ func ParesRetentionPolicyByTenant(logger log.Logger, retentionTenants []string)
8082
if _, ok := retentionByTenant[tenant]; ok {
8183
return nil, errors.Errorf("duplicate retention policy for tenant: %s", tenant)
8284
}
83-
if cutoffDate, err := time.Parse(time.DateOnly, matches[3]); err != nil && matches[3] != "" {
84-
return nil, errors.Wrapf(invalidFormat, "error parsing cutoff date: %v", err)
85-
} else if matches[3] != "" {
85+
if cutoffDate, err := time.Parse(time.DateOnly, matches[3]); matches[3] != "" {
86+
if err != nil {
87+
return nil, errors.Wrapf(invalidFormat, "error parsing cutoff date: %v", err)
88+
}
8689
policy.CutoffDate = cutoffDate
8790
}
88-
if duration, err := model.ParseDuration(matches[4]); err != nil && matches[4] != "" {
89-
return nil, errors.Wrapf(invalidFormat, "error parsing duration: %v", err)
90-
} else if matches[4] != "" {
91+
if duration, err := model.ParseDuration(matches[4]); matches[4] != "" {
92+
if err != nil {
93+
return nil, errors.Wrapf(invalidFormat, "error parsing duration: %v", err)
94+
} else if duration == 0 {
95+
return nil, errors.Wrapf(invalidFormat, "duration must be greater than 0")
96+
}
9197
policy.RetentionDuration = time.Duration(duration)
9298
}
9399
level.Info(logger).Log("msg", "retention policy for tenant is enabled", "tenant", tenant, "retention policy", fmt.Sprintf("%v", policy))

pkg/compact/retention_test.go

Lines changed: 210 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"testing"
1313
"time"
1414

15-
"github.com/efficientgo/core/testutil"
1615
"github.com/go-kit/log"
1716
"github.com/oklog/ulid"
1817
"github.com/prometheus/client_golang/prometheus"
@@ -21,6 +20,8 @@ import (
2120
"github.com/prometheus/prometheus/tsdb"
2221
"github.com/thanos-io/objstore"
2322

23+
"github.com/efficientgo/core/testutil"
24+
2425
"github.com/thanos-io/thanos/pkg/block"
2526
"github.com/thanos-io/thanos/pkg/block/metadata"
2627
"github.com/thanos-io/thanos/pkg/compact"
@@ -281,6 +282,31 @@ func TestApplyRetentionPolicyByResolution(t *testing.T) {
281282
}
282283
}
283284

285+
func uploadMockBlock(t *testing.T, bkt objstore.Bucket, id string, minTime, maxTime time.Time, resolutionLevel int64) {
286+
t.Helper()
287+
meta1 := metadata.Meta{
288+
BlockMeta: tsdb.BlockMeta{
289+
ULID: ulid.MustParse(id),
290+
MinTime: minTime.Unix() * 1000,
291+
MaxTime: maxTime.Unix() * 1000,
292+
Version: 1,
293+
},
294+
Thanos: metadata.Thanos{
295+
Downsample: metadata.ThanosDownsample{
296+
Resolution: resolutionLevel,
297+
},
298+
},
299+
}
300+
301+
b, err := json.Marshal(meta1)
302+
testutil.Ok(t, err)
303+
304+
testutil.Ok(t, bkt.Upload(context.Background(), id+"/meta.json", bytes.NewReader(b)))
305+
testutil.Ok(t, bkt.Upload(context.Background(), id+"/chunks/000001", strings.NewReader("@test-data@")))
306+
testutil.Ok(t, bkt.Upload(context.Background(), id+"/chunks/000002", strings.NewReader("@test-data@")))
307+
testutil.Ok(t, bkt.Upload(context.Background(), id+"/chunks/000003", strings.NewReader("@test-data@")))
308+
}
309+
284310
func TestParseRetentionPolicyByTenant(t *testing.T) {
285311
t.Parallel()
286312

@@ -298,15 +324,15 @@ func TestParseRetentionPolicyByTenant(t *testing.T) {
298324
},
299325
{
300326
"valid",
301-
[]string{"tenant-1:2021-01-01", "tenant-2:3d"},
327+
[]string{"tenant-1:2021-01-01", "tenant-2:11d"},
302328
map[string]compact.RetentionPolicy{
303329
"tenant-1": {
304330
CutoffDate: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
305331
RetentionDuration: time.Duration(0),
306332
},
307333
"tenant-2": {
308334
CutoffDate: time.Time{},
309-
RetentionDuration: 3 * 24 * time.Hour,
335+
RetentionDuration: 11 * 24 * time.Hour,
310336
},
311337
},
312338
false,
@@ -329,6 +355,12 @@ func TestParseRetentionPolicyByTenant(t *testing.T) {
329355
nil,
330356
true,
331357
},
358+
{
359+
"invalid duration which is 0",
360+
[]string{"tenant1:2021-01-01", "tenant2:0d"},
361+
nil,
362+
true,
363+
},
332364
} {
333365
t.Run(tt.name, func(t *testing.T) {
334366
got, err := compact.ParesRetentionPolicyByTenant(log.NewNopLogger(), tt.retentionTenants)
@@ -341,7 +373,179 @@ func TestParseRetentionPolicyByTenant(t *testing.T) {
341373
}
342374
}
343375

344-
func uploadMockBlock(t *testing.T, bkt objstore.Bucket, id string, minTime, maxTime time.Time, resolutionLevel int64) {
376+
func TestApplyRetentionPolicyByTenant(t *testing.T) {
377+
t.Parallel()
378+
379+
type testBlock struct {
380+
id, tenant string
381+
minTime time.Time
382+
maxTime time.Time
383+
}
384+
385+
logger := log.NewNopLogger()
386+
ctx := context.TODO()
387+
388+
for _, tt := range []struct {
389+
name string
390+
blocks []testBlock
391+
retentionByTenant map[string]compact.RetentionPolicy
392+
want []string
393+
wantErr bool
394+
}{
395+
{
396+
"empty bucket",
397+
[]testBlock{},
398+
map[string]compact.RetentionPolicy{},
399+
[]string{},
400+
false,
401+
},
402+
{
403+
"tenant retention disabled",
404+
[]testBlock{
405+
{
406+
"01CPHBEX20729MJQZXE3W0BW48",
407+
"tenant-1",
408+
time.Now().Add(-3 * 24 * time.Hour),
409+
time.Now().Add(-2 * 24 * time.Hour),
410+
},
411+
{
412+
"01CPHBEX20729MJQZXE3W0BW49",
413+
"tenant-2",
414+
time.Now().Add(-3 * 24 * time.Hour),
415+
time.Now().Add(-2 * 24 * time.Hour),
416+
},
417+
},
418+
map[string]compact.RetentionPolicy{},
419+
[]string{
420+
"01CPHBEX20729MJQZXE3W0BW48/",
421+
"01CPHBEX20729MJQZXE3W0BW49/",
422+
},
423+
false,
424+
},
425+
{
426+
"tenant retention with duration",
427+
[]testBlock{
428+
{
429+
"01CPHBEX20729MJQZXE3W0BW48",
430+
"tenant-1",
431+
time.Now().Add(-3 * 24 * time.Hour),
432+
time.Now().Add(-2 * 24 * time.Hour),
433+
},
434+
{
435+
"01CPHBEX20729MJQZXE3W0BW49",
436+
"tenant-1",
437+
time.Now().Add(-2 * 24 * time.Hour),
438+
time.Now().Add(-24 * time.Hour),
439+
},
440+
{
441+
"01CPHBEX20729MJQZXE3W0BW50",
442+
"tenant-2",
443+
time.Now().Add(-24 * time.Hour),
444+
time.Now().Add(-23 * time.Hour),
445+
},
446+
{
447+
"01CPHBEX20729MJQZXE3W0BW51",
448+
"tenant-2",
449+
time.Now().Add(-23 * time.Hour),
450+
time.Now().Add(-6 * time.Hour),
451+
},
452+
},
453+
map[string]compact.RetentionPolicy{
454+
"tenant-2": {
455+
CutoffDate: time.Time{},
456+
RetentionDuration: 10 * time.Hour,
457+
},
458+
},
459+
[]string{
460+
"01CPHBEX20729MJQZXE3W0BW48/",
461+
"01CPHBEX20729MJQZXE3W0BW49/",
462+
"01CPHBEX20729MJQZXE3W0BW51/",
463+
},
464+
false,
465+
},
466+
{
467+
"tenant retention with cutoff date",
468+
[]testBlock{
469+
{
470+
"01CPHBEX20729MJQZXE3W0BW48",
471+
"tenant-1",
472+
time.Now().Add(-3 * 24 * time.Hour),
473+
time.Now().Add(-2 * 24 * time.Hour),
474+
},
475+
{
476+
"01CPHBEX20729MJQZXE3W0BW49",
477+
"tenant-1",
478+
time.Now().Add(-2 * 24 * time.Hour),
479+
time.Now().Add(-24 * time.Hour),
480+
},
481+
{
482+
"01CPHBEX20729MJQZXE3W0BW50",
483+
"tenant-2",
484+
time.Date(2024, 11, 1, 0, 0, 0, 0, time.UTC),
485+
time.Date(2024, 11, 1, 0, 0, 0, 0, time.UTC),
486+
},
487+
{
488+
"01CPHBEX20729MJQZXE3W0BW51",
489+
"tenant-2",
490+
time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
491+
time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
492+
},
493+
},
494+
map[string]compact.RetentionPolicy{
495+
"tenant-2": {
496+
CutoffDate: time.Date(2024, 10, 1, 0, 0, 0, 0, time.UTC),
497+
RetentionDuration: 0,
498+
},
499+
},
500+
[]string{
501+
"01CPHBEX20729MJQZXE3W0BW48/",
502+
"01CPHBEX20729MJQZXE3W0BW49/",
503+
"01CPHBEX20729MJQZXE3W0BW50/",
504+
},
505+
false,
506+
},
507+
} {
508+
t.Run(tt.name, func(t *testing.T) {
509+
bkt := objstore.WithNoopInstr(objstore.NewInMemBucket())
510+
for _, b := range tt.blocks {
511+
uploadTenantBlock(t, bkt, b.id, b.tenant, b.minTime, b.maxTime)
512+
}
513+
514+
baseBlockIDsFetcher := block.NewConcurrentLister(logger, bkt)
515+
metaFetcher, err := block.NewMetaFetcher(logger, 32, bkt, baseBlockIDsFetcher, "", nil, nil)
516+
testutil.Ok(t, err)
517+
518+
blocksMarkedForDeletion := promauto.With(nil).NewCounter(prometheus.CounterOpts{})
519+
520+
metas, _, err := metaFetcher.Fetch(ctx)
521+
testutil.Ok(t, err)
522+
523+
if err := compact.ApplyRetentionPolicyByTenant(ctx, logger, bkt, metas, tt.retentionByTenant, blocksMarkedForDeletion); (err != nil) != tt.wantErr {
524+
t.Errorf("ApplyRetentionPolicyByResolution() error = %v, wantErr %v", err, tt.wantErr)
525+
}
526+
527+
got := []string{}
528+
gotMarkedBlocksCount := 0.0
529+
testutil.Ok(t, bkt.Iter(context.TODO(), "", func(name string) error {
530+
exists, err := bkt.Exists(ctx, filepath.Join(name, metadata.DeletionMarkFilename))
531+
if err != nil {
532+
return err
533+
}
534+
if !exists {
535+
got = append(got, name)
536+
return nil
537+
}
538+
gotMarkedBlocksCount += 1.0
539+
return nil
540+
}))
541+
542+
testutil.Equals(t, got, tt.want)
543+
testutil.Equals(t, gotMarkedBlocksCount, promtest.ToFloat64(blocksMarkedForDeletion))
544+
})
545+
}
546+
}
547+
548+
func uploadTenantBlock(t *testing.T, bkt objstore.Bucket, id, tenant string, minTime, maxTime time.Time) {
345549
t.Helper()
346550
meta1 := metadata.Meta{
347551
BlockMeta: tsdb.BlockMeta{
@@ -351,8 +555,8 @@ func uploadMockBlock(t *testing.T, bkt objstore.Bucket, id string, minTime, maxT
351555
Version: 1,
352556
},
353557
Thanos: metadata.Thanos{
354-
Downsample: metadata.ThanosDownsample{
355-
Resolution: resolutionLevel,
558+
Labels: map[string]string{
559+
metadata.TenantLabel: tenant,
356560
},
357561
},
358562
}

0 commit comments

Comments
 (0)