test(tvix/nar-bridge): start testing handlers

We currently only had some integration tests (as part of tvix-boot)
testing nar-bridge functionality as a smoketest, but with axum-test we
can test individual handlers and peek at the store afterwards, which is
much more granular.

This adds tests for the nar-specific request handlers.

Change-Id: I7f2345df89ac43b9b372ecc66f696e95e2fcad18
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12916
Tested-by: BuildkiteCI
Reviewed-by: raitobezarius <tvl@lahfa.xyz>
Autosubmit: flokli <flokli@flokli.de>
This commit is contained in:
Florian Klink 2024-11-24 18:37:03 +02:00 committed by clbot
parent 4f9112f1cd
commit 30b631ea72
5 changed files with 870 additions and 49 deletions

177
tvix/Cargo.lock generated
View file

@ -126,6 +126,16 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-channel"
version = "2.3.1"
@ -277,6 +287,12 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auto-future"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373"
[[package]]
name = "auto_impl"
version = "1.2.0"
@ -305,7 +321,7 @@ dependencies = [
"axum-macros",
"bytes",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"hyper",
@ -338,7 +354,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"mime",
@ -361,7 +377,7 @@ dependencies = [
"bytes",
"futures-util",
"headers",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"mime",
@ -399,6 +415,36 @@ dependencies = [
"tokio",
]
[[package]]
name = "axum-test"
version = "16.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "017cbca2776229a7100ebee44e065fcf5baccea6fc4cb9e5bea8328d83863a03"
dependencies = [
"anyhow",
"assert-json-diff",
"auto-future",
"axum",
"bytes",
"bytesize",
"cookie",
"http 1.1.0",
"http-body-util",
"hyper",
"hyper-util",
"mime",
"pretty_assertions",
"reserve-port",
"rust-multipart-rfc7578_2",
"serde",
"serde_json",
"serde_urlencoded",
"smallvec",
"tokio",
"tower 0.5.1",
"url",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -438,7 +484,7 @@ version = "0.2.10"
source = "git+https://github.com/liufuyang/bigtable_rs?rev=1818355a5373a5bc2c84287e3a4e3807154ac8ef#1818355a5373a5bc2c84287e3a4e3807154ac8ef"
dependencies = [
"gcp_auth",
"http",
"http 1.1.0",
"hyper-util",
"log",
"prost",
@ -533,6 +579,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "bytesize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
[[package]]
name = "bzip2"
version = "0.4.4"
@ -736,6 +788,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"time",
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -1413,7 +1475,7 @@ dependencies = [
"bytes",
"chrono",
"home",
"http",
"http 1.1.0",
"http-body-util",
"hyper",
"hyper-rustls",
@ -1515,7 +1577,7 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http",
"http 1.1.0",
"indexmap 2.6.0",
"slab",
"tokio",
@ -1565,7 +1627,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http",
"http 1.1.0",
"httpdate",
"mime",
"sha1",
@ -1577,7 +1639,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
"http 1.1.0",
]
[[package]]
@ -1625,6 +1687,17 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.1.0"
@ -1643,7 +1716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
"http 1.1.0",
]
[[package]]
@ -1654,7 +1727,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [
"bytes",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"pin-project-lite",
]
@ -1687,7 +1760,7 @@ dependencies = [
"futures-channel",
"futures-util",
"h2",
"http",
"http 1.1.0",
"http-body",
"httparse",
"httpdate",
@ -1705,7 +1778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http",
"http 1.1.0",
"hyper",
"hyper-util",
"rustls",
@ -1738,7 +1811,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"hyper",
"pin-project-lite",
@ -2159,6 +2232,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -2237,6 +2320,7 @@ dependencies = [
"axum",
"axum-extra",
"axum-range",
"axum-test",
"bytes",
"clap",
"data-encoding",
@ -2252,6 +2336,7 @@ dependencies = [
"prost-build",
"rstest",
"serde",
"sha2",
"thiserror",
"tokio",
"tokio-listener",
@ -2263,6 +2348,7 @@ dependencies = [
"tower-otel-http-metrics",
"tracing",
"tracing-subscriber",
"tracing-test",
"tvix-castore",
"tvix-store",
"tvix-tracing",
@ -2591,7 +2677,7 @@ checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80"
dependencies = [
"async-trait",
"bytes",
"http",
"http 1.1.0",
"opentelemetry",
]
@ -2603,7 +2689,7 @@ checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76"
dependencies = [
"async-trait",
"futures-core",
"http",
"http 1.1.0",
"opentelemetry",
"opentelemetry-proto",
"opentelemetry_sdk",
@ -3303,7 +3389,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"hyper",
@ -3344,7 +3430,7 @@ source = "git+https://github.com/TrueLayer/reqwest-middleware?rev=8a494c165734e2
dependencies = [
"anyhow",
"async-trait",
"http",
"http 1.1.0",
"reqwest",
"serde",
"thiserror",
@ -3359,7 +3445,7 @@ dependencies = [
"anyhow",
"async-trait",
"getrandom",
"http",
"http 1.1.0",
"matchit 0.8.4",
"opentelemetry",
"reqwest",
@ -3368,6 +3454,16 @@ dependencies = [
"tracing-opentelemetry",
]
[[package]]
name = "reserve-port"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9838134a2bfaa8e1f40738fcc972ac799de6e0e06b5157acb95fc2b05a0ea283"
dependencies = [
"lazy_static",
"thiserror",
]
[[package]]
name = "ring"
version = "0.17.8"
@ -3445,6 +3541,22 @@ dependencies = [
"syn 2.0.79",
]
[[package]]
name = "rust-multipart-rfc7578_2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03b748410c0afdef2ebbe3685a6a862e2ee937127cdaae623336a459451c8d57"
dependencies = [
"bytes",
"futures-core",
"futures-util",
"http 0.2.12",
"mime",
"mime_guess",
"rand",
"thiserror",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -4334,7 +4446,7 @@ dependencies = [
"base64 0.22.1",
"bytes",
"h2",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"hyper",
@ -4441,7 +4553,7 @@ dependencies = [
"bitflags 2.6.0",
"bytes",
"futures-core",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"pin-project-lite",
@ -4466,7 +4578,7 @@ checksum = "ed0ba983713ec0f5d512dc28091fa3c1cb8fa5487de32a1b0bc0cb4159f9f89f"
dependencies = [
"axum",
"futures-util",
"http",
"http 1.1.0",
"opentelemetry",
"pin-project-lite",
"tower 0.5.1",
@ -4582,6 +4694,27 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "tracing-test"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68"
dependencies = [
"tracing-core",
"tracing-subscriber",
"tracing-test-macro",
]
[[package]]
name = "tracing-test-macro"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568"
dependencies = [
"quote",
"syn 2.0.79",
]
[[package]]
name = "tracing-tracy"
version = "0.11.3"
@ -4924,7 +5057,7 @@ name = "tvix-tracing"
version = "0.1.0"
dependencies = [
"axum",
"http",
"http 1.1.0",
"indicatif",
"opentelemetry",
"opentelemetry-http",

View file

@ -492,6 +492,34 @@ rec {
"zeroize" = [ "dep:zeroize" ];
};
};
"assert-json-diff" = rec {
crateName = "assert-json-diff";
version = "2.0.2";
edition = "2018";
sha256 = "04mg3w0rh3schpla51l18362hsirl23q93aisws2irrj32wg5r27";
libName = "assert_json_diff";
authors = [
"David Pedersen <david.pdrsn@gmail.com>"
];
dependencies = [
{
name = "serde";
packageId = "serde";
}
{
name = "serde_json";
packageId = "serde_json";
}
];
devDependencies = [
{
name = "serde";
packageId = "serde";
features = [ "derive" ];
}
];
};
"async-channel" = rec {
crateName = "async-channel";
version = "2.3.1";
@ -1002,6 +1030,17 @@ rec {
"portable-atomic" = [ "dep:portable-atomic" ];
};
};
"auto-future" = rec {
crateName = "auto-future";
version = "1.0.0";
edition = "2021";
sha256 = "0wykbakzh227vz6frx9p48zsq0wpswgmb7v3917m53m7gr2pw7iw";
libName = "auto_future";
authors = [
"Joseph Lenton <josephlenton@gmail.com>"
];
};
"auto_impl" = rec {
crateName = "auto_impl";
version = "1.2.0";
@ -1070,7 +1109,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -1243,7 +1282,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -1331,7 +1370,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -1500,6 +1539,135 @@ rec {
];
features = { };
};
"axum-test" = rec {
crateName = "axum-test";
version = "16.4.0";
edition = "2021";
sha256 = "00rshs1qscm8pvjvjk7wlv7aqnygbw34xr5y1q8afab2fyibqz01";
libName = "axum_test";
authors = [
"Joseph Lenton <josephlenton@gmail.com>"
];
dependencies = [
{
name = "anyhow";
packageId = "anyhow";
}
{
name = "assert-json-diff";
packageId = "assert-json-diff";
}
{
name = "auto-future";
packageId = "auto-future";
}
{
name = "axum";
packageId = "axum";
}
{
name = "bytes";
packageId = "bytes";
}
{
name = "bytesize";
packageId = "bytesize";
}
{
name = "cookie";
packageId = "cookie";
}
{
name = "http";
packageId = "http 1.1.0";
}
{
name = "http-body-util";
packageId = "http-body-util";
}
{
name = "hyper";
packageId = "hyper";
features = [ "http1" ];
}
{
name = "hyper-util";
packageId = "hyper-util";
features = [ "client" "http1" "client-legacy" ];
}
{
name = "mime";
packageId = "mime";
}
{
name = "pretty_assertions";
packageId = "pretty_assertions";
optional = true;
}
{
name = "reserve-port";
packageId = "reserve-port";
}
{
name = "rust-multipart-rfc7578_2";
packageId = "rust-multipart-rfc7578_2";
}
{
name = "serde";
packageId = "serde";
}
{
name = "serde_json";
packageId = "serde_json";
}
{
name = "serde_urlencoded";
packageId = "serde_urlencoded";
}
{
name = "smallvec";
packageId = "smallvec";
}
{
name = "tokio";
packageId = "tokio";
features = [ "rt" ];
}
{
name = "tower";
packageId = "tower 0.5.1";
features = [ "util" "make" ];
}
{
name = "url";
packageId = "url";
}
];
devDependencies = [
{
name = "axum";
packageId = "axum";
features = [ "multipart" "tokio" "ws" ];
}
{
name = "tokio";
packageId = "tokio";
features = [ "rt" "rt-multi-thread" "sync" "time" "macros" ];
}
];
features = {
"all" = [ "pretty-assertions" "yaml" "msgpack" "reqwest" "shuttle" "typed-routing" "ws" ];
"default" = [ "pretty-assertions" ];
"msgpack" = [ "dep:rmp-serde" ];
"pretty-assertions" = [ "dep:pretty_assertions" ];
"reqwest" = [ "dep:reqwest" ];
"shuttle" = [ "dep:shuttle-axum" ];
"typed-routing" = [ "dep:axum-extra" ];
"ws" = [ "axum/ws" "tokio/time" "dep:uuid" "dep:base64" "dep:tokio-tungstenite" "dep:futures-util" ];
"yaml" = [ "dep:serde_yaml" ];
};
resolvedDefaultFeatures = [ "default" "pretty-assertions" ];
};
"backtrace" = rec {
crateName = "backtrace";
version = "0.3.74";
@ -1618,7 +1786,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "hyper-util";
@ -1917,6 +2085,19 @@ rec {
};
resolvedDefaultFeatures = [ "default" "std" ];
};
"bytesize" = rec {
crateName = "bytesize";
version = "1.3.0";
edition = "2015";
sha256 = "1k3aak70iwz4s2gsjbxf0ws4xnixqbdz6p2ha96s06748fpniqx3";
authors = [
"Hyunsik Choi <hyunsik.choi@gmail.com>"
];
features = {
"serde" = [ "dep:serde" ];
};
resolvedDefaultFeatures = [ "default" ];
};
"bzip2" = rec {
crateName = "bzip2";
version = "0.4.4";
@ -2471,6 +2652,45 @@ rec {
];
features = { };
};
"cookie" = rec {
crateName = "cookie";
version = "0.18.1";
edition = "2018";
sha256 = "0iy749flficrlvgr3hjmf3igr738lk81n5akzf4ym4cs6cxg7pjd";
authors = [
"Sergio Benitez <sb@sergio.bz>"
"Alex Crichton <alex@alexcrichton.com>"
];
dependencies = [
{
name = "time";
packageId = "time";
usesDefaultFeatures = false;
features = [ "std" "parsing" "formatting" "macros" ];
}
];
buildDependencies = [
{
name = "version_check";
packageId = "version_check";
}
];
features = {
"aes-gcm" = [ "dep:aes-gcm" ];
"base64" = [ "dep:base64" ];
"hkdf" = [ "dep:hkdf" ];
"hmac" = [ "dep:hmac" ];
"key-expansion" = [ "sha2" "hkdf" ];
"percent-encode" = [ "percent-encoding" ];
"percent-encoding" = [ "dep:percent-encoding" ];
"private" = [ "aes-gcm" "base64" "rand" "subtle" ];
"rand" = [ "dep:rand" ];
"secure" = [ "private" "signed" "key-expansion" ];
"sha2" = [ "dep:sha2" ];
"signed" = [ "hmac" "sha2" "base64" "rand" "subtle" ];
"subtle" = [ "dep:subtle" ];
};
};
"core-foundation" = rec {
crateName = "core-foundation";
version = "0.9.4";
@ -4481,7 +4701,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body-util";
@ -4796,7 +5016,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "indexmap";
@ -4983,7 +5203,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "httpdate";
@ -5012,7 +5232,7 @@ rec {
dependencies = [
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
];
@ -5116,7 +5336,33 @@ rec {
];
};
"http" = rec {
"http 0.2.12" = rec {
crateName = "http";
version = "0.2.12";
edition = "2018";
sha256 = "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730";
authors = [
"Alex Crichton <alex@alexcrichton.com>"
"Carl Lerche <me@carllerche.com>"
"Sean McArthur <sean@seanmonstar.com>"
];
dependencies = [
{
name = "bytes";
packageId = "bytes";
}
{
name = "fnv";
packageId = "fnv";
}
{
name = "itoa";
packageId = "itoa";
}
];
};
"http 1.1.0" = rec {
crateName = "http";
version = "1.1.0";
edition = "2018";
@ -5163,7 +5409,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
];
@ -5191,7 +5437,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -5268,7 +5514,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -5354,7 +5600,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "hyper";
@ -5505,7 +5751,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -6731,6 +6977,35 @@ rec {
];
};
"mime_guess" = rec {
crateName = "mime_guess";
version = "2.0.5";
edition = "2015";
sha256 = "03jmg3yx6j39mg0kayf7w4a886dl3j15y8zs119zw01ccy74zi7p";
authors = [
"Austin Bonander <austin.bonander@gmail.com>"
];
dependencies = [
{
name = "mime";
packageId = "mime";
}
{
name = "unicase";
packageId = "unicase";
}
];
buildDependencies = [
{
name = "unicase";
packageId = "unicase";
}
];
features = {
"default" = [ "rev-mappings" ];
};
resolvedDefaultFeatures = [ "default" "rev-mappings" ];
};
"minimal-lexical" = rec {
crateName = "minimal-lexical";
version = "0.2.1";
@ -7072,6 +7347,10 @@ rec {
name = "tracing-subscriber";
packageId = "tracing-subscriber";
}
{
name = "tracing-test";
packageId = "tracing-test";
}
{
name = "tvix-castore";
packageId = "tvix-castore";
@ -7101,6 +7380,10 @@ rec {
}
];
devDependencies = [
{
name = "axum-test";
packageId = "axum-test";
}
{
name = "hex-literal";
packageId = "hex-literal";
@ -7109,6 +7392,10 @@ rec {
name = "rstest";
packageId = "rstest";
}
{
name = "sha2";
packageId = "sha2";
}
];
features = {
"default" = [ "otlp" ];
@ -8243,7 +8530,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
usesDefaultFeatures = false;
features = [ "std" ];
}
@ -8277,7 +8564,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
optional = true;
usesDefaultFeatures = false;
features = [ "std" ];
@ -10480,7 +10767,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -10771,7 +11058,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "reqwest";
@ -10837,7 +11124,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "matchit";
@ -10904,6 +11191,27 @@ rec {
};
resolvedDefaultFeatures = [ "opentelemetry_0_27" "opentelemetry_0_27_pkg" "tracing-opentelemetry_0_28_pkg" ];
};
"reserve-port" = rec {
crateName = "reserve-port";
version = "2.0.1";
edition = "2021";
sha256 = "10x21rdb1hjzp6n5flbbw3hfd7brmirckz1q0zsf3a7s5d516f4q";
libName = "reserve_port";
authors = [
"Joseph Lenton <josephlenton@gmail.com>"
];
dependencies = [
{
name = "lazy_static";
packageId = "lazy_static";
}
{
name = "thiserror";
packageId = "thiserror";
}
];
};
"ring" = rec {
crateName = "ring";
version = "0.17.8";
@ -11141,6 +11449,63 @@ rec {
}
];
};
"rust-multipart-rfc7578_2" = rec {
crateName = "rust-multipart-rfc7578_2";
version = "0.6.1";
edition = "2021";
sha256 = "0mwd3i2mk91n6diaxnkw28vyjbifhrm5ls73pcpfzz8a1i0lidq3";
libName = "rust_multipart_rfc7578_2";
authors = [
"Joseph Lenton <josephlenton@gmail.com>"
"Ferris Tseng <ferristseng@fastmail.fm>"
];
dependencies = [
{
name = "bytes";
packageId = "bytes";
}
{
name = "futures-core";
packageId = "futures-core";
}
{
name = "futures-util";
packageId = "futures-util";
usesDefaultFeatures = false;
features = [ "io" ];
}
{
name = "http";
packageId = "http 0.2.12";
}
{
name = "mime";
packageId = "mime";
}
{
name = "mime_guess";
packageId = "mime_guess";
}
{
name = "rand";
packageId = "rand";
features = [ "small_rng" ];
}
{
name = "thiserror";
packageId = "thiserror";
}
];
devDependencies = [
{
name = "futures-util";
packageId = "futures-util";
usesDefaultFeatures = false;
features = [ "std" ];
}
];
};
"rustc-demangle" = rec {
crateName = "rustc-demangle";
@ -13068,7 +13433,7 @@ rec {
"std" = [ "alloc" "deranged/std" ];
"wasm-bindgen" = [ "dep:js-sys" ];
};
resolvedDefaultFeatures = [ "alloc" "formatting" "parsing" "serde" "serde-well-known" "std" ];
resolvedDefaultFeatures = [ "alloc" "formatting" "macros" "parsing" "serde" "serde-well-known" "std" ];
};
"time-core" = rec {
crateName = "time-core";
@ -13956,7 +14321,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -14484,7 +14849,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
}
{
name = "http-body";
@ -14608,7 +14973,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
usesDefaultFeatures = false;
features = [ "std" ];
}
@ -15072,6 +15437,57 @@ rec {
};
resolvedDefaultFeatures = [ "alloc" "ansi" "default" "env-filter" "fmt" "matchers" "nu-ansi-term" "once_cell" "regex" "registry" "sharded-slab" "smallvec" "std" "thread_local" "tracing" "tracing-log" ];
};
"tracing-test" = rec {
crateName = "tracing-test";
version = "0.2.5";
edition = "2018";
sha256 = "0s0x076wpga7k1a3cl8da76rrgvs45zzq9rl6q75w3gy6qa8jysm";
libName = "tracing_test";
authors = [
"Danilo Bargen <mail@dbrgn.ch>"
];
dependencies = [
{
name = "tracing-core";
packageId = "tracing-core";
}
{
name = "tracing-subscriber";
packageId = "tracing-subscriber";
features = [ "env-filter" ];
}
{
name = "tracing-test-macro";
packageId = "tracing-test-macro";
}
];
features = {
"no-env-filter" = [ "tracing-test-macro/no-env-filter" ];
};
};
"tracing-test-macro" = rec {
crateName = "tracing-test-macro";
version = "0.2.5";
edition = "2018";
sha256 = "0s3m7a3pycn8r4xyql5gv5b85sdrqp4w24k1aqy26zf80vdrsr84";
procMacro = true;
libName = "tracing_test_macro";
authors = [
"Danilo Bargen <mail@dbrgn.ch>"
];
dependencies = [
{
name = "quote";
packageId = "quote";
}
{
name = "syn";
packageId = "syn 2.0.79";
features = [ "full" ];
}
];
features = { };
};
"tracing-tracy" = rec {
crateName = "tracing-tracy";
version = "0.11.3";
@ -16446,7 +16862,7 @@ rec {
}
{
name = "http";
packageId = "http";
packageId = "http 1.1.0";
optional = true;
}
{

View file

@ -149,6 +149,7 @@ tracing = "0.1.40"
tracing-indicatif = "0.3.6"
tracing-opentelemetry = "0.28.0"
tracing-subscriber = "0.3.18"
tracing-test = "0.2.5"
tracing-tracy = "0.11.2"
trybuild = "1.0.99"
url = "2.5.2"

View file

@ -28,6 +28,7 @@ tvix-store = { path = "../store" }
tvix-tracing = { path = "../tracing", features = ["tonic", "axum"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-test = { workspace = true }
url = { workspace = true }
serde = { workspace = true, features = ["derive"] }
lru = { workspace = true }
@ -44,8 +45,10 @@ otlp = ["tvix-tracing/otlp", "tower-otel-http-metrics"]
xp-store-composition-cli = ["tvix-store/xp-composition-cli"]
[dev-dependencies]
axum-test = "16.4.0"
hex-literal = { workspace = true }
rstest = { workspace = true }
sha2.workspace = true
[lints]
workspace = true

View file

@ -18,7 +18,7 @@ use crate::AppState;
#[derive(Debug, Deserialize)]
pub(crate) struct GetNARParams {
#[serde(rename = "narsize")]
nar_size: u64,
nar_size: Option<u64>,
}
#[instrument(skip_all)]
@ -34,6 +34,13 @@ pub async fn get_head(
}): axum::extract::State<AppState>,
) -> Result<impl axum::response::IntoResponse, StatusCode> {
use prost::Message;
// We insist on the nar_size field being set.
// If it's not present, the client is misbehaving somehow.
let nar_size = nar_size.ok_or_else(|| {
warn!("no nar_size parameter set");
StatusCode::BAD_REQUEST
})?;
// b64decode the root node passed *by the user*
let root_node_proto = BASE64URL_NOPAD
.decode(root_node_enc.as_bytes())
@ -57,7 +64,7 @@ pub async fn get_head(
let root_node = root_node.try_into_anonymous_node().map_err(|e| {
warn!(err=%e, "root node validation failed");
StatusCode::BAD_REQUEST
StatusCode::NOT_FOUND
})?;
Ok((
@ -116,7 +123,7 @@ pub async fn get_head(
}
/// Handler to respond to GET/HEAD requests for recently uploaded NAR files.
/// Nix probes at {narhash}.nar[.compression_suffix] to determine whether a NAR
/// Nix probes at {filehash}.nar[.compression_suffix] to determine whether a NAR
/// has already been uploaded, by responding to (some of) these requests we
/// avoid it unnecessarily uploading.
/// We don't keep a full K/V from NAR hash to root note around, only the
@ -205,3 +212,264 @@ pub async fn put(
Ok("")
}
#[cfg(test)]
mod tests {
use std::{
num::NonZero,
sync::{Arc, LazyLock},
};
use axum::{http::Method, Router};
use bytes::Bytes;
use data_encoding::BASE64URL_NOPAD;
use nix_compat::nixbase32;
use sha2::Digest;
use tracing_test::traced_test;
use tvix_castore::{
blobservice::{BlobService, MemoryBlobService},
directoryservice::{DirectoryService, MemoryDirectoryService},
fixtures::HELLOWORLD_BLOB_DIGEST,
};
use tvix_store::{
fixtures::{
CASTORE_NODE_COMPLICATED, CASTORE_NODE_SYMLINK, NAR_CONTENTS_COMPLICATED,
NAR_CONTENTS_HELLOWORLD, NAR_CONTENTS_SYMLINK,
},
pathinfoservice::{MemoryPathInfoService, PathInfoService},
};
use crate::AppState;
pub static NAR_STR_SYMLINK: LazyLock<String> = LazyLock::new(|| {
use prost::Message;
BASE64URL_NOPAD.encode(
&tvix_castore::proto::Node::from_name_and_node("".into(), CASTORE_NODE_SYMLINK.clone())
.encode_to_vec(),
)
});
/// Accepts a router without state, and returns a [axum_test::TestServer].
fn gen_server(
router: axum::Router<AppState>,
) -> (
axum_test::TestServer,
impl BlobService,
impl DirectoryService,
impl PathInfoService,
) {
let blob_service = Arc::new(MemoryBlobService::default());
let directory_service = Arc::new(MemoryDirectoryService::default());
let path_info_service = Arc::new(MemoryPathInfoService::default());
let app = router.with_state(AppState::new(
blob_service.clone(),
directory_service.clone(),
path_info_service.clone(),
NonZero::new(100).unwrap(),
));
(
axum_test::TestServer::new(app).unwrap(),
blob_service,
directory_service,
path_info_service,
)
}
#[traced_test]
#[tokio::test]
async fn test_get_head() {
let (server, _blob_service, _directory_service, _path_info_service) =
gen_server(Router::new().route(
"/nar/tvix-castore/:root_node_enc",
axum::routing::get(super::get_head),
));
// Empty nar_str should be NotFound
server
.method(Method::HEAD, "/nar/tvix-castore/")
.expect_failure()
.await
.assert_status_not_found();
let valid_url = &format!("/nar/tvix-castore/{}", &*NAR_STR_SYMLINK);
let qps = &[("narsize", &NAR_CONTENTS_SYMLINK.len().to_string())];
// Missing narsize should be BadRequest
server
.method(Method::HEAD, valid_url)
.expect_failure()
.await
.assert_status_bad_request();
let invalid_url = {
use prost::Message;
let n = tvix_castore::proto::Node {
node: Some(tvix_castore::proto::node::Node::Directory(
tvix_castore::proto::DirectoryNode {
name: "".into(),
digest: "invalid b64".into(),
size: 1,
},
)),
};
&format!(
"/nar/tvix-castore/{}",
BASE64URL_NOPAD.encode(&n.encode_to_vec())
)
};
// Invalid node proto should return NotFound
server
.method(Method::HEAD, invalid_url)
.add_query_params(qps)
.expect_failure()
.await
.assert_status_not_found();
// success, HEAD
server
.method(Method::HEAD, valid_url)
.add_query_params(qps)
.expect_success()
.await;
// success, GET
assert_eq!(
NAR_CONTENTS_SYMLINK.as_slice(),
server
.get(valid_url)
.add_query_params(qps)
.expect_success()
.await
.into_bytes(),
"Expected to get back NAR_CONTENTS_SYMLINK"
)
}
/// Uploading a NAR with a different file hash than what's specified in the URL
/// is considered an error.
#[traced_test]
#[tokio::test]
async fn test_put_wrong_narhash() {
let (server, _blob_service, _directory_service, _path_info_service) =
gen_server(Router::new().route("/nar/:nar_str", axum::routing::put(super::put)));
server
.put("/nar/0000000000000000000000000000000000000000000000000000.nar")
.bytes(Bytes::from_static(&NAR_CONTENTS_SYMLINK))
.expect_failure()
.await;
}
/// Uploading a NAR with compression is not supported.
#[traced_test]
#[tokio::test]
async fn test_put_with_compression_fail() {
let (server, _blob_service, _directory_service, _path_info_service) =
gen_server(Router::new().route("/nar/:nar_str", axum::routing::put(super::put)));
let nar_sha256: [u8; 32] = sha2::Sha256::new_with_prefix(NAR_CONTENTS_SYMLINK.as_slice())
.finalize()
.into();
let nar_url = format!("/nar/{}.nar.zst", nixbase32::encode(&nar_sha256));
server
.put(&nar_url)
.bytes(Bytes::from_static(&NAR_CONTENTS_SYMLINK))
.expect_failure()
.await
.assert_status_unauthorized();
}
/// Upload a NAR with a single file, ensure the blob exists later on.
#[traced_test]
#[tokio::test]
async fn test_put_success() {
let (server, blob_service, _directory_service, _path_info_service) =
gen_server(Router::new().route("/nar/:nar_str", axum::routing::put(super::put)));
let nar_sha256: [u8; 32] =
sha2::Sha256::new_with_prefix(NAR_CONTENTS_HELLOWORLD.as_slice())
.finalize()
.into();
let nar_url = format!("/nar/{}.nar", nixbase32::encode(&nar_sha256));
server
.put(&nar_url)
.bytes(Bytes::from_static(&NAR_CONTENTS_HELLOWORLD))
.expect_success()
.await;
assert!(blob_service
.has(&HELLOWORLD_BLOB_DIGEST)
.await
.expect("blobservice"))
}
// Upload a NAR with blobs and directories, ensure blobs and directories
// were uploaded, by rendering the NAR stream from the root node we know
// describes these contents.
#[traced_test]
#[tokio::test]
async fn test_put_success2() {
let (server, blob_service, directory_service, _path_info_service) =
gen_server(Router::new().route("/nar/:nar_str", axum::routing::put(super::put)));
let nar_sha256: [u8; 32] =
sha2::Sha256::new_with_prefix(NAR_CONTENTS_COMPLICATED.as_slice())
.finalize()
.into();
let nar_url = format!("/nar/{}.nar", nixbase32::encode(&nar_sha256));
server
.put(&nar_url)
.bytes(Bytes::from_static(&NAR_CONTENTS_COMPLICATED))
.expect_success()
.await;
let mut buf = Vec::new();
tvix_store::nar::write_nar(
&mut buf,
&CASTORE_NODE_COMPLICATED,
blob_service,
directory_service,
)
.await
.expect("write nar");
assert_eq!(NAR_CONTENTS_COMPLICATED, buf[..]);
}
/// Upload a NAR, ensure a HEAD by NarHash returns a 2xx code.
#[traced_test]
#[tokio::test]
async fn test_put_root_nodes() {
let (server, _blob_service, _directory_servicee, _path_info_service) = gen_server(
Router::new()
.route("/nar/:nar_str", axum::routing::put(super::put))
.route("/nar/:nar_str", axum::routing::get(super::head_root_nodes)),
);
let nar_sha256: [u8; 32] =
sha2::Sha256::new_with_prefix(NAR_CONTENTS_COMPLICATED.as_slice())
.finalize()
.into();
let nar_url = format!("/nar/{}.nar", nixbase32::encode(&nar_sha256));
// upload NAR
server
.put(&nar_url)
.bytes(Bytes::from_static(&NAR_CONTENTS_COMPLICATED))
.expect_success()
.await;
// check HEAD by NarHash
server.method(Method::HEAD, &nar_url).expect_success().await;
}
}