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 }