From e8abc2945e55e8ee3814d085a5d27790c4210ff4 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Thu, 3 Oct 2024 19:38:26 +0000 Subject: [PATCH] chore: prepare new prerelease(s) --- Cargo.lock | 1086 ++++++++++------- Cargo.toml | 95 +- qcs-api-client-common/CHANGELOG-py.md | 18 + qcs-api-client-common/CHANGELOG.md | 18 + qcs-api-client-common/Cargo.toml | 32 +- qcs-api-client-common/pyproject.toml | 2 +- .../src/tracing_configuration.rs | 169 ++- qcs-api-client-grpc/CHANGELOG.md | 18 + qcs-api-client-grpc/Cargo.toml | 99 +- qcs-api-client-grpc/README.md | 9 + qcs-api-client-grpc/src/tonic/channel.rs | 120 +- qcs-api-client-grpc/src/tonic/common.rs | 77 ++ qcs-api-client-grpc/src/tonic/error.rs | 4 +- qcs-api-client-grpc/src/tonic/grpc_web.rs | 2 +- qcs-api-client-grpc/src/tonic/mod.rs | 483 +++----- qcs-api-client-grpc/src/tonic/refresh.rs | 91 +- qcs-api-client-grpc/src/tonic/retry.rs | 54 +- qcs-api-client-grpc/src/tonic/trace/mod.rs | 47 + qcs-api-client-grpc/src/tonic/trace/shared.rs | 103 ++ .../src/tonic/trace/trace_layer.rs | 129 ++ .../src/tonic/trace/trace_layer_otel_ext.rs | 379 ++++++ qcs-api-client-openapi/CHANGELOG.md | 18 + qcs-api-client-openapi/Cargo.toml | 41 +- .../src/apis/configuration.rs | 87 +- 24 files changed, 2266 insertions(+), 915 deletions(-) create mode 100644 qcs-api-client-grpc/src/tonic/common.rs create mode 100644 qcs-api-client-grpc/src/tonic/trace/mod.rs create mode 100644 qcs-api-client-grpc/src/tonic/trace/shared.rs create mode 100644 qcs-api-client-grpc/src/tonic/trace/trace_layer.rs create mode 100644 qcs-api-client-grpc/src/tonic/trace/trace_layer_otel_ext.rs diff --git a/Cargo.lock b/Cargo.lock index e014440..816c673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,11 +205,10 @@ dependencies = [ [[package]] name = "async-socks5" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f634add2445eb2c1f785642a67ca1073fedd71e73dc3ca69435ef9b9bdedc7" +checksum = "8da2537846e16b96d2972ee52a3b355663872a1a687ce6d57a3b6f6b6a181c89" dependencies = [ - "async-trait", "thiserror", "tokio", ] @@ -244,9 +243,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -255,13 +254,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -272,13 +271,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -298,51 +297,53 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", "pin-project-lite", "rustversion", "serde", - "sync_wrapper", - "tower", + "sync_wrapper 1.0.1", + "tower 0.5.1", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", ] @@ -414,12 +415,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -468,15 +463,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.20" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bcde016d64c21da4be18b655631e5ab6d3107607e71a73a9f53eb48aae23fb" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -541,15 +536,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -593,7 +579,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -604,7 +590,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -634,7 +620,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -644,7 +630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -657,7 +643,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -727,15 +713,6 @@ dependencies = [ "log", ] -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -891,7 +868,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -979,17 +956,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http", - "indexmap 2.5.0", + "http 1.1.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1004,20 +981,20 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "headers" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -1025,11 +1002,11 @@ dependencies = [ [[package]] name = "headers-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.1.0", ] [[package]] @@ -1076,6 +1053,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1083,21 +1071,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1120,7 +1125,7 @@ dependencies = [ "crossbeam-utils", "form_urlencoded", "futures-util", - "hyper", + "hyper 0.14.30", "lazy_static", "levenshtein", "log", @@ -1143,9 +1148,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1158,63 +1162,129 @@ dependencies = [ ] [[package]] -name = "hyper-proxy" -version = "0.9.1" -source = "git+https://github.com/rigetti/hyper-proxy#e08329b56787326d6ec8d0bbcc1f96913af105c8" +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-proxy2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9043b7b23fb0bc4a1c7014c27b50a4fc42cc76206f71d34fc0dfe5b28ddc3faf" dependencies = [ "bytes", "futures-util", "headers", - "http", - "hyper", - "hyper-rustls", - "rustls-native-certs 0.6.3", + "http 1.1.0", + "hyper 1.4.1", + "hyper-rustls 0.26.0", + "hyper-util", + "pin-project-lite", + "rustls-native-certs 0.7.3", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", "tower-service", "webpki", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", "log", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", + "rustls 0.22.4", + "rustls-native-certs 0.7.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.13", + "rustls-native-certs 0.8.0", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] name = "hyper-socks2" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc38166fc2732d450e9372388d269eb38ff0b75a3cfb4c542e65b2f6893629c4" +checksum = "51c227614c208f7e7c2e040526912604a1a957fe467c9c2f5b06c5d032337dab" dependencies = [ "async-socks5", - "futures", - "http", - "hyper", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", "thiserror", "tokio", + "tower-service", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.4.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ - "hyper", + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", + "socket2", "tokio", - "tokio-io-timeout", + "tower-service", + "tracing", ] [[package]] @@ -1268,12 +1338,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -1305,9 +1375,9 @@ checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itertools" @@ -1327,6 +1397,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1411,9 +1490,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" @@ -1421,7 +1500,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags", "libc", ] @@ -1456,6 +1535,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.4" @@ -1584,9 +1669,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "openssl-probe" @@ -1596,62 +1684,81 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.20.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c365a63eec4f55b7efeceb724f1336f26a9cf3427b70e59e2cd2a5b947fba96" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", ] [[package]] name = "opentelemetry-http" -version = "0.9.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" +checksum = "ad31e9de44ee3538fb9d64fe3376c1362f406162434609e79aea2a41a0af78ab" dependencies = [ "async-trait", "bytes", - "http", - "opentelemetry_api", + "http 1.1.0", + "opentelemetry 0.24.0", ] [[package]] -name = "opentelemetry_api" -version = "0.20.0" +name = "opentelemetry_sdk" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" dependencies = [ + "async-trait", "futures-channel", + "futures-executor", "futures-util", - "indexmap 1.9.3", - "js-sys", + "glob", + "lazy_static", "once_cell", - "pin-project-lite", + "opentelemetry 0.23.0", + "ordered-float", + "percent-encoding", + "rand", "thiserror", - "urlencoding", ] [[package]] name = "opentelemetry_sdk" -version = "0.20.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +checksum = "692eac490ec80f24a17828d49b40b60f5aeaccdfe6a503f939713afd22bc28df" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", + "glob", "once_cell", - "opentelemetry_api", - "ordered-float", + "opentelemetry 0.24.0", "percent-encoding", "rand", - "regex", + "serde_json", "thiserror", - "tokio", - "tokio-stream", ] [[package]] @@ -1662,9 +1769,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "3.9.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +checksum = "44d501f1a72f71d3c063a6bbc8f7271fa73aa09fe5d6283b6571e2ed176a2537" dependencies = [ "num-traits", ] @@ -1712,9 +1819,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbjson" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" dependencies = [ "base64 0.21.7", "serde", @@ -1728,22 +1835,34 @@ checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" dependencies = [ "heck 0.4.1", "itertools 0.11.0", - "prost", - "prost-types", + "prost 0.12.6", + "prost-types 0.12.6", +] + +[[package]] +name = "pbjson-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" +dependencies = [ + "heck 0.5.0", + "itertools 0.13.0", + "prost 0.13.3", + "prost-types 0.13.3", ] [[package]] name = "pbjson-types" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" dependencies = [ "bytes", "chrono", "pbjson", - "pbjson-build", - "prost", - "prost-build", + "pbjson-build 0.7.0", + "prost 0.13.3", + "prost-build 0.13.3", "serde", ] @@ -1767,7 +1886,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1793,7 +1912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -1828,7 +1947,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1871,9 +1990,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -1903,16 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit", + "syn 2.0.79", ] [[package]] @@ -1932,7 +2042,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "version_check", "yansi", ] @@ -1944,7 +2054,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +dependencies = [ + "bytes", + "prost-derive 0.13.3", ] [[package]] @@ -1961,10 +2081,31 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost", - "prost-types", + "prost 0.12.6", + "prost-types 0.12.6", + "regex", + "syn 2.0.79", + "tempfile", +] + +[[package]] +name = "prost-build" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.3", + "prost-types 0.13.3", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -1978,7 +2119,20 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", +] + +[[package]] +name = "prost-derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.79", ] [[package]] @@ -1987,7 +2141,16 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost", + "prost 0.12.6", +] + +[[package]] +name = "prost-types" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +dependencies = [ + "prost 0.13.3", ] [[package]] @@ -2062,7 +2225,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2075,12 +2238,12 @@ dependencies = [ "proc-macro2", "pyo3-build-config 0.20.3", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "qcs-api-client-common" -version = "0.10.2" +version = "0.11.0-rc.0" dependencies = [ "async-trait", "backoff", @@ -2090,7 +2253,7 @@ dependencies = [ "figment", "futures", "home", - "http", + "http 1.1.0", "httpmock", "jsonwebtoken", "paste", @@ -2099,7 +2262,7 @@ dependencies = [ "pyo3-build-config 0.22.3", "reqwest", "rigetti-pyo3", - "rstest 0.21.0", + "rstest", "serde", "serial_test", "shellexpand", @@ -2114,29 +2277,30 @@ dependencies = [ [[package]] name = "qcs-api-client-grpc" -version = "0.10.2" +version = "0.11.0-rc.0" dependencies = [ "async-std", "backoff", "futures-util", - "http", - "http-body", - "hyper", - "hyper-proxy", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-proxy2", "hyper-socks2", + "hyper-util", "jsonwebtoken", "once_cell", - "opentelemetry", + "opentelemetry 0.24.0", "opentelemetry-http", - "opentelemetry_api", - "opentelemetry_sdk", + "opentelemetry_sdk 0.24.1", "pbjson", - "pbjson-build", + "pbjson-build 0.6.2", "pbjson-types", - "prost", - "prost-build", + "prost 0.13.3", + "prost-build 0.12.6", "qcs-api-client-common", - "rstest 0.17.0", + "rstest", "serde", "serde_json", "tempfile", @@ -2147,33 +2311,84 @@ dependencies = [ "tonic-build", "tonic-health", "tonic-web", - "tower", + "tower 0.4.13", + "tower-http", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.25.0", "tracing-subscriber", "url", "urlpattern", + "uuid", ] [[package]] name = "qcs-api-client-openapi" -version = "0.11.2" +version = "0.12.0-rc.0" dependencies = [ "anyhow", + "http 1.1.0", "qcs-api-client-common", "reqwest", "reqwest-middleware", "reqwest-tracing", - "rstest 0.17.0", + "rstest", "serde", "serde_json", - "task-local-extensions", "tokio", "tracing", + "tracing-opentelemetry 0.25.0", "url", "urlpattern", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.13", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.13", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -2215,11 +2430,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -2235,9 +2450,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -2247,9 +2462,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -2258,32 +2473,26 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "relative-path" -version = "1.9.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-util", "ipnet", "js-sys", "log", @@ -2292,56 +2501,57 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "rustls-pemfile 1.0.4", + "quinn", + "rustls 0.23.13", + "rustls-native-certs 0.8.0", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 1.0.1", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.0", "tokio-socks", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "windows-registry", ] [[package]] name = "reqwest-middleware" -version = "0.2.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" +checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" dependencies = [ "anyhow", "async-trait", - "http", + "http 1.1.0", "reqwest", "serde", - "task-local-extensions", "thiserror", + "tower-service", ] [[package]] name = "reqwest-tracing" -version = "0.4.8" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190838e54153d7a7e2ea98851304b3ce92daeabf14c54d32b01b84a3e636f683" +checksum = "bfdd9bfa64c72233d8dd99ab7883efcdefe9e16d46488ecb9228b71a2e2ceb45" dependencies = [ "anyhow", "async-trait", "getrandom", - "matchit", - "opentelemetry", + "http 1.1.0", + "matchit 0.8.4", + "opentelemetry 0.23.0", "reqwest", "reqwest-middleware", - "task-local-extensions", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.24.0", ] [[package]] @@ -2380,19 +2590,7 @@ checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" dependencies = [ "futures", "futures-timer", - "rstest_macros 0.17.0", - "rustc_version", -] - -[[package]] -name = "rstest" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros 0.21.0", + "rstest_macros", "rustc_version", ] @@ -2410,30 +2608,18 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "rstest_macros" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" -dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.77", - "unicode-ident", -] - [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2449,7 +2635,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -2458,50 +2644,54 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", - "rustls-webpki 0.101.7", - "sct", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.4", + "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", @@ -2509,38 +2699,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - -[[package]] -name = "rustls-webpki" -version = "0.101.7" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -2576,9 +2746,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.17" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c947adb109a8afce5fc9c7bf951f87f146e9147b3a6a58413105628fb1d1e66" +checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50" dependencies = [ "sdd", ] @@ -2598,16 +2768,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sdd" version = "3.0.3" @@ -2620,7 +2780,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -2629,9 +2789,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -2660,7 +2820,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2687,9 +2847,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2728,7 +2888,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2868,9 +3028,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2884,24 +3044,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ - "core-foundation-sys", - "libc", + "futures-core", ] [[package]] @@ -2910,20 +3058,11 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" -[[package]] -name = "task-local-extensions" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" -dependencies = [ - "pin-utils", -] - [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2945,22 +3084,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3046,16 +3185,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.4.0" @@ -3064,26 +3193,27 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.12", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.4", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -3147,11 +3277,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -3160,30 +3290,32 @@ dependencies = [ [[package]] name = "tonic" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.7", + "base64 0.22.1", "bytes", "h2", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", "hyper-timeout", + "hyper-util", "percent-encoding", "pin-project", - "prost", - "rustls-native-certs 0.7.3", - "rustls-pemfile 2.1.3", - "rustls-pki-types", + "prost 0.13.3", + "rustls-native-certs 0.8.0", + "rustls-pemfile", + "socket2", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -3197,19 +3329,19 @@ checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", + "prost-build 0.12.6", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "tonic-health" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cef6e24bc96871001a7e48e820ab240b3de2201e59b517cf52835df2f1d2350" +checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" dependencies = [ "async-stream", - "prost", + "prost 0.13.3", "tokio", "tokio-stream", "tonic", @@ -3217,15 +3349,15 @@ dependencies = [ [[package]] name = "tonic-web" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" +checksum = "5299dd20801ad736dccb4a5ea0da7376e59cd98f213bf1c3d478cf53f4834b58" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "pin-project", "tokio-stream", "tonic", @@ -3256,21 +3388,34 @@ dependencies = [ ] [[package]] -name = "tower-http" -version = "0.4.4" +name = "tower" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ - "bitflags 2.6.0", - "bytes", "futures-core", "futures-util", - "http", - "http-body", - "http-range-header", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -3305,7 +3450,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3320,9 +3465,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", @@ -3330,28 +3475,39 @@ dependencies = [ ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "tracing-opentelemetry" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" dependencies = [ - "log", + "js-sys", "once_cell", + "opentelemetry 0.23.0", + "opentelemetry_sdk 0.23.0", + "smallvec", + "tracing", "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", ] [[package]] name = "tracing-opentelemetry" -version = "0.20.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc09e402904a5261e42cf27aea09ccb7d5318c6717a9eec3d8e2e65c56b18f19" +checksum = "a9784ed4da7d921bc8df6963f8c80a0e4ce34ba6ba76668acadd3edbd985ff3b" dependencies = [ + "js-sys", "once_cell", - "opentelemetry", + "opentelemetry 0.24.0", + "opentelemetry_sdk 0.24.1", + "smallvec", "tracing", "tracing-core", - "tracing-log 0.1.4", + "tracing-log", "tracing-subscriber", + "web-time", ] [[package]] @@ -3365,7 +3521,7 @@ dependencies = [ "smallvec", "thread_local", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -3441,9 +3597,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -3462,9 +3618,9 @@ dependencies = [ [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unindent" @@ -3489,12 +3645,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "urlpattern" version = "0.2.0" @@ -3508,6 +3658,15 @@ dependencies = [ "url", ] +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -3573,7 +3732,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -3607,7 +3766,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3628,6 +3787,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki" version = "0.22.4" @@ -3678,6 +3847,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3828,23 +4027,13 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "yansi" version = "1.0.1" @@ -3869,7 +4058,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3877,3 +4066,8 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[patch.unused]] +name = "hyper-proxy" +version = "0.9.1" +source = "git+https://github.com/rigetti/hyper-proxy#e08329b56787326d6ec8d0bbcc1f96913af105c8" diff --git a/Cargo.toml b/Cargo.toml index 2868dc6..d8d0ef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,20 +8,95 @@ resolver = '2' [workspace.dependencies] + futures-util = '0.3.30' + http = '1.1' + http-body = '1' + http-body-util = '0.1.2' + hyper = '1.4.1' jsonwebtoken = '9.3.0' - opentelemetry = '0.20.0' - opentelemetry-http = '0.9.0' - opentelemetry_api = '0.20.0' - opentelemetry_sdk = '0.20.0' - reqwest-middleware = '0.2.0' - tracing-opentelemetry = '0.20.0' - tracing-subscriber = '0.3.17' + once_cell = '1.17.0' + opentelemetry = '0.24.0' + opentelemetry-http = '0.13.0' + opentelemetry_sdk = '0.24.0' + pbjson = '0.7.0' + pbjson-build = '0.6.0' + pbjson-types = '0.7.0' + prost = '0.13.1' + prost-build = '0.12.3' + rstest = '0.17.0' + serde = '1.0.140' + serde_json = '1.0.91' + tempfile = '3.3.0' + thiserror = '1.0.32' + tonic-build = '0.11.0' + tonic-health = '0.12.1' + tracing-opentelemetry = '0.25.0' + tracing-subscriber = '0.3.18' + urlpattern = '0.2.0' + + [workspace.dependencies.async-std] + features = ['attributes'] + version = '1.12.0' + + [workspace.dependencies.backoff] + features = ['tokio'] + version = '0.4.0' + + [workspace.dependencies.hyper-proxy2] + default-features = false + features = ['rustls'] + version = '0.1.0' + + [workspace.dependencies.hyper-socks2] + default-features = false + version = '0.9.1' + + [workspace.dependencies.hyper-util] + features = ['client-legacy'] + version = '0.1.6' [workspace.dependencies.reqwest] default-features = false features = ['json', 'rustls-tls-native-roots'] - version = '0.11.27' + version = '0.12.5' + + [workspace.dependencies.reqwest-middleware] + features = ['json'] + version = '0.3.2' [workspace.dependencies.reqwest-tracing] - features = ['opentelemetry_0_20'] - version = '0.4.6' + features = ['opentelemetry_0_23', 'tracing-opentelemetry_0_24_pkg'] + version = '0.5.2' + + [workspace.dependencies.tokio] + features = ['time'] + version = '1.38.0' + + [workspace.dependencies.tokio-stream] + features = ['net'] + version = '0.1.15' + + [workspace.dependencies.tonic] + features = ['tls-roots'] + version = '0.12.3' + + [workspace.dependencies.tonic-web] + version = '0.12.1' + + [workspace.dependencies.tower] + features = ['retry'] + version = '0.4' + + [workspace.dependencies.tower-http] + features = ['trace'] + version = '0.5.2' + + [workspace.dependencies.tracing] + version = '0.1.37' + + [workspace.dependencies.url] + version = '2.3.1' + + [workspace.dependencies.uuid] + features = ['v4'] + version = '1.10.0' diff --git a/qcs-api-client-common/CHANGELOG-py.md b/qcs-api-client-common/CHANGELOG-py.md index e968fa9..b351cff 100644 --- a/qcs-api-client-common/CHANGELOG-py.md +++ b/qcs-api-client-common/CHANGELOG-py.md @@ -1,3 +1,21 @@ +## 0.11.0-rc.0 (2024-10-03) + +### Breaking Changes + +#### update http and support grpc trace layer + +### Fixes + +#### misc mr feedback + +#### avoid overloading clone copy rust semantics + +#### tracing configuration features import + +#### avoid string deref + +#### update tonic per cargo-deny ddos warning + ## 0.10.2 (2024-09-17) ### Fixes diff --git a/qcs-api-client-common/CHANGELOG.md b/qcs-api-client-common/CHANGELOG.md index d4e752d..1ad033e 100644 --- a/qcs-api-client-common/CHANGELOG.md +++ b/qcs-api-client-common/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.11.0-rc.0 (2024-10-03) + +### Breaking Changes + +#### update http and support grpc trace layer + +### Fixes + +#### misc mr feedback + +#### avoid overloading clone copy rust semantics + +#### tracing configuration features import + +#### avoid string deref + +#### update tonic per cargo-deny ddos warning + ## 0.10.2 (2024-09-17) ### Fixes diff --git a/qcs-api-client-common/Cargo.toml b/qcs-api-client-common/Cargo.toml index 4a4f86d..f4e274e 100644 --- a/qcs-api-client-common/Cargo.toml +++ b/qcs-api-client-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qcs-api-client-common" -version = "0.10.2" +version = "0.11.0-rc.0" edition = "2021" license = "Apache-2.0" readme = "README.md" @@ -28,36 +28,36 @@ python = ["dep:pyo3", "dep:pyo3-asyncio", "dep:paste", "dep:rigetti-pyo3", "toki [dependencies] async-trait = "0.1" -backoff = { version = "0.4.0", features = ["tokio"] } +backoff = { workspace = true } +base64 = "0.22.1" derive_builder = "0.20.0" +figment = { version = "0.10.18", features = ["env", "toml"] } futures = "0.3.26" home = "0.5.5" -http = "0.2.8" -figment = {version = "0.10.18", features = ["env", "toml"]} +http = { workspace = true } jsonwebtoken = { workspace = true } paste = { version = "1.0.15", optional = true } pyo3 = { version = "0.20.3", features = ["multiple-pymethods"], optional = true } -rigetti-pyo3 = { version = "0.3.6", optional = true } -reqwest = { workspace = true, default-features = false, features = ["socks"] } pyo3-asyncio = { version = "0.20.0", features = ["tokio-runtime"], optional = true } -serde = { version = "1.0", features = ["derive"] } +reqwest = { workspace = true, default-features = false, features = ["socks"] } +rigetti-pyo3 = { version = "0.3.6", optional = true } +serde = { workspace = true, features = ["derive"] } shellexpand = "3.1.0" -thiserror = "1.0.32" +thiserror = { workspace = true } time = { version = "0.3.34", features = ["parsing"] } -tokio = { version = "1.20.1", features = ["sync"] } +tokio = { workspace = true, features = ["sync"] } toml = "0.8.14" -tracing = { version = "0.1.37", optional = true } -url = { version = "2.3.1", optional = true } -urlpattern = { version = "0.2.0", optional = true } -base64 = "0.22.1" +tracing = { workspace = true, optional = true } +url = { workspace = true, optional = true } +urlpattern = { workspace = true, optional = true } [dev-dependencies] chrono = "0.4.38" -figment = {version = "0.10.18", features = ["env", "toml", "test"]} +figment = { version = "0.10.18", features = ["env", "toml", "test"] } httpmock = "0.7.0" -rstest = "0.21.0" +rstest = { workspace = true } serial_test = "3.1.1" -tokio = { version = "1.20.1", features = ["fs", "macros"] } +tokio = { workspace = true, features = ["fs", "macros"] } [build-dependencies] pyo3-build-config = { version = "0.22.1", optional = true } diff --git a/qcs-api-client-common/pyproject.toml b/qcs-api-client-common/pyproject.toml index dfb6c94..3567f9c 100644 --- a/qcs-api-client-common/pyproject.toml +++ b/qcs-api-client-common/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "qcs-api-client-common" -version = "0.10.2" +version = "0.11.0-rc.0" description = "Contains core QCS client functionality and middleware implementations." readme = "README-py.md" license = { text = "Apache-2.0" } diff --git a/qcs-api-client-common/src/tracing_configuration.rs b/qcs-api-client-common/src/tracing_configuration.rs index 910fc4b..87ecee0 100644 --- a/qcs-api-client-common/src/tracing_configuration.rs +++ b/qcs-api-client-common/src/tracing_configuration.rs @@ -55,12 +55,85 @@ //! }); //! ``` -use std::str::FromStr; +use std::{collections::HashSet, str::FromStr}; pub use urlpattern::{UrlPatternInit, UrlPatternMatchInput, UrlPatternResult}; use {std::env, thiserror::Error, urlpattern::UrlPattern}; +/// A utility for configuring values either by inclusion or exclusion. +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum IncludeExclude +where + T: std::hash::Hash + Eq, +{ + /// Include these values. All other values are excluded. + Include(HashSet), + /// Exclude these values. All other values are included. + Exclude(HashSet), +} + +impl IncludeExclude +where + T: std::hash::Hash + Eq, +{ + /// Returns a new instance that includes all values. + #[must_use] + #[allow(dead_code)] + pub fn include_all() -> Self { + Self::Exclude(HashSet::new()) + } + + /// Returns a new instance that excludes all values. + #[must_use] + pub fn include_none() -> Self { + Self::Include(HashSet::new()) + } +} + +/// A trait for filtering header values into a [`Vec<(String, String)>`] of tracing attributes. +pub trait HeaderAttributesFilter { + /// Given a [`http::HeaderMap`], return a corresponding list of tracing attributes. + fn get_header_attributes(&self, headers: &http::HeaderMap) -> Vec<(String, String)>; +} + +impl HeaderAttributesFilter for IncludeExclude { + /// Any header values that return an error on [`http::HeaderValue::to_str`] will be excluded. + /// Note, this implementation intentionally scales linearly with the number of included headers + /// when [`Self::Include`] is used and scales linearly with [`http::HeaderMap::len`] + /// when [`Self::Exclude`] is used. + #[must_use] + fn get_header_attributes(&self, headers: &http::HeaderMap) -> Vec<(String, String)> { + match self { + Self::Include(set) => { + let mut header_attributes = Vec::new(); + for header_name in set { + if let Some(header_value) = headers + .get(header_name) + .and_then(|header_value| header_value.to_str().ok()) + { + header_attributes.push((header_name.to_string(), header_value.to_string())); + } + } + header_attributes + } + Self::Exclude(set) => { + let mut header_attributes = Vec::new(); + for (header_name, header_value) in headers { + if !set.contains(header_name.as_str()) { + if let Ok(header_value) = header_value.to_str() { + header_attributes + .push((header_name.to_string(), header_value.to_string())); + } + } + } + header_attributes + } + } + } +} + /// Environment variable for controlling whether any network API calls are traced. pub static QCS_API_TRACING_ENABLED: &str = "QCS_API_TRACING_ENABLED"; /// Environment variable for controlling whether network API calls set Open Telemetry @@ -91,10 +164,23 @@ pub enum TracingFilterError { /// A builder for [`TracingConfiguration`]. #[allow(clippy::module_name_repetitions)] -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct TracingConfigurationBuilder { filter: Option, propagate_otel_context: bool, + request_headers: IncludeExclude, + response_headers: IncludeExclude, +} + +impl Default for TracingConfigurationBuilder { + fn default() -> Self { + Self { + filter: None, + propagate_otel_context: false, + request_headers: IncludeExclude::include_none(), + response_headers: IncludeExclude::include_none(), + } + } } impl From for TracingConfigurationBuilder { @@ -102,6 +188,8 @@ impl From for TracingConfigurationBuilder { Self { filter: tracing_configuration.filter, propagate_otel_context: tracing_configuration.propagate_otel_context, + request_headers: tracing_configuration.request_headers, + response_headers: tracing_configuration.response_headers, } } } @@ -117,7 +205,7 @@ impl TracingConfigurationBuilder { self } - /// Sets `propagate_otel_context` which indicates whether Open Telelmetry context propagation + /// Sets `propagate_otel_context` which indicates whether OpenTelemetry context propagation /// headers should be set on network API calls. #[must_use] pub fn set_propagate_otel_context(mut self, propagate_otel_context: bool) -> Self { @@ -125,26 +213,80 @@ impl TracingConfigurationBuilder { self } + /// Set the request headers to include in the trace request attributes. As per + /// OpenTelemetry semantic conventions, these will be included as + /// `rpc.grpc.request.metadata.{key}` attributes. + #[must_use] + pub fn set_request_headers(mut self, request_headers: IncludeExclude) -> Self { + self.request_headers = request_headers; + self + } + + /// Set the response headers to include in the trace response attributes. As per + /// OpenTelemetry semantic conventions, these will be included as + /// `rpc.grpc.response.metadata.{key}` attributes. + #[must_use] + pub fn set_response_headers(mut self, response_headers: IncludeExclude) -> Self { + self.response_headers = response_headers; + self + } + /// Build a [`TracingConfiguration`] based on this builder's values. #[must_use] pub fn build(self) -> TracingConfiguration { TracingConfiguration { filter: self.filter, propagate_otel_context: self.propagate_otel_context, + request_headers: self.request_headers, + response_headers: self.response_headers, } } } /// Configuration for tracing of network API calls. Note, this does not configure any trace /// processing or collector. Rather, it configures which network API calls should be traced. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct TracingConfiguration { - /// An optional [`TracingFilter`]. + /// An optional [`TracingFilter`]. If `None`, all network API calls will be traced. filter: Option, - /// Whether or not API calls should set Open Telemetry context propagation headers. + /// Whether or not API calls should set OpenTelemetry context propagation headers. propagate_otel_context: bool, + /// The headers to include in the trace request attributes. As per + /// OpenTelemetry semantic conventions, these will be included as + /// `rpc.grpc.request.metadata.{key}` attributes. + request_headers: IncludeExclude, + /// The headers to include in the trace response attributes. As per + /// OpenTelemetry semantic conventions, these will be included as + /// `rpc.grpc.response.metadata.{key}` attributes. + response_headers: IncludeExclude, +} + +impl Default for TracingConfiguration { + fn default() -> Self { + Self { + filter: None, + propagate_otel_context: false, + request_headers: IncludeExclude::Include( + [KEY_X_REQUEST_ID, KEY_X_REQUEST_RETRY_INDEX] + .iter() + .map(ToString::to_string) + .collect(), + ), + response_headers: IncludeExclude::Include( + std::iter::once(KEY_X_REQUEST_ID.to_string()).collect(), + ), + } + } } +/// The canonical metadata key for request and response request ids. +const KEY_X_REQUEST_ID: &str = "x-request-id"; + +/// A metadata key that may be used to indicate the number of times a request has been +/// retried. This may be useful to distinguish between retried requests that share +/// the same request id. +const KEY_X_REQUEST_RETRY_INDEX: &str = "x-request-retry-index"; + impl TracingConfiguration { #![allow(clippy::missing_const_for_fn)] @@ -169,6 +311,7 @@ impl TracingConfiguration { Ok(Some(Self { filter, propagate_otel_context, + ..Self::default() })) } @@ -178,13 +321,25 @@ impl TracingConfiguration { self.filter.as_ref() } - /// Indicates whether Open Telemetry context propagation headers should be set on + /// Indicates whether OpenTelemetry context propagation headers should be set on /// API calls. #[must_use] pub fn propagate_otel_context(&self) -> bool { self.propagate_otel_context } + /// Get the request headers that should be included in the trace request attributes. + #[must_use] + pub fn request_headers(&self) -> &IncludeExclude { + &self.request_headers + } + + /// Get the response headers that should be included in the trace response attributes. + #[must_use] + pub fn response_headers(&self) -> &IncludeExclude { + &self.response_headers + } + /// Returns `true` if the specified URL should be traced. For details on how this is determined, /// see [`TracingFilter`]. /// diff --git a/qcs-api-client-grpc/CHANGELOG.md b/qcs-api-client-grpc/CHANGELOG.md index 42acc23..1082eee 100644 --- a/qcs-api-client-grpc/CHANGELOG.md +++ b/qcs-api-client-grpc/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.11.0-rc.0 (2024-10-03) + +### Breaking Changes + +#### update http and support grpc trace layer + +### Fixes + +#### misc mr feedback + +#### avoid overloading clone copy rust semantics + +#### tracing configuration features import + +#### avoid string deref + +#### update tonic per cargo-deny ddos warning + ## 0.10.2 (2024-09-17) ### Fixes diff --git a/qcs-api-client-grpc/Cargo.toml b/qcs-api-client-grpc/Cargo.toml index 3d32297..00135eb 100644 --- a/qcs-api-client-grpc/Cargo.toml +++ b/qcs-api-client-grpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qcs-api-client-grpc" -version = "0.10.2" +version = "0.11.0-rc.0" edition = "2021" license = "Apache-2.0" readme = "README.md" @@ -9,57 +9,55 @@ homepage = "https://github.com/rigetti/qcs-api-client-rust" categories = ["api-bindings", "authentication"] keywords = ["qcs", "quantum", "rigetti", "api"] description = "gRPC client for the QCS API" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -qcs-api-client-common = { path = "../qcs-api-client-common", version = "0.10.2" } -http-body = "0.4.5" -hyper = "0.14.23" -hyper-proxy = { version = "0.9.1", default-features = false, features = [ - "rustls", -] } -hyper-socks2 = { version = "0.8.0", default-features = false } -pbjson = "0.6.0" -pbjson-types = "0.6.0" -prost = "0.12.3" -serde = "1.0" -tonic = { version = "0.11.0", features = ["tls-roots"] } -tonic-web = { version = "0.11.0", optional = true } -tower = { version = "0.4", features = ["retry"] } -thiserror = "1.0.32" -tracing = { version = "0.1.37", optional = true } -# These have to be kept in sync with tracing-opentelemetry (dev-dependency) +backoff = { workspace = true, features = ["tokio"] } +futures-util = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } +hyper-proxy2 = { workspace = true, default-features = false, features = ["rustls"] } +hyper-socks2 = { workspace = true, default-features = false } +hyper-util = { workspace = true, features = ["client-legacy"] } opentelemetry = { workspace = true, optional = true } -opentelemetry_api = { workspace = true, optional = true } opentelemetry-http = { workspace = true, optional = true } -urlpattern = { version = "0.2.0", optional = true } -url = { version = "2.3.1" } -http = "0.2.8" -backoff = { version = "0.4.0", features = ["tokio"] } -tokio = { version = "1.24.1", features = ["time"] } +opentelemetry_sdk = { workspace = true } +pbjson = { workspace = true } +pbjson-types = { workspace = true } +prost = { workspace = true } +qcs-api-client-common = { path = "../qcs-api-client-common", version = "0.11.0-rc.0" } +serde = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tonic = { workspace = true, features = ["tls-roots"] } +tonic-web = { workspace = true, optional = true } +tower = { workspace = true, features = ["retry"] } +tower-http = { workspace = true, features = ["trace"] } +tracing = { workspace = true, optional = true } +tracing-opentelemetry = { workspace = true } +url = { workspace = true } +urlpattern = { workspace = true, optional = true } +uuid = { workspace = true, features = ["v4"] } [dev-dependencies] -async-std = { version = "1.12.0", features = ["attributes"] } -futures-util = "0.3.25" +async-std = { workspace = true, features = ["attributes"] } jsonwebtoken = { workspace = true } -once_cell = "1.17.0" -# These have to be kept in sync with tracing-opentelemetry -opentelemetry = { workspace = true, features = ["trace", "rt-tokio"] } -opentelemetry_sdk = { workspace = true } -rstest = "0.17.0" -serde_json = "1.0.91" -tempfile = "3.3.0" -tokio = { version = "1.24.1", features = ["parking_lot", "rt-multi-thread"] } -tokio-stream = { version = "0.1.11", features = ["net"] } -tonic-health = "0.11.0" -tracing-opentelemetry = { workspace = true } +once_cell = { workspace = true } +opentelemetry = { workspace = true, features = ["trace"] } +rstest = { workspace = true } +serde_json = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true, features = ["parking_lot", "rt-multi-thread"] } +tokio-stream = { workspace = true, features = ["net"] } +tonic-health = { workspace = true } tracing-subscriber = { workspace = true } [build-dependencies] -tonic-build = "0.11.0" -pbjson-build = "0.6.0" -prost-build = "0.12.3" +pbjson-build = { workspace = true } +prost-build = { workspace = true } +tonic-build = { workspace = true } [features] server = [] @@ -68,6 +66,19 @@ regen = [] grpc-web = ["dep:tonic-web"] # The old name of the tracing-opentelemetry feature, here for backwards compatibility otel-tracing = ["tracing-opentelemetry"] -tracing-opentelemetry = ["dep:opentelemetry", "dep:opentelemetry_api", "dep:opentelemetry-http", "tracing-config"] -tracing = ["dep:tracing", "urlpattern", "qcs-api-client-common/tracing"] -tracing-config = ["tracing", "qcs-api-client-common/tracing-config"] +tracing-opentelemetry = [ + "dep:opentelemetry", + "dep:opentelemetry-http", + "qcs-api-client-common/tracing-config", + "tracing-config", + "tracing", +] +tracing = [ + "dep:tracing", + "qcs-api-client-common/tracing-config", + "urlpattern", +] +tracing-config = [ + "qcs-api-client-common/tracing-config", + "tracing", +] diff --git a/qcs-api-client-grpc/README.md b/qcs-api-client-grpc/README.md index b6076a8..7ab2224 100644 --- a/qcs-api-client-grpc/README.md +++ b/qcs-api-client-grpc/README.md @@ -28,8 +28,17 @@ There are some caveats to the proxy configuration: - If both variables are defined, neither can be a `socks5` proxy, unless they are both the same value. - If only one variable is defined, and it is a `socks5` proxy, *all* traffic will routed through it. +## Tracing + +This crates also supports tracing via [`tower_http::trace`][tower_http]. It additionally customizes spans according +to [OpenTelemetry gRPC semantic conventions][otel_semconv]. + +This functionality is available using the "tracing" feature. The "tracing-opentelemetry" feature extends capabilities by supporting dynamically configured span attributes (such as "rpc.grpc.request.metadata.") and context propagation. See [`qcs_api_client_common`][qcs_api_client_common] for configuration details. [crates.io]: https://crates.io/crates/qcs-api-client-grpc [docs.rs]: https://docs.rs/qcs-api-client-grpc [get_channel]: https://docs.rs/qcs-api-client-grpc/latest/qcs_api_client_grpc/fn.get_channel.html [wrap_channel]: https://docs.rs/qcs-api-client-grpc/latest/qcs_api_client_grpc/fn.wrap_channel.html +[tower_http]: https://docs.rs/tower-http/latest/tower_http/trace/index.html +[otel_semconv]: https://opentelemetry.io/docs/specs/semconv/rpc/grpc/ +[qcs_api_client_common]: https://docs.rs/qcs-api-client-common/latest/qcs_api_client_common/ diff --git a/qcs-api-client-grpc/src/tonic/channel.rs b/qcs-api-client-grpc/src/tonic/channel.rs index 7e384eb..09e9b33 100644 --- a/qcs-api-client-grpc/src/tonic/channel.rs +++ b/qcs-api-client-grpc/src/tonic/channel.rs @@ -4,13 +4,10 @@ use std::time::Duration; use backoff::ExponentialBackoff; -use http::{ - uri::{InvalidUri, Scheme}, - Uri, -}; -use hyper::client::HttpConnector; -use hyper_proxy::{Intercept, Proxy, ProxyConnector}; +use http::{uri::InvalidUri, Uri}; +use hyper_proxy2::{Intercept, Proxy, ProxyConnector}; use hyper_socks2::{Auth, SocksConnector}; +use hyper_util::client::legacy::connect::HttpConnector; use tonic::{ body::BoxBody, client::GrpcService, @@ -20,11 +17,15 @@ use tower::{Layer, ServiceBuilder}; use url::Url; use qcs_api_client_common::{ - backoff, - backoff::default_backoff, + backoff::{self, default_backoff}, configuration::{ClientConfiguration, LoadError, TokenError, TokenRefresher}, }; +#[cfg(feature = "tracing")] +use qcs_api_client_common::tracing_configuration::TracingConfiguration; + +#[cfg(feature = "tracing")] +use super::trace::{build_trace_layer, CustomTraceLayer, CustomTraceService}; use super::{Error, RefreshLayer, RefreshService, RetryLayer, RetryService}; /// Errors that may occur when configuring a channel connection @@ -143,31 +144,60 @@ where #[derive(Clone, Debug)] pub struct ChannelBuilder { endpoint: Endpoint, + #[cfg(feature = "tracing")] + trace_layer: CustomTraceLayer, options: O, } impl From for ChannelBuilder<()> { fn from(endpoint: Endpoint) -> Self { - Self { + #[cfg(feature = "tracing")] + { + let base_url = endpoint.uri().to_string(); + Self { + endpoint, + trace_layer: build_trace_layer(base_url, None), + options: (), + } + } + + #[cfg(not(feature = "tracing"))] + return Self { endpoint, options: (), - } + }; } } impl ChannelBuilder<()> { /// Create a [`ChannelBuilder`] using the given [`Uri`] pub fn from_uri(uri: Uri) -> Self { - Self { + #[cfg(feature = "tracing")] + { + let base_url = uri.to_string(); + Self { + endpoint: get_endpoint(uri), + trace_layer: build_trace_layer(base_url, None), + options: (), + } + } + + #[cfg(not(feature = "tracing"))] + return Self { endpoint: get_endpoint(uri), options: (), - } + }; } } +#[cfg(feature = "tracing")] +type TargetService = CustomTraceService; +#[cfg(not(feature = "tracing"))] +type TargetService = Channel; + impl ChannelBuilder where - O: IntoService, + O: IntoService, { /// Wrap the channel with a timeout. #[must_use] @@ -184,13 +214,23 @@ where where T: TokenRefresher + Clone + Send + Sync, { - ChannelBuilder { + #[cfg(feature = "tracing")] + return ChannelBuilder { endpoint: self.endpoint, + trace_layer: self.trace_layer, options: RefreshOptions { layer, other: self.options, }, - } + }; + #[cfg(not(feature = "tracing"))] + return ChannelBuilder { + endpoint: self.endpoint, + options: RefreshOptions { + layer, + other: self.options, + }, + }; } /// Wrap the channel with QCS authentication using the given [`TokenRefresher`]. @@ -206,7 +246,18 @@ where self, config: ClientConfiguration, ) -> ChannelBuilder> { - self.with_token_refresher(config) + #[cfg(feature = "tracing")] + { + let base_url = self.endpoint.uri().to_string(); + let trace_layer = build_trace_layer(base_url, config.tracing_configuration()); + let mut builder = self.with_token_refresher(config); + builder.trace_layer = trace_layer; + builder + } + #[cfg(not(feature = "tracing"))] + { + self.with_token_refresher(config) + } } /// Wrap the channel with QCS authentication for the given QCS profile. @@ -228,13 +279,23 @@ where /// Wrap the channel with the given [`RetryLayer`]. pub fn with_retry_layer(self, layer: RetryLayer) -> ChannelBuilder> { - ChannelBuilder { + #[cfg(feature = "tracing")] + return ChannelBuilder { endpoint: self.endpoint, + trace_layer: self.trace_layer, options: RetryOptions { layer, other: self.options, }, - } + }; + #[cfg(not(feature = "tracing"))] + return ChannelBuilder { + endpoint: self.endpoint, + options: RetryOptions { + layer, + other: self.options, + }, + }; } /// Wrap the channel with the given [`ExponentialBackoff`] configuration. @@ -257,6 +318,13 @@ where /// Returns a [`ChannelError`] if the service cannot be built. pub fn build(self) -> Result { let channel = get_channel_with_endpoint(&self.endpoint)?; + #[cfg(feature = "tracing")] + { + let traced_channel = self.trace_layer.layer(channel); + Ok(self.options.into_service(traced_channel)) + } + + #[cfg(not(feature = "tracing"))] Ok(self.options.into_service(channel)) } } @@ -385,7 +453,7 @@ pub fn get_channel_with_endpoint(endpoint: &Endpoint) -> Result { @@ -397,7 +465,7 @@ pub fn get_channel_with_endpoint(endpoint: &Endpoint) -> Result { - let is_http = uri.scheme() == Some(&Scheme::HTTP); + let is_http = uri.scheme() == Some(&http::uri::Scheme::HTTP); let proxy = Proxy::new(intercept, uri); let mut proxy_connector = ProxyConnector::from_proxy(connector, proxy)?; if is_http { @@ -530,3 +598,15 @@ where .layer(RetryLayer::default()) .service(channel) } + +#[cfg(feature = "tracing")] +/// Add a tracing layer with OpenTelemetry semantics to the `channel`. +pub fn wrap_channel_with_tracing( + channel: Channel, + base_url: String, + configuration: TracingConfiguration, +) -> CustomTraceService { + ServiceBuilder::new() + .layer(build_trace_layer(base_url, Some(&configuration))) + .service(channel) +} diff --git a/qcs-api-client-grpc/src/tonic/common.rs b/qcs-api-client-grpc/src/tonic/common.rs new file mode 100644 index 0000000..838edc7 --- /dev/null +++ b/qcs-api-client-grpc/src/tonic/common.rs @@ -0,0 +1,77 @@ +// Copyright 2024 Rigetti Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains common utilities for the tonic client. + +/// The HTTP2 header to check for the gRPC status code. +/// See . +const GRPC_STATUS_CODE_HEADER_NAME: &str = "grpc-status"; + +#[derive(Debug)] +pub(crate) enum ParsedStatusCodeError { + StatusHeaderMissing, + HeaderNotString, + HeaderNotInt, +} + +/// Extract the gRPC status code from the headers of a response. +/// +/// # Errors +/// +/// See [`ParsedStatusCodeError`] for the possible error variants. +pub(crate) fn get_status_code_from_headers( + header_map: &http::header::HeaderMap, +) -> Result { + header_map + .get(GRPC_STATUS_CODE_HEADER_NAME) + .ok_or(ParsedStatusCodeError::StatusHeaderMissing) + .and_then(|status| { + status + .to_str() + .map_err(|_| ParsedStatusCodeError::HeaderNotString) + }) + .and_then(|status| { + status + .parse::() + .map_err(|_| ParsedStatusCodeError::HeaderNotInt) + }) + .map(tonic::Code::from) +} + +/// Wrap the future in [`opentelemetry::trace::WithContext`] if the "tracing-opentelemetry" feature +/// is enabled. This ensures the OpenTelemetry context is propagated across async boundaries. The +/// result is then pinned and boxed. +/// +/// See for more +/// information. +/// +/// Note, this function is intended to draw attention to the common pattern of wrapping +/// futures within [`Box::pin`] when [`tonic::client::GrpcService::call`] requires +/// asynchonous processing and we want the OpenTelemetry context propagated. +#[cfg(feature = "tracing-opentelemetry")] +pub(super) fn pin_future_with_otel_context_if_available( + fut: F, +) -> std::pin::Pin>> { + Box::pin(opentelemetry::trace::FutureExt::with_current_context(fut)) +} + +/// Pin and box a future, as is common when [`tonic::client::GrpcService::call`] requires +/// asynchronous processing. +/// +/// This trivial implementation is used to distinguish between the implementation +/// with and without the "tracing-opentelemetry" feature. +#[cfg(not(feature = "tracing-opentelemetry"))] +pub(super) fn pin_future_with_otel_context_if_available(fut: F) -> std::pin::Pin> { + Box::pin(fut) +} diff --git a/qcs-api-client-grpc/src/tonic/error.rs b/qcs-api-client-grpc/src/tonic/error.rs index e18b5cd..4e57ab1 100644 --- a/qcs-api-client-grpc/src/tonic/error.rs +++ b/qcs-api-client-grpc/src/tonic/error.rs @@ -3,7 +3,7 @@ use tonic::transport::Error as TransportError; use qcs_api_client_common::configuration::LoadError; -use super::channel::ChannelError; +use super::{channel::ChannelError, RequestBodyDuplicationError}; /// Errors that may occur when using gRPC. #[derive(Debug, thiserror::Error)] @@ -33,4 +33,6 @@ where #[cfg(feature = "grpc-web")] #[error("The hyper grpc-web client returned an error: {0}")] HyperError(#[from] hyper::Error), + #[error("failed to duplicate request body for retry: {0}")] + CloneBody(#[from] RequestBodyDuplicationError), } diff --git a/qcs-api-client-grpc/src/tonic/grpc_web.rs b/qcs-api-client-grpc/src/tonic/grpc_web.rs index 3ed0f56..7656f95 100644 --- a/qcs-api-client-grpc/src/tonic/grpc_web.rs +++ b/qcs-api-client-grpc/src/tonic/grpc_web.rs @@ -78,7 +78,7 @@ where // Unpack the GrpcWebCall part let mut service = self.service.clone(); std::mem::swap(&mut self.service, &mut service); - Box::pin(async move { + super::common::pin_future_with_otel_context_if_available(async move { let headers = req.headers().clone(); let method = req.method().clone(); let uri = req.uri().clone(); diff --git a/qcs-api-client-grpc/src/tonic/mod.rs b/qcs-api-client-grpc/src/tonic/mod.rs index 12c696c..8e6fafc 100644 --- a/qcs-api-client-grpc/src/tonic/mod.rs +++ b/qcs-api-client-grpc/src/tonic/mod.rs @@ -1,22 +1,20 @@ /// QCS Middleware for [`tonic`] clients. -use http::StatusCode; -use http_body::{Body, Full}; -use tonic::{ - body::BoxBody, - client::GrpcService, - codegen::http::{Request, Response}, - Status, -}; - -#[cfg(feature = "tracing")] -use tonic::transport::Uri; +/// +use futures_util::pin_mut; +use http::Request; +use http_body::Body; +use http_body_util::BodyExt; +use tonic::body::BoxBody; mod channel; +mod common; mod error; #[cfg(feature = "grpc-web")] mod grpc_web; mod refresh; mod retry; +#[cfg(feature = "tracing")] +mod trace; pub use channel::*; pub use error::*; @@ -25,66 +23,59 @@ pub use grpc_web::*; pub use refresh::*; pub use retry::*; -use qcs_api_client_common::configuration::TokenRefresher; - -async fn service_call( - req: Request, - token_refresher: T, - mut channel: C, -) -> Result>::ResponseBody>, Error> -where - C: GrpcService + Send, - >::ResponseBody: Send, - >::Future: Send, - T: TokenRefresher + Send, - T::Error: std::error::Error, - Error: From, -{ - #[cfg(feature = "tracing")] - { - if should_trace( - &token_refresher, - &get_full_url_string(&token_refresher, req.uri()), - true, - ) { - tracing::debug!("making gRPC request to {}", req.uri()); - } - } - - let token = token_refresher - .validated_access_token() - .await - .map_err(Error::Refresh)?; - - let (req, retry_req) = clone_request(req).await; - let resp = make_request(&mut channel, req, token).await?; +/// An error observed while duplicating a request body. This may be returned by any +/// [`tower::Service`] that duplicates a request body for the purpose of retrying a request. +#[derive(Debug, thiserror::Error)] +pub enum RequestBodyDuplicationError { + /// The inner service returned an error from the server, or the client cancelled the + /// request. + #[error(transparent)] + Status(#[from] tonic::Status), + /// Failed to read the request body for cloning. + #[error("failed to read request body for request clone: {0}")] + HttpBody(#[from] http::Error), +} - match resp.status() { - StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { - // Refresh token and try again - let token = token_refresher - .refresh_access_token() - .await - .map_err(Error::Refresh)?; - make_request(&mut channel, retry_req, token).await +impl From for tonic::Status { + fn from(err: RequestBodyDuplicationError) -> tonic::Status { + match err { + RequestBodyDuplicationError::Status(status) => status, + RequestBodyDuplicationError::HttpBody(error) => tonic::Status::cancelled(format!( + "failed to read request body for request clone: {error}" + )), } - _ => Ok(resp), } } -async fn clone_body(body: Request) -> (BoxBody, BoxBody) { +type RequestBodyDuplicationResult = Result; + +async fn build_duplicate_frame_bytes( + mut request: Request, +) -> RequestBodyDuplicationResult<(tonic::body::BoxBody, tonic::body::BoxBody)> { let mut bytes = Vec::new(); - let mut body = body.into_body(); - while let Some(result) = body.data().await { - bytes.extend(result.expect("loading request body should not fail here")); + + let body = request.body_mut(); + pin_mut!(body); + while let Some(result) = std::future::poll_fn(|cx| body.as_mut().poll_frame(cx)).await { + let frame_bytes = result?.into_data().map_err(|frame| { + tonic::Status::cancelled(format!( + "cannot duplicate a frame that is not a data frame: {frame:?}" + )) + })?; + bytes.extend(frame_bytes); } - let bytes = - Full::from(bytes).map_err(|_| Status::internal("this will never happen from Infallible")); - (BoxBody::new(bytes.clone()), BoxBody::new(bytes)) + let bytes = http_body_util::Full::from(bytes) + .map_err(|_| tonic::Status::cancelled("failed to initialize single chunk body")); + Ok(( + tonic::body::BoxBody::new(bytes.clone()), + tonic::body::BoxBody::new(bytes), + )) } -async fn clone_request(req: Request) -> (Request, Request) { +async fn build_duplicate_request( + req: Request, +) -> RequestBodyDuplicationResult<(Request, Request)> { let mut builder_1 = Request::builder() .method(req.method().clone()) .uri(req.uri().clone()) @@ -100,263 +91,131 @@ async fn clone_request(req: Request) -> (Request, Request( - service: &mut C, - mut request: Request, - token: String, -) -> Result>::ResponseBody>, Error> -where - C: GrpcService + Send, - >::ResponseBody: Send, - >::Future: Send, - Error: From, -{ - let header_val = format!("Bearer {token}") - .try_into() - .map_err(Error::InvalidAccessToken)?; - request.headers_mut().insert("authorization", header_val); - service.call(request).await.map_err(Error::from) -} - -#[cfg(feature = "tracing")] -fn get_full_url_string(token_refresher: &T, uri: &Uri) -> String { - format!("{}{}", token_refresher.base_url(), uri) -} - -#[cfg(feature = "tracing")] -fn should_trace(token_refresher: &T, url_str: &str, default: bool) -> bool { - use urlpattern::UrlPatternMatchInput; - - let url = url_str.parse::<::url::Url>().ok(); - - url.map_or(default, |url| { - token_refresher.should_trace(&UrlPatternMatchInput::Url(url)) - }) -} - -#[cfg(feature = "tracing-opentelemetry")] -async fn make_traced_request( - service: &mut C, - mut request: Request, - token: String, -) -> Result>::ResponseBody>, Error> -where - C: GrpcService + Send, - >::Future: Send, - Error: From, -{ - use opentelemetry::trace::FutureExt; - use tracing::Instrument; - - let header_val = format!("Bearer {token}") - .try_into() - .map_err(Error::InvalidAccessToken)?; - request.headers_mut().insert("authorization", header_val); - - let span = make_grpc_request_span(&request); - service - .call(request) - .with_current_context() - .instrument(span) - .await - .map_err(Error::from) -} - -#[cfg(feature = "tracing-opentelemetry")] -async fn traced_service_call( - original_req: Request, - config: T, - mut channel: C, -) -> Result>::ResponseBody>, Error> -where - C: GrpcService + Send, - >::ResponseBody: Send, - >::Future: Send, - T::Error: std::error::Error, - Error: From, -{ - use opentelemetry::{propagation::TextMapPropagator, sdk::propagation::TraceContextPropagator}; - use opentelemetry_api::trace::FutureExt; - use opentelemetry_http::HeaderInjector; - use qcs_api_client_common::tracing_configuration::TracingConfiguration; - - // The request URI here doesn't include the base url, so we have to manually add it here to evaluate request filter patterns. - let full_request_url = format!("{}{}", config.base_url(), &original_req.uri()); - - if should_trace(&config, &full_request_url, true) { - tracing::debug!("making traced gRPC request to {}", full_request_url); - } - - let should_otel_trace = - config.tracing_configuration().is_some() && should_trace(&config, &full_request_url, false); - - if !should_otel_trace { - return service_call(original_req, config, channel).await; - } - - let token = config - .validated_access_token() - .with_current_context() - .await - .map_err(Error::Refresh)?; - - let (mut req, mut retry_req) = clone_request(original_req).with_current_context().await; - - if config - .tracing_configuration() - .is_some_and(TracingConfiguration::propagate_otel_context) - { - // Poor semantics here, but adding custom gRPC metadata is equivalent to setting request - // headers: https://chromium.googlesource.com/external/github.com/grpc/grpc/+/HEAD/doc/PROTOCOL-HTTP2.md. - let propagator = TraceContextPropagator::new(); - let mut injector = HeaderInjector(req.headers_mut()); - propagator.inject_context(&opentelemetry::Context::current(), &mut injector); - } - - let resp = make_traced_request(&mut channel, req, token) - .with_current_context() - .await?; - - match resp.status() { - StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => { - tracing::info!("refreshing token after receiving unauthorized or forbidden status",); - // Refresh token and try again - let token = config - .refresh_access_token() - .with_current_context() - .await - .map_err(Error::Refresh)?; - tracing::info!("token refreshed"); - let propagator = TraceContextPropagator::new(); - let mut injector = HeaderInjector(retry_req.headers_mut()); - propagator.inject_context(&opentelemetry::Context::current(), &mut injector); - - make_traced_request(&mut channel, retry_req, token) - .with_current_context() - .await - } - _ => Ok(resp), - } -} - -#[cfg(feature = "tracing-opentelemetry")] -static GRPC_SPAN_NAME: &str = "gRPC request"; - -/// Creates a gRPC request span that conforms to the gRPC semantic conventions. See for details. -#[cfg(feature = "tracing-opentelemetry")] -fn make_grpc_request_span(request: &Request) -> tracing::Span { - let _method = request.method(); - let url = request.uri(); - let path = url.path(); - let mut path_split = path.split('/'); - let (_, service, method) = (path_split.next(), path_split.next(), path_split.next()); - let service = service.unwrap_or(""); - let method = method.unwrap_or(""); - let _scheme = url.scheme(); - let host = url.host().unwrap_or(""); - let host_port = url.port().map_or(0u16, |p| p.as_u16()); - tracing::span!( - tracing::Level::INFO, - GRPC_SPAN_NAME, - rpc.system = "grpc", - rpc.service = %service, - rpc.method = %method, - net.peer.name = %host, - net.peer.port = %host_port, - "message.type" = "sent", - // We would like to include this attribute according to the gRPC semantic conventions, but - // the issue is we cannot record it on the span until trailers have been received (in order - // to get the gRPC status code). The current way the tower layer is setup does not allow us - // to do this. - // rpc.grpc.status_code = i32::from(Code::Unknown as u8), - otel.kind = "client", - otel.name = %path, - ) + Ok((req_1, req_2)) } /// This module manages a gRPC server-client connection over a Unix domain socket. Useful for unit testing /// servers or clients within unit tests - supports parallelization within same process and /// requires no port management. /// -/// Derived largely from and -/// +/// Derived largely from and +/// . #[cfg(test)] pub(crate) mod uds_grpc_stream { + use hyper_util::rt::TokioIo; + use opentelemetry::trace::FutureExt; use std::convert::Infallible; - use std::sync::Arc; - use tempfile::NamedTempFile; - use tokio::net::{UnixListener, UnixStream}; + use tempfile::TempDir; + use tokio::net::UnixStream; use tokio_stream::wrappers::UnixListenerStream; - use tonic::client::GrpcService; use tonic::server::NamedService; - use tonic::transport::{Channel, Endpoint, Server, Uri}; - use tower::service_fn; + use tonic::transport::{Channel, Endpoint, Server}; /// The can be any valid URL. It is necessary to initialize an [`Endpoint`]. #[allow(dead_code)] static FAUX_URL: &str = "http://api.example.rigetti.com"; - async fn build_uds_stream() -> Result<(UnixListenerStream, Channel), String> { - let socket = NamedTempFile::new().unwrap(); - let socket = Arc::new(socket.into_temp_path()); - std::fs::remove_file(&*socket).unwrap(); - - let uds = UnixListener::bind(&*socket).unwrap(); - let stream = UnixListenerStream::new(uds); + fn build_server_stream(path: String) -> Result { + let uds = + tokio::net::UnixListener::bind(path.clone()).map_err(|_| Error::BindUnixPath(path))?; + Ok(UnixListenerStream::new(uds)) + } - let socket = Arc::clone(&socket); - // Connect to the server over a Unix socket - // The URL will be ignored. + async fn build_client_channel(path: String) -> Result { + let connector = tower::service_fn(move |_: tonic::transport::Uri| { + let path = path.clone(); + async move { + let connection = UnixStream::connect(path).await?; + Ok::<_, std::io::Error>(TokioIo::new(connection)) + } + }); let channel = Endpoint::try_from(FAUX_URL) - .unwrap() - .connect_with_connector(service_fn(move |_: Uri| { - let socket = Arc::clone(&socket); - async move { UnixStream::connect(&*socket).await } - })) + .map_err(|source| Error::Endpoint { + url: FAUX_URL.to_string(), + source, + })? + .connect_with_connector(connector) .await - .map_err(|e| format!("failed to connect to server: {e}"))?; + .map_err(|source| Error::Connect { source })?; + Ok(channel) + } - Ok((stream, channel)) + /// Errors when connecting a server-client [`Channel`] over a Unix domain socket for testing gRPC + /// services. + #[derive(thiserror::Error, Debug)] + pub enum Error { + /// Failed to initialize an [`Endpoint`] for the provided URL. + #[error("failed to initialize endpoint for {url}: {source}")] + Endpoint { + /// The URL that failed to initialize. + url: String, + /// The source of the error. + #[source] + source: tonic::transport::Error, + }, + /// Failed to connect to the provided endpoint. + #[error("failed to connect to endpoint: {source}")] + Connect { + /// The source of the error. + #[source] + source: tonic::transport::Error, + }, + /// Failed to bind the provided path as a Unix domain socket. + #[error("failed to bind path as unix listener: {0}")] + BindUnixPath(String), + /// Failed to initialize a temporary file for the Unix domain socket. + #[error("failed in initialize tempfile: {0}")] + TempFile(#[from] std::io::Error), + /// Failed to convert the tempfile to an [`OsString`]. + #[error("failed to convert tempfile to OsString")] + TempFileOsString, + /// Failed to bind to the Unix domain socket. + #[error("failed to bind to UDS: {0}")] + TonicTransport(#[from] tonic::transport::Error), } - /// Serve the provide gRPC service over a Unix domain socket for the duration of the + /// Serve the provided gRPC service over a Unix domain socket for the duration of the /// provided callback. - pub(crate) async fn serve(service: S, f: F) -> Result<(), String> + /// + /// # Errors + /// + /// See [`Error`]. + #[allow(clippy::significant_drop_tightening)] + pub async fn serve(service: S, f: F) -> Result<(), Error> where S: tower::Service< - http::Request, + http::Request, Response = http::Response, Error = Infallible, > + NamedService - + GrpcService + Clone + Send + 'static, + S::Future: Send, F: FnOnce(Channel) -> R + Send, R: std::future::Future + Send, - >>::Future: Send, { - let (stream, channel) = build_uds_stream().await.unwrap(); + let directory = TempDir::new()?; + let file = directory.path().as_os_str(); + let file = file.to_os_string(); + let file = file.into_string().map_err(|_| Error::TempFileOsString)?; + let file = format!("{file}/test.sock"); + let stream = build_server_stream(file.clone())?; + + let channel = build_client_channel(file).await?; let serve_future = Server::builder() .add_service(service) .serve_with_incoming(stream); tokio::select! { - result = serve_future => result.map_err(|e| format!("server unexpectedly exited with error: {e}")), - () = f(channel) => Ok(()), + result = serve_future => result.map_err(Error::TonicTransport), + () = f(channel).with_current_context() => Ok(()), } } } @@ -377,16 +236,14 @@ mod otel_tests { use tonic_health::pb::health_server::{Health, HealthServer}; use tonic_health::{pb::health_client::HealthClient, server::HealthService}; - use crate::tonic::uds_grpc_stream; - use qcs_api_client_common::configuration::AuthServer; + use crate::tonic::{uds_grpc_stream, wrap_channel_with_tracing}; use qcs_api_client_common::configuration::ClientConfiguration; - use qcs_api_client_common::configuration::OAuthSession; - use qcs_api_client_common::configuration::RefreshToken; - - use super::channel::{wrap_channel_with, wrap_channel_with_token_refresher}; + use qcs_api_client_common::configuration::{AuthServer, OAuthSession, RefreshToken}; static HEALTH_CHECK_PATH: &str = "/grpc.health.v1.Health/Check"; + const FAUX_BASE_URL: &str = "http://api.example.rigetti.com"; + /// Test that when tracing is enabled and no filter is set, any request is properly traced. #[tokio::test] async fn test_tracing_enabled_no_filter() { @@ -482,9 +339,13 @@ mod otel_tests { uds_grpc_stream::serve(health_server, |channel| { async { - let response = HealthClient::new(wrap_channel_with_token_refresher( + let response = HealthClient::new(wrap_channel_with_tracing( channel, - client_configuration, + FAUX_BASE_URL.to_string(), + client_configuration + .tracing_configuration() + .unwrap() + .clone(), )) .check(Request::new(tonic_health::pb::HealthCheckRequest { service: as NamedService>::NAME @@ -512,20 +373,11 @@ mod otel_tests { .duration_since(grpc_span.start_time) .expect("span should have valid timestamps"); assert!(duration.as_millis() >= 50u128); - } - /// Test that when tracing is disabled, the request is not traced. - #[tokio::test] - async fn test_tracing_disabled() { - let client_config = ClientConfiguration::builder() - .oauth_session(Some(OAuthSession::from_refresh_token( - RefreshToken::new("refresh_token".to_string()), - AuthServer::default(), - Some(create_jwt()), - ))) - .build() - .expect("should not fail to build client config"); - assert_grpc_health_check_not_traced(client_config).await; + let status_code_attribute = + tracing_test::get_span_attribute(grpc_span, "rpc.grpc.status_code") + .expect("gRPC span should have status code attribute"); + assert_eq!(status_code_attribute, (tonic::Code::Ok as u8).to_string()); } /// Test that when tracing is enabled but the request does not match the configured filter, the @@ -576,14 +428,20 @@ mod otel_tests { uds_grpc_stream::serve(health_server, |channel| { async { - let response = - HealthClient::new(wrap_channel_with(channel, client_configuration)) - .check(Request::new(tonic_health::pb::HealthCheckRequest { - service: as NamedService>::NAME - .to_string(), - })) - .await - .unwrap(); + let response = HealthClient::new(wrap_channel_with_tracing( + channel, + FAUX_BASE_URL.to_string(), + client_configuration + .tracing_configuration() + .unwrap() + .clone(), + )) + .check(Request::new(tonic_health::pb::HealthCheckRequest { + service: as NamedService>::NAME + .to_string(), + })) + .await + .unwrap(); assert_eq!(response.into_inner().status(), ServingStatus::Serving); } .with_current_context() @@ -883,9 +741,22 @@ mod otel_tests { Ok(()) } - fn shutdown(&mut self) -> TraceResult<()> { + fn shutdown(&self) -> TraceResult<()> { Ok(()) } } + + /// Get the Opentelemetry attribute value for the provided key. + #[must_use] + pub(super) fn get_span_attribute( + span_data: &SpanData, + key: &'static str, + ) -> Option { + span_data + .attributes + .iter() + .find(|attr| attr.key.to_string() == *key) + .map(|kv| kv.value.to_string()) + } } } diff --git a/qcs-api-client-grpc/src/tonic/refresh.rs b/qcs-api-client-grpc/src/tonic/refresh.rs index b38bba5..f5650a0 100644 --- a/qcs-api-client-grpc/src/tonic/refresh.rs +++ b/qcs-api-client-grpc/src/tonic/refresh.rs @@ -4,6 +4,7 @@ use std::{ task::{Context, Poll}, }; +use http::StatusCode; use tonic::{ body::BoxBody, client::GrpcService, @@ -13,12 +14,6 @@ use tower::Layer; use qcs_api_client_common::configuration::{ClientConfiguration, TokenError, TokenRefresher}; -#[cfg(feature = "tracing-opentelemetry")] -use super::traced_service_call; - -#[cfg(not(feature = "tracing-opentelemetry"))] -use super::service_call; - use super::error::Error; /// The [`GrpcService`] that wraps the gRPC client in order to provide QCS authentication. @@ -114,17 +109,77 @@ where // https://github.com/tower-rs/tower/issues/547 let service = std::mem::replace(&mut self.service, service); let token_refresher = self.token_refresher.clone(); - Box::pin(async move { - #[cfg(feature = "tracing-opentelemetry")] - use opentelemetry_api::trace::FutureExt; - - #[cfg(feature = "tracing-opentelemetry")] - return traced_service_call(req, token_refresher, service) - .with_current_context() - .await; - - #[cfg(not(feature = "tracing-opentelemetry"))] - return service_call(req, token_refresher, service).await; - }) + super::common::pin_future_with_otel_context_if_available(service_call( + req, + token_refresher, + service, + )) + } +} + +async fn service_call( + req: Request, + token_refresher: T, + mut channel: C, +) -> Result>::ResponseBody>, Error> +where + C: GrpcService + Send, + >::ResponseBody: Send, + >::Future: Send, + T: TokenRefresher + Send, + T::Error: std::error::Error, + Error: From, +{ + let token = token_refresher + .validated_access_token() + .await + .map_err(Error::Refresh)?; + let (req, retry_req) = super::build_duplicate_request(req).await?; + let resp = make_request(&mut channel, req, token).await?; + + let grpc_authnz_failure = matches!( + super::common::get_status_code_from_headers(resp.headers()).ok(), + Some(tonic::Code::Unauthenticated) | Some(tonic::Code::PermissionDenied) + ); + let http_authnz_failure = + resp.status() == StatusCode::UNAUTHORIZED || resp.status() == StatusCode::FORBIDDEN; + + if grpc_authnz_failure || http_authnz_failure { + #[cfg(feature = "tracing")] + { + tracing::info!("refreshing token after receiving unauthorized or forbidden status",); + } + + // Refresh token and try again + let token = token_refresher + .validated_access_token() + .await + .map_err(Error::Refresh)?; + + #[cfg(feature = "tracing")] + { + tracing::info!("token refreshed"); + } + make_request(&mut channel, retry_req, token).await + } else { + Ok(resp) } } + +async fn make_request( + service: &mut C, + mut request: Request, + token: String, +) -> Result>::ResponseBody>, Error> +where + C: GrpcService + Send, + >::ResponseBody: Send, + >::Future: Send, + Error: From, +{ + let header_val = format!("Bearer {token}") + .try_into() + .map_err(Error::InvalidAccessToken)?; + request.headers_mut().insert("authorization", header_val); + service.call(request).await.map_err(Error::from) +} diff --git a/qcs-api-client-grpc/src/tonic/retry.rs b/qcs-api-client-grpc/src/tonic/retry.rs index c02d67f..47c0b21 100644 --- a/qcs-api-client-grpc/src/tonic/retry.rs +++ b/qcs-api-client-grpc/src/tonic/retry.rs @@ -1,5 +1,8 @@ -use http::{Request, Response}; -use qcs_api_client_common::backoff::{self, backoff::Backoff, ExponentialBackoff}; +use http::{HeaderValue, Request, Response}; +use qcs_api_client_common::{ + backoff::{self, backoff::Backoff, ExponentialBackoff}, + configuration::TokenError, +}; use tonic::{body::BoxBody, client::GrpcService, Status}; use qcs_api_client_common::backoff::duration_from_response as duration_from_http_response; @@ -10,7 +13,7 @@ use std::{ time::Duration, }; -use super::clone_request; +use super::{build_duplicate_request, RequestBodyDuplicationError}; use tower::Layer; /// The [`Layer`] used to apply exponential backoff retry logic to requests. @@ -41,6 +44,10 @@ impl> Layer for RetryLayer { /// The [`GrpcService`] that wraps the gRPC client in order to provide exponential backoff retry /// logic. /// +/// This middleware will add a `x-request-id` header to each request with a unique UUID and a +/// `x-request-retry-index` header with the number of retries that have been attempted for the +/// request. +/// /// See also: [`RetryLayer`]. #[derive(Clone, Debug)] pub struct RetryService> { @@ -68,17 +75,24 @@ where S: GrpcService + Send + Clone + 'static, S::Future: Send, S::ResponseBody: Send, + super::error::Error: From + From, { type ResponseBody = >::ResponseBody; - type Error = >::Error; + type Error = super::error::Error; type Future = Pin, Self::Error>> + Send>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) + self.service + .poll_ready(cx) + .map_err(super::error::Error::from) } fn call(&mut self, mut req: Request) -> Self::Future { + if let Ok(request_id) = new_request_id() { + req.headers_mut().insert(KEY_X_REQUEST_ID, request_id); + } + // Clone the `backoff` so that new requests don't reuse it // and so that the `backoff` can be moved into the async closure. let mut backoff = self.backoff.clone(); @@ -90,15 +104,25 @@ where // https://github.com/tower-rs/tower/issues/547 std::mem::swap(&mut self.service, &mut service); - Box::pin(async move { + super::common::pin_future_with_otel_context_if_available(async move { + let mut attempt = 0; loop { - let (request, retained) = clone_request(req).await; + let (mut request, retained) = build_duplicate_request(req).await?; req = retained; // Ensure that the service is ready before trying to use it. // Failure to do this *will* cause a panic. - poll_fn(|cx| service.poll_ready(cx)).await?; + poll_fn(|cx| service.poll_ready(cx)) + .await + .map_err(super::error::Error::from)?; + if let Ok(retry_index_header_value) = + http::HeaderValue::from_str(attempt.to_string().as_str()) + { + request + .headers_mut() + .insert(KEY_X_REQUEST_RETRY_INDEX, retry_index_header_value); + } let duration = match service.call(request).await { Ok(response) => { if let Some(duration) = duration_from_response(&response, &mut backoff) { @@ -107,15 +131,24 @@ where break Ok(response); } } - Err(error) => break Err(error), + Err(error) => break Err(super::error::Error::from(error)), }; tokio::time::sleep(duration).await; + attempt += 1; } }) } } +fn new_request_id() -> Result { + let request_id = uuid::Uuid::new_v4().to_string(); + HeaderValue::from_str(request_id.as_str()) +} + +const KEY_X_REQUEST_ID: &str = "x-request-id"; +const KEY_X_REQUEST_RETRY_INDEX: &str = "x-request-retry-index"; + #[cfg(test)] mod tests { use std::sync::atomic::{AtomicUsize, Ordering}; @@ -193,7 +226,8 @@ mod tests { let health_server = HealthServer::new(FlakyHealthService::default()); uds_grpc_stream::serve(health_server, |channel| async { - let response = HealthClient::new(wrap_channel_with_retry(channel)) + let wrapped_channel = wrap_channel_with_retry(channel); + let response = HealthClient::new(wrapped_channel) .check(Request::new(tonic_health::pb::HealthCheckRequest { service: as NamedService>::NAME.to_string(), })) diff --git a/qcs-api-client-grpc/src/tonic/trace/mod.rs b/qcs-api-client-grpc/src/tonic/trace/mod.rs new file mode 100644 index 0000000..ac0b1a2 --- /dev/null +++ b/qcs-api-client-grpc/src/tonic/trace/mod.rs @@ -0,0 +1,47 @@ +// Copyright 2024 Rigetti Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This modules supports tracing of gRPC requests using the +//! [`tower_http::trace::TraceLayer`]. This module customizes that functionality +//! by adhering to OpenTelemetry conventions for gRPC tracing (see +//! ). Note this is the +//! case whether the "tracing-opentelemetry" feature is enabled or not. +//! +//! The "tracing-opentelemetry" feature extends the base "tracing" feature in +//! two ways: +//! +//! 1. It adds additional, dynamically defined span attributes to the gRPC span +//! using [`tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute`]. This +//! is leveraged to add "rpc.grpc.{request, response}.metadata" attributes as +//! configured by the user. +//! 2. It supports propagation of the OpenTelemetry context via +//! [`opentelemetry_sdk::propagation::TraceContextPropagator`]. +//! +//! All of this behavior is configurable via +//! [`qcs_api_client_common::tracing_configuration::TracingConfiguration`]. +pub(super) mod shared; +#[cfg(not(feature = "tracing-opentelemetry"))] +mod trace_layer; +#[cfg(feature = "tracing-opentelemetry")] +mod trace_layer_otel_ext; + +#[cfg(feature = "tracing-opentelemetry")] +pub(crate) use trace_layer_otel_ext::{ + build_layer as build_trace_layer, CustomTraceLayer, CustomTraceService, +}; + +#[cfg(not(feature = "tracing-opentelemetry"))] +pub(crate) use trace_layer::{ + build_layer as build_trace_layer, CustomTraceLayer, CustomTraceService, +}; diff --git a/qcs-api-client-grpc/src/tonic/trace/shared.rs b/qcs-api-client-grpc/src/tonic/trace/shared.rs new file mode 100644 index 0000000..bd9c0d5 --- /dev/null +++ b/qcs-api-client-grpc/src/tonic/trace/shared.rs @@ -0,0 +1,103 @@ +// Copyright 2024 Rigetti Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shared utilities for tracing gRPC requests across the "tracing-opentelemetry" and +//! base "tracing" feature sets. +use qcs_api_client_common::tracing_configuration::TracingFilter; +use tower_http::classify::GrpcFailureClass; +use tracing::Span; +use tracing_opentelemetry::OpenTelemetrySpanExt; +use urlpattern::UrlPatternMatchInput; + +/// Creates a gRPC request span that conforms to the gRPC semantic conventions. +/// See +/// for details. +pub(super) fn make_grpc_request_span(request: &http::Request) -> tracing::Span { + let url = request.uri(); + let path = url.path(); + let mut path_split = path.split('/'); + let (_, service, method) = (path_split.next(), path_split.next(), path_split.next()); + let service = service.unwrap_or(""); + let method = method.unwrap_or(""); + let host = url.host().unwrap_or(""); + let host_port = url.port().map_or(0u16, |p| p.as_u16()); + + tracing::span!( + tracing::Level::INFO, + "gRPC request", + rpc.system = "grpc", + rpc.service = %service, + rpc.method = %method, + net.peer.name = %host, + net.peer.port = %host_port, + "message.type" = "sent", + otel.kind = "client", + otel.name = %path, + rpc.grpc.status_code = tracing::field::Empty, + ) +} + +/// Determines whether a request should be traced based on the request URL and the +/// configured tracing filter. +/// +/// If `filter` is `None`, the request should be traced. +pub(super) fn should_trace_request( + base_url: &str, + request: &http::Request, + filter: Option<&TracingFilter>, +) -> bool { + // The request URI here doesn't include the base url, so we have to manually add it here to evaluate request filter patterns. + let full_request_url = format!("{base_url}{}", request.uri()); + + let parsed = full_request_url.parse::<::url::Url>(); + let url = parsed.ok(); + filter + .and_then(|filter| url.map(|url| (filter, url))) + .map_or(true, |(filter, url)| { + filter.is_enabled(&UrlPatternMatchInput::Url(url)) + }) +} + +/// A [`tower_http::trace::OnFailure`] implementation for gRPC requests. +/// +/// Sets the "rpc.grpc.status_code" attribute on the span if the failure classification +/// is [`GrpcFailureClass::Code`]; otherwise, it sets the status code to +/// [`tonic::Code::Unknown`]. +#[derive(Clone, Debug, Default)] +pub struct OnFailure { + pub(super) inner: tower_http::trace::DefaultOnFailure, +} + +impl tower_http::trace::OnFailure for OnFailure { + fn on_failure( + &mut self, + failure_classification: GrpcFailureClass, + latency: std::time::Duration, + span: &Span, + ) { + match failure_classification { + GrpcFailureClass::Code(code) => { + span.set_attribute("rpc.grpc.status_code", format!("{code}")); + } + GrpcFailureClass::Error(_) => { + span.set_attribute( + "rpc.grpc.status_code", + format!("{}", tonic::Code::Unknown as u8), + ); + } + } + + self.inner.on_failure(failure_classification, latency, span); + } +} diff --git a/qcs-api-client-grpc/src/tonic/trace/trace_layer.rs b/qcs-api-client-grpc/src/tonic/trace/trace_layer.rs new file mode 100644 index 0000000..8e76abd --- /dev/null +++ b/qcs-api-client-grpc/src/tonic/trace/trace_layer.rs @@ -0,0 +1,129 @@ +// Copyright 2024 Rigetti Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides implementations of [`tower_http::trace`] traits for gRPC requests with +//! OpenTelemetry attributes. See +//! for details on the OpenTelemetry semantic conventions for gRPC. It does so to the +//! extent possible without requiring the "tracing-opentelemetry" feature dependencies. +//! +//! All of this behavior is configurable via +//! [`qcs_api_client_common::tracing_configuration::TracingConfiguration`]. +//! +//! The "tracing-opentelemetry" feature extends the set of conventions supported. +//! See [`super::trace_layer_otel_ext`] module documentation for more details. +use super::shared::{make_grpc_request_span, should_trace_request}; +use crate::tonic::common::get_status_code_from_headers; +use http::HeaderMap; +use qcs_api_client_common::tracing_configuration::{TracingConfiguration, TracingFilter}; +use tracing::Span; + +/// A [`tower_http::trace::MakeSpan`] implementation for gRPC requests. +#[derive(Clone, Debug)] +pub struct MakeSpan { + enabled: bool, + filter: Option, + base_url: String, +} + +impl tower_http::trace::MakeSpan for MakeSpan { + fn make_span(&mut self, request: &http::Request) -> tracing::Span { + if self.enabled + && should_trace_request(self.base_url.as_str(), request, self.filter.as_ref()) + { + make_grpc_request_span(request) + } else { + tracing::Span::none() + } + } +} + +/// A [`tower_http::trace::OnEos`] implementation for gRPC requests. This will set +/// the "rpc.grpc.status_code" attribute on the span if the status code is present +/// in the trailers. +#[derive(Clone, Debug)] +pub struct OnEos { + inner: tower_http::trace::DefaultOnEos, +} + +impl tower_http::trace::OnEos for OnEos { + fn on_eos( + self, + trailers: Option<&HeaderMap>, + stream_duration: std::time::Duration, + span: &Span, + ) { + if let Some(trailers) = trailers { + if let Ok(status_code) = get_status_code_from_headers(trailers) { + span.record("rpc.grpc.status_code", format!("{}", status_code as u8)); + } + } + self.inner.on_eos(trailers, stream_duration, span); + } +} + +/// A [`tower_http::trace::TraceLayer`] implementation for gRPC requests. This customization +/// of this layer ensures that the span has the "rpc.grpc.status_code" attribute set. +pub type CustomTraceLayer = tower_http::trace::TraceLayer< + tower_http::classify::SharedClassifier, + MakeSpan, + tower_http::trace::DefaultOnRequest, + tower_http::trace::DefaultOnResponse, + tower_http::trace::DefaultOnBodyChunk, + OnEos, + super::shared::OnFailure, +>; + +/// A [`tower_http::trace::Trace`] implementation for gRPC requests. This customization +/// of this service ensures that the span has the "rpc.grpc.status_code" attribute set. +pub type CustomTraceService = tower_http::trace::Trace< + tonic::transport::Channel, + tower_http::classify::SharedClassifier, + MakeSpan, + tower_http::trace::DefaultOnRequest, + tower_http::trace::DefaultOnResponse, + tower_http::trace::DefaultOnBodyChunk, + OnEos, + super::shared::OnFailure, +>; + +/// Builds a trace layer for gRPC requests with basic OpenTelemetry attributes. +/// +/// # Arguments +/// +/// * `base_url` - The base URL of the gRPC service. This is used for matching +/// against the configured [`TracingFilter`]. +/// * `configuration` - The tracing configuration. If `None`, no requests will +/// be traced. +#[must_use] +pub fn build_layer( + base_url: String, + configuration: Option<&TracingConfiguration>, +) -> CustomTraceLayer { + tower_http::trace::TraceLayer::new_for_grpc() + .on_eos(OnEos { + inner: tower_http::trace::DefaultOnEos::default(), + }) + .make_span_with(MakeSpan { + enabled: configuration.is_some(), + filter: configuration + .as_ref() + .and_then(|configuration| configuration.filter()) + .cloned(), + base_url: base_url.clone(), + }) + .on_failure(super::shared::OnFailure { + inner: tower_http::trace::DefaultOnFailure::default(), + }) + .on_response(tower_http::trace::DefaultOnResponse::default()) +} diff --git a/qcs-api-client-grpc/src/tonic/trace/trace_layer_otel_ext.rs b/qcs-api-client-grpc/src/tonic/trace/trace_layer_otel_ext.rs new file mode 100644 index 0000000..7679465 --- /dev/null +++ b/qcs-api-client-grpc/src/tonic/trace/trace_layer_otel_ext.rs @@ -0,0 +1,379 @@ +// Copyright 2024 Rigetti Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides implementations of [`tower_http::trace`] traits for gRPC requests with +//! OpenTelemetry attributes. See +//! for details on the OpenTelemetry semantic conventions for gRPC. +//! +//! It extends the set of features provided by the base "tracing" feature in +//! three ways: +//! +//! 1. The [`tower_http::trace::MakeSpan`] implementation creates a span with +//! the current [`opentelemetry::Context`] as the parent. +//! 2. It supports propagation of the OpenTelemetry context to the server via +//! [`opentelemetry_sdk::propagation::TraceContextPropagator`]. +//! 3. It leverages the interior mutability supported by +//! [`tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute`] to add +//! "rpc.grpc.{request, response}.metadata" attributes. +//! +//! All of this behavior is configurable via +//! [`qcs_api_client_common::tracing_configuration::TracingConfiguration`]. +use crate::tonic::common::get_status_code_from_headers; +use http::{HeaderMap, HeaderValue}; +use opentelemetry::propagation::TextMapPropagator; +use opentelemetry::trace::FutureExt; +use opentelemetry::trace::WithContext; +use opentelemetry_http::HeaderInjector; +use opentelemetry_sdk::propagation::TraceContextPropagator; +use qcs_api_client_common::tracing_configuration::HeaderAttributesFilter; +use qcs_api_client_common::tracing_configuration::{ + IncludeExclude, TracingConfiguration, TracingFilter, +}; +use tonic::{body::BoxBody, client::GrpcService}; +use tracing::Span; +use tracing_opentelemetry::OpenTelemetrySpanExt; + +use super::shared::make_grpc_request_span; +use super::shared::should_trace_request; + +#[derive(Debug, Clone, Copy)] +enum MetadataAttributeType { + Request, + Response, +} + +impl std::fmt::Display for MetadataAttributeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Request => write!(f, "request"), + Self::Response => write!(f, "response"), + } + } +} + +/// Call [`tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute`] on the specified +/// [`tracing::Span`] for each span attribute produced by +/// [`IncludeExclude::get_header_attributes`]. The attributes are formatted according to +/// the OpenTelemetry semantic conventions for gRPC. +fn set_metadata_attribute( + span: &tracing::Span, + include_exclude: &IncludeExclude, + headers: &HeaderMap, + metadata_attribute_type: MetadataAttributeType, +) { + let headers_to_trace = include_exclude.get_header_attributes(headers); + for (key, value) in headers_to_trace.into_iter() { + span.set_attribute( + format!("rpc.grpc.{metadata_attribute_type}.metadata.{key}"), + value, + ); + } +} + +/// A [`tower_http::trace::MakeSpan`] implementation for gRPC requests. +/// +/// This will set any "rpc.grpc.request.metadata" attributes configured by the user. +/// It will also call [`tracing_opentelemetry::span_ext::OpenTelemetrySpanExt::set_parent`] +/// with the current [`opentelemetry::Context`]. +#[derive(Clone, Debug)] +pub struct MakeSpan { + enabled: bool, + request_headers: IncludeExclude, + filter: Option, + base_url: String, +} + +impl tower_http::trace::MakeSpan for MakeSpan { + fn make_span(&mut self, request: &http::Request) -> tracing::Span { + if self.enabled + && should_trace_request(self.base_url.as_str(), request, self.filter.as_ref()) + { + let span = make_grpc_request_span(request); + + span.set_parent(opentelemetry::Context::current()); + set_metadata_attribute( + &span, + &self.request_headers, + request.headers(), + MetadataAttributeType::Request, + ); + span + } else { + tracing::Span::none() + } + } +} + +/// A [`tower_http::trace::OnEos`] implementation for gRPC requests. +/// +/// This will set the "rpc.grpc.status_code" and "rpc.grpc.response.metadata" attributes +/// configured by the user if trailers are present. +#[derive(Clone, Debug)] +pub struct OnEos { + response_headers: IncludeExclude, + inner: tower_http::trace::DefaultOnEos, +} + +impl tower_http::trace::OnEos for OnEos { + fn on_eos( + self, + trailers: Option<&HeaderMap>, + stream_duration: std::time::Duration, + span: &Span, + ) { + use tracing_opentelemetry::OpenTelemetrySpanExt; + + if let Some(trailers) = trailers { + if let Ok(status_code) = get_status_code_from_headers(trailers) { + span.set_attribute("rpc.grpc.status_code", format!("{}", status_code as u8)); + } + set_metadata_attribute( + span, + &self.response_headers, + trailers, + MetadataAttributeType::Response, + ); + } + self.inner.on_eos(trailers, stream_duration, span); + } +} + +/// A [`tower_http::trace::OnResponse`] implementation for gRPC requests. +/// +/// This will set any "rpc.grpc.response.metadata" attributes configured by the user. +#[derive(Clone, Debug)] +pub struct OnResponse { + response_headers: IncludeExclude, + inner: tower_http::trace::DefaultOnResponse, +} + +impl Default for OnResponse { + fn default() -> Self { + Self { + response_headers: IncludeExclude::include_none(), + inner: tower_http::trace::DefaultOnResponse::default(), + } + } +} + +impl tower_http::trace::OnResponse for OnResponse { + fn on_response(self, response: &http::Response, latency: std::time::Duration, span: &Span) { + set_metadata_attribute( + span, + &self.response_headers, + response.headers(), + MetadataAttributeType::Response, + ); + self.inner.on_response(response, latency, span); + } +} + +type BaseTraceLayer = tower_http::trace::TraceLayer< + tower_http::classify::SharedClassifier, + MakeSpan, + tower_http::trace::DefaultOnRequest, + OnResponse, + tower_http::trace::DefaultOnBodyChunk, + OnEos, + super::shared::OnFailure, +>; + +type BaseTraceService = tower_http::trace::Trace< + tonic::transport::Channel, + tower_http::classify::SharedClassifier, + MakeSpan, + tower_http::trace::DefaultOnRequest, + OnResponse, + tower_http::trace::DefaultOnBodyChunk, + OnEos, + super::shared::OnFailure, +>; + +/// An implementation of [`GrpcService`] that propagates the OpenTelemetry context +/// via the [`TraceContextPropagator`]. It additionally extends the base +/// [`tower_http::trace::Trace`] implementation to include gRPC span attributes. +pub struct CustomTraceService { + propagate_trace_id: bool, + filter: Option, + base_url: String, + inner: BaseTraceService, +} + +impl CustomTraceService { + /// Creates a new [`CustomTraceService`]. + /// + /// # Arguments + /// + /// * `propagate_trace_id` - Whether to propagate the OpenTelemetry context. + /// * `base_url` - The base URL of the gRPC service. This is used for matching + /// against the configured `TracingFilter`. + /// * `filter` - A filter to determine which requests should be traced. If `None`, + /// all requests will be traced. + /// * `inner` - The base trace service. + pub fn new( + propagate_trace_id: bool, + base_url: String, + filter: Option, + inner: BaseTraceService, + ) -> Self { + Self { + propagate_trace_id, + filter, + base_url, + inner, + } + } +} + +impl GrpcService for CustomTraceService { + type ResponseBody = >::ResponseBody; + type Error = >::Error; + type Future = WithContext<>::Future>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + GrpcService::poll_ready(&mut self.inner, cx) + } + + fn call(&mut self, mut request: http::Request) -> Self::Future { + if self.propagate_trace_id + && should_trace_request(self.base_url.as_str(), &request, self.filter.as_ref()) + { + let propagator = TraceContextPropagator::new(); + let mut injector = HeaderInjector(request.headers_mut()); + propagator.inject_context(&opentelemetry::Context::current(), &mut injector); + } + + self.inner.call(request).with_current_context() + } +} + +/// A [`tower::Layer`] implementation for gRPC requests with OpenTelemetry attributes +/// and propagate the OpenTelemetry context if configured by the user. +#[derive(Debug, Clone)] +pub struct CustomTraceLayer { + propagate_trace_id: bool, + filter: Option, + pub(super) base_url: String, + base_trace_layer: BaseTraceLayer, +} + +impl CustomTraceLayer { + /// Creates a new [`CustomTraceLayer`]. + /// + /// # Arguments + /// + /// * `propagate_trace_id` - Whether to propagate the OpenTelemetry context. + /// * `base_url` - The base URL of the gRPC service. This is used for matching + /// against the configured `TracingFilter`. + /// * `filter` - A filter to determine which requests should be traced. If `None`, + /// all requests will be traced. + /// * `base_trace_layer` - The base trace layer. + pub fn new( + propagate_trace_id: bool, + base_url: String, + filter: Option, + base_trace_layer: BaseTraceLayer, + ) -> Self { + Self { + propagate_trace_id, + filter, + base_url, + base_trace_layer, + } + } +} + +impl tower::Layer for CustomTraceLayer { + type Service = CustomTraceService; + + fn layer(&self, inner: tonic::transport::Channel) -> Self::Service { + let traced_channel = self.base_trace_layer.layer(inner); + CustomTraceService::new( + self.propagate_trace_id, + self.base_url.clone(), + self.filter.clone(), + traced_channel, + ) + } +} + +#[must_use] +fn build_base_trace_layer( + base_url: String, + configuration: Option<&TracingConfiguration>, +) -> BaseTraceLayer { + tower_http::trace::TraceLayer::new_for_grpc() + .on_eos(OnEos { + inner: tower_http::trace::DefaultOnEos::default(), + response_headers: configuration + .as_ref() + .map(|configuration| configuration.response_headers().clone()) + .unwrap_or_else(IncludeExclude::include_none), + }) + .make_span_with(MakeSpan { + enabled: configuration.is_some(), + request_headers: configuration + .as_ref() + .map(|configuration| configuration.request_headers().clone()) + .unwrap_or_else(IncludeExclude::include_none), + filter: configuration + .as_ref() + .and_then(|configuration| configuration.filter()) + .cloned(), + base_url: base_url.clone(), + }) + .on_failure(super::shared::OnFailure { + inner: tower_http::trace::DefaultOnFailure::default(), + }) + .on_response(OnResponse { + inner: tower_http::trace::DefaultOnResponse::default(), + response_headers: configuration + .as_ref() + .map(|configuration| configuration.response_headers().clone()) + .unwrap_or_else(IncludeExclude::include_none), + }) +} + +/// Builds a base trace layer for gRPC requests with OpenTelemetry attributes, +/// including attributes dynamically configured in [`TracingConfiguration`]. +/// +/// # Arguments +/// +/// * `base_url` - The base URL of the gRPC service. This is used for matching +/// against the configured [`TracingFilter`]. +/// * `configuration` - The tracing configuration. If `None`, no requests will +/// be traced. +#[must_use] +pub fn build_layer( + base_url: String, + configuration: Option<&TracingConfiguration>, +) -> CustomTraceLayer { + let trace_layer = build_base_trace_layer(base_url.clone(), configuration); + + CustomTraceLayer::new( + configuration + .as_ref() + .map(|configuration| configuration.propagate_otel_context()) + .unwrap_or(false), + base_url, + configuration + .as_ref() + .and_then(|configuration| configuration.filter()) + .cloned(), + trace_layer, + ) +} diff --git a/qcs-api-client-openapi/CHANGELOG.md b/qcs-api-client-openapi/CHANGELOG.md index 6225343..04d173f 100644 --- a/qcs-api-client-openapi/CHANGELOG.md +++ b/qcs-api-client-openapi/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.12.0-rc.0 (2024-10-03) + +### Breaking Changes + +#### update http and support grpc trace layer + +### Fixes + +#### misc mr feedback + +#### avoid overloading clone copy rust semantics + +#### tracing configuration features import + +#### avoid string deref + +#### update tonic per cargo-deny ddos warning + ## 0.11.2 (2024-09-17) ### Fixes diff --git a/qcs-api-client-openapi/Cargo.toml b/qcs-api-client-openapi/Cargo.toml index 36ab55e..7818a0c 100644 --- a/qcs-api-client-openapi/Cargo.toml +++ b/qcs-api-client-openapi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "qcs-api-client-openapi" description = "Auto-generated bindings to the QCS OpenAPI" -version = "0.11.2" +version = "0.12.0-rc.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/rigetti/qcs-api-client-rust" @@ -10,36 +10,43 @@ keywords = ["API", "QCS", "Rigetti", "quantum"] categories = ["api-bindings"] [dependencies] -qcs-api-client-common = { path = "../qcs-api-client-common", version = "0.10.2" } -serde = { version = "1.0.140", features = ["derive"] } -serde_json = "1.0.82" -url = "2.2.2" +anyhow = { version = "1.0.68", optional = true } +http = { workspace = true } +qcs-api-client-common = { path = "../qcs-api-client-common", version = "0.11.0-rc.0" } reqwest-middleware = { workspace = true, optional = true } reqwest-tracing = { workspace = true, optional = true } -task-local-extensions = { version = "0.1.3", optional = true } -urlpattern = { version = "0.2.0", optional = true } -tracing = { version = "0.1.37", optional = true } -anyhow = { version = "1.0.68", optional = true } -tokio = { version = "1.20.1", features = ["time"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tracing = { workspace = true, optional = true } +tracing-opentelemetry = { workspace = true, optional = true } +url = { workspace = true } +urlpattern = { workspace = true, optional = true } [dependencies.reqwest] -version = "0.11.11" -default-features = false +workspace = true features = ["json", "multipart", "rustls-tls-native-roots"] [dev-dependencies] -rstest = "0.17.0" -tokio = { version = "1.20.1", features = ["rt-multi-thread", "rt", "macros"] } +rstest = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "rt", "macros"] } [features] # The old name of the tracing-opentelemetry feature, here for backwards compatibility otel-tracing = ["tracing-opentelemetry"] -tracing = ["dep:tracing", "urlpattern", "qcs-api-client-common/tracing"] -tracing-config = ["tracing", "qcs-api-client-common/tracing-config"] +tracing = [ + "dep:tracing", + "qcs-api-client-common/tracing", + "urlpattern", + ] +tracing-config = [ + "qcs-api-client-common/tracing-config", + "tracing", +] tracing-opentelemetry = [ "dep:anyhow", - "dep:task-local-extensions", "dep:reqwest-middleware", "dep:reqwest-tracing", + "dep:tracing-opentelemetry", "tracing-config", ] diff --git a/qcs-api-client-openapi/src/apis/configuration.rs b/qcs-api-client-openapi/src/apis/configuration.rs index 1a89aca..74407fa 100644 --- a/qcs-api-client-openapi/src/apis/configuration.rs +++ b/qcs-api-client-openapi/src/apis/configuration.rs @@ -26,8 +26,9 @@ use qcs_api_client_common::backoff; use reqwest; #[cfg(feature = "otel-tracing")] use { + qcs_api_client_common::tracing_configuration::HeaderAttributesFilter, reqwest_middleware::ClientBuilder, reqwest_tracing::reqwest_otel_span, - reqwest_tracing::TracingMiddleware, tracing, + reqwest_tracing::TracingMiddleware, tracing, tracing::Span, }; #[derive(Debug, Clone)] @@ -74,11 +75,7 @@ impl Configuration { let mut client_builder = ClientBuilder::new(client); if let Some(tracing_configuration) = qcs_config.tracing_configuration() { - // if tracing configuration set, tracing is enabled. - if let Some(tracing_filter) = tracing_configuration.filter() { - // if a filter is set it, pass it to the middleware via Extension. - client_builder = client_builder.with_init(Extension(tracing_filter.clone())); - } + client_builder = client_builder.with_init(Extension(tracing_configuration.clone())); let middleware = TracingMiddleware::::new(); client_builder = client_builder.with(middleware); } @@ -96,20 +93,57 @@ impl Configuration { #[cfg(feature = "otel-tracing")] struct FilteredSpanBackend; +#[cfg(feature = "otel-tracing")] +#[derive(Debug, Clone, Copy)] +enum MetadataAttributeType { + Request, + Response, +} + +#[cfg(feature = "otel-tracing")] +impl std::fmt::Display for MetadataAttributeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Request => write!(f, "request"), + Self::Response => write!(f, "response"), + } + } +} + #[cfg(feature = "otel-tracing")] impl FilteredSpanBackend { - fn is_enabled( - req: &reqwest::Request, - extensions: &mut task_local_extensions::Extensions, - ) -> bool { - if let Some(filter) = - extensions.get::() + fn is_enabled(req: &reqwest::Request, extensions: &mut http::Extensions) -> bool { + if let Some(filter) = extensions + .get::() + .and_then(|tracing_configuration| tracing_configuration.filter()) { let input = urlpattern::UrlPatternMatchInput::Url(req.url().clone()); return filter.is_enabled(&input); } true } + + fn add_header_metadata( + span: &Span, + header_map: &http::HeaderMap, + extensions: &http::Extensions, + metadata_attribute_type: MetadataAttributeType, + ) { + if let Some(tracing_configuration) = + extensions.get::() + { + let request_headers_to_trace = tracing_configuration + .request_headers() + .get_header_attributes(header_map); + for (key, value) in request_headers_to_trace { + tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute( + span, + format!("http.{metadata_attribute_type}.header.{key}"), + value, + ); + } + } + } } #[cfg(feature = "otel-tracing")] @@ -120,7 +154,7 @@ impl reqwest_tracing::ReqwestOtelSpanBackend for FilteredSpanBackend { /// for details about the related semantic conventions. fn on_request_start( req: &reqwest::Request, - extensions: &mut task_local_extensions::Extensions, + extensions: &mut http::Extensions, ) -> tracing::Span { if !Self::is_enabled(req, extensions) { return tracing::Span::none(); @@ -132,20 +166,37 @@ impl reqwest_tracing::ReqwestOtelSpanBackend for FilteredSpanBackend { .get("User-Agent") .and_then(|ua| ua.to_str().ok()) .unwrap_or(""); - reqwest_otel_span!( + let span = reqwest_otel_span!( name = "HTTP request", req, http.url = uri, http.target = http_target, http.user_agent = user_agent - ) + ); + Self::add_header_metadata( + &span, + req.headers(), + extensions, + MetadataAttributeType::Request, + ); + + span } fn on_request_end( span: &tracing::Span, outcome: &reqwest_middleware::Result, - _extension: &mut task_local_extensions::Extensions, + extension: &mut http::Extensions, ) { + if let Ok(response) = outcome { + Self::add_header_metadata( + span, + response.headers(), + extension, + MetadataAttributeType::Response, + ); + } + reqwest_tracing::default_on_request_end(span, outcome) } } @@ -173,7 +224,7 @@ mod tests { .parse() .expect("test url should be valid"), ); - let mut extensions = task_local_extensions::Extensions::new(); + let mut extensions = http::Extensions::new(); assert!(FilteredSpanBackend::is_enabled(&request, &mut extensions)); } @@ -196,7 +247,7 @@ mod tests { let url = url.parse().expect("test url should be valid"); let request = reqwest::Request::new(reqwest::Method::GET, url); - let mut extensions = task_local_extensions::Extensions::new(); + let mut extensions = http::Extensions::new(); extensions.insert(tracing_filter.clone()); assert_eq!( expected,