github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/ref/manager_test.go (about) 1 package ref_test 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "reflect" 9 "sort" 10 "strconv" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/go-test/deep" 16 "github.com/golang/mock/gomock" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 "github.com/treeverse/lakefs/pkg/batch" 20 "github.com/treeverse/lakefs/pkg/graveler" 21 "github.com/treeverse/lakefs/pkg/graveler/ref" 22 "github.com/treeverse/lakefs/pkg/ident" 23 "github.com/treeverse/lakefs/pkg/kv" 24 "github.com/treeverse/lakefs/pkg/kv/mock" 25 "github.com/treeverse/lakefs/pkg/testutil" 26 "google.golang.org/protobuf/proto" 27 "google.golang.org/protobuf/types/known/timestamppb" 28 ) 29 30 // TestManager_GetRepositoryCache test get repository information while using cache. Match the number of times we 31 // call get repository vs number of times we fetch the data. 32 func TestManager_GetRepositoryCache(t *testing.T) { 33 const ( 34 times = 1 35 calls = 3 36 ) 37 ctrl := gomock.NewController(t) 38 mockStore := mock.NewMockStore(ctrl) 39 ctx := context.Background() 40 mockStore.EXPECT().Get(ctx, []byte("graveler"), []byte("repos/repo1")).Times(times).Return(&kv.ValueWithPredicate{}, nil) 41 cacheConfig := ref.CacheConfig{ 42 Size: 100, 43 Expiry: 20 * time.Millisecond, 44 Jitter: 0, 45 } 46 cfg := ref.ManagerConfig{ 47 Executor: batch.NopExecutor(), 48 KVStore: mockStore, 49 AddressProvider: ident.NewHexAddressProvider(), 50 RepositoryCacheConfig: cacheConfig, 51 CommitCacheConfig: cacheConfig, 52 } 53 refManager := ref.NewRefManager(cfg) 54 for i := 0; i < calls; i++ { 55 _, err := refManager.GetRepository(ctx, "repo1") 56 if err != nil { 57 t.Fatalf("Failed to get repository (iteration %d): %s", i, err) 58 } 59 } 60 61 // wait for cache to expire and call again 62 time.Sleep(cacheConfig.Expiry + cacheConfig.Jitter + time.Second) 63 mockStore.EXPECT().Get(ctx, []byte("graveler"), []byte("repos/repo1")).Times(1).Return(&kv.ValueWithPredicate{}, nil) 64 _, err := refManager.GetRepository(ctx, "repo1") 65 if err != nil { 66 t.Fatalf("Failed to get repository: %s", err) 67 } 68 } 69 70 // TestManager_GetCommitCache test gets commit record while using cache. We match the number of times we call get repository vs number of times we fetch the data. 71 func TestManager_GetCommitCache(t *testing.T) { 72 const ( 73 times = 1 74 calls = 3 75 ) 76 ctrl := gomock.NewController(t) 77 mockStore := mock.NewMockStore(ctrl) 78 ctx := context.Background() 79 80 const commitID = "8a3e3f677ed588ab1e19b6cdb050cbce383f9f1166200e7b7252932ceb61189c" 81 const repoID = "repo2" 82 const repoInstanceID = "iuid" 83 mockStore.EXPECT(). 84 Get(ctx, []byte(repoID+"-"+repoInstanceID), []byte("commits/"+commitID)). 85 Times(times). 86 Return(&kv.ValueWithPredicate{}, nil) 87 88 cacheConfig := ref.CacheConfig{ 89 Size: 100, 90 Expiry: 20 * time.Millisecond, 91 } 92 cfg := ref.ManagerConfig{ 93 Executor: batch.NopExecutor(), 94 KVStore: mockStore, 95 AddressProvider: ident.NewHexAddressProvider(), 96 RepositoryCacheConfig: cacheConfig, 97 CommitCacheConfig: cacheConfig, 98 } 99 refManager := ref.NewRefManager(cfg) 100 for i := 0; i < calls; i++ { 101 _, err := refManager.GetCommit(ctx, &graveler.RepositoryRecord{ 102 RepositoryID: repoID, 103 Repository: &graveler.Repository{InstanceUID: repoInstanceID}, 104 }, commitID) 105 if err != nil { 106 t.Fatalf("Failed to get commit (iteration %d): %s", i, err) 107 } 108 } 109 110 // wait for cache to expire and call again 111 time.Sleep(cacheConfig.Expiry + cacheConfig.Jitter + time.Second) 112 mockStore.EXPECT(). 113 Get(ctx, []byte(repoID+"-"+repoInstanceID), []byte("commits/"+commitID)). 114 Times(times). 115 Return(&kv.ValueWithPredicate{}, nil) 116 _, err := refManager.GetCommit(ctx, &graveler.RepositoryRecord{ 117 RepositoryID: repoID, 118 Repository: &graveler.Repository{InstanceUID: repoInstanceID}, 119 }, commitID) 120 if err != nil { 121 t.Fatalf("Failed to get repository: %s", err) 122 } 123 } 124 125 func TestManager_GetRepository(t *testing.T) { 126 r, _ := testRefManager(t) 127 t.Run("repo_doesnt_exist", func(t *testing.T) { 128 _, err := r.GetRepository(context.Background(), "example-repo") 129 if !errors.Is(err, graveler.ErrRepositoryNotFound) { 130 t.Fatalf("expected ErrRepositoryNotFound got error: %v", err) 131 } 132 }) 133 t.Run("repo_exists", func(t *testing.T) { 134 repoID := graveler.RepositoryID("example-repo") 135 branchID := graveler.BranchID("weird-branch") 136 137 repository, err := r.CreateRepository(context.Background(), repoID, graveler.Repository{ 138 StorageNamespace: "s3://foo", 139 CreationDate: time.Now(), 140 DefaultBranchID: branchID, 141 }) 142 testutil.Must(t, err) 143 144 repo, err := r.GetRepository(context.Background(), repoID) 145 if err != nil { 146 t.Fatalf("unexpected error: %v", err) 147 } 148 149 if repo.DefaultBranchID != repository.DefaultBranchID { 150 t.Fatalf("got '%s' branch ID, expected '%s'", repo.DefaultBranchID, repository.DefaultBranchID) 151 } 152 branch, err := r.GetBranch(context.Background(), repo, branchID) 153 if err != nil { 154 t.Fatalf("unexpected error: %v", err) 155 } 156 if branch.CommitID == "" { 157 t.Fatal("empty first commit - first commit wasn't created") 158 } 159 160 commit, err := r.GetCommit(context.Background(), repo, branch.CommitID) 161 if err != nil { 162 t.Fatalf("unexpected error: %v", err) 163 } 164 if len(commit.Parents) != 0 { 165 t.Fatalf("first commit parents should be empty: %v", commit.Parents) 166 } 167 if commit.MetaRangeID != "" { 168 t.Fatalf("first commit metarange should be empty: %v", commit.MetaRangeID) 169 } 170 }) 171 } 172 173 func TestManager_ListRepositories(t *testing.T) { 174 r, _ := testRefManager(t) 175 repoIDs := []graveler.RepositoryID{"a", "aa", "b", "c", "e", "d"} 176 for _, repoId := range repoIDs { 177 _, err := r.CreateRepository(context.Background(), repoId, graveler.Repository{ 178 StorageNamespace: "s3://foo", 179 CreationDate: time.Now(), 180 DefaultBranchID: "main", 181 }) 182 testutil.Must(t, err) 183 } 184 185 t.Run("listing all repos", func(t *testing.T) { 186 iter, err := r.ListRepositories(context.Background()) 187 if err != nil { 188 t.Fatalf("unexpected error: %v", err) 189 } 190 defer iter.Close() 191 192 repoIds := make([]graveler.RepositoryID, 0) 193 for iter.Next() { 194 repo := iter.Value() 195 repoIds = append(repoIds, repo.RepositoryID) 196 } 197 if iter.Err() != nil { 198 t.Fatalf("unexpected error: %v", iter.Err()) 199 } 200 201 if !reflect.DeepEqual(repoIds, []graveler.RepositoryID{"a", "aa", "b", "c", "d", "e"}) { 202 t.Fatalf("got wrong list of repo IDs") 203 } 204 }) 205 206 t.Run("listing repos from prefix", func(t *testing.T) { 207 iter, err := r.ListRepositories(context.Background()) 208 if err != nil { 209 t.Fatalf("unexpected error: %v", err) 210 } 211 defer iter.Close() 212 iter.SeekGE("aaa") 213 214 repoIds := make([]graveler.RepositoryID, 0) 215 for iter.Next() { 216 repo := iter.Value() 217 repoIds = append(repoIds, repo.RepositoryID) 218 } 219 if iter.Err() != nil { 220 t.Fatalf("unexpected error: %v", iter.Err()) 221 } 222 223 if !reflect.DeepEqual(repoIds, []graveler.RepositoryID{"b", "c", "d", "e"}) { 224 t.Fatalf("got wrong list of repo IDs") 225 } 226 }) 227 } 228 229 func TestManager_DeleteRepository(t *testing.T) { 230 r, store := testRefManager(t) 231 ctx := context.Background() 232 repoID := graveler.RepositoryID("example-repo") 233 234 t.Run("repo_exists", func(t *testing.T) { 235 repository, err := r.CreateRepository(ctx, repoID, graveler.Repository{ 236 StorageNamespace: "s3://foo", 237 CreationDate: time.Now(), 238 DefaultBranchID: "weird-branch", 239 }) 240 testutil.Must(t, err) 241 242 _, err = r.GetRepository(context.Background(), "example-repo") 243 if err != nil { 244 t.Fatalf("unexpected error: %v", err) 245 } 246 247 // Create repository entities and ensure their deletion afterwards 248 testutil.Must(t, r.CreateTag(ctx, repository, "v1.0", "c1")) 249 testutil.Must(t, r.CreateBranch(ctx, repository, "f1", graveler.Branch{CommitID: "c1", StagingToken: "s1"})) 250 c := graveler.Commit{ 251 Committer: "user1", 252 Message: "message1", 253 MetaRangeID: "deadbeef123", 254 CreationDate: time.Now(), 255 Parents: graveler.CommitParents{"deadbeef1", "deadbeef12"}, 256 Metadata: graveler.Metadata{"foo": "bar"}, 257 } 258 _, err = r.AddCommit(ctx, repository, c) 259 testutil.Must(t, err) 260 261 err = r.DeleteRepository(context.Background(), "example-repo") 262 if err != nil { 263 t.Fatalf("unexpected error: %v", err) 264 } 265 266 // wait for cache expiry 267 time.Sleep(testRepoCacheConfig.Expiry + testRepoCacheConfig.Jitter + time.Second) 268 269 _, err = r.GetRepository(context.Background(), "example-repo") 270 if !errors.Is(err, graveler.ErrRepositoryNotFound) { 271 t.Fatalf("expected ErrRepositoryNotFound, got: %v", err) 272 } 273 274 // Verify no keys on repo partition 275 itr := kv.NewPartitionIterator(ctx, store, (&graveler.RepoMetadata{}).ProtoReflect().Type(), graveler.RepoPartition(repository), 10) 276 defer itr.Close() 277 for itr.Next() { 278 entry := itr.Entry() 279 t.Fatalf("partition expected empty: %s", string(entry.Key)) 280 } 281 // Check itr.Next() not false on an error 282 require.NoError(t, itr.Err()) 283 284 // Create after delete 285 _, err = r.CreateRepository(ctx, repoID, graveler.Repository{ 286 StorageNamespace: "s3://foo", 287 CreationDate: time.Now(), 288 DefaultBranchID: "weird-branch", 289 }) 290 testutil.Must(t, err) 291 _, err = r.GetRepository(context.Background(), "example-repo") 292 if err != nil { 293 t.Fatalf("unexpected error: %v", err) 294 } 295 }) 296 297 t.Run("repo_does_not_exist", func(t *testing.T) { 298 err := r.DeleteRepository(context.Background(), "example-repo11111") 299 if !errors.Is(err, graveler.ErrRepositoryNotFound) { 300 t.Fatalf("unexpected error: %v", err) 301 } 302 }) 303 } 304 305 func TestManager_GetBranch(t *testing.T) { 306 r, _ := testRefManager(t) 307 repository, err := r.CreateRepository(context.Background(), "repo1", graveler.Repository{ 308 StorageNamespace: "s3://", 309 CreationDate: time.Now(), 310 DefaultBranchID: "main", 311 }) 312 testutil.Must(t, err) 313 314 t.Run("get_branch_exists", func(t *testing.T) { 315 branch, err := r.GetBranch(context.Background(), repository, "main") 316 if err != nil { 317 t.Fatalf("unexpected error: %v", err) 318 } 319 if branch.CommitID == "" { 320 t.Fatal("unexpected empty branch commit received") 321 } 322 }) 323 324 t.Run("get_branch_doesnt_exists", func(t *testing.T) { 325 _, err := r.GetBranch(context.Background(), repository, "mainnnnn") 326 if !errors.Is(err, graveler.ErrBranchNotFound) { 327 t.Fatalf("expected ErrBranchNotFound, got error: %v", err) 328 } 329 }) 330 } 331 332 func TestManager_CreateBranch(t *testing.T) { 333 r, _ := testRefManager(t) 334 ctx := context.Background() 335 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 336 StorageNamespace: "s3://", 337 CreationDate: time.Now(), 338 DefaultBranchID: "main", 339 }) 340 testutil.Must(t, err) 341 342 err = r.CreateBranch(ctx, repository, "f1", graveler.Branch{CommitID: "c1", StagingToken: "s1"}) 343 testutil.MustDo(t, "create branch f1", err) 344 345 br, err := r.GetBranch(ctx, repository, "f1") 346 testutil.MustDo(t, "get f1 branch", err) 347 if br == nil { 348 t.Fatal("get branch got nil") 349 } 350 if br.CommitID != "c1" { 351 t.Fatalf("unexpected commit for branch f1: %s - expected: c1", br.CommitID) 352 } 353 354 // check we can't create existing 355 err = r.CreateBranch(ctx, repository, "f1", graveler.Branch{CommitID: "c2", StagingToken: "s2"}) 356 if !errors.Is(err, graveler.ErrBranchExists) { 357 t.Fatalf("CreateBranch() err = %s, expected already exists", err) 358 } 359 // overwrite by delete and create 360 err = r.DeleteBranch(ctx, repository, "f1") 361 testutil.MustDo(t, "delete branch f1", err) 362 363 err = r.CreateBranch(ctx, repository, "f1", graveler.Branch{CommitID: "c2", StagingToken: "s2"}) 364 testutil.MustDo(t, "create branch f1", err) 365 366 br, err = r.GetBranch(ctx, repository, "f1") 367 testutil.MustDo(t, "get f1 branch", err) 368 369 if br == nil { 370 t.Fatal("get branch got nil") 371 } 372 if br.CommitID != "c2" { 373 t.Fatalf("unexpected commit for branch f1: %s - expected: c2", br.CommitID) 374 } 375 } 376 377 func TestManager_SetBranch(t *testing.T) { 378 r, _ := testRefManager(t) 379 repository, err := r.CreateRepository(context.Background(), "repo1", graveler.Repository{ 380 StorageNamespace: "s3://", 381 CreationDate: time.Now(), 382 DefaultBranchID: "main", 383 }) 384 testutil.Must(t, err) 385 386 testutil.Must(t, r.SetBranch(context.Background(), repository, "branch2", graveler.Branch{ 387 CommitID: "c2", 388 })) 389 390 b, err := r.GetBranch(context.Background(), repository, "branch2") 391 if err != nil { 392 t.Fatalf("unexpected error: %v", err) 393 } 394 395 if b.CommitID != "c2" { 396 t.Fatalf("unexpected commit for branch2: %s - expected: c2", b.CommitID) 397 } 398 399 // overwrite 400 testutil.Must(t, r.SetBranch(context.Background(), repository, "branch2", graveler.Branch{ 401 CommitID: "c3", 402 })) 403 404 b, err = r.GetBranch(context.Background(), repository, "branch2") 405 if err != nil { 406 t.Fatalf("unexpected error: %v", err) 407 } 408 409 if b.CommitID != "c3" { 410 t.Fatalf("unexpected commit for branch2: %s - expected: c3", b.CommitID) 411 } 412 } 413 414 func TestManager_BranchUpdate(t *testing.T) { 415 ctx := context.Background() 416 r, _ := testRefManager(t) 417 const ( 418 repoID = "repo1" 419 branchID = "branch1" 420 commitID1 = "c1" 421 commitID2 = "c2" 422 ) 423 repository, err := r.CreateRepository(context.Background(), repoID, graveler.Repository{ 424 StorageNamespace: "s3://", 425 CreationDate: time.Now(), 426 DefaultBranchID: "main", 427 }) 428 testutil.Must(t, err) 429 tests := []struct { 430 name string 431 f graveler.BranchUpdateFunc 432 err error 433 expectedCommit string 434 }{ 435 { 436 name: "success_branch_update", 437 f: func(*graveler.Branch) (*graveler.Branch, error) { 438 newBranch := &graveler.Branch{ 439 CommitID: commitID2, 440 StagingToken: "", 441 SealedTokens: nil, 442 } 443 return newBranch, nil 444 }, 445 expectedCommit: commitID2, 446 }, 447 { 448 name: "failed_branch_update_due_to_branch_change", 449 f: func(*graveler.Branch) (*graveler.Branch, error) { 450 b := graveler.Branch{ 451 CommitID: "Another commit during validation", 452 } 453 _ = r.SetBranch(ctx, repository, branchID, b) 454 return &b, nil 455 }, 456 err: kv.ErrPredicateFailed, 457 expectedCommit: "Another commit during validation", 458 }, 459 { 460 name: "failed_branch_update_on_validation", 461 f: func(*graveler.Branch) (*graveler.Branch, error) { 462 return nil, graveler.ErrInvalid 463 }, 464 err: graveler.ErrInvalid, 465 expectedCommit: commitID1, 466 }, 467 } 468 for _, tt := range tests { 469 t.Run(tt.name, func(t *testing.T) { 470 testutil.Must(t, r.SetBranch(context.Background(), repository, branchID, graveler.Branch{ 471 CommitID: commitID1, 472 })) 473 474 err := r.BranchUpdate(ctx, repository, branchID, tt.f) 475 require.ErrorIs(t, err, tt.err) 476 477 b, err := r.GetBranch(context.Background(), repository, branchID) 478 require.NoError(t, err) 479 require.Equal(t, tt.expectedCommit, b.CommitID.String()) 480 }) 481 } 482 } 483 484 func TestManager_DeleteBranch(t *testing.T) { 485 r, _ := testRefManager(t) 486 ctx := context.Background() 487 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 488 StorageNamespace: "s3://", 489 CreationDate: time.Now(), 490 DefaultBranchID: "main", 491 }) 492 testutil.Must(t, err) 493 494 testutil.Must(t, r.SetBranch(ctx, repository, "branch2", graveler.Branch{ 495 CommitID: "c2", 496 })) 497 498 testutil.Must(t, r.DeleteBranch(ctx, repository, "branch2")) 499 500 _, err = r.GetBranch(ctx, repository, "branch2") 501 if !errors.Is(err, graveler.ErrBranchNotFound) { 502 t.Fatalf("Expected ErrBranchNotFound, got error: %v", err) 503 } 504 } 505 506 func TestManager_ListBranches(t *testing.T) { 507 r, _ := testRefManager(t) 508 repository, err := r.CreateRepository(context.Background(), "repo1", graveler.Repository{ 509 StorageNamespace: "s3://", 510 CreationDate: time.Now(), 511 DefaultBranchID: "main", 512 }) 513 testutil.Must(t, err) 514 515 for _, b := range []graveler.BranchID{"a", "aa", "c", "b", "z", "f"} { 516 testutil.Must(t, r.SetBranch(context.Background(), repository, b, graveler.Branch{ 517 CommitID: "c2", 518 })) 519 } 520 521 iter, err := r.ListBranches(context.Background(), repository) 522 if err != nil { 523 t.Fatalf("unexpected error: %v", err) 524 } 525 defer iter.Close() 526 527 var bs []graveler.BranchID 528 for iter.Next() { 529 b := iter.Value() 530 bs = append(bs, b.BranchID) 531 } 532 if iter.Err() != nil { 533 t.Fatalf("unexpected error: %v", iter.Err()) 534 } 535 if !reflect.DeepEqual(bs, []graveler.BranchID{"a", "aa", "b", "c", "f", "main", "z"}) { 536 t.Fatalf("unexpected branch list: %v", bs) 537 } 538 } 539 540 func TestManager_GetTag(t *testing.T) { 541 r, _ := testRefManager(t) 542 ctx := context.Background() 543 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 544 StorageNamespace: "s3://", 545 CreationDate: time.Now(), 546 DefaultBranchID: "main", 547 }) 548 testutil.MustDo(t, "create repo", err) 549 550 t.Run("exists", func(t *testing.T) { 551 err := r.CreateTag(ctx, repository, "v1.0", "c1") 552 testutil.MustDo(t, "set tag", err) 553 commitID, err := r.GetTag(context.Background(), repository, "v1.0") 554 testutil.MustDo(t, "get existing tag", err) 555 if commitID == nil { 556 t.Fatal("get tag, missing commit id") 557 } 558 if *commitID != "c1" { 559 t.Fatalf("get tag, commit id: %s, expected c1", *commitID) 560 } 561 }) 562 563 t.Run("not_exists", func(t *testing.T) { 564 commitID, err := r.GetTag(context.Background(), repository, "v1.bad") 565 if !errors.Is(err, graveler.ErrNotFound) { 566 t.Fatalf("expected ErrNotFound, got error: %v", err) 567 } 568 if commitID != nil { 569 t.Fatalf("get not existing commitID: %s, expected nil", *commitID) 570 } 571 }) 572 } 573 574 func TestManager_CreateTag(t *testing.T) { 575 r, _ := testRefManager(t) 576 ctx := context.Background() 577 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 578 StorageNamespace: "s3://", 579 CreationDate: time.Now(), 580 DefaultBranchID: "main", 581 }) 582 testutil.Must(t, err) 583 584 err = r.CreateTag(ctx, repository, "v2", "c2") 585 testutil.MustDo(t, "create tag v2", err) 586 587 commit, err := r.GetTag(ctx, repository, "v2") 588 testutil.MustDo(t, "get v2 tag", err) 589 if commit == nil { 590 t.Fatal("get tag got nil") 591 } 592 if *commit != "c2" { 593 t.Fatalf("unexpected commit for tag v2: %s - expected: c2", *commit) 594 } 595 596 // check we can't create existing 597 err = r.CreateTag(ctx, repository, "v2", "c5") 598 if !errors.Is(err, graveler.ErrTagAlreadyExists) { 599 t.Fatalf("CreateTag() err = %s, expected already exists", err) 600 } 601 // overwrite by delete and create 602 err = r.DeleteTag(ctx, repository, "v2") 603 testutil.MustDo(t, "delete tag v2", err) 604 605 err = r.CreateTag(ctx, repository, "v2", "c3") 606 testutil.MustDo(t, "re-create tag v2", err) 607 608 commit, err = r.GetTag(ctx, repository, "v2") 609 testutil.MustDo(t, "get tag v2", err) 610 if commit == nil { 611 t.Fatal("get tag got nil") 612 } 613 if *commit != "c3" { 614 t.Fatalf("unexpected commit for v2: %s - expected: c3", *commit) 615 } 616 } 617 618 func TestManager_DeleteTag(t *testing.T) { 619 r, _ := testRefManager(t) 620 ctx := context.Background() 621 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 622 StorageNamespace: "s3://", 623 CreationDate: time.Now(), 624 DefaultBranchID: "main", 625 }) 626 testutil.Must(t, err) 627 testutil.Must(t, r.CreateTag(ctx, repository, "v1", "c2")) 628 testutil.Must(t, r.DeleteTag(ctx, repository, "v1")) 629 commitID, err := r.GetTag(ctx, repository, "v1") 630 if !errors.Is(err, graveler.ErrNotFound) { 631 t.Fatal("unexpected error:", err) 632 } 633 if commitID != nil { 634 t.Fatal("expected commit ID:", *commitID) 635 } 636 } 637 638 func TestManager_ListTags(t *testing.T) { 639 r, _ := testRefManager(t) 640 ctx := context.Background() 641 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 642 StorageNamespace: "s3://", 643 CreationDate: time.Now(), 644 DefaultBranchID: "main", 645 }) 646 testutil.Must(t, err) 647 648 var commitsTagged []graveler.CommitID 649 tags := []string{"tag-a", "tag-b", "the-end", "v1", "v1.1"} 650 sort.Strings(tags) 651 for i, tag := range tags { 652 commitID := graveler.CommitID(fmt.Sprintf("c%d", i)) 653 commitsTagged = append(commitsTagged, commitID) 654 err := r.CreateTag(ctx, repository, graveler.TagID(tag), commitID) 655 testutil.MustDo(t, "set tag "+tag, err) 656 } 657 658 iter, err := r.ListTags(ctx, repository) 659 if err != nil { 660 t.Fatal("unexpected error:", err) 661 } 662 defer iter.Close() 663 var commits []graveler.CommitID 664 for iter.Next() { 665 commits = append(commits, iter.Value().CommitID) 666 } 667 testutil.MustDo(t, "list tags completed", iter.Err()) 668 669 if diff := deep.Equal(commits, commitsTagged); diff != nil { 670 t.Fatal("ListTags found mismatch:", diff) 671 } 672 } 673 674 func TestManager_AddCommit(t *testing.T) { 675 r, _ := testRefManager(t) 676 ctx := context.Background() 677 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 678 StorageNamespace: "s3://", 679 CreationDate: time.Now(), 680 DefaultBranchID: "main", 681 }) 682 testutil.Must(t, err) 683 684 ts, _ := time.Parse(time.RFC3339, "2020-12-01T15:00:00Z00:00") 685 c := graveler.Commit{ 686 Committer: "user1", 687 Message: "message1", 688 MetaRangeID: "deadbeef123", 689 CreationDate: ts, 690 Parents: graveler.CommitParents{"deadbeef1", "deadbeef12"}, 691 Metadata: graveler.Metadata{"foo": "bar"}, 692 } 693 694 cid, err := r.AddCommit(ctx, repository, c) 695 if err != nil { 696 t.Fatalf("unexpected error: %v", err) 697 } 698 699 const expectedCommitID = "2277b5abd2d3ba6b4d35c48a0e358b0c4bcf5cd6d891c67437fb4c4af0d2fd4b" 700 if cid != expectedCommitID { 701 t.Fatalf("Commit ID '%s', expected '%s'", cid, expectedCommitID) 702 } 703 704 commit, err := r.GetCommit(ctx, repository, cid) 705 if err != nil { 706 t.Fatalf("unexpected error: %v", err) 707 } 708 709 if commit.Parents[0] != "deadbeef1" { 710 t.Fatalf("expected parent1 to be deadbeef1, got %v", commit.Parents) 711 } 712 713 if commit.Metadata["foo"] != "bar" { 714 t.Fatalf("unexpected metadata value for foo: %v", commit.Metadata["foo"]) 715 } 716 } 717 718 func TestManager_Log(t *testing.T) { 719 r, _ := testRefManager(t) 720 ctx := context.Background() 721 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 722 StorageNamespace: "s3://", 723 CreationDate: time.Now(), 724 DefaultBranchID: "main", 725 }) 726 testutil.Must(t, err) 727 728 ts, _ := time.Parse(time.RFC3339, "2020-12-01T15:00:00Z") 729 var previous graveler.CommitID 730 for i := 0; i < 20; i++ { 731 c := graveler.Commit{ 732 Committer: "user1", 733 Message: "message1", 734 MetaRangeID: "deadbeef123", 735 CreationDate: ts, 736 Parents: graveler.CommitParents{}, 737 Metadata: graveler.Metadata{"foo": "bar"}, 738 } 739 if previous != "" { 740 c.Parents = append(c.Parents, previous) 741 } 742 cid, err := r.AddCommit(ctx, repository, c) 743 if err != nil { 744 t.Fatalf("unexpected error: %v", err) 745 } 746 previous = cid 747 ts = ts.Add(time.Second) 748 } 749 750 iter, err := r.Log(ctx, repository, previous, false, nil) 751 if err != nil { 752 t.Fatalf("unexpected error: %v", err) 753 } 754 defer iter.Close() 755 756 ids := make([]graveler.CommitID, 0) 757 for iter.Next() { 758 c := iter.Value() 759 ids = append(ids, c.CommitID) 760 } 761 if iter.Err() != nil { 762 t.Fatalf("unexpected error: %v", iter.Err()) 763 } 764 765 expected := []graveler.CommitID{ 766 "663b7520a2a05aaeed17de6136fa80eb5cd8417982011eb551230571ee412f2f", 767 "87856d024fbe092852118edd958717d905019fa4eae40bac18a719e2f869e0f7", 768 "25e51c6a8675c52558f8e303757624fca629bbc81f53afffa71a560df1c03948", 769 "7653a24da53a43b229a64c7fec4a3259ed2cd3cba8b0021650dd54ea286a06cd", 770 "d19e92e3b0717236255b529b35a7f1ec33e716be58af01c0f2fda80f4ded5a7a", 771 "ac2f92fbefff7914f82c148a391c4705555aacb4ef9fe2c43c21e88f92e459ec", 772 "fecd3e1f97cc1e54df6a06737d98931a8298c7ab1c870042666a10b42f7f7c0a", 773 "1d6e9e55a600eceead14e70a903cea94df5a7b74a6ca4de6f8206ab45d60a5bd", 774 "1f147e667ad0db53c2e9392d4bd35cb649269762f1da19e9e4d2e7b444dfd875", 775 "61d527f08cc67522728f8ffc93bcb91cc789de80beb9c43a41ee225fb9c446b2", 776 "32700dc4b5355be186976745fbef029f8ef7533170c0766ed77c9e3f574178c5", 777 "4d82f11b02d6cb609d2bc1007620f578733ffff971a749ff4462dc69b834c20a", 778 "2b4bb867adb1ac94c2f569dca92b9156a40ba0cd22a4bdc63cac1eb6b21b6f63", 779 "72ee57f5cb8dddb264624f9ac7266c6ebc82af509e69f84d592a6732e74af06c", 780 "3bbd01827326eba3f2a60e2ec98573cff1d2ead6c53336a5796ccf9d6401b052", 781 "8107b1d0a1ce6f75a1848f31ab3261eb86acdfe9b4e84b7eaf329b3904179de9", 782 "988c38b9f7d9b5df7242c3e837dac93e91dc0ff73da7dae1e010bcf18e3e0fa6", 783 "cb5dca579b23b81f8148fc9153a4c9c733d830c26be1d5f8d12496300c02dd89", 784 "ebbd689937253304ae29a541a727bfd11ab59a5659bb293e8ab2ed407c9a74c1", 785 "fac53a04432b2e6e7185f3ac8314a874c556b2557adf3aa8d5b1df985cf96566", 786 } 787 if diff := deep.Equal(ids, expected); diff != nil { 788 t.Fatal("Commits log wrong result:", diff) 789 } 790 } 791 792 func TestManager_LogGraph(t *testing.T) { 793 r, _ := testRefManager(t) 794 ctx := context.Background() 795 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 796 StorageNamespace: "s3://", 797 CreationDate: time.Now(), 798 DefaultBranchID: "main", 799 }) 800 testutil.MustDo(t, "Create repository", err) 801 dag := map[string][]string{ 802 "c1": {}, 803 "c2": {"c1"}, 804 "c3": {"c1"}, 805 "c4": {"c2"}, 806 "c5": {"c3"}, 807 "c6": {"c5"}, 808 "c7": {"c4"}, 809 "c8": {"c6", "c7"}, 810 } 811 tests := map[string]struct { 812 firstParent bool 813 seek string 814 start string 815 since time.Time 816 expected []string 817 }{ 818 /* 819 ---1----2----4----7 820 \ \ 821 3----5----6----8--- 822 */ 823 "full_graph": { 824 start: "c8", 825 expected: []string{"c8", "c7", "c6", "c5", "c4", "c3", "c2", "c1"}, 826 }, 827 "full_graph_first_parent": { 828 start: "c8", 829 firstParent: true, 830 expected: []string{"c8", "c6", "c5", "c3", "c1"}, 831 }, 832 "with_seek": { 833 start: "c8", 834 seek: "c4", 835 expected: []string{"c4", "c3", "c2", "c1"}, 836 }, 837 "with_seek_first_parent": { 838 start: "c8", 839 seek: "c5", 840 firstParent: true, 841 expected: []string{"c5", "c3", "c1"}, 842 }, 843 "start_from": { 844 start: "c7", 845 expected: []string{"c7", "c4", "c2", "c1"}, 846 }, 847 "since": { 848 start: "c8", 849 since: time.Date(2020, time.December, 1, 15, 5, 0, 0, time.UTC), 850 expected: []string{"c8", "c7", "c6", "c5"}, 851 }, 852 } 853 for name, tst := range tests { 854 t.Run(name, func(t *testing.T) { 855 nextCommitTS := time.Date(2020, time.December, 1, 15, 0, 0, 0, time.UTC) 856 commitNameToID := map[string]graveler.CommitID{} 857 addCommit := func(commitName string, parentNames ...string) graveler.CommitID { 858 nextCommitTS = nextCommitTS.Add(time.Minute) 859 parentIDs := make([]graveler.CommitID, 0, len(parentNames)) 860 for _, parentName := range parentNames { 861 parentIDs = append(parentIDs, commitNameToID[parentName]) 862 } 863 c := graveler.Commit{ 864 Committer: "user1", 865 Message: commitName, 866 MetaRangeID: "fefe1221", 867 CreationDate: nextCommitTS, 868 Parents: parentIDs, 869 Metadata: graveler.Metadata{"foo": "bar"}, 870 } 871 cid, err := r.AddCommit(ctx, repository, c) 872 commitNameToID[commitName] = cid 873 testutil.MustDo(t, "Add commit "+commitName, err) 874 return cid 875 } 876 commitNames := make([]string, 0, len(dag)) 877 for commitName := range dag { 878 commitNames = append(commitNames, commitName) 879 } 880 sort.Strings(commitNames) 881 for _, commitName := range commitNames { 882 addCommit(commitName, dag[commitName]...) 883 } 884 885 // setup time since 886 var since *time.Time 887 if !tst.since.IsZero() { 888 since = &tst.since 889 } 890 891 it, err := r.Log(ctx, repository, commitNameToID[tst.start], tst.firstParent, since) 892 if err != nil { 893 t.Fatal("Error during create Log iterator", err) 894 } 895 defer it.Close() 896 if tst.seek != "" { 897 it.SeekGE(commitNameToID[tst.seek]) 898 } 899 var commits []string 900 for it.Next() { 901 c := it.Value() 902 commits = append(commits, c.Message) 903 } 904 if err := it.Err(); err != nil { 905 t.Fatal("Iteration ended with error", err) 906 } 907 if diff := deep.Equal(commits, tst.expected); diff != nil { 908 t.Fatal("Found diff between expected commits:", diff) 909 } 910 }) 911 } 912 } 913 914 func TestConsistentCommitIdentity(t *testing.T) { 915 addressProvider := ident.NewHexAddressProvider() 916 commit := graveler.Commit{ 917 Committer: "some-committer", 918 Message: "I just committed", 919 MetaRangeID: "123456789987654321", 920 CreationDate: time.Date(2021, time.January, 24, 15, 10, 11, 1564956600, time.UTC), 921 Parents: graveler.CommitParents{ 922 graveler.CommitID("132456987153687sdfsdf"), 923 graveler.CommitID("1324569csfvdkjhcsdkjc"), 924 }, 925 Metadata: map[string]string{ 926 "sdkafjnb": "1234", 927 "sdkjvcnbkjndsc": "asnjkdl", 928 }, 929 } 930 931 // Should NOT be changed (unless you really know what you're doing): 932 // If this is failing, and you're tempted to change this value, 933 // then you are probably introducing a breaking change to commits identity. 934 // All previous references to commits (if not migrated) may be lost. 935 const expected = "f1a106bbeb12d3eb54418d6000f4507501d289d0d0879dcce6f4d31425587df1" 936 937 // Running many times to check that it's actually consistent (see issue #1291) 938 const iterations = 50 939 940 for i := 0; i < iterations; i++ { 941 res := addressProvider.ContentAddress(commit) 942 assert.Equalf(t, expected, res, "iteration %d content mismatch", i+1) 943 } 944 } 945 946 func TestManager_GetCommitByPrefix(t *testing.T) { 947 commitIDs := []string{"c1234", "d1", "b1", "c1245", "a1"} 948 identityToFakeIdentity := make(map[string]string) 949 950 provider := &fakeAddressProvider{identityToFakeIdentity: identityToFakeIdentity} 951 r, _ := testRefManagerWithAddressProvider(t, provider) 952 ctx := context.Background() 953 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 954 StorageNamespace: "s3://", 955 CreationDate: time.Now(), 956 DefaultBranchID: "main", 957 }) 958 testutil.MustDo(t, "Create repository", err) 959 for _, commitID := range commitIDs { 960 c := graveler.Commit{ 961 Committer: "user1", 962 Message: fmt.Sprintf("id_%s", commitID), 963 MetaRangeID: "deadbeef123", 964 CreationDate: time.Now(), 965 Parents: graveler.CommitParents{"deadbeef1"}, 966 Metadata: graveler.Metadata{"foo": "bar"}, 967 } 968 identityToFakeIdentity[hex.EncodeToString(c.Identity())] = commitID 969 _, err := r.AddCommit(ctx, repository, c) 970 testutil.MustDo(t, "add commit", err) 971 if err != nil { 972 t.Fatalf("unexpected error on adding commit: %v", err) 973 } 974 } 975 tests := []struct { 976 Prefix string 977 ExpectedCommitMessage string 978 ExpectedErr error 979 }{ 980 { 981 Prefix: "a", 982 ExpectedCommitMessage: "id_a1", 983 }, 984 { 985 Prefix: "c123", 986 ExpectedCommitMessage: "id_c1234", 987 }, 988 { 989 Prefix: "c1", 990 ExpectedErr: graveler.ErrCommitNotFound, 991 }, 992 { 993 Prefix: "e", 994 ExpectedErr: graveler.ErrCommitNotFound, 995 }, 996 } 997 for _, tst := range tests { 998 t.Run(tst.Prefix, func(t *testing.T) { 999 c, err := r.GetCommitByPrefix(ctx, repository, graveler.CommitID(tst.Prefix)) 1000 if !errors.Is(err, tst.ExpectedErr) { 1001 t.Fatalf("expected error %v, got=%v", tst.ExpectedErr, err) 1002 } 1003 if tst.ExpectedErr != nil { 1004 return 1005 } 1006 if c.Message != tst.ExpectedCommitMessage { 1007 t.Fatalf("got commit different than expected. expected=%s, got=%s", tst.ExpectedCommitMessage, c.Message) 1008 } 1009 }) 1010 } 1011 } 1012 1013 func TestManager_ListCommits(t *testing.T) { 1014 r, _ := testRefManager(t) 1015 ctx := context.Background() 1016 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 1017 StorageNamespace: "s3://", 1018 CreationDate: time.Now(), 1019 DefaultBranchID: "main", 1020 }) 1021 testutil.Must(t, err) 1022 nextCommitNumber := 0 1023 addNextCommit := func(parents ...graveler.CommitID) graveler.CommitID { 1024 nextCommitNumber++ 1025 id := "c" + strconv.Itoa(nextCommitNumber) 1026 c := graveler.Commit{ 1027 Message: id, 1028 Parents: parents, 1029 } 1030 cid, err := r.AddCommit(ctx, repository, c) 1031 testutil.MustDo(t, "Add commit "+id, err) 1032 return cid 1033 } 1034 c1 := addNextCommit() 1035 c2 := addNextCommit(c1) 1036 c3 := addNextCommit(c1) 1037 c4 := addNextCommit(c2) 1038 c5 := addNextCommit(c3) 1039 c6 := addNextCommit(c5) 1040 addNextCommit(c4) 1041 addNextCommit(c6, c1) 1042 /* 1043 1----2----4---7 1044 | \ 1045 | 3----5----6 1046 | \ 1047 ---------------8 1048 */ 1049 1050 iter, err := r.ListCommits(ctx, repository) 1051 testutil.MustDo(t, "fill generations", err) 1052 defer iter.Close() 1053 var lastCommit string 1054 var i int 1055 for iter.Next() { 1056 commit := iter.Value() 1057 if i == 0 { 1058 gravelerCommitReflection := reflect.Indirect(reflect.ValueOf(graveler.Commit{})) 1059 listCommitReflection := reflect.Indirect(reflect.ValueOf(*commit.Commit)) 1060 listCommitFields := make(map[string]struct{}) 1061 for i := 0; i < listCommitReflection.NumField(); i++ { 1062 listCommitFields[listCommitReflection.Type().Field(i).Name] = struct{}{} 1063 } 1064 for i := 0; i < gravelerCommitReflection.NumField(); i++ { 1065 fieldName := gravelerCommitReflection.Type().Field(i).Name 1066 _, exists := listCommitFields[fieldName] 1067 if !exists { 1068 t.Errorf("missing field: %s in commit response from list commits.", fieldName) 1069 } 1070 } 1071 } else if string(commit.CommitID) < lastCommit { 1072 t.Errorf("wrong commitId order for commit number%d in ListCommits response. commitId: %s came after commitId: %s", i, string(commit.CommitID), lastCommit) 1073 } 1074 lastCommit = string(commit.CommitID) 1075 i++ 1076 } 1077 } 1078 1079 func TestManager_DeleteExpiredImports(t *testing.T) { 1080 r, store := testRefManager(t) 1081 ctx := context.Background() 1082 repository, err := r.CreateRepository(ctx, "repo1", graveler.Repository{ 1083 StorageNamespace: "s3://", 1084 CreationDate: time.Now(), 1085 DefaultBranchID: "main", 1086 }) 1087 testutil.Must(t, err) 1088 1089 imports := []*graveler.ImportStatusData{ 1090 { 1091 Id: "not_expired1", 1092 Completed: false, 1093 UpdatedAt: timestamppb.New(time.Now()), 1094 Error: "An error", 1095 }, 1096 { 1097 Id: "not_expired2", 1098 Completed: false, 1099 UpdatedAt: timestamppb.New(time.Now().Add(-ref.ImportExpiryTime + time.Hour)), 1100 Error: "An error", 1101 }, 1102 { 1103 Id: "expired", 1104 Completed: true, 1105 UpdatedAt: timestamppb.New(time.Now().Add(-ref.ImportExpiryTime - time.Hour)), 1106 Error: "", 1107 }, 1108 { 1109 Id: "stale", 1110 Completed: false, 1111 UpdatedAt: timestamppb.New(time.Now().Add(-ref.ImportExpiryTime - time.Hour)), 1112 Error: "", 1113 }, 1114 } 1115 1116 repoPartition := graveler.RepoPartition(repository) 1117 for _, i := range imports { 1118 data, err := proto.Marshal(i) 1119 require.NoError(t, err) 1120 err = store.Set(ctx, []byte(repoPartition), []byte(graveler.ImportsPath(i.Id)), data) 1121 require.NoError(t, err) 1122 } 1123 1124 err = r.DeleteExpiredImports(context.Background(), repository) 1125 require.NoError(t, err) 1126 1127 it, err := kv.NewPrimaryIterator(ctx, store, (&graveler.ImportStatusData{}).ProtoReflect().Type(), repoPartition, []byte(graveler.ImportsPath("")), kv.IteratorOptionsFrom([]byte(""))) 1128 require.NoError(t, err) 1129 defer it.Close() 1130 1131 count := 0 1132 for it.Next() { 1133 entry := it.Entry() 1134 count += 1 1135 id := string(entry.Key) 1136 require.True(t, strings.HasPrefix(id, "imports/not_expired"), id) 1137 } 1138 require.NoError(t, it.Err()) 1139 require.Equal(t, 2, count) 1140 } 1141 1142 func TestManager_GetRepositoryMetadata(t *testing.T) { 1143 ctx := context.Background() 1144 r, _ := testRefManager(t) 1145 const ( 1146 repoID = "repo1" 1147 ) 1148 repository, err := r.CreateRepository(context.Background(), repoID, graveler.Repository{ 1149 StorageNamespace: "s3://", 1150 CreationDate: time.Now(), 1151 DefaultBranchID: "main", 1152 }) 1153 testutil.Must(t, err) 1154 1155 t.Run("get_on_non_existing_repo", func(t *testing.T) { 1156 _, err := r.GetRepositoryMetadata(ctx, "not_exist") 1157 require.ErrorIs(t, err, graveler.ErrNotFound) 1158 }) 1159 1160 t.Run("basic", func(t *testing.T) { 1161 metadata, err := r.GetRepositoryMetadata(ctx, repository.RepositoryID) 1162 require.NoError(t, err) 1163 require.Nil(t, metadata) 1164 }) 1165 } 1166 1167 func TestManager_SetRepositoryMetadata(t *testing.T) { 1168 ctx := context.Background() 1169 r, store := testRefManager(t) 1170 const ( 1171 repoID = "repo1" 1172 key = "test_key" 1173 ) 1174 repository, err := r.CreateRepository(context.Background(), repoID, graveler.Repository{ 1175 StorageNamespace: "s3://", 1176 CreationDate: time.Now(), 1177 DefaultBranchID: "main", 1178 }) 1179 testutil.Must(t, err) 1180 tests := []struct { 1181 name string 1182 f graveler.RepoMetadataUpdateFunc 1183 err error 1184 expectedMetadata graveler.RepositoryMetadata 1185 }{ 1186 { 1187 name: "success_branch_update", 1188 f: func(metadata graveler.RepositoryMetadata) (graveler.RepositoryMetadata, error) { 1189 metadata[key] = "success" 1190 return metadata, nil 1191 }, 1192 expectedMetadata: graveler.RepositoryMetadata{key: "success"}, 1193 }, 1194 { 1195 name: "failed_metadata_update_due_to_changes", 1196 f: func(metadata graveler.RepositoryMetadata) (graveler.RepositoryMetadata, error) { 1197 m := graveler.RepoMetadata{ 1198 Metadata: graveler.RepositoryMetadata{ 1199 key: "failed", 1200 }, 1201 } 1202 _ = kv.SetMsg(ctx, store, graveler.RepoPartition(repository), []byte(graveler.RepoMetadataPath()), &m) 1203 metadata[key] = "not_expected" 1204 return metadata, nil 1205 }, 1206 err: kv.ErrPredicateFailed, 1207 expectedMetadata: graveler.RepositoryMetadata{key: "failed"}, 1208 }, 1209 { 1210 name: "failed_update_on_validation", 1211 f: func(metadata graveler.RepositoryMetadata) (graveler.RepositoryMetadata, error) { 1212 return nil, graveler.ErrInvalid 1213 }, 1214 err: graveler.ErrInvalid, 1215 expectedMetadata: graveler.RepositoryMetadata{key: "failed"}, 1216 }, 1217 } 1218 for _, tt := range tests { 1219 t.Run(tt.name, func(t *testing.T) { 1220 err = r.SetRepositoryMetadata(ctx, repository, tt.f) 1221 require.ErrorIs(t, err, tt.err) 1222 1223 metadata, err := r.GetRepositoryMetadata(ctx, repository.RepositoryID) 1224 require.NoError(t, err) 1225 require.Equal(t, tt.expectedMetadata, metadata) 1226 }) 1227 } 1228 }