diff --git a/Cargo.lock b/Cargo.lock
index c213cd4d0a..867008808b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -221,9 +221,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcdcf0d683fe9c23d32cf5b53c9918ea0a500375a9fb20109802552658e576c9"
+checksum = "de3d533e0263bf453cc80af4c8bcc4d64e2aca293bd16f81633a36f1bf4a97cb"
dependencies = [
"aws-credential-types",
"aws-http",
@@ -236,7 +236,7 @@ dependencies = [
"aws-smithy-types",
"aws-types",
"bytes",
- "fastrand",
+ "fastrand 2.0.0",
"http",
"hyper",
"time",
@@ -247,37 +247,23 @@ dependencies = [
[[package]]
name = "aws-credential-types"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fcdb2f7acbc076ff5ad05e7864bdb191ca70a6fd07668dc3a1a8bcd051de5ae"
+checksum = "e4834ba01c5ad1ed9740aa222de62190e3c565d11ab7e72cc68314a258994567"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
- "fastrand",
+ "fastrand 2.0.0",
"tokio",
"tracing",
"zeroize",
]
-[[package]]
-name = "aws-endpoint"
-version = "0.55.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cce1c41a6cfaa726adee9ebb9a56fcd2bbfd8be49fd8a04c5e20fd968330b04"
-dependencies = [
- "aws-smithy-http",
- "aws-smithy-types",
- "aws-types",
- "http",
- "regex",
- "tracing",
-]
-
[[package]]
name = "aws-http"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aadbc44e7a8f3e71c8b374e03ecd972869eb91dd2bc89ed018954a52ba84bc44"
+checksum = "72badf9de83cc7d66b21b004f09241836823b8302afb25a24708769e576a8d8f"
dependencies = [
"aws-credential-types",
"aws-smithy-http",
@@ -293,23 +279,45 @@ dependencies = [
]
[[package]]
-name = "aws-sdk-s3"
-version = "0.27.0"
+name = "aws-runtime"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37c77060408d653d3efa6ea7b66c1389bc35a0342352984c8bf8bcb814a8fc27"
+checksum = "cf832f522111225c02547e1e1c28137e840e4b082399d93a236e4b29193a4667"
dependencies = [
"aws-credential-types",
- "aws-endpoint",
"aws-http",
- "aws-sig-auth",
+ "aws-sigv4",
+ "aws-smithy-async",
+ "aws-smithy-eventstream",
+ "aws-smithy-http",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "aws-types",
+ "fastrand 2.0.0",
+ "http",
+ "percent-encoding",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "aws-sdk-s3"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e30370b61599168d38190ad272bb91842cd81870a6ca035c05dd5726d22832c"
+dependencies = [
+ "aws-credential-types",
+ "aws-http",
+ "aws-runtime",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-checksums",
"aws-smithy-client",
"aws-smithy-eventstream",
"aws-smithy-http",
- "aws-smithy-http-tower",
"aws-smithy-json",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
@@ -320,57 +328,39 @@ dependencies = [
"percent-encoding",
"regex",
"tokio-stream",
- "tower",
"tracing",
"url",
]
[[package]]
name = "aws-sdk-sts"
-version = "0.28.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "265fac131fbfc188e5c3d96652ea90ecc676a934e3174eaaee523c6cec040b3b"
+checksum = "79e21aa1a5b0853969a1ef96ccfaa8ff5d57c761549786a4d5f86c1902b2586a"
dependencies = [
"aws-credential-types",
- "aws-endpoint",
"aws-http",
- "aws-sig-auth",
+ "aws-runtime",
"aws-smithy-async",
"aws-smithy-client",
"aws-smithy-http",
- "aws-smithy-http-tower",
"aws-smithy-json",
"aws-smithy-query",
+ "aws-smithy-runtime",
+ "aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
- "bytes",
"http",
"regex",
- "tower",
- "tracing",
-]
-
-[[package]]
-name = "aws-sig-auth"
-version = "0.55.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b94acb10af0c879ecd5c7bdf51cda6679a0a4f4643ce630905a77673bfa3c61"
-dependencies = [
- "aws-credential-types",
- "aws-sigv4",
- "aws-smithy-eventstream",
- "aws-smithy-http",
- "aws-types",
- "http",
"tracing",
]
[[package]]
name = "aws-sigv4"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c"
+checksum = "2cb40a93429794065f41f0581734fc56a345f6a38d8e2e3c25c7448d930cd132"
dependencies = [
"aws-smithy-eventstream",
"aws-smithy-http",
@@ -389,9 +379,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13bda3996044c202d75b91afeb11a9afae9db9a721c6a7a427410018e286b880"
+checksum = "6ee6d17d487c8b579423067718b3580c0908d0f01d7461813f94ec4323bad623"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -401,9 +391,9 @@ dependencies = [
[[package]]
name = "aws-smithy-checksums"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07ed8b96d95402f3f6b8b57eb4e0e45ee365f78b1a924faf20ff6e97abf1eae6"
+checksum = "0d1849fd5916904513fb0862543b36f8faab43c07984dbc476132b7da1aed056"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -422,23 +412,23 @@ dependencies = [
[[package]]
name = "aws-smithy-client"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a86aa6e21e86c4252ad6a0e3e74da9617295d8d6e374d552be7d3059c41cedd"
+checksum = "bdbe0a3ad15283cc5f863a68cb6adc8e256e7c109c43c01bdd09be407219a1e9"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-http-tower",
"aws-smithy-types",
"bytes",
- "fastrand",
+ "fastrand 2.0.0",
"http",
"http-body",
"hyper",
- "hyper-rustls 0.23.2",
+ "hyper-rustls",
"lazy_static",
"pin-project-lite",
- "rustls 0.20.8",
+ "rustls",
"tokio",
"tower",
"tracing",
@@ -446,9 +436,9 @@ dependencies = [
[[package]]
name = "aws-smithy-eventstream"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460c8da5110835e3d9a717c61f5556b20d03c32a1dec57f8fc559b360f733bb8"
+checksum = "a56afef1aa766f512b4970b4c3150b9bf2df8035939723830df4b30267e2d7cb"
dependencies = [
"aws-smithy-types",
"bytes",
@@ -457,9 +447,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28"
+checksum = "34dc313472d727f5ef44fdda93e668ebfe17380c99dee512c403e3ca51863bb9"
dependencies = [
"aws-smithy-eventstream",
"aws-smithy-types",
@@ -480,9 +470,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http-tower"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae4f6c5798a247fac98a867698197d9ac22643596dc3777f0c76b91917616b9"
+checksum = "1dd50fca5a4ea4ec3771689ee93bf06b32de02a80af01ed93a8f8a4ed90e8483"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -496,50 +486,88 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23f9f42fbfa96d095194a632fbac19f60077748eba536eb0b9fecc28659807f8"
+checksum = "3591dd7c2fe01ab8025e4847a0a0f6d0c2b2269714688ffb856f9cf6c6d465cf"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-query"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98819eb0b04020a1c791903533b638534ae6c12e2aceda3e6e6fba015608d51d"
+checksum = "dbabb1145e65dd57ae72d91a2619d3f5fba40b68a5f40ba009c30571dfd60aff"
dependencies = [
"aws-smithy-types",
"urlencoding",
]
[[package]]
-name = "aws-smithy-types"
-version = "0.55.3"
+name = "aws-smithy-runtime"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8"
+checksum = "3687fb838d4ad1c883b62eb59115bc9fb02c4f308aac49a7df89627067f6eb0d"
+dependencies = [
+ "aws-smithy-async",
+ "aws-smithy-client",
+ "aws-smithy-http",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "bytes",
+ "fastrand 2.0.0",
+ "http",
+ "http-body",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "aws-smithy-runtime-api"
+version = "0.56.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cfbf1e5c2108b41f5ca607cde40dd5109fecc448f5d30c8e614b61f36dce704"
+dependencies = [
+ "aws-smithy-async",
+ "aws-smithy-http",
+ "aws-smithy-types",
+ "bytes",
+ "http",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "aws-smithy-types"
+version = "0.56.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eed0a94eefd845a2a78677f1b72f02fa75802d38f7f59be675add140279aa8bf"
dependencies = [
"base64-simd",
"itoa",
"num-integer",
"ryu",
+ "serde",
"time",
]
[[package]]
name = "aws-smithy-xml"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1b9d12875731bd07e767be7baad95700c3137b56730ec9ddeedb52a5e5ca63b"
+checksum = "c88052c812f696143ad7ba729c63535209ff0e0f49e31a6d2b1205208ea6ea79"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
-version = "0.55.3"
+version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dd209616cc8d7bfb82f87811a5c655dc97537f592689b18743bddf5dc5c4829"
+checksum = "6bceb8cf724ad057ad7f327d0d256d7147b3eac777b39849a26189e003dc9782"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -1402,6 +1430,12 @@ dependencies = [
"instant",
]
+[[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
[[package]]
name = "filetime"
version = "0.2.21"
@@ -1837,21 +1871,6 @@ dependencies = [
"want",
]
-[[package]]
-name = "hyper-rustls"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
-dependencies = [
- "http",
- "hyper",
- "log",
- "rustls 0.20.8",
- "rustls-native-certs",
- "tokio",
- "tokio-rustls 0.23.4",
-]
-
[[package]]
name = "hyper-rustls"
version = "0.24.0"
@@ -1860,9 +1879,11 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
dependencies = [
"http",
"hyper",
- "rustls 0.21.1",
+ "log",
+ "rustls",
+ "rustls-native-certs",
"tokio",
- "tokio-rustls 0.24.0",
+ "tokio-rustls",
]
[[package]]
@@ -2054,7 +2075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378"
dependencies = [
"base64 0.21.1",
- "pem",
+ "pem 1.1.1",
"ring",
"serde",
"serde_json",
@@ -2780,6 +2801,16 @@ dependencies = [
"base64 0.13.1",
]
+[[package]]
+name = "pem"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a"
+dependencies = [
+ "base64 0.21.1",
+ "serde",
+]
+
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -2942,14 +2973,14 @@ dependencies = [
"futures",
"once_cell",
"pq_proto",
- "rustls 0.20.8",
+ "rustls",
"rustls-pemfile",
"serde",
"thiserror",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
"tracing",
"workspace_hack",
]
@@ -3174,7 +3205,7 @@ dependencies = [
"reqwest-tracing",
"routerify",
"rstest",
- "rustls 0.20.8",
+ "rustls",
"rustls-pemfile",
"scopeguard",
"serde",
@@ -3187,7 +3218,7 @@ dependencies = [
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
"tokio-util",
"tracing",
"tracing-opentelemetry",
@@ -3196,7 +3227,7 @@ dependencies = [
"url",
"utils",
"uuid",
- "webpki-roots 0.23.0",
+ "webpki-roots 0.25.2",
"workspace_hack",
"x509-parser",
]
@@ -3264,11 +3295,11 @@ dependencies = [
[[package]]
name = "rcgen"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b"
+checksum = "4954fbc00dcd4d8282c987710e50ba513d351400dbdd00e803a05172a90d8976"
dependencies = [
- "pem",
+ "pem 2.0.1",
"ring",
"time",
"yasna",
@@ -3324,6 +3355,12 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+[[package]]
+name = "relative-path"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca"
+
[[package]]
name = "remote_storage"
version = "0.1.0"
@@ -3354,9 +3391,9 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.11.18"
+version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+checksum = "20b9b67e2ca7dd9e9f9285b759de30ff538aab981abaaf7bc9bd90b84a0126c3"
dependencies = [
"base64 0.21.1",
"bytes",
@@ -3367,7 +3404,7 @@ dependencies = [
"http",
"http-body",
"hyper",
- "hyper-rustls 0.24.0",
+ "hyper-rustls",
"ipnet",
"js-sys",
"log",
@@ -3376,19 +3413,19 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
- "rustls 0.21.1",
+ "rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
- "tokio-rustls 0.24.0",
+ "tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "webpki-roots 0.22.6",
+ "webpki-roots 0.25.2",
"winreg",
]
@@ -3498,9 +3535,9 @@ dependencies = [
[[package]]
name = "rstest"
-version = "0.17.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
+checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199"
dependencies = [
"futures",
"futures-timer",
@@ -3510,15 +3547,18 @@ dependencies = [
[[package]]
name = "rstest_macros"
-version = "0.17.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
+checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605"
dependencies = [
"cfg-if",
+ "glob",
"proc-macro2",
"quote",
+ "regex",
+ "relative-path",
"rustc_version",
- "syn 1.0.109",
+ "syn 2.0.28",
"unicode-ident",
]
@@ -3582,25 +3622,13 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.20.8"
+version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
dependencies = [
"log",
"ring",
- "sct",
- "webpki",
-]
-
-[[package]]
-name = "rustls"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
-dependencies = [
- "log",
- "ring",
- "rustls-webpki",
+ "rustls-webpki 0.101.4",
"sct",
]
@@ -3635,6 +3663,16 @@ dependencies = [
"untrusted",
]
+[[package]]
+name = "rustls-webpki"
+version = "0.101.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
[[package]]
name = "rustversion"
version = "1.0.12"
@@ -3772,27 +3810,28 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "sentry"
-version = "0.30.0"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5ce6d3512e2617c209ec1e86b0ca2fea06454cd34653c91092bf0f3ec41f8e3"
+checksum = "2e95efd0cefa32028cdb9766c96de71d96671072f9fb494dc9fb84c0ef93e52b"
dependencies = [
"httpdate",
"reqwest",
- "rustls 0.20.8",
+ "rustls",
"sentry-backtrace",
"sentry-contexts",
"sentry-core",
"sentry-panic",
+ "sentry-tracing",
"tokio",
"ureq",
- "webpki-roots 0.22.6",
+ "webpki-roots 0.25.2",
]
[[package]]
name = "sentry-backtrace"
-version = "0.30.0"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7fe408d4d1f8de188a9309916e02e129cbe51ca19e55badea5a64899399b1a"
+checksum = "6ac2bac6f310c4c4c4bb094d1541d32ae497f8c5c23405e85492cefdfe0971a9"
dependencies = [
"backtrace",
"once_cell",
@@ -3802,9 +3841,9 @@ dependencies = [
[[package]]
name = "sentry-contexts"
-version = "0.30.0"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5695096a059a89973ec541062d331ff4c9aeef9c2951416c894f0fff76340e7d"
+checksum = "6c3e17295cecdbacf66c5bd38d6e1147e09e1e9d824d2d5341f76638eda02a3a"
dependencies = [
"hostname",
"libc",
@@ -3816,9 +3855,9 @@ dependencies = [
[[package]]
name = "sentry-core"
-version = "0.30.0"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b22828bfd118a7b660cf7a155002a494755c0424cebb7061e4743ecde9c7dbc"
+checksum = "8339474f587f36cb110fa1ed1b64229eea6d47b0b886375579297b7e47aeb055"
dependencies = [
"once_cell",
"rand",
@@ -3829,19 +3868,31 @@ dependencies = [
[[package]]
name = "sentry-panic"
-version = "0.30.0"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4ced2a7a8c14899d58eec402d946f69d5ed26a3fc363a7e8b1e5cb88473a01"
+checksum = "875b69f506da75bd664029eafb05f8934297d2990192896d17325f066bd665b7"
dependencies = [
"sentry-backtrace",
"sentry-core",
]
[[package]]
-name = "sentry-types"
-version = "0.30.0"
+name = "sentry-tracing"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "360ee3270f7a4a1eee6c667f7d38360b995431598a73b740dfe420da548d9cc9"
+checksum = "89feead9bdd116f8035e89567651340fc382db29240b6c55ef412078b08d1aa3"
+dependencies = [
+ "sentry-backtrace",
+ "sentry-core",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "sentry-types"
+version = "0.31.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99dc599bd6646884fc403d593cdcb9816dd67c50cff3271c01ff123617908dcd"
dependencies = [
"debugid",
"getrandom",
@@ -4248,7 +4299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [
"cfg-if",
- "fastrand",
+ "fastrand 1.9.0",
"redox_syscall 0.3.5",
"rustix 0.37.19",
"windows-sys 0.45.0",
@@ -4378,16 +4429,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tls-listener"
-version = "0.6.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97abcaa5d5850d3b469898d1e0939b57c3afb4475122e792cdd1c82b07f5de06"
+checksum = "81294c017957a1a69794f506723519255879e15a870507faf45dfed288b763dd"
dependencies = [
"futures-util",
"hyper",
"pin-project-lite",
"thiserror",
"tokio",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
]
[[package]]
@@ -4464,27 +4515,16 @@ dependencies = [
[[package]]
name = "tokio-postgres-rustls"
-version = "0.9.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "606f2b73660439474394432239c82249c0d45eb5f23d91f401be1e33590444a7"
+checksum = "dd5831152cb0d3f79ef5523b357319ba154795d64c7078b2daa95a803b54057f"
dependencies = [
"futures",
"ring",
- "rustls 0.20.8",
+ "rustls",
"tokio",
"tokio-postgres",
- "tokio-rustls 0.23.4",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.23.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
-dependencies = [
- "rustls 0.20.8",
- "tokio",
- "webpki",
+ "tokio-rustls",
]
[[package]]
@@ -4493,7 +4533,7 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
dependencies = [
- "rustls 0.21.1",
+ "rustls",
"tokio",
]
@@ -4651,7 +4691,7 @@ dependencies = [
"rustls-native-certs",
"rustls-pemfile",
"tokio",
- "tokio-rustls 0.24.0",
+ "tokio-rustls",
"tokio-stream",
"tower",
"tower-layer",
@@ -4949,17 +4989,17 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
-version = "2.6.2"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
+checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
dependencies = [
- "base64 0.13.1",
+ "base64 0.21.1",
"log",
"once_cell",
- "rustls 0.20.8",
+ "rustls",
+ "rustls-webpki 0.100.2",
"url",
- "webpki",
- "webpki-roots 0.22.6",
+ "webpki-roots 0.23.1",
]
[[package]]
@@ -5230,32 +5270,19 @@ dependencies = [
]
[[package]]
-name = "webpki"
-version = "0.22.0"
+name = "webpki-roots"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
dependencies = [
- "ring",
- "untrusted",
+ "rustls-webpki 0.100.2",
]
[[package]]
name = "webpki-roots"
-version = "0.22.6"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
-dependencies = [
- "webpki",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125"
-dependencies = [
- "rustls-webpki",
-]
+checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "which"
@@ -5466,11 +5493,12 @@ dependencies = [
[[package]]
name = "winreg"
-version = "0.10.1"
+version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
- "winapi",
+ "cfg-if",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -5479,6 +5507,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
+ "base64 0.21.1",
"bytes",
"cc",
"chrono",
@@ -5509,7 +5538,7 @@ dependencies = [
"regex-syntax 0.7.2",
"reqwest",
"ring",
- "rustls 0.20.8",
+ "rustls",
"scopeguard",
"serde",
"serde_json",
@@ -5518,7 +5547,7 @@ dependencies = [
"syn 1.0.109",
"syn 2.0.28",
"tokio",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
"tokio-util",
"toml_datetime",
"toml_edit",
diff --git a/Cargo.toml b/Cargo.toml
index f38e67f487..d545be266f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,11 +37,11 @@ async-compression = { version = "0.4.0", features = ["tokio", "gzip"] }
flate2 = "1.0.26"
async-stream = "0.3"
async-trait = "0.1"
-aws-config = { version = "0.55", default-features = false, features=["rustls"] }
-aws-sdk-s3 = "0.27"
-aws-smithy-http = "0.55"
-aws-credential-types = "0.55"
-aws-types = "0.55"
+aws-config = { version = "0.56", default-features = false, features=["rustls"] }
+aws-sdk-s3 = "0.29"
+aws-smithy-http = "0.56"
+aws-credential-types = "0.56"
+aws-types = "0.56"
axum = { version = "0.6.20", features = ["ws"] }
base64 = "0.13.0"
bincode = "1.3"
@@ -105,12 +105,12 @@ reqwest-middleware = "0.2.0"
reqwest-retry = "0.2.2"
routerify = "3"
rpds = "0.13"
-rustls = "0.20"
+rustls = "0.21"
rustls-pemfile = "1"
rustls-split = "0.3"
scopeguard = "1.1"
sysinfo = "0.29.2"
-sentry = { version = "0.30", default-features = false, features = ["backtrace", "contexts", "panic", "rustls", "reqwest" ] }
+sentry = { version = "0.31", default-features = false, features = ["backtrace", "contexts", "panic", "rustls", "reqwest" ] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
serde_with = "2.0"
@@ -125,11 +125,11 @@ sync_wrapper = "0.1.2"
tar = "0.4"
test-context = "0.1"
thiserror = "1.0"
-tls-listener = { version = "0.6", features = ["rustls", "hyper-h1"] }
+tls-listener = { version = "0.7", features = ["rustls", "hyper-h1"] }
tokio = { version = "1.17", features = ["macros"] }
tokio-io-timeout = "1.2.0"
-tokio-postgres-rustls = "0.9.0"
-tokio-rustls = "0.23"
+tokio-postgres-rustls = "0.10.0"
+tokio-rustls = "0.24"
tokio-stream = "0.1"
tokio-tar = "0.3"
tokio-util = { version = "0.7", features = ["io"] }
@@ -143,7 +143,7 @@ tracing-subscriber = { version = "0.3", default_features = false, features = ["s
url = "2.2"
uuid = { version = "1.2", features = ["v4", "serde"] }
walkdir = "2.3.2"
-webpki-roots = "0.23"
+webpki-roots = "0.25"
x509-parser = "0.15"
## TODO replace this with tracing
@@ -182,8 +182,8 @@ workspace_hack = { version = "0.1", path = "./workspace_hack/" }
## Build dependencies
criterion = "0.5.1"
-rcgen = "0.10"
-rstest = "0.17"
+rcgen = "0.11"
+rstest = "0.18"
tempfile = "3.4"
tonic-build = "0.9"
diff --git a/compute_tools/src/checker.rs b/compute_tools/src/checker.rs
index b6a287bdeb..6f52004fa8 100644
--- a/compute_tools/src/checker.rs
+++ b/compute_tools/src/checker.rs
@@ -1,12 +1,39 @@
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Ok, Result};
+use postgres::Client;
use tokio_postgres::NoTls;
use tracing::{error, instrument};
use crate::compute::ComputeNode;
+/// Create a special service table for availability checks
+/// only if it does not exist already.
+pub fn create_availability_check_data(client: &mut Client) -> Result<()> {
+ let query = "
+ DO $$
+ BEGIN
+ IF NOT EXISTS(
+ SELECT 1
+ FROM pg_catalog.pg_tables
+ WHERE tablename = 'health_check'
+ )
+ THEN
+ CREATE TABLE health_check (
+ id serial primary key,
+ updated_at timestamptz default now()
+ );
+ INSERT INTO health_check VALUES (1, now())
+ ON CONFLICT (id) DO UPDATE
+ SET updated_at = now();
+ END IF;
+ END
+ $$;";
+ client.execute(query, &[])?;
+
+ Ok(())
+}
+
/// Update timestamp in a row in a special service table to check
/// that we can actually write some data in this particular timeline.
-/// Create table if it's missing.
#[instrument(skip_all)]
pub async fn check_writability(compute: &ComputeNode) -> Result<()> {
// Connect to the database.
@@ -24,19 +51,15 @@ pub async fn check_writability(compute: &ComputeNode) -> Result<()> {
});
let query = "
- CREATE TABLE IF NOT EXISTS health_check (
- id serial primary key,
- updated_at timestamptz default now()
- );
INSERT INTO health_check VALUES (1, now())
ON CONFLICT (id) DO UPDATE
SET updated_at = now();";
let result = client.simple_query(query).await?;
- if result.len() != 2 {
+ if result.len() != 1 {
return Err(anyhow::format_err!(
- "expected 2 query results, but got {}",
+ "expected 1 query result, but got {}",
result.len()
));
}
diff --git a/compute_tools/src/compute.rs b/compute_tools/src/compute.rs
index 595ecb453e..5c08ebe06a 100644
--- a/compute_tools/src/compute.rs
+++ b/compute_tools/src/compute.rs
@@ -27,6 +27,7 @@ use utils::measured_stream::MeasuredReader;
use remote_storage::{DownloadError, GenericRemoteStorage, RemotePath};
+use crate::checker::create_availability_check_data;
use crate::pg_helpers::*;
use crate::spec::*;
use crate::sync_sk::{check_if_synced, ping_safekeeper};
@@ -696,6 +697,7 @@ impl ComputeNode {
handle_role_deletions(spec, self.connstr.as_str(), &mut client)?;
handle_grants(spec, self.connstr.as_str())?;
handle_extensions(spec, &mut client)?;
+ create_availability_check_data(&mut client)?;
// 'Close' connection
drop(client);
@@ -1078,7 +1080,8 @@ LIMIT 100",
let mut download_tasks = Vec::new();
for library in &libs_vec {
- let (ext_name, ext_path) = remote_extensions.get_ext(library, true)?;
+ let (ext_name, ext_path) =
+ remote_extensions.get_ext(library, true, &self.build_tag, &self.pgversion)?;
download_tasks.push(self.download_extension(ext_name, ext_path));
}
let results = join_all(download_tasks).await;
diff --git a/compute_tools/src/extension_server.rs b/compute_tools/src/extension_server.rs
index cb54a603e0..54c22026e7 100644
--- a/compute_tools/src/extension_server.rs
+++ b/compute_tools/src/extension_server.rs
@@ -180,7 +180,19 @@ pub async fn download_extension(
// Create extension control files from spec
pub fn create_control_files(remote_extensions: &RemoteExtSpec, pgbin: &str) {
let local_sharedir = Path::new(&get_pg_config("--sharedir", pgbin)).join("extension");
- for ext_data in remote_extensions.extension_data.values() {
+ for (ext_name, ext_data) in remote_extensions.extension_data.iter() {
+ // Check if extension is present in public or custom.
+ // If not, then it is not allowed to be used by this compute.
+ if let Some(public_extensions) = &remote_extensions.public_extensions {
+ if !public_extensions.contains(ext_name) {
+ if let Some(custom_extensions) = &remote_extensions.custom_extensions {
+ if !custom_extensions.contains(ext_name) {
+ continue; // skip this extension, it is not allowed
+ }
+ }
+ }
+ }
+
for (control_name, control_content) in &ext_data.control_data {
let control_path = local_sharedir.join(control_name);
if !control_path.exists() {
diff --git a/compute_tools/src/http/api.rs b/compute_tools/src/http/api.rs
index 841e533a3a..a571628770 100644
--- a/compute_tools/src/http/api.rs
+++ b/compute_tools/src/http/api.rs
@@ -169,7 +169,12 @@ async fn routes(req: Request
, compute: &Arc) -> Response anyhow::Result<(String, RemotePath)> {
let mut real_ext_name = ext_name;
if is_library {
@@ -104,11 +106,32 @@ impl RemoteExtSpec {
.ok_or(anyhow::anyhow!("library {} is not found", lib_raw_name))?;
}
+ // Check if extension is present in public or custom.
+ // If not, then it is not allowed to be used by this compute.
+ if let Some(public_extensions) = &self.public_extensions {
+ if !public_extensions.contains(&real_ext_name.to_string()) {
+ if let Some(custom_extensions) = &self.custom_extensions {
+ if !custom_extensions.contains(&real_ext_name.to_string()) {
+ return Err(anyhow::anyhow!("extension {} is not found", real_ext_name));
+ }
+ }
+ }
+ }
+
match self.extension_data.get(real_ext_name) {
- Some(ext_data) => Ok((
- real_ext_name.to_string(),
- RemotePath::from_string(&ext_data.archive_path)?,
- )),
+ Some(_ext_data) => {
+ // Construct the path to the extension archive
+ // BUILD_TAG/PG_MAJOR_VERSION/extensions/EXTENSION_NAME.tar.zst
+ //
+ // Keep it in sync with path generation in
+ // https://github.com/neondatabase/build-custom-extensions/tree/main
+ let archive_path_str =
+ format!("{build_tag}/{pg_major_version}/extensions/{real_ext_name}.tar.zst");
+ Ok((
+ real_ext_name.to_string(),
+ RemotePath::from_string(&archive_path_str)?,
+ ))
+ }
None => Err(anyhow::anyhow!(
"real_ext_name {} is not found",
real_ext_name
diff --git a/libs/utils/src/generation.rs b/libs/utils/src/generation.rs
new file mode 100644
index 0000000000..87c6361255
--- /dev/null
+++ b/libs/utils/src/generation.rs
@@ -0,0 +1,113 @@
+use std::fmt::Debug;
+
+use serde::{Deserialize, Serialize};
+
+/// Tenant generations are used to provide split-brain safety and allow
+/// multiple pageservers to attach the same tenant concurrently.
+///
+/// See docs/rfcs/025-generation-numbers.md for detail on how generation
+/// numbers are used.
+#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
+pub enum Generation {
+ // Generations with this magic value will not add a suffix to S3 keys, and will not
+ // be included in persisted index_part.json. This value is only to be used
+ // during migration from pre-generation metadata to generation-aware metadata,
+ // and should eventually go away.
+ //
+ // A special Generation is used rather than always wrapping Generation in an Option,
+ // so that code handling generations doesn't have to be aware of the legacy
+ // case everywhere it touches a generation.
+ None,
+ // Generations with this magic value may never be used to construct S3 keys:
+ // we will panic if someone tries to. This is for Tenants in the "Broken" state,
+ // so that we can satisfy their constructor with a Generation without risking
+ // a code bug using it in an S3 write (broken tenants should never write)
+ Broken,
+ Valid(u32),
+}
+
+/// The Generation type represents a number associated with a Tenant, which
+/// increments every time the tenant is attached to a new pageserver, or
+/// an attached pageserver restarts.
+///
+/// It is included as a suffix in S3 keys, as a protection against split-brain
+/// scenarios where pageservers might otherwise issue conflicting writes to
+/// remote storage
+impl Generation {
+ /// Create a new Generation that represents a legacy key format with
+ /// no generation suffix
+ pub fn none() -> Self {
+ Self::None
+ }
+
+ // Create a new generation that will panic if you try to use get_suffix
+ pub fn broken() -> Self {
+ Self::Broken
+ }
+
+ pub fn new(v: u32) -> Self {
+ Self::Valid(v)
+ }
+
+ pub fn is_none(&self) -> bool {
+ matches!(self, Self::None)
+ }
+
+ pub fn get_suffix(&self) -> String {
+ match self {
+ Self::Valid(v) => {
+ format!("-{:08x}", v)
+ }
+ Self::None => "".into(),
+ Self::Broken => {
+ panic!("Tried to use a broken generation");
+ }
+ }
+ }
+}
+
+impl Serialize for Generation {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ if let Self::Valid(v) = self {
+ v.serialize(serializer)
+ } else {
+ // We should never be asked to serialize a None or Broken. Structures
+ // that include an optional generation should convert None to an
+ // Option::None
+ Err(serde::ser::Error::custom(
+ "Tried to serialize invalid generation ({self})",
+ ))
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for Generation {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'de>,
+ {
+ Ok(Self::Valid(u32::deserialize(deserializer)?))
+ }
+}
+
+// We intentionally do not implement Display for Generation, to reduce the
+// risk of a bug where the generation is used in a format!() string directly
+// instead of using get_suffix().
+impl Debug for Generation {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Valid(v) => {
+ write!(f, "{:08x}", v)
+ }
+ Self::None => {
+ write!(f, "")
+ }
+ Self::Broken => {
+ write!(f, "")
+ }
+ }
+ }
+}
diff --git a/libs/utils/src/lib.rs b/libs/utils/src/lib.rs
index 6cf829a67c..160e082833 100644
--- a/libs/utils/src/lib.rs
+++ b/libs/utils/src/lib.rs
@@ -27,6 +27,9 @@ pub mod id;
// http endpoint utils
pub mod http;
+// definition of the Generation type for pageserver attachment APIs
+pub mod generation;
+
// common log initialisation routine
pub mod logging;
diff --git a/pageserver/src/config.rs b/pageserver/src/config.rs
index f2aa2f365e..5394f17398 100644
--- a/pageserver/src/config.rs
+++ b/pageserver/src/config.rs
@@ -643,23 +643,6 @@ impl PageServerConf {
.join(METADATA_FILE_NAME)
}
- /// Files on the remote storage are stored with paths, relative to the workdir.
- /// That path includes in itself both tenant and timeline ids, allowing to have a unique remote storage path.
- ///
- /// Errors if the path provided does not start from pageserver's workdir.
- pub fn remote_path(&self, local_path: &Path) -> anyhow::Result {
- local_path
- .strip_prefix(&self.workdir)
- .context("Failed to strip workdir prefix")
- .and_then(RemotePath::new)
- .with_context(|| {
- format!(
- "Failed to resolve remote part of path {:?} for base {:?}",
- local_path, self.workdir
- )
- })
- }
-
/// Turns storage remote path of a file into its local path.
pub fn local_path(&self, remote_path: &RemotePath) -> PathBuf {
remote_path.with_base(&self.workdir)
diff --git a/pageserver/src/tenant.rs b/pageserver/src/tenant.rs
index a204f8a22b..2168db57de 100644
--- a/pageserver/src/tenant.rs
+++ b/pageserver/src/tenant.rs
@@ -85,6 +85,7 @@ pub use pageserver_api::models::TenantState;
use toml_edit;
use utils::{
crashsafe,
+ generation::Generation,
id::{TenantId, TimelineId},
lsn::{Lsn, RecordLsn},
};
@@ -178,6 +179,11 @@ pub struct Tenant {
tenant_conf: Arc>,
tenant_id: TenantId,
+
+ /// The remote storage generation, used to protect S3 objects from split-brain.
+ /// Does not change over the lifetime of the [`Tenant`] object.
+ generation: Generation,
+
timelines: Mutex>>,
// This mutex prevents creation of new timelines during GC.
// Adding yet another mutex (in addition to `timelines`) is needed because holding
@@ -522,6 +528,7 @@ impl Tenant {
pub(crate) fn spawn_attach(
conf: &'static PageServerConf,
tenant_id: TenantId,
+ generation: Generation,
broker_client: storage_broker::BrokerClientChannel,
tenants: &'static tokio::sync::RwLock,
remote_storage: GenericRemoteStorage,
@@ -538,6 +545,7 @@ impl Tenant {
tenant_conf,
wal_redo_manager,
tenant_id,
+ generation,
Some(remote_storage.clone()),
));
@@ -648,12 +656,8 @@ impl Tenant {
.as_ref()
.ok_or_else(|| anyhow::anyhow!("cannot attach without remote storage"))?;
- let remote_timeline_ids = remote_timeline_client::list_remote_timelines(
- remote_storage,
- self.conf,
- self.tenant_id,
- )
- .await?;
+ let remote_timeline_ids =
+ remote_timeline_client::list_remote_timelines(remote_storage, self.tenant_id).await?;
info!("found {} timelines", remote_timeline_ids.len());
@@ -665,6 +669,7 @@ impl Tenant {
self.conf,
self.tenant_id,
timeline_id,
+ self.generation,
);
part_downloads.spawn(
async move {
@@ -851,6 +856,7 @@ impl Tenant {
TenantConfOpt::default(),
wal_redo_manager,
tenant_id,
+ Generation::broken(),
None,
))
}
@@ -868,6 +874,7 @@ impl Tenant {
pub(crate) fn spawn_load(
conf: &'static PageServerConf,
tenant_id: TenantId,
+ generation: Generation,
resources: TenantSharedResources,
init_order: Option,
tenants: &'static tokio::sync::RwLock,
@@ -893,6 +900,7 @@ impl Tenant {
tenant_conf,
wal_redo_manager,
tenant_id,
+ generation,
remote_storage.clone(),
);
let tenant = Arc::new(tenant);
@@ -2274,6 +2282,7 @@ impl Tenant {
ancestor,
new_timeline_id,
self.tenant_id,
+ self.generation,
Arc::clone(&self.walredo_mgr),
resources,
pg_version,
@@ -2291,6 +2300,7 @@ impl Tenant {
tenant_conf: TenantConfOpt,
walredo_mgr: Arc,
tenant_id: TenantId,
+ generation: Generation,
remote_storage: Option,
) -> Tenant {
let (state, mut rx) = watch::channel(state);
@@ -2349,6 +2359,7 @@ impl Tenant {
Tenant {
tenant_id,
+ generation,
conf,
// using now here is good enough approximation to catch tenants with really long
// activation times.
@@ -2931,6 +2942,7 @@ impl Tenant {
self.conf,
self.tenant_id,
timeline_id,
+ self.generation,
);
Some(remote_client)
} else {
@@ -3454,6 +3466,7 @@ pub mod harness {
pub conf: &'static PageServerConf,
pub tenant_conf: TenantConf,
pub tenant_id: TenantId,
+ pub generation: Generation,
}
static LOG_HANDLE: OnceCell<()> = OnceCell::new();
@@ -3495,6 +3508,7 @@ pub mod harness {
conf,
tenant_conf,
tenant_id,
+ generation: Generation::new(0xdeadbeef),
})
}
@@ -3521,6 +3535,7 @@ pub mod harness {
TenantConfOpt::from(self.tenant_conf),
walredo_mgr,
self.tenant_id,
+ self.generation,
remote_storage,
));
tenant
diff --git a/pageserver/src/tenant/mgr.rs b/pageserver/src/tenant/mgr.rs
index a558c7d0ba..87617b544c 100644
--- a/pageserver/src/tenant/mgr.rs
+++ b/pageserver/src/tenant/mgr.rs
@@ -25,6 +25,7 @@ use crate::tenant::{create_tenant_files, CreateTenantFilesMode, Tenant, TenantSt
use crate::{InitializationOrder, IGNORED_TENANT_FILE_NAME};
use utils::fs_ext::PathExt;
+use utils::generation::Generation;
use utils::id::{TenantId, TimelineId};
use super::delete::DeleteTenantError;
@@ -202,6 +203,7 @@ pub(crate) fn schedule_local_tenant_processing(
match Tenant::spawn_attach(
conf,
tenant_id,
+ Generation::none(),
resources.broker_client,
tenants,
remote_storage,
@@ -224,7 +226,15 @@ pub(crate) fn schedule_local_tenant_processing(
} else {
info!("tenant {tenant_id} is assumed to be loadable, starting load operation");
// Start loading the tenant into memory. It will initially be in Loading state.
- Tenant::spawn_load(conf, tenant_id, resources, init_order, tenants, ctx)
+ Tenant::spawn_load(
+ conf,
+ tenant_id,
+ Generation::none(),
+ resources,
+ init_order,
+ tenants,
+ ctx,
+ )
};
Ok(tenant)
}
diff --git a/pageserver/src/tenant/remote_timeline_client.rs b/pageserver/src/tenant/remote_timeline_client.rs
index e46205810a..50bb8b43de 100644
--- a/pageserver/src/tenant/remote_timeline_client.rs
+++ b/pageserver/src/tenant/remote_timeline_client.rs
@@ -216,7 +216,7 @@ use utils::backoff::{
};
use std::collections::{HashMap, VecDeque};
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
@@ -235,6 +235,7 @@ use crate::task_mgr::shutdown_token;
use crate::tenant::debug_assert_current_span_has_tenant_and_timeline_id;
use crate::tenant::remote_timeline_client::index::LayerFileMetadata;
use crate::tenant::upload_queue::Delete;
+use crate::tenant::TIMELINES_SEGMENT_NAME;
use crate::{
config::PageServerConf,
task_mgr,
@@ -252,6 +253,7 @@ use self::index::IndexPart;
use super::storage_layer::LayerFileName;
use super::upload_queue::SetDeletedFlagProgress;
+use super::Generation;
// Occasional network issues and such can cause remote operations to fail, and
// that's expected. If a download fails, we log it at info-level, and retry.
@@ -315,6 +317,7 @@ pub struct RemoteTimelineClient {
tenant_id: TenantId,
timeline_id: TimelineId,
+ generation: Generation,
upload_queue: Mutex,
@@ -335,12 +338,14 @@ impl RemoteTimelineClient {
conf: &'static PageServerConf,
tenant_id: TenantId,
timeline_id: TimelineId,
+ generation: Generation,
) -> RemoteTimelineClient {
RemoteTimelineClient {
conf,
runtime: BACKGROUND_RUNTIME.handle().to_owned(),
tenant_id,
timeline_id,
+ generation,
storage_impl: remote_storage,
upload_queue: Mutex::new(UploadQueue::Uninitialized),
metrics: Arc::new(RemoteTimelineClientMetrics::new(&tenant_id, &timeline_id)),
@@ -449,10 +454,10 @@ impl RemoteTimelineClient {
);
let index_part = download::download_index_part(
- self.conf,
&self.storage_impl,
&self.tenant_id,
&self.timeline_id,
+ self.generation,
)
.measure_remote_op(
self.tenant_id,
@@ -650,22 +655,41 @@ impl RemoteTimelineClient {
// from latest_files, but not yet scheduled for deletion. Use a closure
// to syntactically forbid ? or bail! calls here.
let no_bail_here = || {
- for name in names {
- if upload_queue.latest_files.remove(name).is_some() {
- upload_queue.latest_files_changes_since_metadata_upload_scheduled += 1;
- }
- }
+ // Decorate our list of names with each name's generation, dropping
+ // makes that are unexpectedly missing from our metadata.
+ let with_generations: Vec<_> = names
+ .iter()
+ .filter_map(|name| {
+ // Remove from latest_files, learning the file's remote generation in the process
+ let meta = upload_queue.latest_files.remove(name);
+
+ if let Some(meta) = meta {
+ upload_queue.latest_files_changes_since_metadata_upload_scheduled += 1;
+ Some((name, meta.generation))
+ } else {
+ // This can only happen if we forgot to to schedule the file upload
+ // before scheduling the delete. Log it because it is a rare/strange
+ // situation, and in case something is misbehaving, we'd like to know which
+ // layers experienced this.
+ info!(
+ "Deleting layer {name} not found in latest_files list, never uploaded?"
+ );
+ None
+ }
+ })
+ .collect();
if upload_queue.latest_files_changes_since_metadata_upload_scheduled > 0 {
self.schedule_index_upload(upload_queue, metadata);
}
// schedule the actual deletions
- for name in names {
+ for (name, generation) in with_generations {
let op = UploadOp::Delete(Delete {
file_kind: RemoteOpFileKind::Layer,
layer_file_name: name.clone(),
scheduled_from_timeline_delete: false,
+ generation,
});
self.calls_unfinished_metric_begin(&op);
upload_queue.queued_operations.push_back(op);
@@ -761,10 +785,10 @@ impl RemoteTimelineClient {
backoff::retry(
|| {
upload::upload_index_part(
- self.conf,
&self.storage_impl,
&self.tenant_id,
&self.timeline_id,
+ self.generation,
&index_part_with_deleted_at,
)
},
@@ -822,12 +846,14 @@ impl RemoteTimelineClient {
.reserve(stopped.upload_queue_for_deletion.latest_files.len());
// schedule the actual deletions
- for name in stopped.upload_queue_for_deletion.latest_files.keys() {
+ for (name, meta) in &stopped.upload_queue_for_deletion.latest_files {
let op = UploadOp::Delete(Delete {
file_kind: RemoteOpFileKind::Layer,
layer_file_name: name.clone(),
scheduled_from_timeline_delete: true,
+ generation: meta.generation,
});
+
self.calls_unfinished_metric_begin(&op);
stopped
.upload_queue_for_deletion
@@ -850,8 +876,7 @@ impl RemoteTimelineClient {
// Do not delete index part yet, it is needed for possible retry. If we remove it first
// and retry will arrive to different pageserver there wont be any traces of it on remote storage
- let timeline_path = self.conf.timeline_path(&self.tenant_id, &self.timeline_id);
- let timeline_storage_path = self.conf.remote_path(&timeline_path)?;
+ let timeline_storage_path = remote_timeline_path(&self.tenant_id, &self.timeline_id);
let remaining = backoff::retry(
|| async {
@@ -1055,15 +1080,17 @@ impl RemoteTimelineClient {
let upload_result: anyhow::Result<()> = match &task.op {
UploadOp::UploadLayer(ref layer_file_name, ref layer_metadata) => {
- let path = &self
+ let path = self
.conf
.timeline_path(&self.tenant_id, &self.timeline_id)
.join(layer_file_name.file_name());
+
upload::upload_timeline_layer(
self.conf,
&self.storage_impl,
- path,
+ &path,
layer_metadata,
+ self.generation,
)
.measure_remote_op(
self.tenant_id,
@@ -1085,10 +1112,10 @@ impl RemoteTimelineClient {
};
let res = upload::upload_index_part(
- self.conf,
&self.storage_impl,
&self.tenant_id,
&self.timeline_id,
+ self.generation,
index_part,
)
.measure_remote_op(
@@ -1113,7 +1140,7 @@ impl RemoteTimelineClient {
.conf
.timeline_path(&self.tenant_id, &self.timeline_id)
.join(delete.layer_file_name.file_name());
- delete::delete_layer(self.conf, &self.storage_impl, path)
+ delete::delete_layer(self.conf, &self.storage_impl, path, delete.generation)
.measure_remote_op(
self.tenant_id,
self.timeline_id,
@@ -1360,6 +1387,71 @@ impl RemoteTimelineClient {
}
}
+pub fn remote_timelines_path(tenant_id: &TenantId) -> RemotePath {
+ let path = format!("tenants/{tenant_id}/{TIMELINES_SEGMENT_NAME}");
+ RemotePath::from_string(&path).expect("Failed to construct path")
+}
+
+pub fn remote_timeline_path(tenant_id: &TenantId, timeline_id: &TimelineId) -> RemotePath {
+ remote_timelines_path(tenant_id).join(&PathBuf::from(timeline_id.to_string()))
+}
+
+pub fn remote_layer_path(
+ tenant_id: &TenantId,
+ timeline_id: &TimelineId,
+ layer_file_name: &LayerFileName,
+ layer_meta: &LayerFileMetadata,
+) -> RemotePath {
+ // Generation-aware key format
+ let path = format!(
+ "tenants/{tenant_id}/{TIMELINES_SEGMENT_NAME}/{timeline_id}/{0}{1}",
+ layer_file_name.file_name(),
+ layer_meta.generation.get_suffix()
+ );
+
+ RemotePath::from_string(&path).expect("Failed to construct path")
+}
+
+pub fn remote_index_path(
+ tenant_id: &TenantId,
+ timeline_id: &TimelineId,
+ generation: Generation,
+) -> RemotePath {
+ RemotePath::from_string(&format!(
+ "tenants/{tenant_id}/{TIMELINES_SEGMENT_NAME}/{timeline_id}/{0}{1}",
+ IndexPart::FILE_NAME,
+ generation.get_suffix()
+ ))
+ .expect("Failed to construct path")
+}
+
+/// Files on the remote storage are stored with paths, relative to the workdir.
+/// That path includes in itself both tenant and timeline ids, allowing to have a unique remote storage path.
+///
+/// Errors if the path provided does not start from pageserver's workdir.
+pub fn remote_path(
+ conf: &PageServerConf,
+ local_path: &Path,
+ generation: Generation,
+) -> anyhow::Result {
+ let stripped = local_path
+ .strip_prefix(&conf.workdir)
+ .context("Failed to strip workdir prefix")?;
+
+ let suffixed = format!(
+ "{0}{1}",
+ stripped.to_string_lossy(),
+ generation.get_suffix()
+ );
+
+ RemotePath::new(&PathBuf::from(suffixed)).with_context(|| {
+ format!(
+ "to resolve remote part of path {:?} for base {:?}",
+ local_path, conf.workdir
+ )
+ })
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -1367,7 +1459,7 @@ mod tests {
context::RequestContext,
tenant::{
harness::{TenantHarness, TIMELINE_ID},
- Tenant, Timeline,
+ Generation, Tenant, Timeline,
},
DEFAULT_PG_VERSION,
};
@@ -1409,8 +1501,11 @@ mod tests {
assert_eq!(avec, bvec);
}
- fn assert_remote_files(expected: &[&str], remote_path: &Path) {
- let mut expected: Vec = expected.iter().map(|x| String::from(*x)).collect();
+ fn assert_remote_files(expected: &[&str], remote_path: &Path, generation: Generation) {
+ let mut expected: Vec = expected
+ .iter()
+ .map(|x| format!("{}{}", x, generation.get_suffix()))
+ .collect();
expected.sort();
let mut found: Vec = Vec::new();
@@ -1461,6 +1556,8 @@ mod tests {
storage: RemoteStorageKind::LocalFs(remote_fs_dir.clone()),
};
+ let generation = Generation::new(0xdeadbeef);
+
let storage = GenericRemoteStorage::from_config(&storage_config).unwrap();
let client = Arc::new(RemoteTimelineClient {
@@ -1468,6 +1565,7 @@ mod tests {
runtime: tokio::runtime::Handle::current(),
tenant_id: harness.tenant_id,
timeline_id: TIMELINE_ID,
+ generation,
storage_impl: storage,
upload_queue: Mutex::new(UploadQueue::Uninitialized),
metrics: Arc::new(RemoteTimelineClientMetrics::new(
@@ -1526,6 +1624,8 @@ mod tests {
.init_upload_queue_for_empty_remote(&metadata)
.unwrap();
+ let generation = Generation::new(0xdeadbeef);
+
// Create a couple of dummy files, schedule upload for them
let layer_file_name_1: LayerFileName = "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap();
let layer_file_name_2: LayerFileName = "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D9-00000000016B5A52".parse().unwrap();
@@ -1545,13 +1645,13 @@ mod tests {
client
.schedule_layer_file_upload(
&layer_file_name_1,
- &LayerFileMetadata::new(content_1.len() as u64),
+ &LayerFileMetadata::new(content_1.len() as u64, generation),
)
.unwrap();
client
.schedule_layer_file_upload(
&layer_file_name_2,
- &LayerFileMetadata::new(content_2.len() as u64),
+ &LayerFileMetadata::new(content_2.len() as u64, generation),
)
.unwrap();
@@ -1615,7 +1715,7 @@ mod tests {
client
.schedule_layer_file_upload(
&layer_file_name_3,
- &LayerFileMetadata::new(content_3.len() as u64),
+ &LayerFileMetadata::new(content_3.len() as u64, generation),
)
.unwrap();
client
@@ -1639,6 +1739,7 @@ mod tests {
"index_part.json",
],
&remote_timeline_dir,
+ generation,
);
// Finish them
@@ -1651,6 +1752,7 @@ mod tests {
"index_part.json",
],
&remote_timeline_dir,
+ generation,
);
}
@@ -1703,12 +1805,14 @@ mod tests {
// Test
+ let generation = Generation::new(0xdeadbeef);
+
let init = get_bytes_started_stopped();
client
.schedule_layer_file_upload(
&layer_file_name_1,
- &LayerFileMetadata::new(content_1.len() as u64),
+ &LayerFileMetadata::new(content_1.len() as u64, generation),
)
.unwrap();
diff --git a/pageserver/src/tenant/remote_timeline_client/delete.rs b/pageserver/src/tenant/remote_timeline_client/delete.rs
index 3f505d45ab..7324559223 100644
--- a/pageserver/src/tenant/remote_timeline_client/delete.rs
+++ b/pageserver/src/tenant/remote_timeline_client/delete.rs
@@ -5,25 +5,30 @@ use tracing::debug;
use remote_storage::GenericRemoteStorage;
-use crate::config::PageServerConf;
+use crate::{
+ config::PageServerConf,
+ tenant::{remote_timeline_client::remote_path, Generation},
+};
pub(super) async fn delete_layer<'a>(
conf: &'static PageServerConf,
storage: &'a GenericRemoteStorage,
local_layer_path: &'a Path,
+ generation: Generation,
) -> anyhow::Result<()> {
fail::fail_point!("before-delete-layer", |_| {
anyhow::bail!("failpoint before-delete-layer")
});
debug!("Deleting layer from remote storage: {local_layer_path:?}",);
- let path_to_delete = conf.remote_path(local_layer_path)?;
+ let path_to_delete = remote_path(conf, local_layer_path, generation)?;
// We don't want to print an error if the delete failed if the file has
// already been deleted. Thankfully, in this situation S3 already
// does not yield an error. While OS-provided local file system APIs do yield
// errors, we avoid them in the `LocalFs` wrapper.
- storage.delete(&path_to_delete).await.with_context(|| {
- format!("Failed to delete remote layer from storage at {path_to_delete:?}")
- })
+ storage
+ .delete(&path_to_delete)
+ .await
+ .with_context(|| format!("delete remote layer from storage at {path_to_delete:?}"))
}
diff --git a/pageserver/src/tenant/remote_timeline_client/download.rs b/pageserver/src/tenant/remote_timeline_client/download.rs
index 2cb33f07c9..dc8d87b9e1 100644
--- a/pageserver/src/tenant/remote_timeline_client/download.rs
+++ b/pageserver/src/tenant/remote_timeline_client/download.rs
@@ -15,14 +15,16 @@ use tokio_util::sync::CancellationToken;
use utils::{backoff, crashsafe};
use crate::config::PageServerConf;
+use crate::tenant::remote_timeline_client::{remote_layer_path, remote_timelines_path};
use crate::tenant::storage_layer::LayerFileName;
use crate::tenant::timeline::span::debug_assert_current_span_has_tenant_and_timeline_id;
+use crate::tenant::Generation;
use remote_storage::{DownloadError, GenericRemoteStorage};
use utils::crashsafe::path_with_suffix_extension;
use utils::id::{TenantId, TimelineId};
use super::index::{IndexPart, LayerFileMetadata};
-use super::{FAILED_DOWNLOAD_WARN_THRESHOLD, FAILED_REMOTE_OP_RETRIES};
+use super::{remote_index_path, FAILED_DOWNLOAD_WARN_THRESHOLD, FAILED_REMOTE_OP_RETRIES};
static MAX_DOWNLOAD_DURATION: Duration = Duration::from_secs(120);
@@ -41,13 +43,11 @@ pub async fn download_layer_file<'a>(
) -> Result {
debug_assert_current_span_has_tenant_and_timeline_id();
- let timeline_path = conf.timeline_path(&tenant_id, &timeline_id);
+ let local_path = conf
+ .timeline_path(&tenant_id, &timeline_id)
+ .join(layer_file_name.file_name());
- let local_path = timeline_path.join(layer_file_name.file_name());
-
- let remote_path = conf
- .remote_path(&local_path)
- .map_err(DownloadError::Other)?;
+ let remote_path = remote_layer_path(&tenant_id, &timeline_id, layer_file_name, layer_metadata);
// Perform a rename inspired by durable_rename from file_utils.c.
// The sequence:
@@ -64,33 +64,43 @@ pub async fn download_layer_file<'a>(
let (mut destination_file, bytes_amount) = download_retry(
|| async {
// TODO: this doesn't use the cached fd for some reason?
- let mut destination_file = fs::File::create(&temp_file_path).await.with_context(|| {
- format!(
- "create a destination file for layer '{}'",
- temp_file_path.display()
- )
- })
- .map_err(DownloadError::Other)?;
- let mut download = storage.download(&remote_path).await.with_context(|| {
- format!(
+ let mut destination_file = fs::File::create(&temp_file_path)
+ .await
+ .with_context(|| {
+ format!(
+ "create a destination file for layer '{}'",
+ temp_file_path.display()
+ )
+ })
+ .map_err(DownloadError::Other)?;
+ let mut download = storage
+ .download(&remote_path)
+ .await
+ .with_context(|| {
+ format!(
"open a download stream for layer with remote storage path '{remote_path:?}'"
)
- })
- .map_err(DownloadError::Other)?;
-
- let bytes_amount = tokio::time::timeout(MAX_DOWNLOAD_DURATION, tokio::io::copy(&mut download.download_stream, &mut destination_file))
- .await
- .map_err(|e| DownloadError::Other(anyhow::anyhow!("Timed out {:?}", e)))?
- .with_context(|| {
- format!("Failed to download layer with remote storage path '{remote_path:?}' into file {temp_file_path:?}")
})
.map_err(DownloadError::Other)?;
- Ok((destination_file, bytes_amount))
+ let bytes_amount = tokio::time::timeout(
+ MAX_DOWNLOAD_DURATION,
+ tokio::io::copy(&mut download.download_stream, &mut destination_file),
+ )
+ .await
+ .map_err(|e| DownloadError::Other(anyhow::anyhow!("Timed out {:?}", e)))?
+ .with_context(|| {
+ format!(
+ "download layer at remote path '{remote_path:?}' into file {temp_file_path:?}"
+ )
+ })
+ .map_err(DownloadError::Other)?;
+ Ok((destination_file, bytes_amount))
},
&format!("download {remote_path:?}"),
- ).await?;
+ )
+ .await?;
// Tokio doc here: https://docs.rs/tokio/1.17.0/tokio/fs/struct.File.html states that:
// A file will not be closed immediately when it goes out of scope if there are any IO operations
@@ -103,12 +113,7 @@ pub async fn download_layer_file<'a>(
destination_file
.flush()
.await
- .with_context(|| {
- format!(
- "failed to flush source file at {}",
- temp_file_path.display()
- )
- })
+ .with_context(|| format!("flush source file at {}", temp_file_path.display()))
.map_err(DownloadError::Other)?;
let expected = layer_metadata.file_size();
@@ -139,17 +144,12 @@ pub async fn download_layer_file<'a>(
fs::rename(&temp_file_path, &local_path)
.await
- .with_context(|| {
- format!(
- "Could not rename download layer file to {}",
- local_path.display(),
- )
- })
+ .with_context(|| format!("rename download layer file to {}", local_path.display(),))
.map_err(DownloadError::Other)?;
crashsafe::fsync_async(&local_path)
.await
- .with_context(|| format!("Could not fsync layer file {}", local_path.display(),))
+ .with_context(|| format!("fsync layer file {}", local_path.display(),))
.map_err(DownloadError::Other)?;
tracing::debug!("download complete: {}", local_path.display());
@@ -173,21 +173,19 @@ pub fn is_temp_download_file(path: &Path) -> bool {
}
/// List timelines of given tenant in remote storage
-pub async fn list_remote_timelines<'a>(
- storage: &'a GenericRemoteStorage,
- conf: &'static PageServerConf,
+pub async fn list_remote_timelines(
+ storage: &GenericRemoteStorage,
tenant_id: TenantId,
) -> anyhow::Result> {
- let tenant_path = conf.timelines_path(&tenant_id);
- let tenant_storage_path = conf.remote_path(&tenant_path)?;
+ let remote_path = remote_timelines_path(&tenant_id);
fail::fail_point!("storage-sync-list-remote-timelines", |_| {
anyhow::bail!("storage-sync-list-remote-timelines");
});
let timelines = download_retry(
- || storage.list_prefixes(Some(&tenant_storage_path)),
- &format!("list prefixes for {tenant_path:?}"),
+ || storage.list_prefixes(Some(&remote_path)),
+ &format!("list prefixes for {tenant_id}"),
)
.await?;
@@ -202,9 +200,9 @@ pub async fn list_remote_timelines<'a>(
anyhow::anyhow!("failed to get timeline id for remote tenant {tenant_id}")
})?;
- let timeline_id: TimelineId = object_name.parse().with_context(|| {
- format!("failed to parse object name into timeline id '{object_name}'")
- })?;
+ let timeline_id: TimelineId = object_name
+ .parse()
+ .with_context(|| format!("parse object name into timeline id '{object_name}'"))?;
// list_prefixes is assumed to return unique names. Ensure this here.
// NB: it's safer to bail out than warn-log this because the pageserver
@@ -222,21 +220,16 @@ pub async fn list_remote_timelines<'a>(
}
pub(super) async fn download_index_part(
- conf: &'static PageServerConf,
storage: &GenericRemoteStorage,
tenant_id: &TenantId,
timeline_id: &TimelineId,
+ generation: Generation,
) -> Result {
- let index_part_path = conf
- .metadata_path(tenant_id, timeline_id)
- .with_file_name(IndexPart::FILE_NAME);
- let part_storage_path = conf
- .remote_path(&index_part_path)
- .map_err(DownloadError::BadInput)?;
+ let remote_path = remote_index_path(tenant_id, timeline_id, generation);
let index_part_bytes = download_retry(
|| async {
- let mut index_part_download = storage.download(&part_storage_path).await?;
+ let mut index_part_download = storage.download(&remote_path).await?;
let mut index_part_bytes = Vec::new();
tokio::io::copy(
@@ -244,20 +237,16 @@ pub(super) async fn download_index_part(
&mut index_part_bytes,
)
.await
- .with_context(|| {
- format!("Failed to download an index part into file {index_part_path:?}")
- })
+ .with_context(|| format!("download index part at {remote_path:?}"))
.map_err(DownloadError::Other)?;
Ok(index_part_bytes)
},
- &format!("download {part_storage_path:?}"),
+ &format!("download {remote_path:?}"),
)
.await?;
let index_part: IndexPart = serde_json::from_slice(&index_part_bytes)
- .with_context(|| {
- format!("Failed to deserialize index part file into file {index_part_path:?}")
- })
+ .with_context(|| format!("download index part file at {remote_path:?}"))
.map_err(DownloadError::Other)?;
Ok(index_part)
diff --git a/pageserver/src/tenant/remote_timeline_client/index.rs b/pageserver/src/tenant/remote_timeline_client/index.rs
index 28177b097f..9cc5256568 100644
--- a/pageserver/src/tenant/remote_timeline_client/index.rs
+++ b/pageserver/src/tenant/remote_timeline_client/index.rs
@@ -2,7 +2,7 @@
//! Able to restore itself from the storage index parts, that are located in every timeline's remote directory and contain all data about
//! remote timeline layers and its metadata.
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
@@ -12,6 +12,7 @@ use utils::bin_ser::SerializeError;
use crate::tenant::metadata::TimelineMetadata;
use crate::tenant::storage_layer::LayerFileName;
use crate::tenant::upload_queue::UploadQueueInitialized;
+use crate::tenant::Generation;
use utils::lsn::Lsn;
@@ -20,22 +21,28 @@ use utils::lsn::Lsn;
/// Fields have to be `Option`s because remote [`IndexPart`]'s can be from different version, which
/// might have less or more metadata depending if upgrading or rolling back an upgrade.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
-#[cfg_attr(test, derive(Default))]
+//#[cfg_attr(test, derive(Default))]
pub struct LayerFileMetadata {
file_size: u64,
+
+ pub(crate) generation: Generation,
}
impl From<&'_ IndexLayerMetadata> for LayerFileMetadata {
fn from(other: &IndexLayerMetadata) -> Self {
LayerFileMetadata {
file_size: other.file_size,
+ generation: other.generation,
}
}
}
impl LayerFileMetadata {
- pub fn new(file_size: u64) -> Self {
- LayerFileMetadata { file_size }
+ pub fn new(file_size: u64, generation: Generation) -> Self {
+ LayerFileMetadata {
+ file_size,
+ generation,
+ }
}
pub fn file_size(&self) -> u64 {
@@ -62,10 +69,6 @@ pub struct IndexPart {
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option,
- /// Legacy field: equal to the keys of `layer_metadata`, only written out for forward compat
- #[serde(default, skip_deserializing)]
- timeline_layers: HashSet,
-
/// Per layer file name metadata, which can be present for a present or missing layer file.
///
/// Older versions of `IndexPart` will not have this property or have only a part of metadata
@@ -91,7 +94,8 @@ impl IndexPart {
/// - 2: added `deleted_at`
/// - 3: no longer deserialize `timeline_layers` (serialized format is the same, but timeline_layers
/// is always generated from the keys of `layer_metadata`)
- const LATEST_VERSION: usize = 3;
+ /// - 4: timeline_layers is fully removed.
+ const LATEST_VERSION: usize = 4;
pub const FILE_NAME: &'static str = "index_part.json";
pub fn new(
@@ -99,18 +103,14 @@ impl IndexPart {
disk_consistent_lsn: Lsn,
metadata: TimelineMetadata,
) -> Self {
- let mut timeline_layers = HashSet::with_capacity(layers_and_metadata.len());
- let mut layer_metadata = HashMap::with_capacity(layers_and_metadata.len());
-
- for (remote_name, metadata) in &layers_and_metadata {
- timeline_layers.insert(remote_name.to_owned());
- let metadata = IndexLayerMetadata::from(metadata);
- layer_metadata.insert(remote_name.to_owned(), metadata);
- }
+ // Transform LayerFileMetadata into IndexLayerMetadata
+ let layer_metadata = layers_and_metadata
+ .into_iter()
+ .map(|(k, v)| (k, IndexLayerMetadata::from(v)))
+ .collect();
Self {
version: Self::LATEST_VERSION,
- timeline_layers,
layer_metadata,
disk_consistent_lsn,
metadata,
@@ -135,15 +135,20 @@ impl TryFrom<&UploadQueueInitialized> for IndexPart {
}
/// Serialized form of [`LayerFileMetadata`].
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct IndexLayerMetadata {
pub(super) file_size: u64,
+
+ #[serde(default = "Generation::none")]
+ #[serde(skip_serializing_if = "Generation::is_none")]
+ pub(super) generation: Generation,
}
-impl From<&'_ LayerFileMetadata> for IndexLayerMetadata {
- fn from(other: &'_ LayerFileMetadata) -> Self {
+impl From for IndexLayerMetadata {
+ fn from(other: LayerFileMetadata) -> Self {
IndexLayerMetadata {
file_size: other.file_size,
+ generation: other.generation,
}
}
}
@@ -168,15 +173,16 @@ mod tests {
let expected = IndexPart {
// note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
version: 1,
- timeline_layers: HashSet::new(),
layer_metadata: HashMap::from([
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
file_size: 25600000,
+ generation: Generation::none()
}),
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
// serde_json should always parse this but this might be a double with jq for
// example.
file_size: 9007199254741001,
+ generation: Generation::none()
})
]),
disk_consistent_lsn: "0/16960E8".parse::().unwrap(),
@@ -205,15 +211,16 @@ mod tests {
let expected = IndexPart {
// note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
version: 1,
- timeline_layers: HashSet::new(),
layer_metadata: HashMap::from([
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
file_size: 25600000,
+ generation: Generation::none()
}),
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
// serde_json should always parse this but this might be a double with jq for
// example.
file_size: 9007199254741001,
+ generation: Generation::none()
})
]),
disk_consistent_lsn: "0/16960E8".parse::().unwrap(),
@@ -243,15 +250,16 @@ mod tests {
let expected = IndexPart {
// note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
version: 2,
- timeline_layers: HashSet::new(),
layer_metadata: HashMap::from([
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
file_size: 25600000,
+ generation: Generation::none()
}),
("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
// serde_json should always parse this but this might be a double with jq for
// example.
file_size: 9007199254741001,
+ generation: Generation::none()
})
]),
disk_consistent_lsn: "0/16960E8".parse::().unwrap(),
@@ -276,7 +284,6 @@ mod tests {
let expected = IndexPart {
version: 1,
- timeline_layers: HashSet::new(),
layer_metadata: HashMap::new(),
disk_consistent_lsn: "0/2532648".parse::().unwrap(),
metadata: TimelineMetadata::from_bytes(&[
@@ -309,4 +316,41 @@ mod tests {
assert_eq!(empty_layers_parsed, expected);
}
+
+ #[test]
+ fn v4_indexpart_is_parsed() {
+ let example = r#"{
+ "version":4,
+ "layer_metadata":{
+ "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
+ "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
+ },
+ "disk_consistent_lsn":"0/16960E8",
+ "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
+ "deleted_at": "2023-07-31T09:00:00.123"
+ }"#;
+
+ let expected = IndexPart {
+ version: 4,
+ layer_metadata: HashMap::from([
+ ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), IndexLayerMetadata {
+ file_size: 25600000,
+ generation: Generation::none()
+ }),
+ ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), IndexLayerMetadata {
+ // serde_json should always parse this but this might be a double with jq for
+ // example.
+ file_size: 9007199254741001,
+ generation: Generation::none()
+ })
+ ]),
+ disk_consistent_lsn: "0/16960E8".parse::().unwrap(),
+ metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
+ deleted_at: Some(chrono::NaiveDateTime::parse_from_str(
+ "2023-07-31T09:00:00.123000000", "%Y-%m-%dT%H:%M:%S.%f").unwrap())
+ };
+
+ let part = serde_json::from_str::(example).unwrap();
+ assert_eq!(part, expected);
+ }
}
diff --git a/pageserver/src/tenant/remote_timeline_client/upload.rs b/pageserver/src/tenant/remote_timeline_client/upload.rs
index a805e9bd60..c442c4f445 100644
--- a/pageserver/src/tenant/remote_timeline_client/upload.rs
+++ b/pageserver/src/tenant/remote_timeline_client/upload.rs
@@ -5,7 +5,11 @@ use fail::fail_point;
use std::{io::ErrorKind, path::Path};
use tokio::fs;
-use crate::{config::PageServerConf, tenant::remote_timeline_client::index::IndexPart};
+use super::Generation;
+use crate::{
+ config::PageServerConf,
+ tenant::remote_timeline_client::{index::IndexPart, remote_index_path, remote_path},
+};
use remote_storage::GenericRemoteStorage;
use utils::id::{TenantId, TimelineId};
@@ -15,10 +19,10 @@ use tracing::info;
/// Serializes and uploads the given index part data to the remote storage.
pub(super) async fn upload_index_part<'a>(
- conf: &'static PageServerConf,
storage: &'a GenericRemoteStorage,
tenant_id: &TenantId,
timeline_id: &TimelineId,
+ generation: Generation,
index_part: &'a IndexPart,
) -> anyhow::Result<()> {
tracing::trace!("uploading new index part");
@@ -27,20 +31,16 @@ pub(super) async fn upload_index_part<'a>(
bail!("failpoint before-upload-index")
});
- let index_part_bytes = serde_json::to_vec(&index_part)
- .context("Failed to serialize index part file into bytes")?;
+ let index_part_bytes =
+ serde_json::to_vec(&index_part).context("serialize index part file into bytes")?;
let index_part_size = index_part_bytes.len();
let index_part_bytes = tokio::io::BufReader::new(std::io::Cursor::new(index_part_bytes));
- let index_part_path = conf
- .metadata_path(tenant_id, timeline_id)
- .with_file_name(IndexPart::FILE_NAME);
- let storage_path = conf.remote_path(&index_part_path)?;
-
+ let remote_path = remote_index_path(tenant_id, timeline_id, generation);
storage
- .upload_storage_object(Box::new(index_part_bytes), index_part_size, &storage_path)
+ .upload_storage_object(Box::new(index_part_bytes), index_part_size, &remote_path)
.await
- .with_context(|| format!("Failed to upload index part for '{tenant_id} / {timeline_id}'"))
+ .with_context(|| format!("upload index part for '{tenant_id} / {timeline_id}'"))
}
/// Attempts to upload given layer files.
@@ -52,12 +52,13 @@ pub(super) async fn upload_timeline_layer<'a>(
storage: &'a GenericRemoteStorage,
source_path: &'a Path,
known_metadata: &'a LayerFileMetadata,
+ generation: Generation,
) -> anyhow::Result<()> {
fail_point!("before-upload-layer", |_| {
bail!("failpoint before-upload-layer")
});
- let storage_path = conf.remote_path(source_path)?;
+ let storage_path = remote_path(conf, source_path, generation)?;
let source_file_res = fs::File::open(&source_path).await;
let source_file = match source_file_res {
Ok(source_file) => source_file,
@@ -70,16 +71,15 @@ pub(super) async fn upload_timeline_layer<'a>(
info!(path = %source_path.display(), "File to upload doesn't exist. Likely the file has been deleted and an upload is not required any more.");
return Ok(());
}
- Err(e) => Err(e)
- .with_context(|| format!("Failed to open a source file for layer {source_path:?}"))?,
+ Err(e) => {
+ Err(e).with_context(|| format!("open a source file for layer {source_path:?}"))?
+ }
};
let fs_size = source_file
.metadata()
.await
- .with_context(|| {
- format!("Failed to get the source file metadata for layer {source_path:?}")
- })?
+ .with_context(|| format!("get the source file metadata for layer {source_path:?}"))?
.len();
let metadata_size = known_metadata.file_size();
@@ -87,19 +87,13 @@ pub(super) async fn upload_timeline_layer<'a>(
bail!("File {source_path:?} has its current FS size {fs_size} diferent from initially determined {metadata_size}");
}
- let fs_size = usize::try_from(fs_size).with_context(|| {
- format!("File {source_path:?} size {fs_size} could not be converted to usize")
- })?;
+ let fs_size = usize::try_from(fs_size)
+ .with_context(|| format!("convert {source_path:?} size {fs_size} usize"))?;
storage
.upload(source_file, fs_size, &storage_path, None)
.await
- .with_context(|| {
- format!(
- "Failed to upload a layer from local path '{}'",
- source_path.display()
- )
- })?;
+ .with_context(|| format!("upload layer from local path '{}'", source_path.display()))?;
Ok(())
}
diff --git a/pageserver/src/tenant/timeline.rs b/pageserver/src/tenant/timeline.rs
index 04da85a241..f0ae385806 100644
--- a/pageserver/src/tenant/timeline.rs
+++ b/pageserver/src/tenant/timeline.rs
@@ -67,6 +67,7 @@ use postgres_connection::PgConnectionConfig;
use postgres_ffi::to_pg_timestamp;
use utils::{
completion,
+ generation::Generation,
id::{TenantId, TimelineId},
lsn::{AtomicLsn, Lsn, RecordLsn},
seqwait::SeqWait,
@@ -152,6 +153,10 @@ pub struct Timeline {
pub tenant_id: TenantId,
pub timeline_id: TimelineId,
+ /// The generation of the tenant that instantiated us: this is used for safety when writing remote objects.
+ /// Never changes for the lifetime of this [`Timeline`] object.
+ generation: Generation,
+
pub pg_version: u32,
/// The tuple has two elements.
@@ -1199,7 +1204,7 @@ impl Timeline {
Ok(delta) => Some(delta),
};
- let layer_metadata = LayerFileMetadata::new(layer_file_size);
+ let layer_metadata = LayerFileMetadata::new(layer_file_size, self.generation);
let new_remote_layer = Arc::new(match local_layer.filename() {
LayerFileName::Image(image_name) => RemoteLayer::new_img(
@@ -1377,6 +1382,7 @@ impl Timeline {
ancestor: Option>,
timeline_id: TimelineId,
tenant_id: TenantId,
+ generation: Generation,
walredo_mgr: Arc,
resources: TimelineResources,
pg_version: u32,
@@ -1406,6 +1412,7 @@ impl Timeline {
myself: myself.clone(),
timeline_id,
tenant_id,
+ generation,
pg_version,
layers: Arc::new(tokio::sync::RwLock::new(LayerManager::create())),
wanted_image_layers: Mutex::new(None),
@@ -1615,6 +1622,9 @@ impl Timeline {
let (conf, tenant_id, timeline_id) = (self.conf, self.tenant_id, self.timeline_id);
let span = tracing::Span::current();
+ // Copy to move into the task we're about to spawn
+ let generation = self.generation;
+
let (loaded_layers, to_sync, total_physical_size) = tokio::task::spawn_blocking({
move || {
let _g = span.entered();
@@ -1656,8 +1666,12 @@ impl Timeline {
);
}
- let decided =
- init::reconcile(discovered_layers, index_part.as_ref(), disk_consistent_lsn);
+ let decided = init::reconcile(
+ discovered_layers,
+ index_part.as_ref(),
+ disk_consistent_lsn,
+ generation,
+ );
let mut loaded_layers = Vec::new();
let mut needs_upload = Vec::new();
@@ -2669,7 +2683,7 @@ impl Timeline {
(
HashMap::from([(
layer.filename(),
- LayerFileMetadata::new(layer.layer_desc().file_size),
+ LayerFileMetadata::new(layer.layer_desc().file_size, self.generation),
)]),
Some(layer),
)
@@ -3065,7 +3079,10 @@ impl Timeline {
.metadata()
.with_context(|| format!("reading metadata of layer file {}", path.file_name()))?;
- layer_paths_to_upload.insert(path, LayerFileMetadata::new(metadata.len()));
+ layer_paths_to_upload.insert(
+ path,
+ LayerFileMetadata::new(metadata.len(), self.generation),
+ );
self.metrics
.resident_physical_size_gauge
@@ -3740,7 +3757,7 @@ impl Timeline {
if let Some(remote_client) = &self.remote_client {
remote_client.schedule_layer_file_upload(
&l.filename(),
- &LayerFileMetadata::new(metadata.len()),
+ &LayerFileMetadata::new(metadata.len(), self.generation),
)?;
}
@@ -3749,7 +3766,10 @@ impl Timeline {
.resident_physical_size_gauge
.add(metadata.len());
- new_layer_paths.insert(new_delta_path, LayerFileMetadata::new(metadata.len()));
+ new_layer_paths.insert(
+ new_delta_path,
+ LayerFileMetadata::new(metadata.len(), self.generation),
+ );
l.access_stats().record_residence_event(
LayerResidenceStatus::Resident,
LayerResidenceEventReason::LayerCreate,
diff --git a/pageserver/src/tenant/timeline/init.rs b/pageserver/src/tenant/timeline/init.rs
index a270d96677..33effb4318 100644
--- a/pageserver/src/tenant/timeline/init.rs
+++ b/pageserver/src/tenant/timeline/init.rs
@@ -7,6 +7,7 @@ use crate::{
index::{IndexPart, LayerFileMetadata},
},
storage_layer::LayerFileName,
+ Generation,
},
METADATA_FILE_NAME,
};
@@ -104,6 +105,7 @@ pub(super) fn reconcile(
discovered: Vec<(LayerFileName, u64)>,
index_part: Option<&IndexPart>,
disk_consistent_lsn: Lsn,
+ generation: Generation,
) -> Vec<(LayerFileName, Result)> {
use Decision::*;
@@ -112,7 +114,15 @@ pub(super) fn reconcile(
let mut discovered = discovered
.into_iter()
- .map(|(name, file_size)| (name, (Some(LayerFileMetadata::new(file_size)), None)))
+ .map(|(name, file_size)| {
+ (
+ name,
+ // The generation here will be corrected to match IndexPart in the merge below, unless
+ // it is not in IndexPart, in which case using our current generation makes sense
+ // because it will be uploaded in this generation.
+ (Some(LayerFileMetadata::new(file_size, generation)), None),
+ )
+ })
.collect::();
// merge any index_part information, when available
@@ -137,7 +147,11 @@ pub(super) fn reconcile(
Err(FutureLayer { local })
} else {
Ok(match (local, remote) {
- (Some(local), Some(remote)) if local != remote => UseRemote { local, remote },
+ (Some(local), Some(remote)) if local != remote => {
+ assert_eq!(local.generation, remote.generation);
+
+ UseRemote { local, remote }
+ }
(Some(x), Some(_)) => UseLocal(x),
(None, Some(x)) => Evicted(x),
(Some(x), None) => NeedsUpload(x),
diff --git a/pageserver/src/tenant/upload_queue.rs b/pageserver/src/tenant/upload_queue.rs
index 6026825b0d..28822335b0 100644
--- a/pageserver/src/tenant/upload_queue.rs
+++ b/pageserver/src/tenant/upload_queue.rs
@@ -1,6 +1,7 @@
use crate::metrics::RemoteOpFileKind;
use super::storage_layer::LayerFileName;
+use super::Generation;
use crate::tenant::metadata::TimelineMetadata;
use crate::tenant::remote_timeline_client::index::IndexPart;
use crate::tenant::remote_timeline_client::index::LayerFileMetadata;
@@ -205,6 +206,7 @@ pub(crate) struct Delete {
pub(crate) file_kind: RemoteOpFileKind,
pub(crate) layer_file_name: LayerFileName,
pub(crate) scheduled_from_timeline_delete: bool,
+ pub(crate) generation: Generation,
}
#[derive(Debug)]
@@ -228,17 +230,21 @@ impl std::fmt::Display for UploadOp {
UploadOp::UploadLayer(path, metadata) => {
write!(
f,
- "UploadLayer({}, size={:?})",
+ "UploadLayer({}, size={:?}, gen={:?})",
path.file_name(),
- metadata.file_size()
+ metadata.file_size(),
+ metadata.generation,
)
}
- UploadOp::UploadMetadata(_, lsn) => write!(f, "UploadMetadata(lsn: {})", lsn),
+ UploadOp::UploadMetadata(_, lsn) => {
+ write!(f, "UploadMetadata(lsn: {})", lsn)
+ }
UploadOp::Delete(delete) => write!(
f,
- "Delete(path: {}, scheduled_from_timeline_delete: {})",
+ "Delete(path: {}, scheduled_from_timeline_delete: {}, gen: {:?})",
delete.layer_file_name.file_name(),
- delete.scheduled_from_timeline_delete
+ delete.scheduled_from_timeline_delete,
+ delete.generation
),
UploadOp::Barrier(_) => write!(f, "Barrier"),
}
diff --git a/proxy/src/auth/password_hack.rs b/proxy/src/auth/password_hack.rs
index 33441e8c88..d1da208fef 100644
--- a/proxy/src/auth/password_hack.rs
+++ b/proxy/src/auth/password_hack.rs
@@ -12,13 +12,19 @@ pub struct PasswordHackPayload {
impl PasswordHackPayload {
pub fn parse(bytes: &[u8]) -> Option {
- // The format is `project=;`.
- let mut iter = bytes.splitn_str(2, ";");
- let endpoint = iter.next()?.to_str().ok()?;
- let endpoint = parse_endpoint_param(endpoint)?.to_owned();
- let password = iter.next()?.to_owned();
+ // The format is `project=;` or `project=$`.
+ let separators = [";", "$"];
+ for sep in separators {
+ if let Some((endpoint, password)) = bytes.split_once_str(sep) {
+ let endpoint = endpoint.to_str().ok()?;
+ return Some(Self {
+ endpoint: parse_endpoint_param(endpoint)?.to_owned(),
+ password: password.to_owned(),
+ });
+ }
+ }
- Some(Self { endpoint, password })
+ None
}
}
@@ -91,4 +97,23 @@ mod tests {
assert_eq!(payload.endpoint, "foobar");
assert_eq!(payload.password, b"pass;word");
}
+
+ #[test]
+ fn parse_password_hack_payload_dollar() {
+ let bytes = b"";
+ assert!(PasswordHackPayload::parse(bytes).is_none());
+
+ let bytes = b"endpoint=";
+ assert!(PasswordHackPayload::parse(bytes).is_none());
+
+ let bytes = b"endpoint=$";
+ let payload = PasswordHackPayload::parse(bytes).expect("parsing failed");
+ assert_eq!(payload.endpoint, "");
+ assert_eq!(payload.password, b"");
+
+ let bytes = b"endpoint=foobar$pass$word";
+ let payload = PasswordHackPayload::parse(bytes).expect("parsing failed");
+ assert_eq!(payload.endpoint, "foobar");
+ assert_eq!(payload.password, b"pass$word");
+ }
}
diff --git a/proxy/src/console/provider/neon.rs b/proxy/src/console/provider/neon.rs
index 3f4cee6e34..3322d5a5be 100644
--- a/proxy/src/console/provider/neon.rs
+++ b/proxy/src/console/provider/neon.rs
@@ -16,12 +16,21 @@ use tracing::{error, info, info_span, warn, Instrument};
pub struct Api {
endpoint: http::Endpoint,
caches: &'static ApiCaches,
+ jwt: String,
}
impl Api {
/// Construct an API object containing the auth parameters.
pub fn new(endpoint: http::Endpoint, caches: &'static ApiCaches) -> Self {
- Self { endpoint, caches }
+ let jwt: String = match std::env::var("NEON_PROXY_TO_CONTROLPLANE_TOKEN") {
+ Ok(v) => v,
+ Err(_) => "".to_string(),
+ };
+ Self {
+ endpoint,
+ caches,
+ jwt,
+ }
}
pub fn url(&self) -> &str {
@@ -39,6 +48,7 @@ impl Api {
.endpoint
.get("proxy_get_role_secret")
.header("X-Request-ID", &request_id)
+ .header("Authorization", &self.jwt)
.query(&[("session_id", extra.session_id)])
.query(&[
("application_name", extra.application_name),
@@ -83,6 +93,7 @@ impl Api {
.endpoint
.get("proxy_wake_compute")
.header("X-Request-ID", &request_id)
+ .header("Authorization", &self.jwt)
.query(&[("session_id", extra.session_id)])
.query(&[
("application_name", extra.application_name),
diff --git a/proxy/src/http/websocket.rs b/proxy/src/http/websocket.rs
index c85450a074..72ae3dc26f 100644
--- a/proxy/src/http/websocket.rs
+++ b/proxy/src/http/websocket.rs
@@ -304,7 +304,7 @@ pub async fn task_main(
let make_svc =
hyper::service::make_service_fn(|stream: &tokio_rustls::server::TlsStream| {
- let sni_name = stream.get_ref().1.sni_hostname().map(|s| s.to_string());
+ let sni_name = stream.get_ref().1.server_name().map(|s| s.to_string());
let conn_pool = conn_pool.clone();
async move {
diff --git a/proxy/src/stream.rs b/proxy/src/stream.rs
index 7cb292ed58..6210601a80 100644
--- a/proxy/src/stream.rs
+++ b/proxy/src/stream.rs
@@ -141,7 +141,7 @@ impl Stream {
pub fn sni_hostname(&self) -> Option<&str> {
match self {
Stream::Raw { .. } => None,
- Stream::Tls { tls } => tls.get_ref().1.sni_hostname(),
+ Stream::Tls { tls } => tls.get_ref().1.server_name(),
}
}
}
diff --git a/test_runner/regress/test_remote_storage.py b/test_runner/regress/test_remote_storage.py
index b865e3ce24..0bd365efaa 100644
--- a/test_runner/regress/test_remote_storage.py
+++ b/test_runner/regress/test_remote_storage.py
@@ -604,6 +604,7 @@ def test_timeline_deletion_with_files_stuck_in_upload_queue(
checkpoint_allowed_to_fail.set()
env.pageserver.allowed_errors.append(
".* ERROR .*Error processing HTTP request: InternalServerError\\(timeline is Stopping"
+ ".* ERROR .*[Cc]ould not flush frozen layer.*"
)
# Generous timeout, because currently deletions can get blocked waiting for compaction
diff --git a/workspace_hack/Cargo.toml b/workspace_hack/Cargo.toml
index 831cc6f6b1..4ec4b01f66 100644
--- a/workspace_hack/Cargo.toml
+++ b/workspace_hack/Cargo.toml
@@ -15,6 +15,7 @@ publish = false
[dependencies]
anyhow = { version = "1", features = ["backtrace"] }
axum = { version = "0.6", features = ["ws"] }
+base64 = { version = "0.21", features = ["alloc"] }
bytes = { version = "1", features = ["serde"] }
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
clap = { version = "4", features = ["derive", "string"] }
@@ -44,14 +45,14 @@ regex = { version = "1" }
regex-syntax = { version = "0.7" }
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"] }
ring = { version = "0.16", features = ["std"] }
-rustls = { version = "0.20", features = ["dangerous_configuration"] }
+rustls = { version = "0.21", features = ["dangerous_configuration"] }
scopeguard = { version = "1" }
serde = { version = "1", features = ["alloc", "derive"] }
serde_json = { version = "1", features = ["raw_value"] }
smallvec = { version = "1", default-features = false, features = ["write"] }
socket2 = { version = "0.4", default-features = false, features = ["all"] }
tokio = { version = "1", features = ["fs", "io-std", "io-util", "macros", "net", "process", "rt-multi-thread", "signal", "test-util"] }
-tokio-rustls = { version = "0.23" }
+tokio-rustls = { version = "0.24" }
tokio-util = { version = "0.7", features = ["codec", "io"] }
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
toml_edit = { version = "0.19", features = ["serde"] }
@@ -74,7 +75,7 @@ prost = { version = "0.11" }
regex = { version = "1" }
regex-syntax = { version = "0.7" }
serde = { version = "1", features = ["alloc", "derive"] }
-syn-dff4ba8e3ae991db = { package = "syn", version = "1", features = ["extra-traits", "full", "visit", "visit-mut"] }
-syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "full", "visit-mut"] }
+syn-dff4ba8e3ae991db = { package = "syn", version = "1", features = ["extra-traits", "full", "visit"] }
+syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "full", "visit", "visit-mut"] }
### END HAKARI SECTION