github.com/thanos-io/thanos@v0.32.5/pkg/replicate/scheme_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package replicate
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"io"
    11  	"math/rand"
    12  	"os"
    13  	"path"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/go-kit/log"
    18  	"github.com/go-kit/log/level"
    19  	"github.com/oklog/ulid"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"github.com/prometheus/prometheus/tsdb"
    22  
    23  	"github.com/thanos-io/objstore"
    24  
    25  	"github.com/efficientgo/core/testutil"
    26  	"github.com/thanos-io/thanos/pkg/block/metadata"
    27  	"github.com/thanos-io/thanos/pkg/compact"
    28  	"github.com/thanos-io/thanos/pkg/model"
    29  )
    30  
    31  var (
    32  	minTime         = time.Unix(0, 0)
    33  	maxTime, _      = time.Parse(time.RFC3339, "9999-12-31T23:59:59Z")
    34  	minTimeDuration = model.TimeOrDurationValue{Time: &minTime}
    35  	maxTimeDuration = model.TimeOrDurationValue{Time: &maxTime}
    36  )
    37  
    38  func testLogger(testName string) log.Logger {
    39  	return log.With(
    40  		level.NewFilter(log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)), level.AllowDebug()),
    41  		"test", testName,
    42  	)
    43  }
    44  
    45  func testULID(inc int64) ulid.ULID {
    46  	timestamp := time.Unix(1000000+inc, 0)
    47  	entropy := ulid.Monotonic(rand.New(rand.NewSource(timestamp.UnixNano())), 0)
    48  	ulid := ulid.MustNew(ulid.Timestamp(timestamp), entropy)
    49  
    50  	return ulid
    51  }
    52  
    53  func testMeta(ulid ulid.ULID) *metadata.Meta {
    54  	return &metadata.Meta{
    55  		Thanos: metadata.Thanos{
    56  			Labels: map[string]string{
    57  				"test-labelname": "test-labelvalue",
    58  			},
    59  			Downsample: metadata.ThanosDownsample{
    60  				Resolution: int64(compact.ResolutionLevelRaw),
    61  			},
    62  		},
    63  		BlockMeta: tsdb.BlockMeta{
    64  			ULID: ulid,
    65  			Compaction: tsdb.BlockMetaCompaction{
    66  				Level: 1,
    67  			},
    68  			Version: metadata.TSDBVersion1,
    69  		},
    70  	}
    71  }
    72  
    73  func testDeletionMark(ulid ulid.ULID) *metadata.DeletionMark {
    74  	return &metadata.DeletionMark{
    75  		ID:           ulid,
    76  		Version:      metadata.DeletionMarkVersion1,
    77  		Details:      "tests deletion mark",
    78  		DeletionTime: time.Time{}.Unix(),
    79  	}
    80  }
    81  
    82  func TestReplicationSchemeAll(t *testing.T) {
    83  	testBlockID := testULID(0)
    84  	var cases = []struct {
    85  		name                    string
    86  		selector                labels.Selector
    87  		blockIDs                []ulid.ULID
    88  		ignoreMarkedForDeletion bool
    89  		prepare                 func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket)
    90  		assert                  func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket)
    91  	}{
    92  		{
    93  			name:    "EmptyOrigin",
    94  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {},
    95  			assert:  func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {},
    96  		},
    97  		{
    98  			name: "NoMeta",
    99  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   100  				_ = originBucket.Upload(ctx, path.Join(testULID(0).String(), "chunks", "000001"), bytes.NewReader(nil))
   101  			},
   102  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   103  				if len(targetBucket.Objects()) != 0 {
   104  					t.Fatal("TargetBucket should have been empty but is not.")
   105  				}
   106  			},
   107  		},
   108  		{
   109  			name: "PartialMeta",
   110  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   111  				_ = originBucket.Upload(ctx, path.Join(testULID(0).String(), "meta.json"), bytes.NewReader([]byte("{")))
   112  			},
   113  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   114  				if len(targetBucket.Objects()) != 0 {
   115  					t.Fatal("TargetBucket should have been empty but is not.")
   116  				}
   117  			},
   118  		},
   119  		{
   120  			name: "FullBlock",
   121  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   122  				ulid := testULID(0)
   123  				meta := testMeta(ulid)
   124  
   125  				b, err := json.Marshal(meta)
   126  				testutil.Ok(t, err)
   127  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   128  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   129  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   130  			},
   131  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   132  				if len(targetBucket.Objects()) != 3 {
   133  					t.Fatal("TargetBucket should have one block made up of three objects replicated.")
   134  				}
   135  			},
   136  		},
   137  		{
   138  			name:                    "MarkedForDeletion",
   139  			ignoreMarkedForDeletion: true,
   140  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   141  				ulid := testULID(0)
   142  				meta := testMeta(ulid)
   143  				deletionMark := testDeletionMark(ulid)
   144  
   145  				b, err := json.Marshal(meta)
   146  				testutil.Ok(t, err)
   147  				d, err := json.Marshal(deletionMark)
   148  				testutil.Ok(t, err)
   149  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   150  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "deletion-mark.json"), bytes.NewReader(d))
   151  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   152  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   153  			},
   154  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   155  				testutil.Equals(t, map[string][]byte{}, targetBucket.Objects())
   156  			},
   157  		},
   158  		{
   159  			name: "PreviousPartialUpload",
   160  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   161  				ulid := testULID(0)
   162  				meta := testMeta(ulid)
   163  
   164  				b, err := json.Marshal(meta)
   165  				testutil.Ok(t, err)
   166  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   167  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   168  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   169  
   170  				_ = targetBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), io.LimitReader(bytes.NewReader(b), int64(len(b)-10)))
   171  				_ = targetBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   172  				_ = targetBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   173  			},
   174  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   175  				for k := range originBucket.Objects() {
   176  					if !bytes.Equal(originBucket.Objects()[k], targetBucket.Objects()[k]) {
   177  						t.Fatalf("Object %s not equal in origin and target bucket.", k)
   178  					}
   179  				}
   180  			},
   181  		},
   182  		{
   183  			name: "OnlyUploadsRaw",
   184  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   185  				ulid := testULID(0)
   186  				meta := testMeta(ulid)
   187  
   188  				b, err := json.Marshal(meta)
   189  				testutil.Ok(t, err)
   190  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   191  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   192  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   193  
   194  				ulid = testULID(1)
   195  				meta = testMeta(ulid)
   196  				meta.Thanos.Downsample.Resolution = int64(compact.ResolutionLevel5m)
   197  
   198  				b, err = json.Marshal(meta)
   199  				testutil.Ok(t, err)
   200  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   201  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   202  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   203  			},
   204  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   205  				expected := 3
   206  				got := len(targetBucket.Objects())
   207  				if got != expected {
   208  					t.Fatalf("TargetBucket should have one block made up of three objects replicated. Got %d but expected %d objects.", got, expected)
   209  				}
   210  			},
   211  		},
   212  		{
   213  			name: "UploadMultipleCandidatesWhenPresent",
   214  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   215  				ulid := testULID(0)
   216  				meta := testMeta(ulid)
   217  
   218  				b, err := json.Marshal(meta)
   219  				testutil.Ok(t, err)
   220  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   221  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   222  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   223  
   224  				ulid = testULID(1)
   225  				meta = testMeta(ulid)
   226  
   227  				b, err = json.Marshal(meta)
   228  				testutil.Ok(t, err)
   229  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   230  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   231  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   232  			},
   233  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   234  				expected := 6
   235  				got := len(targetBucket.Objects())
   236  				if got != expected {
   237  					t.Fatalf("TargetBucket should have two blocks made up of three objects replicated. Got %d but expected %d objects.", got, expected)
   238  				}
   239  			},
   240  		},
   241  		{
   242  			name: "LabelSelector",
   243  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   244  				ulid := testULID(0)
   245  				meta := testMeta(ulid)
   246  
   247  				b, err := json.Marshal(meta)
   248  				testutil.Ok(t, err)
   249  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   250  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   251  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   252  
   253  				ulid = testULID(1)
   254  				meta = testMeta(ulid)
   255  				meta.Thanos.Labels["test-labelname"] = "non-selected-value"
   256  
   257  				b, err = json.Marshal(meta)
   258  				testutil.Ok(t, err)
   259  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   260  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   261  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   262  			},
   263  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   264  				expected := 3
   265  				got := len(targetBucket.Objects())
   266  				if got != expected {
   267  					t.Fatalf("TargetBucket should have one block made up of three objects replicated. Got %d but expected %d objects.", got, expected)
   268  				}
   269  			},
   270  		},
   271  		{
   272  			name: "NonZeroCompaction",
   273  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   274  				ulid := testULID(0)
   275  				meta := testMeta(ulid)
   276  				meta.BlockMeta.Compaction.Level = 2
   277  
   278  				b, err := json.Marshal(meta)
   279  				testutil.Ok(t, err)
   280  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   281  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   282  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   283  			},
   284  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   285  				if len(targetBucket.Objects()) != 0 {
   286  					t.Fatal("TargetBucket should have been empty but is not.")
   287  				}
   288  			},
   289  		},
   290  		{
   291  			name:     "Regression",
   292  			selector: labels.Selector{},
   293  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   294  				b := []byte(`{
   295          "ulid": "01DQYXMK8G108CEBQ79Y84DYVY",
   296          "minTime": 1571911200000,
   297          "maxTime": 1571918400000,
   298          "stats": {
   299                  "numSamples": 90793,
   300                  "numSeries": 3703,
   301                  "numChunks": 3746
   302          },
   303          "compaction": {
   304                  "level": 1,
   305                  "sources": [
   306                          "01DQYXMK8G108CEBQ79Y84DYVY"
   307                  ]
   308          },
   309          "version": 1,
   310          "thanos": {
   311                  "labels": {
   312                          "receive": "true",
   313                          "replica": "thanos-receive-default-0"
   314                  },
   315                  "downsample": {
   316                          "resolution": 0
   317                  },
   318                  "source": "receive"
   319          }
   320  }`)
   321  
   322  				_ = originBucket.Upload(ctx, path.Join("01DQYXMK8G108CEBQ79Y84DYVY", "meta.json"), bytes.NewReader(b))
   323  				_ = originBucket.Upload(ctx, path.Join("01DQYXMK8G108CEBQ79Y84DYVY", "chunks", "000001"), bytes.NewReader(nil))
   324  				_ = originBucket.Upload(ctx, path.Join("01DQYXMK8G108CEBQ79Y84DYVY", "index"), bytes.NewReader(nil))
   325  			},
   326  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   327  				if len(targetBucket.Objects()) != 3 {
   328  					t.Fatal("TargetBucket should have one block does not.")
   329  				}
   330  
   331  				expected := originBucket.Objects()["01DQYXMK8G108CEBQ79Y84DYVY/meta.json"]
   332  				got := targetBucket.Objects()["01DQYXMK8G108CEBQ79Y84DYVY/meta.json"]
   333  				testutil.Equals(t, expected, got)
   334  			},
   335  		},
   336  		{
   337  			name:     "BlockIDs",
   338  			blockIDs: []ulid.ULID{testBlockID},
   339  			prepare: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   340  				meta := testMeta(testBlockID)
   341  
   342  				b, err := json.Marshal(meta)
   343  				testutil.Ok(t, err)
   344  				_ = originBucket.Upload(ctx, path.Join(testBlockID.String(), "meta.json"), bytes.NewReader(b))
   345  				_ = originBucket.Upload(ctx, path.Join(testBlockID.String(), "chunks", "000001"), bytes.NewReader(nil))
   346  				_ = originBucket.Upload(ctx, path.Join(testBlockID.String(), "index"), bytes.NewReader(nil))
   347  
   348  				ulid := testULID(1)
   349  				meta = testMeta(ulid)
   350  
   351  				b, err = json.Marshal(meta)
   352  				testutil.Ok(t, err)
   353  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "meta.json"), bytes.NewReader(b))
   354  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "chunks", "000001"), bytes.NewReader(nil))
   355  				_ = originBucket.Upload(ctx, path.Join(ulid.String(), "index"), bytes.NewReader(nil))
   356  			},
   357  			assert: func(ctx context.Context, t *testing.T, originBucket, targetBucket *objstore.InMemBucket) {
   358  				expected := 3
   359  				got := len(targetBucket.Objects())
   360  				if got != expected {
   361  					t.Fatalf("TargetBucket should have one block made up of three objects replicated. Got %d but expected %d objects.", got, expected)
   362  				}
   363  			},
   364  		},
   365  	}
   366  
   367  	for _, c := range cases {
   368  		ctx := context.Background()
   369  		originBucket := objstore.NewInMemBucket()
   370  		targetBucket := objstore.NewInMemBucket()
   371  		logger := testLogger(t.Name() + "/" + c.name)
   372  
   373  		c.prepare(ctx, t, originBucket, targetBucket)
   374  
   375  		matcher, err := labels.NewMatcher(labels.MatchEqual, "test-labelname", "test-labelvalue")
   376  		testutil.Ok(t, err)
   377  
   378  		selector := labels.Selector{
   379  			matcher,
   380  		}
   381  		if c.selector != nil {
   382  			selector = c.selector
   383  		}
   384  
   385  		filter := NewBlockFilter(logger, selector, []compact.ResolutionLevel{compact.ResolutionLevelRaw}, []int{1}, c.blockIDs).Filter
   386  		fetcher, err := newMetaFetcher(
   387  			logger, objstore.WithNoopInstr(originBucket),
   388  			nil,
   389  			minTimeDuration,
   390  			maxTimeDuration,
   391  			32,
   392  			c.ignoreMarkedForDeletion,
   393  		)
   394  		testutil.Ok(t, err)
   395  
   396  		r := newReplicationScheme(logger, newReplicationMetrics(nil), filter, fetcher, objstore.WithNoopInstr(originBucket), targetBucket, nil)
   397  
   398  		err = r.execute(ctx)
   399  		testutil.Ok(t, err)
   400  
   401  		c.assert(ctx, t, originBucket, targetBucket)
   402  	}
   403  }