From 25cb44013336dc5ad7e6ed8ccec5913eaa1193ea Mon Sep 17 00:00:00 2001 From: Stephen Spalding Date: Mon, 6 May 2024 14:57:38 -0700 Subject: [PATCH 01/21] Add QueryPlannerConfig to QueryPlan key --- apollo-router/src/query_planner/caching_query_planner.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 2772e87db0..8937aded88 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -12,6 +12,7 @@ use rand::seq::SliceRandom; use rand::thread_rng; use router_bridge::planner::PlanOptions; use router_bridge::planner::Planner; +use router_bridge::planner::QueryPlannerConfig; use router_bridge::planner::UsageReporting; use sha2::Digest; use sha2::Sha256; @@ -530,6 +531,7 @@ pub(crate) struct CachingQueryKey { pub(crate) hash: Arc, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, + pub(crate) query_planner_config: QueryPlannerConfig, } const FEDERATION_VERSION: &str = std::env!("FEDERATION_VERSION"); @@ -562,6 +564,7 @@ impl Hash for CachingQueryKey { self.operation.hash(state); self.metadata.hash(state); self.plan_options.hash(state); + self.query_planner_config.hash(state); } } @@ -572,6 +575,7 @@ pub(crate) struct WarmUpCachingQueryKey { pub(crate) hash: Option>, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, + pub(crate) query_planner_config: QueryPlannerConfig, } #[cfg(test)] From 5ca59ef404f0377a13f329b23d7cb4b8afb1d2ab Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 7 May 2024 13:01:44 +0200 Subject: [PATCH 02/21] add the query planner config to the query plan cache key --- apollo-router/src/configuration/mod.rs | 21 ++++++++++ .../src/query_planner/bridge_query_planner.rs | 25 +----------- .../query_planner/caching_query_planner.rs | 38 +++++++++++++++++-- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index e3839b45bb..cc9017e3cb 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -414,6 +414,27 @@ impl Configuration { .build() ).build()) } + + pub(crate) fn js_query_planner_config(&self) -> router_bridge::planner::QueryPlannerConfig { + router_bridge::planner::QueryPlannerConfig { + reuse_query_fragments: self.supergraph.reuse_query_fragments, + generate_query_fragments: Some(self.supergraph.generate_query_fragments), + incremental_delivery: Some(router_bridge::planner::IncrementalDeliverySupport { + enable_defer: Some(self.supergraph.defer_support), + }), + graphql_validation: false, + debug: Some(router_bridge::planner::QueryPlannerDebugConfig { + bypass_planner_for_single_subgraph: None, + max_evaluated_plans: self + .supergraph + .query_planning + .experimental_plans_limit + .or(Some(10000)), + paths_limit: self.supergraph.query_planning.experimental_paths_limit, + }), + type_conditioned_fetching: self.experimental_type_conditioned_fetching, + } + } } impl Default for Configuration { diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 941b34560d..bf989ce941 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -15,12 +15,9 @@ use futures::future::BoxFuture; use opentelemetry_api::metrics::MeterProvider as _; use opentelemetry_api::metrics::ObservableGauge; use opentelemetry_api::KeyValue; -use router_bridge::planner::IncrementalDeliverySupport; use router_bridge::planner::PlanOptions; use router_bridge::planner::PlanSuccess; use router_bridge::planner::Planner; -use router_bridge::planner::QueryPlannerConfig; -use router_bridge::planner::QueryPlannerDebugConfig; use router_bridge::planner::UsageReporting; use serde::Deserialize; use serde_json_bytes::Map; @@ -159,27 +156,7 @@ impl PlannerMode { configuration: &Configuration, old_planner: Option>>, ) -> Result>, ServiceBuildError> { - let query_planner_configuration = QueryPlannerConfig { - reuse_query_fragments: configuration.supergraph.reuse_query_fragments, - generate_query_fragments: Some(configuration.supergraph.generate_query_fragments), - incremental_delivery: Some(IncrementalDeliverySupport { - enable_defer: Some(configuration.supergraph.defer_support), - }), - graphql_validation: false, - debug: Some(QueryPlannerDebugConfig { - bypass_planner_for_single_subgraph: None, - max_evaluated_plans: configuration - .supergraph - .query_planning - .experimental_plans_limit - .or(Some(10000)), - paths_limit: configuration - .supergraph - .query_planning - .experimental_paths_limit, - }), - type_conditioned_fetching: configuration.experimental_type_conditioned_fetching, - }; + let query_planner_configuration = configuration.js_query_planner_config(); let planner = match old_planner { None => Planner::new(sdl.to_owned(), query_planner_configuration).await?, Some(old_planner) => { diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 8937aded88..6aee0a340e 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -14,6 +14,7 @@ use router_bridge::planner::PlanOptions; use router_bridge::planner::Planner; use router_bridge::planner::QueryPlannerConfig; use router_bridge::planner::UsageReporting; +use serde::Serialize; use sha2::Digest; use sha2::Sha256; use tower::BoxError; @@ -51,6 +52,14 @@ pub(crate) type Plugins = IndexMap>; pub(crate) type InMemoryCachePlanner = InMemoryCache>>; +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)] +pub(crate) enum ConfigMode { + //FIXME: add the Rust planner structure once it is hashable and serializable + Rust, + Both(Arc), + Js(Arc), +} + /// A query planner wrapper that caches results. /// /// The query planner performs LRU caching. @@ -63,6 +72,7 @@ pub(crate) struct CachingQueryPlanner { schema: Arc, plugins: Arc, enable_authorization_directives: bool, + config_mode: ConfigMode, } impl CachingQueryPlanner @@ -91,12 +101,23 @@ where let enable_authorization_directives = AuthorizationPlugin::enable_directives(configuration, &schema).unwrap_or(false); + + let config_mode = match configuration.experimental_query_planner_mode { + crate::configuration::QueryPlannerMode::New => ConfigMode::Rust, + crate::configuration::QueryPlannerMode::Legacy => { + ConfigMode::Js(Arc::new(configuration.js_query_planner_config())) + } + crate::configuration::QueryPlannerMode::Both => { + ConfigMode::Both(Arc::new(configuration.js_query_planner_config())) + } + }; Ok(Self { cache, delegate, schema, plugins: Arc::new(plugins), enable_authorization_directives, + config_mode, }) } @@ -142,7 +163,8 @@ where hash, metadata, plan_options, - .. + config_mode: _, + sdl: _, }, _, )| WarmUpCachingQueryKey { @@ -151,6 +173,7 @@ where hash: Some(hash.clone()), metadata: metadata.clone(), plan_options: plan_options.clone(), + config_mode: self.config_mode.clone(), }, ) .take(count) @@ -182,6 +205,7 @@ where hash: None, metadata: CacheKeyMetadata::default(), plan_options: PlanOptions::default(), + config_mode: self.config_mode.clone(), }); } } @@ -196,6 +220,7 @@ where hash, metadata, plan_options, + config_mode: _, } in all_cache_keys { let context = Context::new(); @@ -211,6 +236,7 @@ where sdl: Arc::clone(&self.schema.raw_sdl), metadata, plan_options, + config_mode: self.config_mode.clone(), }; if experimental_reuse_query_plans { @@ -392,6 +418,7 @@ where sdl: Arc::clone(&self.schema.raw_sdl), metadata, plan_options, + config_mode: self.config_mode.clone(), }; let context = request.context.clone(); @@ -531,7 +558,7 @@ pub(crate) struct CachingQueryKey { pub(crate) hash: Arc, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, - pub(crate) query_planner_config: QueryPlannerConfig, + pub(crate) config_mode: ConfigMode, } const FEDERATION_VERSION: &str = std::env!("FEDERATION_VERSION"); @@ -547,6 +574,8 @@ impl std::fmt::Display for CachingQueryKey { hasher.update( &serde_json::to_vec(&self.plan_options).expect("serialization should not fail"), ); + hasher + .update(&serde_json::to_vec(&self.config_mode).expect("serialization should not fail")); hasher.update(&serde_json::to_vec(&self.sdl).expect("serialization should not fail")); let metadata = hex::encode(hasher.finalize()); @@ -560,11 +589,12 @@ impl std::fmt::Display for CachingQueryKey { impl Hash for CachingQueryKey { fn hash(&self, state: &mut H) { + self.sdl.hash(state); self.hash.0.hash(state); self.operation.hash(state); self.metadata.hash(state); self.plan_options.hash(state); - self.query_planner_config.hash(state); + self.config_mode.hash(state); } } @@ -575,7 +605,7 @@ pub(crate) struct WarmUpCachingQueryKey { pub(crate) hash: Option>, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, - pub(crate) query_planner_config: QueryPlannerConfig, + pub(crate) config_mode: ConfigMode, } #[cfg(test)] From 4912f569751d2abd9c0246913931cce45b2318ea Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 7 May 2024 13:54:36 +0200 Subject: [PATCH 03/21] add a cache key version number --- apollo-router/src/query_planner/caching_query_planner.rs | 6 ++++-- apollo-router/tests/integration/redis.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 6aee0a340e..0f2b840e33 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -561,6 +561,8 @@ pub(crate) struct CachingQueryKey { pub(crate) config_mode: ConfigMode, } +// Update this key every time the cache key or the query plan format has to change +const CACHE_KEY_VERSION: usize = 0; const FEDERATION_VERSION: &str = std::env!("FEDERATION_VERSION"); impl std::fmt::Display for CachingQueryKey { @@ -581,8 +583,8 @@ impl std::fmt::Display for CachingQueryKey { write!( f, - "plan:{}:{}:{}:{}", - FEDERATION_VERSION, self.hash, operation, metadata, + "plan:{}:{}:{}:{}:{}", + CACHE_KEY_VERSION, FEDERATION_VERSION, self.hash, operation, metadata, ) } } diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 48da1d7a17..8f2cb5d249 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -28,7 +28,7 @@ mod test { // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:5c7a72fa35639949328548d77b56dba2e77d0dfa90c19b69978da119e996bb92"; + let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:57aba98645d707751907f32b6175e3cfd682356ecadd0318b03dd0aab6b1865f"; let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); From e7b3ad2bc30c3150122857f2769824120ebe2c97 Mon Sep 17 00:00:00 2001 From: bryn Date: Wed, 8 May 2024 16:59:12 +0100 Subject: [PATCH 04/21] Enable redis tests on OSX, fix test failure. --- apollo-router/tests/integration/redis.rs | 1375 +++++++++++----------- 1 file changed, 684 insertions(+), 691 deletions(-) diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 8f2cb5d249..eda4d2f844 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -1,178 +1,116 @@ -#[cfg(all(target_os = "linux", target_arch = "x86_64", test))] -mod test { - use apollo_router::plugin::test::MockSubgraph; - use apollo_router::services::execution::QueryPlan; - use apollo_router::services::router; - use apollo_router::services::supergraph; - use apollo_router::Context; - use apollo_router::MockedSubgraphs; - use fred::cmd; - use fred::prelude::*; - use fred::types::ScanType; - use fred::types::Scanner; - use futures::StreamExt; - use http::header::CACHE_CONTROL; - use http::HeaderValue; - use http::Method; - use serde::Deserialize; - use serde::Serialize; - use serde_json::json; - use serde_json::Value; - use tower::BoxError; - use tower::ServiceExt; - - #[tokio::test(flavor = "multi_thread")] - async fn query_planner() -> Result<(), BoxError> { - // If this test fails and the cache key format changed you'll need to update the key here. - // 1. Force this test to run locally by removing the cfg() line at the top of this file. - // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. - // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. - // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:57aba98645d707751907f32b6175e3cfd682356ecadd0318b03dd0aab6b1865f"; - - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - client.del::(known_cache_key).await.unwrap(); - - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "10s" - } +use apollo_router::plugin::test::MockSubgraph; +use apollo_router::services::execution::QueryPlan; +use apollo_router::services::router; +use apollo_router::services::supergraph; +use apollo_router::Context; +use apollo_router::MockedSubgraphs; +use fred::cmd; +use fred::prelude::*; +use fred::types::ScanType; +use fred::types::Scanner; +use futures::StreamExt; +use http::header::CACHE_CONTROL; +use http::HeaderValue; +use http::Method; +use serde::Deserialize; +use serde::Serialize; +use serde_json::json; +use serde_json::Value; +use tower::BoxError; +use tower::ServiceExt; + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner() -> Result<(), BoxError> { + // If this test fails and the cache key format changed you'll need to update the key here. + // 1. Force this test to run locally by removing the cfg() line at the top of this file. + // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. + // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. + // 4. Run this test and yank the updated cache key from the redis logs. + let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:57aba98645d707751907f32b6175e3cfd682356ecadd0318b03dd0aab6b1865f"; + + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + client.del::(known_cache_key).await.unwrap(); + + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "10s" } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let _ = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await; - - let s: String = match client.get(known_cache_key).await { - Ok(s) => s, - Err(e) => { - println!("keys in Redis server:"); - let mut scan = client.scan("plan:*", None, Some(ScanType::String)); - while let Some(key) = scan.next().await { - let key = key.as_ref().unwrap().results(); - println!("\t{key:?}"); - } - panic!("key {known_cache_key} not found: {e}\nIf you see this error, make sure the federation version you use matches the redis key."); } - }; - let exp: i64 = client - .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) - .await - .and_then(|frame| frame.try_into()) - .and_then(|value: RedisValue| value.convert()) - .unwrap(); - let query_plan_res: serde_json::Value = serde_json::from_str(&s).unwrap(); - // ignore the usage reporting field for which the order of elements in `referenced_fields_by_type` can change - let query_plan = query_plan_res - .as_object() - .unwrap() - .get("Ok") - .unwrap() - .get("Plan") - .unwrap() - .get("plan") - .unwrap() - .get("root"); - - insta::assert_json_snapshot!(query_plan); - - // test expiration refresh - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "10s" - } - } - } - } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .method(Method::POST) - .build() - .unwrap(); - let _ = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await; - let new_exp: i64 = client - .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) - .await - .and_then(|frame| frame.try_into()) - .and_then(|value: RedisValue| value.convert()) - .unwrap(); - - assert!(exp < new_exp); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[derive(Deserialize, Serialize)] - - struct QueryPlannerContent { - plan: QueryPlan, - } - - #[tokio::test(flavor = "multi_thread")] - async fn apq() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - let config = json!({ - "apq": { - "router": { + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let _ = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await; + + let s: String = match client.get(known_cache_key).await { + Ok(s) => s, + Err(e) => { + println!("keys in Redis server:"); + let mut scan = client.scan("plan:*", None, Some(ScanType::String)); + while let Some(key) = scan.next().await { + let key = key.as_ref().unwrap().results(); + println!("\t{key:?}"); + } + panic!("key {known_cache_key} not found: {e}\nIf you see this error, make sure the federation version you use matches the redis key."); + } + }; + let exp: i64 = client + .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) + .await + .and_then(|frame| frame.try_into()) + .and_then(|value: RedisValue| value.convert()) + .unwrap(); + let query_plan_res: serde_json::Value = serde_json::from_str(&s).unwrap(); + // ignore the usage reporting field for which the order of elements in `referenced_fields_by_type` can change + let query_plan = query_plan_res + .as_object() + .unwrap() + .get("Ok") + .unwrap() + .get("Plan") + .unwrap() + .get("plan") + .unwrap() + .get("root"); + + insta::assert_json_snapshot!(query_plan); + + // test expiration refresh + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { "cache": { "in_memory": { "limit": 2 @@ -184,155 +122,215 @@ mod test { } } } - }); - - let router = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(config.clone()) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_router() - .await - .unwrap(); - - let query_hash = "4c45433039407593557f8a982dafd316a66ec03f0e1ed5fa1b7ef8060d76e8ec"; - - client - .del::(&format!("apq:{query_hash}")) - .await - .unwrap(); - - let persisted = json!({ - "version" : 1, - "sha256Hash" : query_hash - }); - - // an APQ should fail if we do not know about the hash - // it should not set a value in Redis - let request: router::Request = supergraph::Request::fake_builder() - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert_eq!( - res.errors.first().unwrap().message, - "PersistedQueryNotFound" - ); - let r: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); - assert!(r.is_none()); - - // Now we register the query - // it should set a value in Redis - let request: router::Request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name name2:name } }"#) - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert!(res.data.is_some()); - assert!(res.errors.is_empty()); - - let s: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); - insta::assert_snapshot!(s.unwrap()); - - // we start a new router with the same config - // it should have the same connection to Redis, but the in memory cache has been reset - let router = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(config.clone()) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_router() - .await - .unwrap(); - - // a request with only the hash should succeed because it is stored in Redis - let request: router::Request = supergraph::Request::fake_builder() - .extension("persistedQuery", persisted.clone()) - .method(Method::POST) - .build() - .unwrap() - .try_into() - .unwrap(); - - let res = router - .clone() - .oneshot(request) - .await - .unwrap() - .into_graphql_response_stream() - .await - .next() - .await - .unwrap() - .unwrap(); - assert!(res.data.is_some()); - assert!(res.errors.is_empty()); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn entity_cache() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); - - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); - subgraphs.insert("reviews", MockSubgraph::builder().with_json( + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .method(Method::POST) + .build() + .unwrap(); + let _ = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await; + let new_exp: i64 = client + .custom_raw(cmd!("EXPIRETIME"), vec![known_cache_key.to_string()]) + .await + .and_then(|frame| frame.try_into()) + .and_then(|value: RedisValue| value.convert()) + .unwrap(); + + assert!(exp < new_exp); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[derive(Deserialize, Serialize)] + +struct QueryPlannerContent { + plan: QueryPlan, +} + +#[tokio::test(flavor = "multi_thread")] +async fn apq() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + let config = json!({ + "apq": { + "router": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "10s" + } + } + } + } + }); + + let router = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(config.clone()) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_router() + .await + .unwrap(); + + let query_hash = "4c45433039407593557f8a982dafd316a66ec03f0e1ed5fa1b7ef8060d76e8ec"; + + client + .del::(&format!("apq:{query_hash}")) + .await + .unwrap(); + + let persisted = json!({ + "version" : 1, + "sha256Hash" : query_hash + }); + + // an APQ should fail if we do not know about the hash + // it should not set a value in Redis + let request: router::Request = supergraph::Request::fake_builder() + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert_eq!( + res.errors.first().unwrap().message, + "PersistedQueryNotFound" + ); + let r: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); + assert!(r.is_none()); + + // Now we register the query + // it should set a value in Redis + let request: router::Request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name name2:name } }"#) + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert!(res.data.is_some()); + assert!(res.errors.is_empty()); + + let s: Option = client.get(&format!("apq:{query_hash}")).await.unwrap(); + insta::assert_snapshot!(s.unwrap()); + + // we start a new router with the same config + // it should have the same connection to Redis, but the in memory cache has been reset + let router = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(config.clone()) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_router() + .await + .unwrap(); + + // a request with only the hash should succeed because it is stored in Redis + let request: router::Request = supergraph::Request::fake_builder() + .extension("persistedQuery", persisted.clone()) + .method(Method::POST) + .build() + .unwrap() + .try_into() + .unwrap(); + + let res = router + .clone() + .oneshot(request) + .await + .unwrap() + .into_graphql_response_stream() + .await + .next() + .await + .unwrap() + .unwrap(); + assert!(res.data.is_some()); + assert!(res.errors.is_empty()); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn entity_cache() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); + subgraphs.insert("reviews", MockSubgraph::builder().with_json( serde_json::json!{{ "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{body}}}}", "variables": { @@ -363,90 +361,90 @@ mod test { }}, ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")).build()); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" + }, + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts { name reviews { body } } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts { name reviews { body } } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:products:Query:0df945dc1bc08f7fc02e8905b4c72aa9112f29bb7a214e4a38d199f0aa635b48:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - - let s: String = client.get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - - // we abuse the query shape to return a response with a different but overlapping set of entities - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts(first:2){__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "3", - "name": "plate" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + + let s: String = client.get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + + // we abuse the query shape to return a response with a different but overlapping set of entities + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts(first:2){__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "3", + "name": "plate" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); - // even though the root operation returned 2 entities, we only need to get one entity from the subgraph here because - // we already have it in cache - subgraphs.insert("reviews", MockSubgraph::builder().with_json( + // even though the root operation returned 2 entities, we only need to get one entity from the subgraph here because + // we already have it in cache + subgraphs.insert("reviews", MockSubgraph::builder().with_json( serde_json::json!{{ "query": "query($representations:[_Any!]!){_entities(representations:$representations){...on Product{reviews{body}}}}", "variables": { @@ -468,74 +466,74 @@ mod test { }}, ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")).build()); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" + }, + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query(r#"{ topProducts(first: 2) { name reviews { body } } }"#) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let request = supergraph::Request::fake_builder() + .query(r#"{ topProducts(first: 2) { name reviews { body } } }"#) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:reviews:Product:d9a4cd73308dd13ca136390c10340823f94c335b9da198d2339c886c738abf0d:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); + let v: Value = serde_json::from_str(&s).unwrap(); + insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} - #[tokio::test(flavor = "multi_thread")] - async fn entity_cache_authorization() -> Result<(), BoxError> { - let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); - let client = RedisClient::new(config, None, None, None); - let connection_task = client.connect(); - client.wait_for_connect().await.unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn entity_cache_authorization() -> Result<(), BoxError> { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); - let mut subgraphs = MockedSubgraphs::default(); - subgraphs.insert( + let mut subgraphs = MockedSubgraphs::default(); + subgraphs.insert( "accounts", MockSubgraph::builder().with_json( serde_json::json!{{ @@ -569,28 +567,28 @@ mod test { ).with_header(CACHE_CONTROL, HeaderValue::from_static("public")) .build(), ); - subgraphs.insert( - "products", - MockSubgraph::builder() - .with_json( - serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, - serde_json::json! {{"data": { - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }}}, - ) - .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) - .build(), - ); - subgraphs.insert( + subgraphs.insert( + "products", + MockSubgraph::builder() + .with_json( + serde_json::json! {{"query":"{topProducts{__typename upc name}}"}}, + serde_json::json! {{"data": { + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }}}, + ) + .with_header(CACHE_CONTROL, HeaderValue::from_static("public")) + .build(), + ); + subgraphs.insert( "reviews", MockSubgraph::builder().with_json( serde_json::json!{{ @@ -666,239 +664,234 @@ mod test { .build(), ); - let supergraph = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "preview_entity_cache": { - "redis": { - "urls": ["redis://127.0.0.1:6379"], - "ttl": "2s" - }, - "enabled": false, - "subgraphs": { - "products": { - "enabled": true, - "ttl": "60s" - }, - "reviews": { - "enabled": true, - "ttl": "10s" - } - } + let supergraph = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "preview_entity_cache": { + "redis": { + "urls": ["redis://127.0.0.1:6379"], + "ttl": "2s" }, - "authorization": { - "preview_directives": { - "enabled": true + "enabled": false, + "subgraphs": { + "products": { + "enabled": true, + "ttl": "60s" + }, + "reviews": { + "enabled": true, + "ttl": "10s" } - }, - "include_subgraph_errors": { - "all": true } - })) - .unwrap() - .extra_plugin(subgraphs) - .schema(include_str!("../fixtures/supergraph-auth.graphql")) - .build_supergraph() - .await - .unwrap(); - - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + }, + "authorization": { + "preview_directives": { + "enabled": true + } + }, + "include_subgraph_errors": { + "all": true + } + })) + .unwrap() + .extra_plugin(subgraphs) + .schema(include_str!("../fixtures/supergraph-auth.graphql")) + .build_supergraph() + .await + .unwrap(); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); + + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:products:Query:0df945dc1bc08f7fc02e8905b4c72aa9112f29bb7a214e4a38d199f0aa635b48:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "topProducts": [{ - "__typename": "Product", - "upc": "1", - "name": "chair" - }, - { - "__typename": "Product", - "upc": "2", - "name": "table" - }] - }} - ); - - let s: String = client + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "topProducts": [{ + "__typename": "Product", + "upc": "1", + "name": "chair" + }, + { + "__typename": "Product", + "upc": "2", + "name": "table" + }] + }} + ); + + let s: String = client .get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:1de543dab57fde0f00247922ccc4f76d4c916ae26a89dd83cd1a62300d0cda20:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "reviews": [ - {"body": "I can sit on it"} - ] - }} - ); + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "reviews": [ + {"body": "I can sit on it"} + ] + }} + ); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + context + .insert( + "apollo_authentication::JWT::claims", + json! {{ "scope": "read:user read:name" }}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - context - .insert( - "apollo_authentication::JWT::claims", - json! {{ "scope": "read:user read:name" }}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - let s:String = client + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + let s:String = client .get("subgraph:reviews:Product:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:3b6ef3c8fd34c469d59f513942c5f4c8f91135e828712de2024e2cd4613c50ae:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); - let v: Value = serde_json::from_str(&s).unwrap(); - assert_eq!( - v.as_object().unwrap().get("data").unwrap(), - &json! {{ - "reviews": [{ - "body": "I can sit on it", - "author": {"__typename": "User", "id": "1"} - }] - }} - ); + let v: Value = serde_json::from_str(&s).unwrap(); + assert_eq!( + v.as_object().unwrap().get("data").unwrap(), + &json! {{ + "reviews": [{ + "body": "I can sit on it", + "author": {"__typename": "User", "id": "1"} + }] + }} + ); + + let context = Context::new(); + context + .insert( + "apollo_authorization::scopes::required", + json! {["profile", "read:user", "read:name"]}, + ) + .unwrap(); + context + .insert( + "apollo_authentication::JWT::claims", + json! {{ "scope": "read:user profile" }}, + ) + .unwrap(); + let request = supergraph::Request::fake_builder() + .query(r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#) + .context(context) + .method(Method::POST) + .build() + .unwrap(); - let context = Context::new(); - context - .insert( - "apollo_authorization::scopes::required", - json! {["profile", "read:user", "read:name"]}, - ) - .unwrap(); - context - .insert( - "apollo_authentication::JWT::claims", - json! {{ "scope": "read:user profile" }}, - ) - .unwrap(); - let request = supergraph::Request::fake_builder() - .query( - r#"{ me { id name } topProducts { name reviews { body author { username } } } }"#, - ) - .context(context) - .method(Method::POST) - .build() - .unwrap(); - - let response = supergraph - .clone() - .oneshot(request) - .await - .unwrap() - .next_response() - .await - .unwrap(); - insta::assert_json_snapshot!(response); - - client.quit().await.unwrap(); - // calling quit ends the connection and event listener tasks - let _ = connection_task.await; - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn connection_failure_blocks_startup() { - let _ = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - // invalid port - "urls": ["redis://127.0.0.1:6378"] - } + let response = supergraph + .clone() + .oneshot(request) + .await + .unwrap() + .next_response() + .await + .unwrap(); + insta::assert_json_snapshot!(response); + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn connection_failure_blocks_startup() { + let _ = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + // invalid port + "urls": ["redis://127.0.0.1:6378"] } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap(); - - let e = apollo_router::TestHarness::builder() - .with_subgraph_network_requests() - .configuration_json(json!({ - "supergraph": { - "query_planning": { - "cache": { - "in_memory": { - "limit": 2 - }, - "redis": { - // invalid port - "urls": ["redis://127.0.0.1:6378"], - "required_to_start": true - } + } + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap(); + + let e = apollo_router::TestHarness::builder() + .with_subgraph_network_requests() + .configuration_json(json!({ + "supergraph": { + "query_planning": { + "cache": { + "in_memory": { + "limit": 2 + }, + "redis": { + // invalid port + "urls": ["redis://127.0.0.1:6378"], + "required_to_start": true } } } - })) - .unwrap() - .schema(include_str!("../fixtures/supergraph.graphql")) - .build_supergraph() - .await - .unwrap_err(); - assert_eq!( - e.to_string(), + } + })) + .unwrap() + .schema(include_str!("../fixtures/supergraph.graphql")) + .build_supergraph() + .await + .unwrap_err(); + //OSX has a different error code for connection refused + let e = e.to_string().replace("61", "111"); // + assert_eq!( + e, "couldn't build Router service: IO Error: Os { code: 111, kind: ConnectionRefused, message: \"Connection refused\" }" ); - } } From 10a6ae7804956a3b86dba5ec0a5a52d1a93d819d Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 11:51:11 +0100 Subject: [PATCH 05/21] Add an integration test that checks that the query plan cache key changes when the query plan gets updated. --- apollo-router/tests/common.rs | 61 +++++++++++++++++++ ...y_planner_redis_config_update1.router.yaml | 9 +++ ...y_planner_redis_config_update2.router.yaml | 10 +++ apollo-router/tests/integration/redis.rs | 31 ++++++++++ 4 files changed, 111 insertions(+) create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index 84ec7a5434..c612ee8959 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -9,6 +9,11 @@ use std::sync::Mutex; use std::time::Duration; use buildstructor::buildstructor; +use fred::clients::RedisClient; +use fred::interfaces::{ClientLike, KeysInterface}; +use fred::prelude::RedisConfig; +use fred::types::{ScanType, Scanner}; +use futures::StreamExt; use http::header::ACCEPT; use http::header::CONTENT_TYPE; use http::HeaderValue; @@ -894,6 +899,62 @@ impl IntegrationTest { } } } + + pub async fn clear_redis_cache(&self, namespace: &'static str) { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client + .wait_for_connect() + .await + .expect("could not connect to redis"); + let mut scan = client.scan(format!("{namespace}:*"), None, Some(ScanType::String)); + while let Some(result) = scan.next().await { + if let Some(page) = result.expect("could not scan redis").take_results() { + for key in page { + let key = key.as_str().expect("key should be a string"); + client + .del::(key) + .await + .expect("could not delete key"); + } + } + } + + client.quit().await.expect("could not quit redis"); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + } + + pub async fn assert_redis_cache_contains(&self, key: &str) -> String { + let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); + let client = RedisClient::new(config, None, None, None); + let connection_task = client.connect(); + client.wait_for_connect().await.unwrap(); + let s = match client.get(key).await { + Ok(s) => s, + Err(e) => { + println!("keys of the same type in Redis server:"); + let prefix = + key[0..key.find(':').expect(&format!("key {key} has no prefix"))].to_string(); + let mut scan = client.scan(format!("{prefix}:*"), None, Some(ScanType::String)); + while let Some(result) = scan.next().await { + let keys = result.as_ref().unwrap().results().as_ref().unwrap(); + for key in keys { + let key = key.as_str().expect("key should be a string"); + println!("\t{key}"); + } + } + panic!("key {key} not found: {e}\n This may be caused by a number of things including federation version changes"); + } + }; + + client.quit().await.unwrap(); + // calling quit ends the connection and event listener tasks + let _ = connection_task.await; + s + } } impl Drop for IntegrationTest { diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml new file mode 100644 index 0000000000..58c9127ad1 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml @@ -0,0 +1,9 @@ +# This config is used for testing query plans to redis +supergraph: + query_planning: + cache: + redis: + namespace: "test_query_planner_redis_update" + required_to_start: true + urls: + - redis://localhost:6379 \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml new file mode 100644 index 0000000000..9d3ebe1252 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml @@ -0,0 +1,10 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + namespace: "test_query_planner_redis_update" + required_to_start: true + urls: + - redis://localhost:6379 + generate_query_fragments: true diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index eda4d2f844..8681052877 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -1,3 +1,4 @@ +use crate::integration::IntegrationTest; use apollo_router::plugin::test::MockSubgraph; use apollo_router::services::execution::QueryPlan; use apollo_router::services::router; @@ -895,3 +896,33 @@ async fn connection_failure_blocks_startup() { "couldn't build Router service: IO Error: Os { code: 111, kind: ConnectionRefused, message: \"Connection refused\" }" ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_query_planner_redis_update() { + // This test shows that the redis key changes when the query planner config changes. + // The test starts a router with a specific config, executes a query, and checks the redis cache key. + // Then it updates the config, executes the query again, and checks the redis cache key. + let mut router = IntegrationTest::builder() + .config(include_str!( + "fixtures/query_planner_redis_config_update1.router.yaml" + )) + .build() + .await; + + router.start().await; + router.assert_started().await; + router + .clear_redis_cache("test_query_planner_redis_update") + .await; + + router.execute_default_query().await; + router.assert_redis_cache_contains("test_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5").await; + router + .update_config(include_str!( + "fixtures/query_planner_redis_config_update2.router.yaml" + )) + .await; + router.assert_reloaded().await; + router.execute_default_query().await; + router.assert_redis_cache_contains("test_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; +} From 3db24b94baee6f05544a661232d90e1070b3cfc3 Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 12:30:21 +0100 Subject: [PATCH 06/21] Add changeset --- .changesets/fix_geal_cache_key_qp_options.md | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .changesets/fix_geal_cache_key_qp_options.md diff --git a/.changesets/fix_geal_cache_key_qp_options.md b/.changesets/fix_geal_cache_key_qp_options.md new file mode 100644 index 0000000000..100c8c371d --- /dev/null +++ b/.changesets/fix_geal_cache_key_qp_options.md @@ -0,0 +1,32 @@ +### Prevent query plan cache collision when planning options change ([PR #5100](https://github.com/apollographql/router/pull/5100)) + +When query planning takes place there are a number of options such as: +* `defer_support` +* `generate_query_fragments` +* `experimental_reuse_query_fragments` +* `experimental_type_conditioned_fetching` +* `experimental_query_planner_mode` + +that will affect the generated query plans. + +If distributed query plan caching is also enabled, then changing any of these will result in different query plans being generated and entering the cache. + +This could cause issue in the following scenarios: +1. The Router configuration changes and a query plan is loaded from cache which is incompatible with the new configuration. +2. Routers with differing configuration are sharing the same cache causing them to cache and load incompatible query plans. + +Now a hash for the entire query planner configuration is included in the cache key to prevent this from happening. + +If you are running previous versions of the Router and wish to change the query planner options then you must take care to +change the cache namespace to prevent collisions. + +For example: +```yaml +supergraph: + query_planning: + cache: + redis: + namespace: "my_unique_identifier" +``` + +By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/5100 From cde9e63cb331c7eab648d05c234bdda593e25cd9 Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 12:34:12 +0100 Subject: [PATCH 07/21] lint --- apollo-router/tests/common.rs | 12 ++++++++---- apollo-router/tests/integration/redis.rs | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index c612ee8959..68c71d73ee 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -10,9 +10,11 @@ use std::time::Duration; use buildstructor::buildstructor; use fred::clients::RedisClient; -use fred::interfaces::{ClientLike, KeysInterface}; +use fred::interfaces::ClientLike; +use fred::interfaces::KeysInterface; use fred::prelude::RedisConfig; -use fred::types::{ScanType, Scanner}; +use fred::types::ScanType; +use fred::types::Scanner; use futures::StreamExt; use http::header::ACCEPT; use http::header::CONTENT_TYPE; @@ -936,8 +938,10 @@ impl IntegrationTest { Ok(s) => s, Err(e) => { println!("keys of the same type in Redis server:"); - let prefix = - key[0..key.find(':').expect(&format!("key {key} has no prefix"))].to_string(); + let prefix = key[0..key + .find(':') + .unwrap_or_else(|| panic!("key {key} has no prefix"))] + .to_string(); let mut scan = client.scan(format!("{prefix}:*"), None, Some(ScanType::String)); while let Some(result) = scan.next().await { let keys = result.as_ref().unwrap().results().as_ref().unwrap(); diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 8681052877..7ab60c33ec 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -1,4 +1,3 @@ -use crate::integration::IntegrationTest; use apollo_router::plugin::test::MockSubgraph; use apollo_router::services::execution::QueryPlan; use apollo_router::services::router; @@ -20,6 +19,8 @@ use serde_json::Value; use tower::BoxError; use tower::ServiceExt; +use crate::integration::IntegrationTest; + #[tokio::test(flavor = "multi_thread")] async fn query_planner() -> Result<(), BoxError> { // If this test fails and the cache key format changed you'll need to update the key here. From 716901a092cd9403ca0e41940997d7e56434cd99 Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 12:52:24 +0100 Subject: [PATCH 08/21] Rename a bunch of files that changed due to the removal of mod test. Rename tests to be in line with other tests in this file --- .../query_planner/caching_query_planner.rs | 9 +++-- ...y_planner_redis_config_update1.router.yaml | 12 +++++++ ...y_planner_redis_config_update2.router.yaml | 14 ++++++++ apollo-router/tests/integration/redis.rs | 33 ++++++++++++++++++- ...ation_tests__integration__redis__apq.snap} | 0 ...__integration__redis__entity_cache-2.snap} | 0 ...__integration__redis__entity_cache-3.snap} | 0 ...__integration__redis__entity_cache-4.snap} | 0 ...__integration__redis__entity_cache-5.snap} | 0 ...ts__integration__redis__entity_cache.snap} | 0 ..._redis__entity_cache_authorization-2.snap} | 0 ..._redis__entity_cache_authorization-3.snap} | 0 ...n__redis__entity_cache_authorization.snap} | 0 ...s__integration__redis__query_planner.snap} | 0 14 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml create mode 100644 apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__apq.snap => integration_tests__integration__redis__apq.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache-2.snap => integration_tests__integration__redis__entity_cache-2.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache-3.snap => integration_tests__integration__redis__entity_cache-3.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache-4.snap => integration_tests__integration__redis__entity_cache-4.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache-5.snap => integration_tests__integration__redis__entity_cache-5.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache.snap => integration_tests__integration__redis__entity_cache.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache_authorization-2.snap => integration_tests__integration__redis__entity_cache_authorization-2.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache_authorization-3.snap => integration_tests__integration__redis__entity_cache_authorization-3.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__entity_cache_authorization.snap => integration_tests__integration__redis__entity_cache_authorization.snap} (100%) rename apollo-router/tests/integration/snapshots/{integration_tests__integration__redis__test__query_planner.snap => integration_tests__integration__redis__query_planner.snap} (100%) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 0f2b840e33..b9b59302fa 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -54,8 +54,9 @@ pub(crate) type InMemoryCachePlanner = #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)] pub(crate) enum ConfigMode { - //FIXME: add the Rust planner structure once it is hashable and serializable - Rust, + //FIXME: add the Rust planner structure once it is hashable and serializable, + // for now use the JS config as it expected to be identical to the Rust one + Rust(Arc), Both(Arc), Js(Arc), } @@ -103,7 +104,9 @@ where AuthorizationPlugin::enable_directives(configuration, &schema).unwrap_or(false); let config_mode = match configuration.experimental_query_planner_mode { - crate::configuration::QueryPlannerMode::New => ConfigMode::Rust, + crate::configuration::QueryPlannerMode::New => { + ConfigMode::Rust(Arc::new(configuration.js_query_planner_config())) + } crate::configuration::QueryPlannerMode::Legacy => { ConfigMode::Js(Arc::new(configuration.js_query_planner_config())) } diff --git a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml b/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml new file mode 100644 index 0000000000..a482fce1c2 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml @@ -0,0 +1,12 @@ +# This config is used for testing query plans to redis +supergraph: + query_planning: + cache: + redis: + namespace: "test_rust_query_planner_redis_update" + required_to_start: true + urls: + - redis://localhost:6379 + +experimental_query_planner_mode: new +experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml b/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml new file mode 100644 index 0000000000..3a2960e9f4 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml @@ -0,0 +1,14 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + namespace: "test_rust_query_planner_redis_update" + required_to_start: true + urls: + - redis://localhost:6379 + generate_query_fragments: true + + +experimental_query_planner_mode: new +experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 7ab60c33ec..46a11b34f4 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -899,7 +899,7 @@ async fn connection_failure_blocks_startup() { } #[tokio::test(flavor = "multi_thread")] -async fn test_query_planner_redis_update() { +async fn query_planner_redis_update() { // This test shows that the redis key changes when the query planner config changes. // The test starts a router with a specific config, executes a query, and checks the redis cache key. // Then it updates the config, executes the query again, and checks the redis cache key. @@ -927,3 +927,34 @@ async fn test_query_planner_redis_update() { router.execute_default_query().await; router.assert_redis_cache_contains("test_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; } + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "extraction of subgraphs from supergraph is not yet implemented"] +async fn rust_query_planner_redis_update() { + // This test shows that the redis key changes when the query planner config changes. + // The test starts a router with a specific config, executes a query, and checks the redis cache key. + // Then it updates the config, executes the query again, and checks the redis cache key. + let mut router = IntegrationTest::builder() + .config(include_str!( + "fixtures/rust_query_planner_redis_config_update1.router.yaml" + )) + .build() + .await; + + router.start().await; + router.assert_started().await; + router + .clear_redis_cache("test_rust_query_planner_redis_update") + .await; + + router.execute_default_query().await; + router.assert_redis_cache_contains("test_rust_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5").await; + router + .update_config(include_str!( + "fixtures/rust_query_planner_redis_config_update2.router.yaml" + )) + .await; + router.assert_reloaded().await; + router.execute_default_query().await; + router.assert_redis_cache_contains("test_rust_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; +} diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__apq.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__apq.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__apq.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__apq.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-2.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-2.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-2.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-2.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-3.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-3.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-3.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-3.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-4.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-4.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-4.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-4.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-5.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-5.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache-5.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache-5.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-2.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-2.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-2.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-2.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-3.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-3.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization-3.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization-3.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__entity_cache_authorization.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__entity_cache_authorization.snap diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__query_planner.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap similarity index 100% rename from apollo-router/tests/integration/snapshots/integration_tests__integration__redis__test__query_planner.snap rename to apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner.snap From e9ffe6647df65a7407f4d60d44dc5482f0c9293b Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 12:59:47 +0100 Subject: [PATCH 09/21] Update changelog to reference original issue. --- .changesets/fix_geal_cache_key_qp_options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changesets/fix_geal_cache_key_qp_options.md b/.changesets/fix_geal_cache_key_qp_options.md index 100c8c371d..ba17743346 100644 --- a/.changesets/fix_geal_cache_key_qp_options.md +++ b/.changesets/fix_geal_cache_key_qp_options.md @@ -1,4 +1,4 @@ -### Prevent query plan cache collision when planning options change ([PR #5100](https://github.com/apollographql/router/pull/5100)) +### Prevent query plan cache collision when planning options change ([Issue #5093](https://github.com/apollographql/router/issues/5093)) When query planning takes place there are a number of options such as: * `defer_support` From 605db971d7ba5e58938898c72a0856dcd2db0635 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Thu, 9 May 2024 13:28:28 +0100 Subject: [PATCH 10/21] Update .changesets/fix_geal_cache_key_qp_options.md Co-authored-by: Jesse Rosenberger --- .changesets/fix_geal_cache_key_qp_options.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.changesets/fix_geal_cache_key_qp_options.md b/.changesets/fix_geal_cache_key_qp_options.md index ba17743346..24f2604a06 100644 --- a/.changesets/fix_geal_cache_key_qp_options.md +++ b/.changesets/fix_geal_cache_key_qp_options.md @@ -1,5 +1,8 @@ ### Prevent query plan cache collision when planning options change ([Issue #5093](https://github.com/apollographql/router/issues/5093)) +> [!IMPORTANT] +> If you have enabled [Distributed query plan caching](https://www.apollographql.com/docs/router/configuration/distributed-caching/#distributed-query-plan-caching), this release changes the hashing algorithm used for the cache keys. On account of this, you should anticipate additional cache regeneration cost when updating between these versions while the new hashing algorithm comes into service. + When query planning takes place there are a number of options such as: * `defer_support` * `generate_query_fragments` From 4968a88a5d5d4d351b9df67b1c241bdcfaf07f94 Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 14:27:45 +0100 Subject: [PATCH 11/21] Remove confusing changlog example --- .changesets/fix_geal_cache_key_qp_options.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.changesets/fix_geal_cache_key_qp_options.md b/.changesets/fix_geal_cache_key_qp_options.md index 24f2604a06..3a973f9d14 100644 --- a/.changesets/fix_geal_cache_key_qp_options.md +++ b/.changesets/fix_geal_cache_key_qp_options.md @@ -20,16 +20,4 @@ This could cause issue in the following scenarios: Now a hash for the entire query planner configuration is included in the cache key to prevent this from happening. -If you are running previous versions of the Router and wish to change the query planner options then you must take care to -change the cache namespace to prevent collisions. - -For example: -```yaml -supergraph: - query_planning: - cache: - redis: - namespace: "my_unique_identifier" -``` - By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/5100 From 1d908d4f50e7dd1f033c61373da699fc6b9d4378 Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 14:43:09 +0100 Subject: [PATCH 12/21] Add redis to non-amd ci --- .circleci/config.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cb5214a584..608cbcd27a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,6 +43,8 @@ executors: environment: CARGO_BUILD_JOBS: 8 RUST_TEST_THREADS: 8 + docker: + - image: cimg/redis:7.2.4 macos_build: &macos_build_executor macos: # See https://circleci.com/docs/xcode-policy along with the support matrix @@ -50,6 +52,8 @@ executors: # We use the major.minor notation to bring in compatible patches. xcode: "14.2.0" resource_class: macos.m1.medium.gen1 + docker: + - image: cimg/redis:7.2.4 macos_test: &macos_test_executor macos: # See https://circleci.com/docs/xcode-policy along with the support matrix @@ -67,7 +71,8 @@ executors: image: "windows-server-2019-vs2019:2024.02.21" resource_class: windows.xlarge shell: bash.exe --login -eo pipefail - + docker: + - image: cimg/redis:7.2.4 # We don't use {{ arch }} because on windows it is unstable https://discuss.circleci.com/t/value-of-arch-unstable-on-windows/40079 parameters: toolchain_version: @@ -505,10 +510,10 @@ commands: condition: equal: [ "dev", "<< pipeline.git.branch >>" ] steps: - - save_cache: - key: "<< pipeline.parameters.merge_version >>-test-<< parameters.variant >>" - paths: - - target + - save_cache: + key: "<< pipeline.parameters.merge_version >>-test-<< parameters.variant >>" + paths: + - target - store_test_results: # The results from nextest that power the CircleCI Insights. path: ./target/nextest/ci/junit.xml @@ -540,7 +545,7 @@ jobs: steps: - when: condition: - equal: [*amd_linux_helm_executor, << parameters.platform >>] + equal: [ *amd_linux_helm_executor, << parameters.platform >> ] steps: - checkout - xtask_check_helm @@ -838,7 +843,7 @@ jobs: not: equal: [ "https://github.com/apollographql/router", << pipeline.project.git_url >> ] steps: - - run: + - run: command: > echo "Not publishing any github release." - when: From 3c6ef294b5dc28c4276fdc40464aa5a3e54c0f8a Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 15:10:51 +0100 Subject: [PATCH 13/21] Remove redis from ci, we can't do this. --- .circleci/config.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 167846a6f9..6c96b3f89f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,8 +40,6 @@ executors: resource_class: arm.xlarge environment: CARGO_BUILD_JOBS: 8 - docker: - - image: cimg/redis:7.2.4 macos_build: &macos_build_executor macos: # See https://circleci.com/docs/xcode-policy along with the support matrix @@ -56,8 +54,6 @@ executors: # We use the major.minor notation to bring in compatible patches. xcode: "14.2.0" resource_class: macos.m1.medium.gen1 - docker: - - image: cimg/redis:7.2.4 windows_build: &windows_build_executor machine: image: "windows-server-2019-vs2019:2024.02.21" @@ -68,8 +64,6 @@ executors: image: "windows-server-2019-vs2019:2024.02.21" resource_class: windows.xlarge shell: bash.exe --login -eo pipefail - docker: - - image: cimg/redis:7.2.4 # We don't use {{ arch }} because on windows it is unstable https://discuss.circleci.com/t/value-of-arch-unstable-on-windows/40079 parameters: From ebd15d019193dbfdf2ac4408708134fc52e18b1e Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 15:28:16 +0100 Subject: [PATCH 14/21] Allow conditional compilation based on CI. Skip redis tests on CI for anything other than x86_64 --- .circleci/config.yml | 2 +- apollo-router/Cargo.toml | 3 +++ apollo-router/tests/common.rs | 2 ++ apollo-router/tests/integration/mod.rs | 2 ++ xtask/src/commands/test.rs | 9 +++++++++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c96b3f89f..0a5db986b6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -492,7 +492,7 @@ commands: environment: # Use the settings from the "ci" profile in nextest configuration. NEXTEST_PROFILE: ci - command: xtask test --workspace --locked + command: xtask test --workspace --locked --features ci - run: name: Delete large files from cache command: | diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 390414bb9e..d2dd9199c5 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -57,6 +57,9 @@ docs_rs = ["router-bridge/docs_rs"] # and not yet ready for production use. telemetry_next = [] +# is set when ci builds take place. It allows us to disable some tests when CI is running on certain platforms. +ci = [] + [package.metadata.docs.rs] features = ["docs_rs"] diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index 68c71d73ee..da5a8433d4 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -902,6 +902,7 @@ impl IntegrationTest { } } + #[allow(dead_code)] pub async fn clear_redis_cache(&self, namespace: &'static str) { let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); @@ -929,6 +930,7 @@ impl IntegrationTest { let _ = connection_task.await; } + #[allow(dead_code)] pub async fn assert_redis_cache_contains(&self, key: &str) -> String { let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); diff --git a/apollo-router/tests/integration/mod.rs b/apollo-router/tests/integration/mod.rs index 97937bac53..be210ca1aa 100644 --- a/apollo-router/tests/integration/mod.rs +++ b/apollo-router/tests/integration/mod.rs @@ -7,6 +7,8 @@ mod docs; mod file_upload; mod lifecycle; mod operation_limits; + +#[cfg(any(not(feature = "ci"), target_arch = "x86_64"))] mod redis; mod rhai; mod subscription; diff --git a/xtask/src/commands/test.rs b/xtask/src/commands/test.rs index efb84af491..dab1db991a 100644 --- a/xtask/src/commands/test.rs +++ b/xtask/src/commands/test.rs @@ -21,6 +21,10 @@ pub struct Test { /// Pass --workspace to cargo test #[clap(long)] workspace: bool, + + /// Pass --features to cargo test + #[clap(long)] + features: Option, } impl Test { @@ -47,6 +51,11 @@ impl Test { args.push("--workspace".to_string()); } + if let Some(features) = &self.features { + args.push("--features".to_string()); + args.push(features.to_owned()); + } + cargo!(args); return Ok(()); } else { From c5375bce7609325b08bde7583ad52a22993a1c2d Mon Sep 17 00:00:00 2001 From: bryn Date: Thu, 9 May 2024 16:14:46 +0100 Subject: [PATCH 15/21] Skip redis tests on CI for anything other than x86_64 && linux --- apollo-router/tests/integration/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apollo-router/tests/integration/mod.rs b/apollo-router/tests/integration/mod.rs index be210ca1aa..b1c1b2ecde 100644 --- a/apollo-router/tests/integration/mod.rs +++ b/apollo-router/tests/integration/mod.rs @@ -8,7 +8,7 @@ mod file_upload; mod lifecycle; mod operation_limits; -#[cfg(any(not(feature = "ci"), target_arch = "x86_64"))] +#[cfg(any(not(feature = "ci"), all(target_arch = "x86_64", target_os = "linux")))] mod redis; mod rhai; mod subscription; From bf0b9380066855d6dca83b8064402ca1efd5acb1 Mon Sep 17 00:00:00 2001 From: bryn Date: Fri, 10 May 2024 10:10:35 +0100 Subject: [PATCH 16/21] Improve testing --- apollo-router/tests/common.rs | 66 ++++++++++---- ...y_planner_redis_config_update.router.yaml} | 1 - ...nner_redis_config_update_defer.router.yaml | 10 +++ ...is_config_update_introspection.router.yaml | 10 +++ ...config_update_query_fragments.router.yaml} | 2 +- ...fig_update_query_planner_mode.router.yaml} | 3 - ...g_update_reuse_query_fragments.router.yaml | 10 +++ ...date_type_conditional_fetching.router.yaml | 10 +++ ...y_planner_redis_config_update1.router.yaml | 12 --- apollo-router/tests/integration/redis.rs | 86 +++++++++++-------- 10 files changed, 139 insertions(+), 71 deletions(-) rename apollo-router/tests/integration/fixtures/{query_planner_redis_config_update1.router.yaml => query_planner_redis_config_update.router.yaml} (78%) create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml rename apollo-router/tests/integration/fixtures/{query_planner_redis_config_update2.router.yaml => query_planner_redis_config_update_query_fragments.router.yaml} (84%) rename apollo-router/tests/integration/fixtures/{rust_query_planner_redis_config_update2.router.yaml => query_planner_redis_config_update_query_planner_mode.router.yaml} (79%) create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml create mode 100644 apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml delete mode 100644 apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index da5a8433d4..b10e484b94 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -88,6 +88,7 @@ pub struct IntegrationTest { _subgraph_overrides: HashMap, bind_address: Arc>>, + redis_namespace: String, } impl IntegrationTest { @@ -279,6 +280,7 @@ impl IntegrationTest { supergraph: Option, mut subgraph_overrides: HashMap, ) -> Self { + let redis_namespace = Uuid::new_v4().to_string(); let telemetry = telemetry.unwrap_or_default(); let tracer_provider_client = telemetry.tracer_provider("client"); let subscriber_client = Self::dispatch(&tracer_provider_client); @@ -292,7 +294,7 @@ impl IntegrationTest { subgraph_overrides.entry("products".into()).or_insert(url); // Insert the overrides into the config - let config_str = merge_overrides(&config, &subgraph_overrides, None); + let config_str = merge_overrides(&config, &subgraph_overrides, None, &redis_namespace); let supergraph = supergraph.unwrap_or(PathBuf::from_iter([ "..", @@ -341,6 +343,7 @@ impl IntegrationTest { subscriber_client, _tracer_provider_subgraph: tracer_provider_subgraph, telemetry, + redis_namespace, } } @@ -463,7 +466,12 @@ impl IntegrationTest { pub async fn update_config(&self, yaml: &str) { tokio::fs::write( &self.test_config_location, - &merge_overrides(yaml, &self._subgraph_overrides, Some(self.bind_address())), + &merge_overrides( + yaml, + &self._subgraph_overrides, + Some(self.bind_address()), + &self.redis_namespace, + ), ) .await .expect("must be able to write config"); @@ -903,7 +911,7 @@ impl IntegrationTest { } #[allow(dead_code)] - pub async fn clear_redis_cache(&self, namespace: &'static str) { + pub async fn clear_redis_cache(&self) { let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); @@ -912,15 +920,18 @@ impl IntegrationTest { .wait_for_connect() .await .expect("could not connect to redis"); + let namespace = &self.redis_namespace; let mut scan = client.scan(format!("{namespace}:*"), None, Some(ScanType::String)); while let Some(result) = scan.next().await { if let Some(page) = result.expect("could not scan redis").take_results() { for key in page { let key = key.as_str().expect("key should be a string"); - client - .del::(key) - .await - .expect("could not delete key"); + if key.starts_with(&self.redis_namespace) { + client + .del::(key) + .await + .expect("could not delete key"); + } } } } @@ -931,25 +942,32 @@ impl IntegrationTest { } #[allow(dead_code)] - pub async fn assert_redis_cache_contains(&self, key: &str) -> String { + pub async fn assert_redis_cache_contains(&self, key: &str, ignore: Option<&str>) -> String { let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); let connection_task = client.connect(); client.wait_for_connect().await.unwrap(); - let s = match client.get(key).await { + let redis_namespace = &self.redis_namespace; + let namespaced_key = format!("{redis_namespace}:{key}"); + let s = match client.get(&namespaced_key).await { Ok(s) => s, Err(e) => { - println!("keys of the same type in Redis server:"); - let prefix = key[0..key - .find(':') - .unwrap_or_else(|| panic!("key {key} has no prefix"))] - .to_string(); - let mut scan = client.scan(format!("{prefix}:*"), None, Some(ScanType::String)); + println!("non-ignored keys in the same namespace in Redis:"); + + let mut scan = client.scan( + format!("{redis_namespace}:*"), + Some(u32::MAX), + Some(ScanType::String), + ); + while let Some(result) = scan.next().await { let keys = result.as_ref().unwrap().results().as_ref().unwrap(); for key in keys { let key = key.as_str().expect("key should be a string"); - println!("\t{key}"); + let unnamespaced_key = key.replace(&format!("{redis_namespace}:"), ""); + if Some(unnamespaced_key.as_str()) != ignore { + println!("\t{unnamespaced_key}"); + } } } panic!("key {key} not found: {e}\n This may be caused by a number of things including federation version changes"); @@ -993,6 +1011,7 @@ fn merge_overrides( yaml: &str, subgraph_overrides: &HashMap, bind_addr: Option, + redis_namespace: &str, ) -> String { let bind_addr = bind_addr .map(|a| a.to_string()) @@ -1072,5 +1091,20 @@ fn merge_overrides( json!({"listen": bind_addr.to_string()}), ); + // Set query plan redis namespace + if let Some(query_plan) = config + .as_object_mut() + .and_then(|o| o.get_mut("supergraph")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("query_planning")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("cache")) + .and_then(|o| o.as_object_mut()) + .and_then(|o| o.get_mut("redis")) + .and_then(|o| o.as_object_mut()) + { + query_plan.insert("namespace".to_string(), redis_namespace.into()); + } + serde_yaml::to_string(&config).unwrap() } diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml similarity index 78% rename from apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml rename to apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml index 58c9127ad1..b9ed5f522d 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update1.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml @@ -3,7 +3,6 @@ supergraph: query_planning: cache: redis: - namespace: "test_query_planner_redis_update" required_to_start: true urls: - redis://localhost:6379 \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml new file mode 100644 index 0000000000..896aef8bef --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml @@ -0,0 +1,10 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + defer_support: false + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml new file mode 100644 index 0000000000..a581d82199 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml @@ -0,0 +1,10 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + introspection: true + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml similarity index 84% rename from apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml rename to apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml index 9d3ebe1252..79d727f42e 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update2.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml @@ -3,8 +3,8 @@ supergraph: query_planning: cache: redis: - namespace: "test_query_planner_redis_update" required_to_start: true urls: - redis://localhost:6379 generate_query_fragments: true + diff --git a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml similarity index 79% rename from apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml rename to apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml index 3a2960e9f4..8f28fd3bea 100644 --- a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update2.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml @@ -3,12 +3,9 @@ supergraph: query_planning: cache: redis: - namespace: "test_rust_query_planner_redis_update" required_to_start: true urls: - redis://localhost:6379 - generate_query_fragments: true - experimental_query_planner_mode: new experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml new file mode 100644 index 0000000000..a39145d265 --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml @@ -0,0 +1,10 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 + experimental_reuse_query_fragments: true + diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml new file mode 100644 index 0000000000..4fc8b3c7ee --- /dev/null +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml @@ -0,0 +1,10 @@ +# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans +supergraph: + query_planning: + cache: + redis: + required_to_start: true + urls: + - redis://localhost:6379 +experimental_type_conditioned_fetching: true + diff --git a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml b/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml deleted file mode 100644 index a482fce1c2..0000000000 --- a/apollo-router/tests/integration/fixtures/rust_query_planner_redis_config_update1.router.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# This config is used for testing query plans to redis -supergraph: - query_planning: - cache: - redis: - namespace: "test_rust_query_planner_redis_update" - required_to_start: true - urls: - - redis://localhost:6379 - -experimental_query_planner_mode: new -experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 46a11b34f4..44a60f1cb1 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -899,62 +899,72 @@ async fn connection_failure_blocks_startup() { } #[tokio::test(flavor = "multi_thread")] -async fn query_planner_redis_update() { - // This test shows that the redis key changes when the query planner config changes. - // The test starts a router with a specific config, executes a query, and checks the redis cache key. - // Then it updates the config, executes the query again, and checks the redis cache key. - let mut router = IntegrationTest::builder() - .config(include_str!( - "fixtures/query_planner_redis_config_update1.router.yaml" - )) - .build() - .await; +async fn query_planner_redis_update_query_fragments() { + test_redis_query_plan_config_update(include_str!( + "fixtures/query_planner_redis_config_update_query_fragments.router.yaml" + ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; +} - router.start().await; - router.assert_started().await; - router - .clear_redis_cache("test_query_planner_redis_update") - .await; +#[tokio::test(flavor = "multi_thread")] +#[ignore = "extraction of subgraphs from supergraph is not yet implemented"] +async fn query_planner_redis_update_planner_mode() { + test_redis_query_plan_config_update(include_str!( + "fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml" + ), "aplan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; +} - router.execute_default_query().await; - router.assert_redis_cache_contains("test_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5").await; - router - .update_config(include_str!( - "fixtures/query_planner_redis_config_update2.router.yaml" - )) - .await; - router.assert_reloaded().await; - router.execute_default_query().await; - router.assert_redis_cache_contains("test_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_introspection() { + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_introspection.router.yaml"), + "", + ) + .await; } #[tokio::test(flavor = "multi_thread")] -#[ignore = "extraction of subgraphs from supergraph is not yet implemented"] -async fn rust_query_planner_redis_update() { +async fn query_planner_redis_update_defer() { + test_redis_query_plan_config_update(include_str!( + "fixtures/query_planner_redis_config_update_defer.router.yaml" + ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:a0b325def7a039b75c63bec4798c9ba825782066356a4c90cdd733b172bd04f5").await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_type_conditional_fetching() { + test_redis_query_plan_config_update(include_str!( + "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" + ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:2af3e950e598bb7d1f015d3646b5aba4abdb95bb4d8d3a8b4da02018b08bef0c").await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn query_planner_redis_update_reuse_query_fragments() { + test_redis_query_plan_config_update(include_str!( + "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" + ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:dcbde5a1190ae7807abdf08af13212926da59126ff4da27175d526aae900fe85").await; +} + +async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key: &str) { // This test shows that the redis key changes when the query planner config changes. // The test starts a router with a specific config, executes a query, and checks the redis cache key. // Then it updates the config, executes the query again, and checks the redis cache key. let mut router = IntegrationTest::builder() .config(include_str!( - "fixtures/rust_query_planner_redis_config_update1.router.yaml" + "fixtures/query_planner_redis_config_update.router.yaml" )) .build() .await; router.start().await; router.assert_started().await; - router - .clear_redis_cache("test_rust_query_planner_redis_update") - .await; + router.clear_redis_cache().await; + let starting_key = "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5"; router.execute_default_query().await; - router.assert_redis_cache_contains("test_rust_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5").await; - router - .update_config(include_str!( - "fixtures/rust_query_planner_redis_config_update2.router.yaml" - )) - .await; + router.assert_redis_cache_contains(starting_key, None).await; + router.update_config(updated_config).await; router.assert_reloaded().await; router.execute_default_query().await; - router.assert_redis_cache_contains("test_rust_query_planner_redis_update:plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; + router + .assert_redis_cache_contains(new_cache_key, Some(starting_key)) + .await; } From b049a211506853ee18fc405b882e3d07127f0aa3 Mon Sep 17 00:00:00 2001 From: bryn Date: Fri, 10 May 2024 10:53:10 +0100 Subject: [PATCH 17/21] Improve testing --- apollo-router/tests/integration/redis.rs | 52 +++++++++++++++--------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 44a60f1cb1..4b3aa46d78 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -28,7 +28,7 @@ async fn query_planner() -> Result<(), BoxError> { // 2. run `docker compose up -d` and connect to the redis container by running `docker-compose exec redis /bin/bash`. // 3. Run the `redis-cli` command from the shell and start the redis `monitor` command. // 4. Run this test and yank the updated cache key from the redis logs. - let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:57aba98645d707751907f32b6175e3cfd682356ecadd0318b03dd0aab6b1865f"; + let known_cache_key = "plan:0:v2.7.5:16385ebef77959fcdc520ad507eb1f7f7df28f1d54a0569e3adabcb4cd00d7ce:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:9c26cb1f820a78848ba3d5d3295c16aa971368c5295422fd33cc19d4a6006a9c"; let config = RedisConfig::from_url("redis://127.0.0.1:6379").unwrap(); let client = RedisClient::new(config, None, None, None); @@ -77,7 +77,7 @@ async fn query_planner() -> Result<(), BoxError> { Ok(s) => s, Err(e) => { println!("keys in Redis server:"); - let mut scan = client.scan("plan:*", None, Some(ScanType::String)); + let mut scan = client.scan("plan:*", Some(u32::MAX), Some(ScanType::String)); while let Some(key) = scan.next().await { let key = key.as_ref().unwrap().results(); println!("\t{key:?}"); @@ -900,47 +900,61 @@ async fn connection_failure_blocks_startup() { #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_query_fragments() { - test_redis_query_plan_config_update(include_str!( - "fixtures/query_planner_redis_config_update_query_fragments.router.yaml" - ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_query_fragments.router.yaml"), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:ae8b525534cb7446a34715fc80edd41d4d29aa65c5f39f9237d4ed8459e3fe82", + ) + .await; } #[tokio::test(flavor = "multi_thread")] #[ignore = "extraction of subgraphs from supergraph is not yet implemented"] async fn query_planner_redis_update_planner_mode() { - test_redis_query_plan_config_update(include_str!( - "fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml" - ), "aplan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8c8fbee8a082e4402d1c44a05e984657ba77572f6cbc9902f1d8679069ef8b2c").await; + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml"), + "", + ) + .await; } #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_introspection() { test_redis_query_plan_config_update( include_str!("fixtures/query_planner_redis_config_update_introspection.router.yaml"), - "", + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c", ) .await; } #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_defer() { - test_redis_query_plan_config_update(include_str!( - "fixtures/query_planner_redis_config_update_defer.router.yaml" - ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:a0b325def7a039b75c63bec4798c9ba825782066356a4c90cdd733b172bd04f5").await; + test_redis_query_plan_config_update( + include_str!("fixtures/query_planner_redis_config_update_defer.router.yaml"), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:8a17c5b196af5e3a18d24596424e9849d198f456dd48297b852a5f2ca847169b", + ) + .await; } #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_type_conditional_fetching() { - test_redis_query_plan_config_update(include_str!( - "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" - ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:2af3e950e598bb7d1f015d3646b5aba4abdb95bb4d8d3a8b4da02018b08bef0c").await; + test_redis_query_plan_config_update( + include_str!( + "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" + ), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:275f78612ed3d45cdf6bf328ef83e368b5a44393bd8c944d4a7d694aed61f017", + ) + .await; } #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_reuse_query_fragments() { - test_redis_query_plan_config_update(include_str!( - "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" - ), "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:dcbde5a1190ae7807abdf08af13212926da59126ff4da27175d526aae900fe85").await; + test_redis_query_plan_config_update( + include_str!( + "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" + ), + "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:15fbb62c94e8da6ea78f28a6eb86a615dcaf27ff6fd0748fac4eb614b0b17662", + ) + .await; } async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key: &str) { @@ -958,7 +972,7 @@ async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key router.assert_started().await; router.clear_redis_cache().await; - let starting_key = "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:7a3d4ce3c2479cdb029f061aa64b4335b2de75d89011190643d302140a1c6ae5"; + let starting_key = "plan:0:v2.7.5:a9e605fa09adc5a4b824e690b4de6f160d47d84ede5956b58a7d300cca1f7204:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:1910d63916aae7a1066cb8c7d622fc3a8e363ed1b6ac8e214deed4046abae85c"; router.execute_default_query().await; router.assert_redis_cache_contains(starting_key, None).await; router.update_config(updated_config).await; From 4bd73a8e40f54f0cf39383670319da1dc7a66cf4 Mon Sep 17 00:00:00 2001 From: bryn Date: Fri, 10 May 2024 10:53:21 +0100 Subject: [PATCH 18/21] Make introspection part of the cache key --- .../src/query_planner/caching_query_planner.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index b9b59302fa..2bc6ed9925 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -74,6 +74,7 @@ pub(crate) struct CachingQueryPlanner { plugins: Arc, enable_authorization_directives: bool, config_mode: ConfigMode, + introspection: bool, } impl CachingQueryPlanner @@ -121,6 +122,7 @@ where plugins: Arc::new(plugins), enable_authorization_directives, config_mode, + introspection: configuration.supergraph.introspection, }) } @@ -168,6 +170,7 @@ where plan_options, config_mode: _, sdl: _, + introspection: _, }, _, )| WarmUpCachingQueryKey { @@ -177,6 +180,7 @@ where metadata: metadata.clone(), plan_options: plan_options.clone(), config_mode: self.config_mode.clone(), + introspection: self.introspection, }, ) .take(count) @@ -209,6 +213,7 @@ where metadata: CacheKeyMetadata::default(), plan_options: PlanOptions::default(), config_mode: self.config_mode.clone(), + introspection: self.introspection, }); } } @@ -224,6 +229,7 @@ where metadata, plan_options, config_mode: _, + introspection: _, } in all_cache_keys { let context = Context::new(); @@ -240,6 +246,7 @@ where metadata, plan_options, config_mode: self.config_mode.clone(), + introspection: self.introspection, }; if experimental_reuse_query_plans { @@ -422,6 +429,7 @@ where metadata, plan_options, config_mode: self.config_mode.clone(), + introspection: self.introspection, }; let context = request.context.clone(); @@ -562,6 +570,7 @@ pub(crate) struct CachingQueryKey { pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, pub(crate) config_mode: ConfigMode, + pub(crate) introspection: bool, } // Update this key every time the cache key or the query plan format has to change @@ -582,6 +591,7 @@ impl std::fmt::Display for CachingQueryKey { hasher .update(&serde_json::to_vec(&self.config_mode).expect("serialization should not fail")); hasher.update(&serde_json::to_vec(&self.sdl).expect("serialization should not fail")); + hasher.update([self.introspection as u8]); let metadata = hex::encode(hasher.finalize()); write!( @@ -611,6 +621,7 @@ pub(crate) struct WarmUpCachingQueryKey { pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, pub(crate) config_mode: ConfigMode, + pub(crate) introspection: bool, } #[cfg(test)] From ce20a8b32d45e4cd42cb810db21eb81994e1e9f9 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Mon, 13 May 2024 09:53:34 +0200 Subject: [PATCH 19/21] Update apollo-router/src/query_planner/caching_query_planner.rs Co-authored-by: Jesse Rosenberger --- apollo-router/src/query_planner/caching_query_planner.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 2bc6ed9925..e72a8e90c9 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -573,7 +573,8 @@ pub(crate) struct CachingQueryKey { pub(crate) introspection: bool, } -// Update this key every time the cache key or the query plan format has to change +// Update this key every time the cache key or the query plan format has to change. +// When changed it MUST BE CALLED OUT PROMINENTLY IN THE CHANGELOG. const CACHE_KEY_VERSION: usize = 0; const FEDERATION_VERSION: &str = std::env!("FEDERATION_VERSION"); From 43ccd9d21b7d6db5e4c16d9d6a2107169e202d57 Mon Sep 17 00:00:00 2001 From: bryn Date: Mon, 13 May 2024 09:31:52 +0100 Subject: [PATCH 20/21] Add `introspection` to `Hash` implementation. --- apollo-router/src/query_planner/caching_query_planner.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index e72a8e90c9..a6b7594ab2 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -611,6 +611,7 @@ impl Hash for CachingQueryKey { self.metadata.hash(state); self.plan_options.hash(state); self.config_mode.hash(state); + self.introspection.hash(state); } } From 17addbcb21bb4e3f4aa263fde2788e3cf2b35459 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Mon, 13 May 2024 14:20:46 +0200 Subject: [PATCH 21/21] add an expiration to Redis configuration fixtures --- .../fixtures/query_planner_redis_config_update.router.yaml | 3 ++- .../query_planner_redis_config_update_defer.router.yaml | 1 + ...query_planner_redis_config_update_introspection.router.yaml | 1 + ...ery_planner_redis_config_update_query_fragments.router.yaml | 1 + ..._planner_redis_config_update_query_planner_mode.router.yaml | 1 + ...anner_redis_config_update_reuse_query_fragments.router.yaml | 1 + ...r_redis_config_update_type_conditional_fetching.router.yaml | 1 + 7 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml index b9ed5f522d..f0900985d1 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update.router.yaml @@ -5,4 +5,5 @@ supergraph: redis: required_to_start: true urls: - - redis://localhost:6379 \ No newline at end of file + - redis://localhost:6379 + ttl: 10s \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml index 896aef8bef..abe5e560eb 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_defer.router.yaml @@ -6,5 +6,6 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s defer_support: false diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml index a581d82199..cd9a3e2d4d 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_introspection.router.yaml @@ -7,4 +7,5 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml index 79d727f42e..ffb1e49152 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_fragments.router.yaml @@ -6,5 +6,6 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s generate_query_fragments: true diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml index 8f28fd3bea..704cf8fb60 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml @@ -6,6 +6,7 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s experimental_query_planner_mode: new experimental_apollo_metrics_generation_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml index a39145d265..14136a0268 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml @@ -6,5 +6,6 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s experimental_reuse_query_fragments: true diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml index 4fc8b3c7ee..5a83d5d692 100644 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml +++ b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml @@ -6,5 +6,6 @@ supergraph: required_to_start: true urls: - redis://localhost:6379 + ttl: 10s experimental_type_conditioned_fetching: true