From 291cd75c6d18daac98fc686591d8eec3274be434 Mon Sep 17 00:00:00 2001 From: Bryn Cooke Date: Wed, 30 Mar 2022 11:54:16 +0200 Subject: [PATCH 1/8] Move dedup to a traffic_shaping plugin (#753) * Move dedup to a traffic_shaping plugin * Only buffer if dedup layer is applied. We'll have to do something more intelligent when we add more functionality. * Take in review comments Co-authored-by: bryn --- CHANGELOG.md | 5 + .../src/layers/deduplication.rs | 1 + apollo-router-core/src/plugins/mod.rs | 1 + .../src/plugins/traffic_shaping.rs | 119 ++++++++++++++++++ ...nfiguration__tests__schema_generation.snap | 28 ++++- apollo-router/src/router_factory.rs | 7 +- docs/source/config.json | 1 + docs/source/configuration/traffic-shaping.mdx | 35 ++++++ router.yaml | 7 ++ 9 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 apollo-router-core/src/plugins/traffic_shaping.rs create mode 100644 docs/source/configuration/traffic-shaping.mdx create mode 100644 router.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2942fb08e2..0dbd43a2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,16 @@ server: ``` ## 🐛 Fixes +- **Move query dedup to an experimental `traffic_shaping` plugin** ([PR #753](https://github.com/apollographql/router/pull/753)) + + The experimental `traffic_shaping` plugin will be a central location where we can add things such as rate limiting and retry. + - **Remove `hasNext` from our response objects** ([PR #733](https://github.com/apollographql/router/pull/733)) `hasNext` is a field in the response that may be used in future to support features such as defer and stream. However, we are some way off supporting this and including it now may break clients. It has been removed. - **Extend Apollo uplink configurability** ([PR #741](https://github.com/apollographql/router/pull/741)) + Uplink url and poll interval can now be configured via command line arg and env variable: ```bash --apollo-schema-config-delivery-endpoint diff --git a/apollo-router-core/src/layers/deduplication.rs b/apollo-router-core/src/layers/deduplication.rs index 970e355e1f..cc4de9183c 100644 --- a/apollo-router-core/src/layers/deduplication.rs +++ b/apollo-router-core/src/layers/deduplication.rs @@ -8,6 +8,7 @@ use std::{ use tokio::sync::broadcast::{self, Sender}; use tower::{BoxError, Layer, ServiceExt}; +#[derive(Default)] pub struct QueryDeduplicationLayer; impl Layer for QueryDeduplicationLayer diff --git a/apollo-router-core/src/plugins/mod.rs b/apollo-router-core/src/plugins/mod.rs index c922e505d1..b7a039aeca 100644 --- a/apollo-router-core/src/plugins/mod.rs +++ b/apollo-router-core/src/plugins/mod.rs @@ -1,2 +1,3 @@ mod forbid_mutations; mod headers; +mod traffic_shaping; diff --git a/apollo-router-core/src/plugins/traffic_shaping.rs b/apollo-router-core/src/plugins/traffic_shaping.rs new file mode 100644 index 0000000000..37f076cb2d --- /dev/null +++ b/apollo-router-core/src/plugins/traffic_shaping.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; + +use schemars::JsonSchema; +use serde::Deserialize; +use tower::util::BoxService; +use tower::{BoxError, ServiceBuilder, ServiceExt}; + +use crate::deduplication::QueryDeduplicationLayer; +use crate::plugin::Plugin; +use crate::{register_plugin, SubgraphRequest, SubgraphResponse}; + +#[derive(PartialEq, Debug, Clone, Deserialize, JsonSchema)] +struct Shaping { + dedup: Option, +} + +impl Shaping { + fn merge(&self, fallback: Option<&Shaping>) -> Shaping { + match fallback { + None => self.clone(), + Some(fallback) => Shaping { + dedup: self.dedup.or(fallback.dedup), + }, + } + } +} + +#[derive(PartialEq, Debug, Clone, Deserialize, JsonSchema)] +struct Config { + #[serde(default)] + all: Option, + #[serde(default)] + subgraphs: HashMap, +} + +struct TrafficShaping { + config: Config, +} + +#[async_trait::async_trait] +impl Plugin for TrafficShaping { + type Config = Config; + + fn new(config: Self::Config) -> Result { + Ok(Self { config }) + } + + fn subgraph_service( + &mut self, + name: &str, + service: BoxService, + ) -> BoxService { + // Either we have the subgraph config and we merge it with the all config, or we just have the all config or we have nothing. + let all_config = self.config.all.as_ref(); + let subgraph_config = self.config.subgraphs.get(name); + let final_config = Self::merge_config(all_config, subgraph_config); + + if let Some(config) = final_config { + ServiceBuilder::new() + .option_layer(config.dedup.unwrap_or_default().then(|| { + //Buffer is required because dedup layer requires a clone service. + ServiceBuilder::new() + .layer(QueryDeduplicationLayer::default()) + .buffer(20_000) + })) + .service(service) + .boxed() + } else { + service + } + } +} + +impl TrafficShaping { + fn merge_config( + all_config: Option<&Shaping>, + subgraph_config: Option<&Shaping>, + ) -> Option { + let merged_subgraph_config = subgraph_config.map(|c| c.merge(all_config)); + merged_subgraph_config.or_else(|| all_config.cloned()) + } +} + +register_plugin!("experimental", "traffic_shaping", TrafficShaping); + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_merge_config() { + let config = serde_yaml::from_str::( + r#" + all: + dedup: true + subgraphs: + products: + dedup: false + "#, + ) + .unwrap(); + + assert_eq!(TrafficShaping::merge_config(None, None), None); + assert_eq!( + TrafficShaping::merge_config(config.all.as_ref(), None), + config.all + ); + assert_eq!( + TrafficShaping::merge_config(config.all.as_ref(), config.subgraphs.get("products")) + .as_ref(), + config.subgraphs.get("products") + ); + + assert_eq!( + TrafficShaping::merge_config(None, config.subgraphs.get("products")).as_ref(), + config.subgraphs.get("products") + ); + } +} diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 0aab1cc106..5afcbe6ded 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1,6 +1,5 @@ --- source: apollo-router/src/configuration/mod.rs -assertion_line: 439 expression: "&schema" --- { @@ -271,6 +270,33 @@ expression: "&schema" } }, "additionalProperties": false + }, + "experimental.traffic_shaping": { + "type": "object", + "properties": { + "all": { + "type": "object", + "properties": { + "dedup": { + "type": "boolean", + "nullable": true + } + }, + "nullable": true + }, + "subgraphs": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "dedup": { + "type": "boolean", + "nullable": true + } + } + } + } + } } }, "additionalProperties": false diff --git a/apollo-router/src/router_factory.rs b/apollo-router/src/router_factory.rs index 0414a02a67..71ecbbf1d4 100644 --- a/apollo-router/src/router_factory.rs +++ b/apollo-router/src/router_factory.rs @@ -1,5 +1,4 @@ use crate::configuration::{Configuration, ConfigurationError}; -use apollo_router_core::deduplication::QueryDeduplicationLayer; use apollo_router_core::{ http_compat::{Request, Response}, PluggableRouterServiceBuilder, ResponseBody, RouterRequest, Schema, @@ -9,7 +8,7 @@ use apollo_router_core::{DynPlugin, ReqwestSubgraphService}; use std::sync::Arc; use tower::buffer::Buffer; use tower::util::{BoxCloneService, BoxService}; -use tower::{BoxError, Layer, ServiceBuilder, ServiceExt}; +use tower::{BoxError, ServiceBuilder, ServiceExt}; use tower_service::Service; /// Factory for creating a RouterService @@ -78,9 +77,7 @@ impl RouterServiceFactory for YamlRouterServiceFactory { } for (name, _) in schema.subgraphs() { - let dedup_layer = QueryDeduplicationLayer; - let subgraph_service = - BoxService::new(dedup_layer.layer(ReqwestSubgraphService::new(name.to_string()))); + let subgraph_service = BoxService::new(ReqwestSubgraphService::new(name.to_string())); builder = builder.with_subgraph_service(name, subgraph_service); } diff --git a/docs/source/config.json b/docs/source/config.json index 3a15c67ad2..336a955198 100644 --- a/docs/source/config.json +++ b/docs/source/config.json @@ -14,6 +14,7 @@ "Logging": "/configuration/logging", "Header propagation": "/configuration/header-propagation", "OpenTelemetry": "/configuration/opentelemetry", + "Traffic shaping": "/configuration/traffic-shaping", "Usage reporting": "/configuration/spaceport" }, "Development workflow": { diff --git a/docs/source/configuration/traffic-shaping.mdx b/docs/source/configuration/traffic-shaping.mdx new file mode 100644 index 0000000000..e66d3dee9b --- /dev/null +++ b/docs/source/configuration/traffic-shaping.mdx @@ -0,0 +1,35 @@ +--- +title: Traffic shaping +description: Configuring traffic shaping +--- + +import { Link } from "gatsby"; + +> ⚠️ Apollo Router support for traffic shaping is currently experimental. + +The Apollo Router provides experimental support for traffic shaping. + +Currently features are limited, but are expected to grow over time: + +* **Sub-query deduplication** - Identical, in-flight, non-mutation sub-queries are compressed into a single request. + +## Configuration +To configure traffic shaping add the `traffic_shaping` plugin to `your router.yaml`: + +```yaml title="router.yaml" +plugins: + experimental.traffic_shaping: + all: + dedup: true # Enable dedup for all subgraphs. + subgraphs: + products: + dedup: false # Disable dedup for products. +``` + +Note that configuration in the `subgraphs` section will take precedence over that in the `all` section. + +### Sub-query deduplication + +Deduplication will cause any identical, in-flight, non-mutation sub-queries to be merged into a single request. This can reduce network bandwidth and CPU at your subgraph. + +Note that only in flight requests are deduplicated. diff --git a/router.yaml b/router.yaml new file mode 100644 index 0000000000..09af46f9d0 --- /dev/null +++ b/router.yaml @@ -0,0 +1,7 @@ +plugins: + experimental.traffic_shaping: + all: + dedup: true + subgraphs: + products: + dedup: true From 2103a1e92673ac838e55cfadc2edcd22f3202f8d Mon Sep 17 00:00:00 2001 From: Jeremy Lempereur Date: Wed, 30 Mar 2022 12:18:26 +0200 Subject: [PATCH 2/8] chore: CORS default Configuration (#759) Fixes :#40 The Router now allows only the `https://studio.apollographql.com` origin by default, instead of any origin. --- CHANGELOG.md | 10 +++++-- apollo-router/src/configuration/mod.rs | 28 +++++++++++++++---- ...nfiguration__tests__schema_generation.snap | 5 ++-- docs/source/configuration/cors.mdx | 11 ++++---- examples/async-auth/router.yaml | 4 --- examples/context/router.yaml | 6 ---- examples/embedded/router.yaml | 4 --- .../forbid-anonymous-operations/router.yaml | 4 --- examples/forbid_mutations/router.yaml | 4 --- examples/hello-world/router.yaml | 4 --- examples/jwt-auth/router.yaml | 8 +----- 11 files changed, 41 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd43a2fb..8764ce619b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm --> - -# [v0.1.0-preview.2] (unreleased) - 2022-mm-dd +# [v0.1.0-preview.2] - 2022-04-01 ## ❗ BREAKING ❗ - **CORS default Configuration** ([#40](https://github.com/apollographql/router/issues/40)) @@ -34,7 +34,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## 🚀 Features - **Hot reload via en environment variable** ([766](https://github.com/apollographql/router/issues/766)) - You can now ust the ROUTER_HOT_RELOAD=true environment variable to have the router watch for configuration and schema changes and automatically reload. + You can now use the `ROUTER_HOT_RELOAD=true` environment variable to have the router watch for configuration and schema changes and automatically reload. - **Container images are now available** ([PR #764](https://github.com/apollographql/router/pull/764)) @@ -46,9 +46,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The images are based on [distroless](https://github.com/GoogleContainerTools/distroless) which is a very constrained image, intended to be secure and small. - We'll provide release and debug images for each release. The debug image has a busybox shell which can be accessed using (for instance) --entrypoint=sh. + We'll provide release and debug images for each release. The debug image has a busybox shell which can be accessed using (for instance) `--entrypoint=sh`. - For more details about thse images, see the docs. + For more details about these images, see the docs. - **Skip and Include directives in post processing** ([PR #626](https://github.com/apollographql/router/pull/626)) @@ -58,10 +58,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm While schema introspection is useful in development, we might not want to expose the entire schema in production, so the router can be configured to forbid introspection queries as follows: -```yaml -server: - introspection: false -``` + ```yaml + server: + introspection: false + ``` ## 🐛 Fixes - **Move query dedup to an experimental `traffic_shaping` plugin** ([PR #753](https://github.com/apollographql/router/pull/753)) @@ -90,7 +90,7 @@ server: - **Relax variables selection for subgraph queries** ([PR #755](https://github.com/apollographql/router/pull/755)) - federated subgraph queries relying on partial or invalid data from previous subgraph queries could result in response failures or empty subgraph queries. The router is now more flexible when selecting data from previous queries, while still keeping a correct form for the final response + Federated subgraph queries relying on partial or invalid data from previous subgraph queries could result in response failures or empty subgraph queries. The router is now more flexible when selecting data from previous queries, while still keeping a correct form for the final response ## 🛠 Maintenance ## 📚 Documentation diff --git a/Cargo.lock b/Cargo.lock index bf8e2b3157..49efa19c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "apollo-router" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "anyhow", "apollo-parser 0.2.4 (git+https://github.com/apollographql/apollo-rs.git?rev=e707e0f78f41ace1c3ecfe69bc10f4144ffbf7ac)", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "apollo-router-benchmarks" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "apollo-router", "apollo-router-core", @@ -194,7 +194,7 @@ dependencies = [ [[package]] name = "apollo-router-core" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "apollo-parser 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "async-trait", @@ -257,7 +257,7 @@ dependencies = [ [[package]] name = "apollo-spaceport" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "bytes", "clap 3.1.6", @@ -278,7 +278,7 @@ dependencies = [ [[package]] name = "apollo-uplink" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "futures", "graphql_client", @@ -5377,7 +5377,7 @@ dependencies = [ [[package]] name = "xtask" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" dependencies = [ "ansi_term", "anyhow", diff --git a/apollo-router-benchmarks/Cargo.toml b/apollo-router-benchmarks/Cargo.toml index cf2eab1b61..b732ebbe09 100644 --- a/apollo-router-benchmarks/Cargo.toml +++ b/apollo-router-benchmarks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router-benchmarks" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" authors = ["Apollo Graph, Inc. "] edition = "2021" license = "LicenseRef-ELv2" diff --git a/apollo-router-core/Cargo.toml b/apollo-router-core/Cargo.toml index 6e193d7af0..0b165b5976 100644 --- a/apollo-router-core/Cargo.toml +++ b/apollo-router-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router-core" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" authors = ["Apollo Graph, Inc. "] edition = "2021" license-file = "./LICENSE" diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index c404a0a094..e67bf0ea79 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-router" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" authors = ["Apollo Graph, Inc. "] edition = "2021" license-file = "./LICENSE" diff --git a/apollo-spaceport/Cargo.toml b/apollo-spaceport/Cargo.toml index 39a83a858e..df77cf4fe4 100644 --- a/apollo-spaceport/Cargo.toml +++ b/apollo-spaceport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-spaceport" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" authors = ["Apollo Graph, Inc. "] edition = "2021" license-file = "./LICENSE" diff --git a/deny.toml b/deny.toml index a713c6acb8..16e1234711 100644 --- a/deny.toml +++ b/deny.toml @@ -64,13 +64,13 @@ license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] [[licenses.clarify]] name = "apollo-router" expression = "LicenseRef-ELv2" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" license-files = [{ path = "LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] name = "apollo-router-core" expression = "LicenseRef-ELv2" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" license-files = [{ path = "LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] @@ -81,7 +81,7 @@ license-files = [{ path = "router-bridge/LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] name = "apollo-spaceport" expression = "LicenseRef-ELv2" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" license-files = [{ path = "LICENSE", hash = 0xaceadac9 }] [[licenses.clarify]] diff --git a/uplink/Cargo.toml b/uplink/Cargo.toml index 6e46ab01cf..7cf785da7d 100644 --- a/uplink/Cargo.toml +++ b/uplink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apollo-uplink" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" edition = "2021" build = "build.rs" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 5420af74ec..e10e8e3804 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xtask" -version = "0.1.0-preview.1" +version = "0.1.0-preview.2" authors = ["Apollo Graph, Inc. "] edition = "2021" license = "LicenseRef-ELv2" From d57b821fbefe91284180e8fbd9dae3145d94d564 Mon Sep 17 00:00:00 2001 From: Gary Pennington Date: Fri, 1 Apr 2022 10:08:41 +0100 Subject: [PATCH 8/8] add a user-agent header to the studio usage ingress submission fixes: #733 --- apollo-spaceport/src/server.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apollo-spaceport/src/server.rs b/apollo-spaceport/src/server.rs index 0b82155102..07e348b05b 100644 --- a/apollo-spaceport/src/server.rs +++ b/apollo-spaceport/src/server.rs @@ -169,6 +169,14 @@ impl ReportSpaceport { .header("Content-Encoding", "gzip") .header("Content-Type", "application/protobuf") .header("Accept", "application/json") + .header( + "User-Agent", + format!( + "{} / {} usage reporting", + std::env!("CARGO_PKG_NAME"), + std::env!("CARGO_PKG_VERSION") + ), + ) .build() .map_err(|e| Status::unavailable(e.to_string()))?;