|
34 | 34 | pkgA |
35 | 35 | pkgB |
36 | 36 | pkgC |
| 37 | + pkgs.coreutils |
37 | 38 | ]; |
38 | 39 | environment.systemPackages = [ pkgs.minio-client ]; |
| 40 | + nix.nixPath = [ "nixpkgs=${pkgs.path}" ]; |
39 | 41 | nix.extraOptions = '' |
40 | 42 | experimental-features = nix-command |
41 | 43 | substituters = |
|
639 | 641 | ) |
640 | 642 | print(" ✓ Fetch with versionId parameter works") |
641 | 643 |
|
| 644 | + @setup_s3() |
| 645 | + def test_multipart_upload_basic(bucket): |
| 646 | + """Test basic multipart upload with a large file""" |
| 647 | + print("\n--- Test: Multipart Upload Basic ---") |
| 648 | +
|
| 649 | + large_file_size = 10 * 1024 * 1024 |
| 650 | + large_pkg = server.succeed( |
| 651 | + "nix-store --add $(dd if=/dev/urandom of=/tmp/large-file bs=1M count=10 2>/dev/null && echo /tmp/large-file)" |
| 652 | + ).strip() |
| 653 | +
|
| 654 | + chunk_size = 5 * 1024 * 1024 |
| 655 | + expected_parts = 3 # 10 MB raw becomes ~10.5 MB compressed (NAR + xz overhead) |
| 656 | +
|
| 657 | + store_url = make_s3_url( |
| 658 | + bucket, |
| 659 | + **{ |
| 660 | + "multipart-upload": "true", |
| 661 | + "multipart-threshold": str(5 * 1024 * 1024), |
| 662 | + "multipart-chunk-size": str(chunk_size), |
| 663 | + } |
| 664 | + ) |
| 665 | +
|
| 666 | + print(f" Uploading {large_file_size} byte file (expect {expected_parts} parts)") |
| 667 | + output = server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {large_pkg} --debug 2>&1") |
| 668 | +
|
| 669 | + if "using S3 multipart upload" not in output: |
| 670 | + raise Exception("Expected multipart upload to be used") |
| 671 | +
|
| 672 | + expected_msg = f"{expected_parts} parts uploaded" |
| 673 | + if expected_msg not in output: |
| 674 | + print("Debug output:") |
| 675 | + print(output) |
| 676 | + raise Exception(f"Expected '{expected_msg}' in output") |
| 677 | +
|
| 678 | + print(f" ✓ Multipart upload used with {expected_parts} parts") |
| 679 | +
|
| 680 | + client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' {large_pkg} --no-check-sigs") |
| 681 | + verify_packages_in_store(client, large_pkg, should_exist=True) |
| 682 | +
|
| 683 | + print(" ✓ Large file downloaded and verified") |
| 684 | +
|
| 685 | + @setup_s3() |
| 686 | + def test_multipart_threshold(bucket): |
| 687 | + """Test that files below threshold use regular upload""" |
| 688 | + print("\n--- Test: Multipart Threshold Behavior ---") |
| 689 | +
|
| 690 | + store_url = make_s3_url( |
| 691 | + bucket, |
| 692 | + **{ |
| 693 | + "multipart-upload": "true", |
| 694 | + "multipart-threshold": str(1024 * 1024 * 1024), |
| 695 | + } |
| 696 | + ) |
| 697 | +
|
| 698 | + print(" Uploading small file with high threshold") |
| 699 | + output = server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKGS['A']} --debug 2>&1") |
| 700 | +
|
| 701 | + if "using S3 multipart upload" in output: |
| 702 | + raise Exception("Should not use multipart for file below threshold") |
| 703 | +
|
| 704 | + if "using S3 regular upload" not in output: |
| 705 | + raise Exception("Expected regular upload to be used") |
| 706 | +
|
| 707 | + print(" ✓ Regular upload used for file below threshold") |
| 708 | +
|
| 709 | + client.succeed(f"{ENV_WITH_CREDS} nix copy --no-check-sigs --from '{store_url}' {PKGS['A']}") |
| 710 | + verify_packages_in_store(client, PKGS['A'], should_exist=True) |
| 711 | +
|
| 712 | + print(" ✓ Small file uploaded and verified") |
| 713 | +
|
| 714 | + @setup_s3() |
| 715 | + def test_multipart_with_log_compression(bucket): |
| 716 | + """Test multipart upload with compressed build logs""" |
| 717 | + print("\n--- Test: Multipart Upload with Log Compression ---") |
| 718 | +
|
| 719 | + # Create a derivation that produces a large text log (12 MB of base64 output) |
| 720 | + drv_path = server.succeed( |
| 721 | + """ |
| 722 | + nix-instantiate --expr ' |
| 723 | + let pkgs = import <nixpkgs> {}; |
| 724 | + in derivation { |
| 725 | + name = "large-log-builder"; |
| 726 | + builder = "/bin/sh"; |
| 727 | + args = ["-c" "$coreutils/bin/dd if=/dev/urandom bs=1M count=12 | $coreutils/bin/base64; echo success > $out"]; |
| 728 | + coreutils = pkgs.coreutils; |
| 729 | + system = builtins.currentSystem; |
| 730 | + } |
| 731 | + ' |
| 732 | + """ |
| 733 | + ).strip() |
| 734 | +
|
| 735 | + print(" Building derivation to generate large log") |
| 736 | + server.succeed(f"nix-store --realize {drv_path} &>/dev/null") |
| 737 | +
|
| 738 | + # Upload logs with compression and multipart |
| 739 | + store_url = make_s3_url( |
| 740 | + bucket, |
| 741 | + **{ |
| 742 | + "multipart-upload": "true", |
| 743 | + "multipart-threshold": str(5 * 1024 * 1024), |
| 744 | + "multipart-chunk-size": str(5 * 1024 * 1024), |
| 745 | + "log-compression": "xz", |
| 746 | + } |
| 747 | + ) |
| 748 | +
|
| 749 | + print(" Uploading build log with compression and multipart") |
| 750 | + output = server.succeed( |
| 751 | + f"{ENV_WITH_CREDS} nix store copy-log --to '{store_url}' {drv_path} --debug 2>&1" |
| 752 | + ) |
| 753 | +
|
| 754 | + # Should use multipart for the compressed log |
| 755 | + if "using S3 multipart upload" not in output or "log/" not in output: |
| 756 | + print("Debug output:") |
| 757 | + print(output) |
| 758 | + raise Exception("Expected multipart upload to be used for compressed log") |
| 759 | +
|
| 760 | + if "parts uploaded" not in output: |
| 761 | + print("Debug output:") |
| 762 | + print(output) |
| 763 | + raise Exception("Expected multipart completion message") |
| 764 | +
|
| 765 | + print(" ✓ Compressed log uploaded with multipart") |
| 766 | +
|
642 | 767 | # ============================================================================ |
643 | 768 | # Main Test Execution |
644 | 769 | # ============================================================================ |
|
669 | 794 | test_compression_disabled() |
670 | 795 | test_nix_prefetch_url() |
671 | 796 | test_versioned_urls() |
| 797 | + # FIXME: enable when multipart fully lands |
| 798 | + # test_multipart_upload_basic() |
| 799 | + # test_multipart_threshold() |
| 800 | + # test_multipart_with_log_compression() |
672 | 801 |
|
673 | 802 | print("\n" + "="*80) |
674 | 803 | print("✓ All S3 Binary Cache Store Tests Passed!") |
|
0 commit comments