github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/tools/thanosconvert/thanosconvert_test.go (about) 1 package thanosconvert 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "strings" 10 "testing" 11 12 "github.com/go-kit/log" 13 "github.com/oklog/ulid" 14 "github.com/pkg/errors" 15 "github.com/prometheus/prometheus/tsdb" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/mock" 18 "github.com/thanos-io/thanos/pkg/block/metadata" 19 "github.com/weaveworks/common/logging" 20 21 "github.com/cortexproject/cortex/pkg/storage/bucket" 22 cortex_tsdb "github.com/cortexproject/cortex/pkg/storage/tsdb" 23 utillog "github.com/cortexproject/cortex/pkg/util/log" 24 ) 25 26 func stringNotEmpty(v string) bool { 27 return v != "" 28 } 29 30 type fakeBucket map[string]map[string]metadata.Meta 31 32 var ( 33 block1 = ulid.MustNew(1, nil).String() 34 block2 = ulid.MustNew(2, nil).String() 35 block3 = ulid.MustNew(3, nil).String() 36 blockWithGetFailure = ulid.MustNew(1000, nil).String() 37 blockWithUploadFailure = ulid.MustNew(1001, nil).String() 38 blockWithMalformedMeta = ulid.MustNew(1002, nil).String() 39 ) 40 41 func TestThanosBlockConverter(t *testing.T) { 42 tests := []struct { 43 name string 44 bucketData fakeBucket 45 assertions func(*testing.T, *bucket.ClientMock, Results, error) 46 }{ 47 { 48 name: "empty bucket is a noop", 49 bucketData: fakeBucket{}, 50 assertions: func(t *testing.T, bkt *bucket.ClientMock, results Results, err error) { 51 bkt.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) 52 bkt.AssertNotCalled(t, "Upload", mock.Anything, mock.Anything, mock.Anything) 53 assert.Len(t, results, 0, "expected no users in results") 54 }, 55 }, 56 { 57 name: "user with no blocks is a noop", 58 bucketData: fakeBucket{"user1": map[string]metadata.Meta{}}, 59 assertions: func(t *testing.T, bkt *bucket.ClientMock, results Results, err error) { 60 bkt.AssertNotCalled(t, "Get", mock.Anything, mock.Anything) 61 bkt.AssertNotCalled(t, "Upload", mock.Anything, mock.Anything, mock.Anything) 62 assert.Contains(t, results, "user1") 63 assert.Len(t, results["user1"].FailedBlocks, 0) 64 assert.Len(t, results["user1"].ConvertedBlocks, 0) 65 assert.Len(t, results["user1"].UnchangedBlocks, 0) 66 }, 67 }, 68 { 69 name: "bucket fully converted is a noop", 70 bucketData: fakeBucket{ 71 "user1": map[string]metadata.Meta{ 72 block1: cortexMeta("user1"), 73 block2: cortexMeta("user1"), 74 block3: cortexMeta("user1"), 75 }, 76 "user2": map[string]metadata.Meta{ 77 block1: cortexMeta("user2"), 78 block2: cortexMeta("user2"), 79 }, 80 "user3": map[string]metadata.Meta{ 81 block1: cortexMeta("user3"), 82 }, 83 }, 84 assertions: func(t *testing.T, bkt *bucket.ClientMock, results Results, err error) { 85 bkt.AssertNotCalled(t, "Upload", mock.Anything, mock.Anything, mock.Anything) 86 assert.Len(t, results, 3, "expected users in results") 87 88 assert.Len(t, results["user1"].FailedBlocks, 0) 89 assert.Len(t, results["user1"].ConvertedBlocks, 0) 90 assert.ElementsMatch(t, results["user1"].UnchangedBlocks, []string{block1, block2, block3}) 91 92 assert.Len(t, results["user2"].FailedBlocks, 0) 93 assert.Len(t, results["user2"].ConvertedBlocks, 0) 94 assert.ElementsMatch(t, results["user2"].UnchangedBlocks, []string{block1, block2}) 95 96 assert.Len(t, results["user3"].FailedBlocks, 0) 97 assert.Len(t, results["user3"].ConvertedBlocks, 0) 98 assert.ElementsMatch(t, results["user3"].UnchangedBlocks, []string{block1}) 99 100 }, 101 }, 102 { 103 name: "bucket with some blocks to convert", 104 bucketData: fakeBucket{ 105 "user1": map[string]metadata.Meta{ 106 block1: cortexMeta("user1"), 107 block2: thanosMeta(), 108 block3: cortexMeta("user1"), 109 }, 110 "user2": map[string]metadata.Meta{ 111 block1: cortexMeta("user2"), 112 block2: cortexMeta("user2"), 113 }, 114 "user3": map[string]metadata.Meta{ 115 block1: thanosMeta(), 116 }, 117 }, 118 assertions: func(t *testing.T, bkt *bucket.ClientMock, results Results, err error) { 119 assert.Len(t, results, 3, "expected users in results") 120 121 assert.Len(t, results["user1"].FailedBlocks, 0) 122 assert.ElementsMatch(t, results["user1"].ConvertedBlocks, []string{block2}) 123 assert.ElementsMatch(t, results["user1"].UnchangedBlocks, []string{block1, block3}) 124 125 assert.Len(t, results["user2"].FailedBlocks, 0) 126 assert.Len(t, results["user2"].ConvertedBlocks, 0) 127 assert.ElementsMatch(t, results["user2"].UnchangedBlocks, []string{block1, block2}) 128 129 assert.Len(t, results["user3"].FailedBlocks, 0) 130 assert.ElementsMatch(t, results["user3"].ConvertedBlocks, []string{block1}) 131 assert.Len(t, results["user3"].UnchangedBlocks, 0) 132 133 bkt.AssertNumberOfCalls(t, "Upload", 2) 134 }, 135 }, 136 { 137 name: "bucket with failed blocks", 138 bucketData: fakeBucket{ 139 "user1": map[string]metadata.Meta{ 140 block1: cortexMeta("user1"), 141 blockWithGetFailure: cortexMeta("user1"), 142 block3: cortexMeta("user1"), 143 }, 144 "user2": map[string]metadata.Meta{ 145 block1: cortexMeta("user2"), 146 block2: cortexMeta("user2"), 147 }, 148 "user3": map[string]metadata.Meta{ 149 blockWithUploadFailure: thanosMeta(), 150 blockWithMalformedMeta: thanosMeta(), 151 }, 152 }, 153 assertions: func(t *testing.T, bkt *bucket.ClientMock, results Results, err error) { 154 assert.Len(t, results["user1"].FailedBlocks, 1) 155 assert.Len(t, results["user2"].FailedBlocks, 0) 156 assert.Len(t, results["user3"].FailedBlocks, 2) 157 }, 158 }, 159 } 160 161 for _, test := range tests { 162 163 t.Run(test.name, func(t *testing.T) { 164 165 bkt := mockBucket(test.bucketData) 166 167 converter := &ThanosBlockConverter{ 168 logger: getLogger(), 169 dryRun: false, 170 bkt: bkt, 171 } 172 173 ctx := context.Background() 174 results, err := converter.Run(ctx) 175 test.assertions(t, bkt, results, err) 176 }) 177 } 178 } 179 180 func cortexMeta(user string) metadata.Meta { 181 return metadata.Meta{ 182 BlockMeta: tsdb.BlockMeta{ 183 Version: metadata.ThanosVersion1, 184 }, 185 Thanos: metadata.Thanos{ 186 Labels: map[string]string{ 187 cortex_tsdb.TenantIDExternalLabel: user, 188 }, 189 }, 190 } 191 } 192 193 func thanosMeta() metadata.Meta { 194 return metadata.Meta{ 195 BlockMeta: tsdb.BlockMeta{ 196 Version: metadata.ThanosVersion1, 197 }, 198 Thanos: metadata.Thanos{ 199 Labels: map[string]string{ 200 "cluster": "foo", 201 }, 202 }, 203 } 204 } 205 206 func getLogger() log.Logger { 207 208 l := logging.Level{} 209 if err := l.Set("info"); err != nil { 210 panic(err) 211 } 212 f := logging.Format{} 213 if err := f.Set("logfmt"); err != nil { 214 panic(err) 215 } 216 217 logger, err := utillog.NewPrometheusLogger(l, f) 218 if err != nil { 219 panic(err) 220 } 221 return logger 222 } 223 224 func mockBucket(data fakeBucket) *bucket.ClientMock { 225 226 bkt := bucket.ClientMock{} 227 228 // UsersScanner checks for deletion marks using Exist 229 bkt.On("Exists", mock.Anything, mock.Anything).Return(false, nil) 230 231 bkt.On("Iter", mock.Anything, "", mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { 232 // we're iterating over the top level users 233 f := args.Get(2).(func(string) error) 234 for user := range data { 235 if err := f(user); err != nil { 236 panic(err) 237 } 238 } 239 }) 240 241 bkt.On("Iter", mock.Anything, mock.MatchedBy(stringNotEmpty), mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { 242 // we're iterating over blocks within a user 243 user := strings.TrimSuffix(args.String(1), "/") 244 f := args.Get(2).(func(string) error) 245 if _, ok := data[user]; !ok { 246 panic(fmt.Sprintf("key %s not found in fake bucket data", user)) 247 } 248 for block := range data[user] { 249 if err := f(fmt.Sprintf("%s/%s/", user, block)); err != nil { 250 panic(err) 251 } 252 } 253 }) 254 255 for user, blocks := range data { 256 for block, meta := range blocks { 257 258 var body bytes.Buffer 259 enc := json.NewEncoder(&body) 260 if err := enc.Encode(meta); err != nil { 261 panic(err) 262 } 263 key := fmt.Sprintf("%s/%s/meta.json", user, block) 264 switch block { 265 case blockWithGetFailure: 266 bkt.On("Get", mock.Anything, key).Return(nil, errors.Errorf("test error in Get")) 267 case blockWithMalformedMeta: 268 bkt.On("Get", mock.Anything, key).Return(ioutil.NopCloser(bytes.NewBufferString("invalid json")), nil) 269 default: 270 bkt.On("Get", mock.Anything, key).Return(ioutil.NopCloser(&body), nil) 271 } 272 273 switch block { 274 case blockWithUploadFailure: 275 bkt.On("Upload", mock.Anything, key, mock.Anything).Return(errors.Errorf("test error in Upload")) 276 default: 277 bkt.On("Upload", mock.Anything, key, mock.Anything).Return(nil) 278 } 279 } 280 } 281 282 return &bkt 283 } 284 285 func TestConvertMetadata(t *testing.T) { 286 tests := []struct { 287 name string 288 expectedUser string 289 in metadata.Meta 290 out metadata.Meta 291 changesRequired []string 292 }{ 293 { 294 name: "no changes required", 295 expectedUser: "user1", 296 in: metadata.Meta{ 297 Thanos: metadata.Thanos{ 298 Labels: map[string]string{ 299 cortex_tsdb.TenantIDExternalLabel: "user1", 300 }, 301 }, 302 }, 303 out: metadata.Meta{ 304 Thanos: metadata.Thanos{ 305 Labels: map[string]string{ 306 cortex_tsdb.TenantIDExternalLabel: "user1", 307 }, 308 }, 309 }, 310 changesRequired: []string{}, 311 }, 312 { 313 name: "add __org_id__ label", 314 expectedUser: "user1", 315 in: metadata.Meta{ 316 Thanos: metadata.Thanos{ 317 Labels: map[string]string{}, 318 }, 319 }, 320 out: metadata.Meta{ 321 Thanos: metadata.Thanos{ 322 Labels: map[string]string{ 323 cortex_tsdb.TenantIDExternalLabel: "user1", 324 }, 325 }, 326 }, 327 changesRequired: []string{"add __org_id__ label"}, 328 }, 329 { 330 name: "nil labels map", 331 expectedUser: "user1", 332 in: metadata.Meta{ 333 Thanos: metadata.Thanos{ 334 Labels: nil, 335 }, 336 }, 337 out: metadata.Meta{ 338 Thanos: metadata.Thanos{ 339 Labels: map[string]string{ 340 cortex_tsdb.TenantIDExternalLabel: "user1", 341 }, 342 }, 343 }, 344 changesRequired: []string{"add __org_id__ label"}, 345 }, 346 { 347 name: "remove extra Thanos labels", 348 expectedUser: "user1", 349 in: metadata.Meta{ 350 Thanos: metadata.Thanos{ 351 Labels: map[string]string{ 352 cortex_tsdb.TenantIDExternalLabel: "user1", 353 "extra": "label", 354 "cluster": "foo", 355 }, 356 }, 357 }, 358 out: metadata.Meta{ 359 Thanos: metadata.Thanos{ 360 Labels: map[string]string{ 361 cortex_tsdb.TenantIDExternalLabel: "user1", 362 }, 363 }, 364 }, 365 changesRequired: []string{"remove extra Thanos labels"}, 366 }, 367 { 368 name: "fix __org_id__", 369 expectedUser: "user1", 370 in: metadata.Meta{ 371 Thanos: metadata.Thanos{ 372 Labels: map[string]string{ 373 cortex_tsdb.TenantIDExternalLabel: "wrong_user", 374 }, 375 }, 376 }, 377 out: metadata.Meta{ 378 Thanos: metadata.Thanos{ 379 Labels: map[string]string{ 380 cortex_tsdb.TenantIDExternalLabel: "user1", 381 }, 382 }, 383 }, 384 changesRequired: []string{"change __org_id__ from wrong_user to user1"}, 385 }, 386 } 387 388 for _, test := range tests { 389 t.Run(test.name, func(t *testing.T) { 390 out, changesRequired := convertMetadata(test.in, test.expectedUser) 391 assert.Equal(t, test.out, out) 392 assert.ElementsMatch(t, changesRequired, test.changesRequired) 393 }) 394 } 395 }