1// Copyright (C) 2020 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package metabase_test 5 6import ( 7 "strconv" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 12 "storj.io/common/testrand" 13 "storj.io/common/uuid" 14 "storj.io/storj/satellite/metabase" 15) 16 17func TestParseBucketPrefixInvalid(t *testing.T) { 18 var testCases = []struct { 19 name string 20 prefix metabase.BucketPrefix 21 }{ 22 {"invalid, not valid UUID", "not UUID string/bucket1"}, 23 {"invalid, not valid UUID, no bucket", "not UUID string"}, 24 {"invalid, no project, no bucket", ""}, 25 } 26 for _, tt := range testCases { 27 tt := tt 28 t.Run(tt.name, func(t *testing.T) { 29 _, err := metabase.ParseBucketPrefix(tt.prefix) 30 require.NotNil(t, err) 31 require.Error(t, err) 32 }) 33 } 34} 35 36func TestParseBucketPrefixValid(t *testing.T) { 37 var testCases = []struct { 38 name string 39 project string 40 bucketName string 41 expectedBucketName string 42 }{ 43 {"valid, no bucket, no objects", "bb6218e3-4b4a-4819-abbb-fa68538e33c0", "", ""}, 44 {"valid, with bucket", "bb6218e3-4b4a-4819-abbb-fa68538e33c0", "testbucket", "testbucket"}, 45 } 46 for _, tt := range testCases { 47 tt := tt 48 t.Run(tt.name, func(t *testing.T) { 49 expectedProjectID, err := uuid.FromString(tt.project) 50 require.NoError(t, err) 51 bucketID := expectedProjectID.String() + "/" + tt.bucketName 52 53 bucketLocation, err := metabase.ParseBucketPrefix(metabase.BucketPrefix(bucketID)) 54 require.NoError(t, err) 55 require.Equal(t, expectedProjectID, bucketLocation.ProjectID) 56 require.Equal(t, tt.expectedBucketName, bucketLocation.BucketName) 57 }) 58 } 59} 60 61func TestParseSegmentKeyInvalid(t *testing.T) { 62 var testCases = []struct { 63 name string 64 segmentKey string 65 }{ 66 { 67 name: "invalid, project ID only", 68 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0", 69 }, 70 { 71 name: "invalid, project ID and segment index only", 72 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s0", 73 }, 74 { 75 name: "invalid, project ID, bucket, and segment index only", 76 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s0/testbucket", 77 }, 78 { 79 name: "invalid, project ID is not UUID", 80 segmentKey: "not UUID string/s0/testbucket/test/object", 81 }, 82 { 83 name: "invalid, last segment with segment number", 84 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/l0/testbucket/test/object", 85 }, 86 { 87 name: "invalid, missing segment number", 88 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s/testbucket/test/object", 89 }, 90 { 91 name: "invalid, missing segment prefix", 92 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/1/testbucket/test/object", 93 }, 94 { 95 name: "invalid, segment index overflows int64", 96 segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s18446744073709551616/testbucket/test/object", 97 }, 98 } 99 for _, tt := range testCases { 100 tt := tt 101 t.Run(tt.name, func(t *testing.T) { 102 _, err := metabase.ParseSegmentKey(metabase.SegmentKey(tt.segmentKey)) 103 require.NotNil(t, err, tt.name) 104 require.Error(t, err, tt.name) 105 }) 106 } 107} 108 109func TestParseSegmentKeyValid(t *testing.T) { 110 projectID := testrand.UUID() 111 112 var testCases = []struct { 113 name string 114 segmentKey string 115 expectedLocation metabase.SegmentLocation 116 }{ 117 { 118 name: "valid, part 0, last segment", 119 segmentKey: projectID.String() + "/l/testbucket/test/object", 120 expectedLocation: metabase.SegmentLocation{ 121 ProjectID: projectID, 122 BucketName: "testbucket", 123 ObjectKey: "test/object", 124 Position: metabase.SegmentPosition{Part: 0, Index: metabase.LastSegmentIndex}, 125 }, 126 }, 127 { 128 name: "valid, part 0, last segment, trailing slash", 129 segmentKey: projectID.String() + "/l/testbucket/test/object/", 130 expectedLocation: metabase.SegmentLocation{ 131 ProjectID: projectID, 132 BucketName: "testbucket", 133 ObjectKey: "test/object/", 134 Position: metabase.SegmentPosition{Part: 0, Index: metabase.LastSegmentIndex}, 135 }, 136 }, 137 { 138 name: "valid, part 0, index 0", 139 segmentKey: projectID.String() + "/s0/testbucket/test/object", 140 expectedLocation: metabase.SegmentLocation{ 141 ProjectID: projectID, 142 BucketName: "testbucket", 143 ObjectKey: "test/object", 144 Position: metabase.SegmentPosition{Part: 0, Index: 0}, 145 }, 146 }, 147 { 148 name: "valid, part 0, index 1", 149 segmentKey: projectID.String() + "/s1/testbucket/test/object", 150 expectedLocation: metabase.SegmentLocation{ 151 ProjectID: projectID, 152 BucketName: "testbucket", 153 ObjectKey: "test/object", 154 Position: metabase.SegmentPosition{Part: 0, Index: 1}, 155 }, 156 }, 157 { 158 name: "valid, part 0, index 315", 159 segmentKey: projectID.String() + "/s315/testbucket/test/object", 160 expectedLocation: metabase.SegmentLocation{ 161 ProjectID: projectID, 162 BucketName: "testbucket", 163 ObjectKey: "test/object", 164 Position: metabase.SegmentPosition{Part: 0, Index: 315}, 165 }, 166 }, 167 { 168 name: "valid, part 1, index 0", 169 segmentKey: projectID.String() + "/s" + strconv.FormatInt(1<<32, 10) + "/testbucket/test/object", 170 expectedLocation: metabase.SegmentLocation{ 171 ProjectID: projectID, 172 BucketName: "testbucket", 173 ObjectKey: "test/object", 174 Position: metabase.SegmentPosition{Part: 1, Index: 0}, 175 }, 176 }, 177 { 178 name: "valid, part 1, index 1", 179 segmentKey: projectID.String() + "/s" + strconv.FormatInt(1<<32+1, 10) + "/testbucket/test/object", 180 expectedLocation: metabase.SegmentLocation{ 181 ProjectID: projectID, 182 BucketName: "testbucket", 183 ObjectKey: "test/object", 184 Position: metabase.SegmentPosition{Part: 1, Index: 1}, 185 }, 186 }, 187 { 188 name: "valid, part 18, index 315", 189 segmentKey: projectID.String() + "/s" + strconv.FormatInt(18<<32+315, 10) + "/testbucket/test/object", 190 expectedLocation: metabase.SegmentLocation{ 191 ProjectID: projectID, 192 BucketName: "testbucket", 193 ObjectKey: "test/object", 194 Position: metabase.SegmentPosition{Part: 18, Index: 315}, 195 }, 196 }, 197 } 198 for _, tt := range testCases { 199 tt := tt 200 t.Run(tt.name, func(t *testing.T) { 201 segmentLocation, err := metabase.ParseSegmentKey(metabase.SegmentKey(tt.segmentKey)) 202 require.NoError(t, err, tt.name) 203 require.Equal(t, tt.expectedLocation, segmentLocation) 204 }) 205 } 206} 207 208func TestPiecesEqual(t *testing.T) { 209 sn1 := testrand.NodeID() 210 sn2 := testrand.NodeID() 211 212 var testCases = []struct { 213 source metabase.Pieces 214 target metabase.Pieces 215 equal bool 216 }{ 217 {metabase.Pieces{}, metabase.Pieces{}, true}, 218 { 219 metabase.Pieces{ 220 {1, sn1}, 221 }, 222 metabase.Pieces{}, false, 223 }, 224 { 225 metabase.Pieces{}, 226 metabase.Pieces{ 227 {1, sn1}, 228 }, false, 229 }, 230 { 231 metabase.Pieces{ 232 {1, sn1}, 233 {2, sn2}, 234 }, 235 metabase.Pieces{ 236 {1, sn1}, 237 {2, sn2}, 238 }, true, 239 }, 240 { 241 metabase.Pieces{ 242 {2, sn2}, 243 {1, sn1}, 244 }, 245 metabase.Pieces{ 246 {1, sn1}, 247 {2, sn2}, 248 }, true, 249 }, 250 { 251 metabase.Pieces{ 252 {1, sn1}, 253 {2, sn2}, 254 }, 255 metabase.Pieces{ 256 {1, sn2}, 257 {2, sn1}, 258 }, false, 259 }, 260 { 261 metabase.Pieces{ 262 {1, sn1}, 263 {3, sn2}, 264 {2, sn2}, 265 }, 266 metabase.Pieces{ 267 {3, sn2}, 268 {1, sn1}, 269 {2, sn2}, 270 }, true, 271 }, 272 } 273 for _, tt := range testCases { 274 require.Equal(t, tt.equal, tt.source.Equal(tt.target)) 275 } 276} 277 278func TestPiecesAdd(t *testing.T) { 279 node0 := testrand.NodeID() 280 node1 := testrand.NodeID() 281 node2 := testrand.NodeID() 282 node3 := testrand.NodeID() 283 284 tests := []struct { 285 name string 286 pieces metabase.Pieces 287 piecesToAdd metabase.Pieces 288 want metabase.Pieces 289 wantErr string 290 }{ 291 { 292 name: "piece exists", 293 pieces: metabase.Pieces{ 294 metabase.Piece{ 295 Number: 0, 296 StorageNode: node0, 297 }, 298 metabase.Piece{ 299 Number: 1, 300 StorageNode: node1, 301 }, 302 }, 303 piecesToAdd: metabase.Pieces{ 304 metabase.Piece{ 305 Number: 1, 306 StorageNode: node1, 307 }, 308 }, 309 wantErr: "metabase: piece to add already exists (piece no: 1)", 310 want: metabase.Pieces{}, 311 }, 312 313 { 314 name: "pieces added", 315 pieces: metabase.Pieces{ 316 metabase.Piece{ 317 Number: 0, 318 StorageNode: node0, 319 }, 320 metabase.Piece{ 321 Number: 3, 322 StorageNode: node3, 323 }, 324 }, 325 piecesToAdd: metabase.Pieces{ 326 metabase.Piece{ 327 Number: 2, 328 StorageNode: node2, 329 }, 330 metabase.Piece{ 331 Number: 1, 332 StorageNode: node1, 333 }, 334 }, 335 wantErr: "", 336 want: metabase.Pieces{ 337 metabase.Piece{ 338 Number: 0, 339 StorageNode: node0, 340 }, 341 metabase.Piece{ 342 Number: 1, 343 StorageNode: node1, 344 }, 345 metabase.Piece{ 346 Number: 2, 347 StorageNode: node2, 348 }, 349 metabase.Piece{ 350 Number: 3, 351 StorageNode: node3, 352 }, 353 }, 354 }, 355 { 356 name: "adding new pieces to empty piece", 357 pieces: metabase.Pieces{}, 358 piecesToAdd: metabase.Pieces{ 359 metabase.Piece{ 360 Number: 1, 361 StorageNode: node1, 362 }, 363 metabase.Piece{ 364 Number: 0, 365 StorageNode: node0, 366 }, 367 }, 368 wantErr: "", 369 want: metabase.Pieces{ 370 metabase.Piece{ 371 Number: 0, 372 StorageNode: node0, 373 }, 374 metabase.Piece{ 375 Number: 1, 376 StorageNode: node1, 377 }, 378 }, 379 }, 380 { 381 name: "adding empty piece", 382 pieces: metabase.Pieces{ 383 metabase.Piece{ 384 Number: 0, 385 StorageNode: node0, 386 }, 387 metabase.Piece{ 388 Number: 1, 389 StorageNode: node1, 390 }, 391 }, 392 piecesToAdd: metabase.Pieces{}, 393 wantErr: "", 394 want: metabase.Pieces{ 395 metabase.Piece{ 396 Number: 0, 397 StorageNode: node0, 398 }, 399 metabase.Piece{ 400 Number: 1, 401 StorageNode: node1, 402 }, 403 }, 404 }, 405 { 406 name: "adding empty piece to empty pieces", 407 pieces: metabase.Pieces{}, 408 piecesToAdd: metabase.Pieces{}, 409 wantErr: "", 410 want: metabase.Pieces{}, 411 }, 412 } 413 for _, tt := range tests { 414 t.Run(tt.name, func(t *testing.T) { 415 require.NotNil(t, tt.pieces, tt.name) 416 got, err := tt.pieces.Add(tt.piecesToAdd) 417 418 if tt.wantErr != "" { 419 require.EqualError(t, err, tt.wantErr, tt.name) 420 } else { 421 require.NoError(t, err, tt.name) 422 } 423 require.Equal(t, got, tt.want, tt.name) 424 }) 425 } 426} 427 428func TestPiecesRemove(t *testing.T) { 429 node0 := testrand.NodeID() 430 node1 := testrand.NodeID() 431 node2 := testrand.NodeID() 432 node3 := testrand.NodeID() 433 434 tests := []struct { 435 name string 436 pieces metabase.Pieces 437 piecesToRemove metabase.Pieces 438 want metabase.Pieces 439 wantErr string 440 }{ 441 { 442 name: "piece missing", 443 pieces: metabase.Pieces{}, 444 piecesToRemove: metabase.Pieces{ 445 metabase.Piece{ 446 Number: 1, 447 StorageNode: node1, 448 }, 449 }, 450 wantErr: "metabase: invalid request: pieces missing", 451 want: metabase.Pieces{}, 452 }, 453 { 454 name: "piecesToRemove struct is empty", 455 pieces: metabase.Pieces{ 456 metabase.Piece{ 457 Number: 1, 458 StorageNode: node1, 459 }, 460 }, 461 piecesToRemove: metabase.Pieces{}, 462 wantErr: "", 463 want: metabase.Pieces{ 464 metabase.Piece{ 465 Number: 1, 466 StorageNode: node1, 467 }, 468 }, 469 }, 470 { 471 name: "both pieces and piecesToRemove struct are empty", 472 pieces: metabase.Pieces{}, 473 piecesToRemove: metabase.Pieces{}, 474 wantErr: "metabase: invalid request: pieces missing", 475 want: metabase.Pieces{}, 476 }, 477 { 478 name: "pieces removed", 479 pieces: metabase.Pieces{ 480 metabase.Piece{ 481 Number: 0, 482 StorageNode: node0, 483 }, 484 metabase.Piece{ 485 Number: 1, 486 StorageNode: node1, 487 }, 488 metabase.Piece{ 489 Number: 2, 490 StorageNode: node2, 491 }, 492 metabase.Piece{ 493 Number: 3, 494 StorageNode: node3, 495 }, 496 }, 497 piecesToRemove: metabase.Pieces{ 498 metabase.Piece{ 499 Number: 2, 500 StorageNode: node2, 501 }, 502 metabase.Piece{ 503 Number: 1, 504 StorageNode: node1, 505 }, 506 }, 507 wantErr: "", 508 want: metabase.Pieces{ 509 metabase.Piece{ 510 Number: 0, 511 StorageNode: node0, 512 }, 513 metabase.Piece{ 514 Number: 3, 515 StorageNode: node3, 516 }, 517 }, 518 }, 519 } 520 for _, tt := range tests { 521 t.Run(tt.name, func(t *testing.T) { 522 require.NotNil(t, tt.pieces, tt.name) 523 got, err := tt.pieces.Remove(tt.piecesToRemove) 524 525 if tt.wantErr != "" { 526 require.EqualError(t, err, tt.wantErr, tt.name) 527 } else { 528 require.NoError(t, err, tt.name) 529 } 530 require.Equal(t, got, tt.want, tt.name) 531 }) 532 } 533} 534 535func TestPiecesUpdate(t *testing.T) { 536 node0 := testrand.NodeID() 537 node1 := testrand.NodeID() 538 node2 := testrand.NodeID() 539 node3 := testrand.NodeID() 540 541 tests := []struct { 542 name string 543 pieces metabase.Pieces 544 piecesToAdd metabase.Pieces 545 piecesToRemove metabase.Pieces 546 want metabase.Pieces 547 wantErr string 548 }{ 549 { 550 name: "add and remove pieces", 551 pieces: metabase.Pieces{ 552 metabase.Piece{ 553 Number: 0, 554 StorageNode: node0, 555 }, 556 metabase.Piece{ 557 Number: 1, 558 StorageNode: node1, 559 }, 560 metabase.Piece{ 561 Number: 2, 562 StorageNode: node2, 563 }, 564 }, 565 piecesToRemove: metabase.Pieces{ 566 metabase.Piece{ 567 Number: 0, 568 StorageNode: node0, 569 }, 570 }, 571 piecesToAdd: metabase.Pieces{ 572 metabase.Piece{ 573 Number: 3, 574 StorageNode: node3, 575 }, 576 }, 577 wantErr: "", 578 want: metabase.Pieces{ 579 metabase.Piece{ 580 Number: 1, 581 StorageNode: node1, 582 }, 583 metabase.Piece{ 584 Number: 2, 585 StorageNode: node2, 586 }, 587 metabase.Piece{ 588 Number: 3, 589 StorageNode: node3, 590 }, 591 }, 592 }, 593 { 594 name: "add pieces only", 595 pieces: metabase.Pieces{ 596 metabase.Piece{ 597 Number: 1, 598 StorageNode: node1, 599 }, 600 metabase.Piece{ 601 Number: 2, 602 StorageNode: node2, 603 }, 604 }, 605 piecesToRemove: metabase.Pieces{}, 606 piecesToAdd: metabase.Pieces{ 607 metabase.Piece{ 608 Number: 0, 609 StorageNode: node0, 610 }, 611 }, 612 wantErr: "", 613 want: metabase.Pieces{ 614 metabase.Piece{ 615 Number: 0, 616 StorageNode: node0, 617 }, 618 metabase.Piece{ 619 Number: 1, 620 StorageNode: node1, 621 }, 622 metabase.Piece{ 623 Number: 2, 624 StorageNode: node2, 625 }, 626 }, 627 }, 628 { 629 name: "remove pieces only", 630 pieces: metabase.Pieces{ 631 metabase.Piece{ 632 Number: 1, 633 StorageNode: node1, 634 }, 635 metabase.Piece{ 636 Number: 2, 637 StorageNode: node2, 638 }, 639 }, 640 piecesToRemove: metabase.Pieces{ 641 metabase.Piece{ 642 Number: 2, 643 StorageNode: node2, 644 }, 645 }, 646 piecesToAdd: metabase.Pieces{}, 647 wantErr: "", 648 want: metabase.Pieces{ 649 metabase.Piece{ 650 Number: 1, 651 StorageNode: node1, 652 }, 653 }, 654 }, 655 { 656 name: "both piecesToAdd and piecesToRemove are empty", 657 pieces: metabase.Pieces{ 658 metabase.Piece{ 659 Number: 1, 660 StorageNode: node1, 661 }, 662 metabase.Piece{ 663 Number: 2, 664 StorageNode: node2, 665 }, 666 }, 667 piecesToRemove: metabase.Pieces{}, 668 piecesToAdd: metabase.Pieces{}, 669 wantErr: "", 670 want: metabase.Pieces{ 671 metabase.Piece{ 672 Number: 1, 673 StorageNode: node1, 674 }, 675 metabase.Piece{ 676 Number: 2, 677 StorageNode: node2, 678 }, 679 }, 680 }, 681 { 682 name: "updating empty pieces", 683 pieces: metabase.Pieces{}, 684 piecesToRemove: metabase.Pieces{ 685 metabase.Piece{ 686 Number: 1, 687 StorageNode: node1, 688 }, 689 }, 690 piecesToAdd: metabase.Pieces{ 691 metabase.Piece{ 692 Number: 0, 693 StorageNode: node1, 694 }, 695 }, 696 wantErr: "", 697 want: metabase.Pieces{ 698 metabase.Piece{ 699 Number: 0, 700 StorageNode: node1, 701 }, 702 }, 703 }, 704 } 705 for _, tt := range tests { 706 t.Run(tt.name, func(t *testing.T) { 707 require.NotNil(t, tt.pieces, tt.name) 708 got, err := tt.pieces.Update(tt.piecesToAdd, tt.piecesToRemove) 709 710 if tt.wantErr != "" { 711 require.EqualError(t, err, tt.wantErr, tt.name) 712 } else { 713 require.NoError(t, err, tt.name) 714 } 715 require.Equal(t, got, tt.want, tt.name) 716 }) 717 } 718} 719