From b3ab553f7c37fff0c75f954ff1faaee064ce56df Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Fri, 31 Mar 2023 18:22:04 -0500 Subject: [PATCH 01/10] Add RFC: client configuration for the orchestrator --- design/src/SUMMARY.md | 1 + design/src/rfcs/overview.md | 2 + ...5_client_configuration_for_orchestrator.md | 270 ++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md diff --git a/design/src/SUMMARY.md b/design/src/SUMMARY.md index ae41ea5246..1b6fe6509b 100644 --- a/design/src/SUMMARY.md +++ b/design/src/SUMMARY.md @@ -57,6 +57,7 @@ - [RFC-0032: Better Constraint Violations](./rfcs/rfc0032_better_constraint_violations.md) - [RFC-0033: Improving access to request IDs in SDK clients](./rfcs/rfc0033_improve_sdk_request_id_access.md) - [RFC-0034: Smithy Orchestrator](./rfcs/rfc0034_smithy_orchestrator.md) + - [RFC-0035: Client configuration for the orchestrator](./rfcs/rfc0035_client_configuration_for_orchestrator.md) - [Contributing](./contributing/overview.md) - [Writing and debugging a low-level feature that relies on HTTP](./contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md) diff --git a/design/src/rfcs/overview.md b/design/src/rfcs/overview.md index e388ec4929..ae62cfab29 100644 --- a/design/src/rfcs/overview.md +++ b/design/src/rfcs/overview.md @@ -43,3 +43,5 @@ - [RFC-0031: Providing Fallback Credentials on Timeout](./rfc0031_providing_fallback_credentials_on_timeout.md) - [RFC-0032: Better Constraint Violations](./rfc0032_better_constraint_violations.md) - [RFC-0033: Improving access to request IDs in SDK clients](./rfc0033_improve_sdk_request_id_access.md) +- [RFC-0034: Smithy Orchestrator](./rfc0034_smithy_orchestrator.md) +- [RFC-0035: Client configuration for the orchestrator](./rfc0035_client_configuration_for_orchestrator.md) diff --git a/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md b/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md new file mode 100644 index 0000000000..c995d195ea --- /dev/null +++ b/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md @@ -0,0 +1,270 @@ +RFC: Client configuration for the orchestrator +======================= + +> Status: RFC +> +> Applies to: client + +This RFC proposes two areas of improvement for configuring an SDK client: +- Support for operation-level configuration +- Support for runtime components required by the orchestrator + +We have encountered use cases where configuring a client for a single operation invocation is necessary ([example](https://github.com/awslabs/aws-sdk-rust/issues/696)). At the time of writing, this feature is not yet supported, but operation-level configuration will address that limitation. + +As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md), the orchestrator uses configured components for an SDK to handle messages between the client and a service. They include: +- `RetryStrategy`: Configures how requests are retried. +- `TraceProbes`: Configures locations to which SDK metrics are published. +- `EndpointProviders`: Configures which hostname an SDK will call when making a request. +- `HTTPClients`: Configures how remote services are called. +- `IdentityProviders: Configures how users identify themselves to remote services. +- `HTTPAuthSchemes` & `AuthSchemeResolver`s: Configures how users authenticate themselves to remote services. +- `Checksum Algorithms`: Configures how an SDK calculates request and response checksums. +- `Interceptors`: Configures specific stages of the request execution pipeline. + +However, not all of these components are covered by the public APIs for configuring them, and for those that are, they do not appear under exactly the same names, e.g. [endpoint_url](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.endpoint_url) and [retry_config](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.retry_config). This RFC proposes allowing users to configure the above components either by keeping current APIs or adding new ones. + +What this RFC does NOT cover: +- Interceptors: A future RFC will provide a detailed description of this feature +- Step-by-step instructions for how to migrate current default runtime components to the orchestrator + +Terminology +----------- + +- Component: An interface coupled with its default implementations to enable an SDK client functionality. +- Fluent Client: A code generated Client that has methods for each service operation on it. A fluent builder is generated alongside it to make construction easier. +- Operation: A high-level abstraction representing an interaction between an SDK Client and a remote service. +- Orchestrator: The code within an SDK client that handles the process of making requests and receiving responses from remote services. +- Remote Service: A remote API that a user wants to use. Communication with a remote service usually happens over HTTP. The remote service is usually, but not necessarily, an AWS service. +- SDK Client: A client generated for the AWS SDK, allowing users to make requests to remote services. + + +The user experience if this RFC is implemented +---------------------------------------------- + + +### Operation-level configuration + +Currently, users are able to customize runtime configuration at multiple levels. `SdkConfig` is used to configure settings for all services, while a service config (e.g., `aws_sdk_s3::config::Config`) is used to configure settings for a specific service client. + +With this RFC, users will be able to go one step further and override configuration for a single operation invocation: +```rust +let sdk_config = aws_config::from_env().load().await; +let s3_client = aws_sdk_s3::client::Client::new(&sdk_config); +s3_client.create_bucket() + .bucket(bucket_name) + .config_override(aws_sdk_s3::config::builder().region("us-west-1")) + .send() + .await; +``` + +This is achieved through the `.config_override` method, which is added to fluent builders, such as `fluent_builders::CreateBucket`. This method sets the `us-west-1` region to override any region setting specified in the service level config. The operation level config takes the highest precedence, followed by the service level config, and then the AWS level config. + +The `config_override` method takes a service config builder instead of a service config, allowing `None` values to be used for fields, so as not to override settings at a lower-precedence configuration. + +The main benefit of this approach is simplicity for users. The only change required is to call the `config_override` method on an operation input fluent builder. If users do not wish to specify the operation level config, their workflow will remain unaffected. + +### Configuring runtime components required by the orchestrator + +While `ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's `Tower`-based infrastructure, they do not cover all of the components required for the orchestrator to perform its job. + +The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the RFC (the right column). + +| Runtime component | Today's builder method | Proposed builder method | +| :-: | --- | --- | +| RetryStrategy | `.retry_config` | `.retry_strategy(&self, impl RetryStrategy + 'static)` | +| TraceProbes | None | (See the Changes checklist) | +| EndpointResolver | `.endpoint_url` | No change | +| HTTPClients | `.http_connector` | No change | +| IdentityProviders | `.credentials_cache` & `.credentials_provider` | No change | +| HTTPAuthSchemes & AuthSchemeResolvers | None | `.auth_scheme(&self, impl HttpAuthScheme + 'static)` | +| Checksum Algorithms | `.checksum_algorithm` only at the operation level for those that support it | No change | +| Interceptors | None | `.interceptor(&self, impl Interceptor + 'static)` | + +The proposed methods generally take a type that implements [the corresponding trait from RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md#traits). However, for backward compatibility reasons, we mark "No change" for `EndpointResolver`, `HttpClients`, and `IdentityProviders`: +- `EndpointResolver` - we [deprecated the previous the .endpoint_resolver method](https://github.com/awslabs/smithy-rs/pull/2464). Introducing a new `.endpoint_resolver` might run counter to Endpoints 2.0 migration. +- `HttpClients` - users have been able to specify custom connections using the existing method, and introducing a new method like `.connection(&self, impl Connection + 'static)` might require non-trivial upgrades. +- `IdentityProviders` - users have been able to specify credentials cache and provider in a flexible manner using the existing methods, and introducing a new method like `.identity_provider(&self, impl IdentityProvider + 'static)` might require non-trivial upgrades. + +We also marked "No change" for `Checksum Algorithms` because it should not be arbitrarily configurable at the service level config. Today, operations that support a predefined set of checksum algorithms expose the `checksum_algorithm` method through their fluent builders. + +How to actually implement this RFC +---------------------------------- +Implementing this RFC is tied to [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md). In that RFC, applying client configuration means putting runtime components and required function parameters into a type map for `aws_smithy_runtime::client::orchestrator::invoke` to use. + +This section covers three parts: +- Where we store operation-level config +- How we put runtime components and required function parameters into a type map +- How we plan to migrate today's runtime components to the world of the orchestrator + +#### Where we store operation-level config + +Given that fluent builders will have the `configure_override` method, it makes sense for operation-level configuration to be stored in fluent builders. The code generator will be updated to add the following field to fluent builders: +```rust +config_override: Option // e.g. Option +``` +The field is of type `Option`, so `None` means a case where a user did not specify the operation-level runtime configuration at all. With that, the `config_override` method will look as follows, also added by the code generator to fluent builders: +```rust +pub fn config_override( + mut self, + config_override: impl Into, +) -> Self { + self.config_override = Some(config_override.into()); + self +} +``` + +#### How we put runtime components and required function parameters into a type map + +First, a high-level picture of the layered configuration needs to be reviewed. When a user has executed the following code: +```rust +let sdk_config = aws_config::from_env().load().await; +let s3_client = aws_sdk_s3::client::Client::new(&sdk_config); +let fluent_builder = s3_client.list_buckets() + .bucket(bucket_name) + .config_override(aws_sdk_s3::config::builder().region("us-west-1")); +``` +The relations between the types can be illustrated in the following diagram: + +client config at different levels + +The `aws_sdk_s3::config::Config` type on the left stores the service-level configuration, which implicitly includes/shadows the AWS-level configuration. `Config` is accessible via `Handle` from a fluent builder `ListBucketsFluentBuilder`, which holds the operation level config. + +After the `send` method is called on the `fluent_builder` variable and before it internally calls `aws_smithy_runtime::client::orchestrator::invoke`, the fields in `self.handle.conf` and those in `self.config_override` will be put into a type map. How this is done exactly is an implementation detail outside the scope of the RFC, but we have been working on [send_v2](https://github.com/awslabs/smithy-rs/blob/b023426d1cfd05e1fd9eef2f92a21cad58b93b86/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L331-L347) to allow for a gradual transition. + +We use the `aws_smithy_runtime_api::config_bag::ConfigBag` type as the type map, and we have defined a trait called `aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin` that allows trait implementors to add key-value pairs to `ConfigBag`. The `RuntimePlugin` trait is defined as follows: +```rust +pub trait RuntimePlugin { + fn configure(&self, cfg: &mut ConfigBag) -> Result<(), BoxError>; +} +``` + +With this in mind, we plan to +- Implement the `RuntimePlugin` trait for service configuration, which will put runtime components and required function parameters specified at the service level config into `ConfigBag` +- Implement the `RuntimePlugin` trait for service configuration builders, which will put runtime components and required function parameters specified at the operation level config into `ConfigBag`. + +Once these changes are made, fluent builders will be able to do something like this: +```rust +// pseudo-code, possibly somewhere down the line of the `send_v2` method + +let mut config_bag = /* create an empty config bag */; + +// first put into the bag things from the service level config +self.handle.conf.configure(&mut config_bag); + +// then put into the bag things from the operation level config +if let Some(config_override) = self.config_override { + self.config_override.configure(&mut config_bag); +} +``` +Note that if `ConfigLoader`, `SdkConfig`, and service configurations, including all of their builders, are implemented in terms of `ConfigBag`, implementing the `RuntimePlugin` trait may become unnecessary because the fields have already been stored in the bag. In that case, however, `ConfigBag`, which is held by `Arc`, may need to be unshared so that it can be passed as `&mut` to `aws_smithy_runtime::client::orchestrator::invoke`. + +#### How we plan to migrate today's runtime components to the world of the orchestrator + +This part is a work in progress because we will learn more about the implementation details as we shift more and more runtime components to the world of the orchestrator. That said, here's our migration plan at a high level, using `DefaultResolver` as an example. + +Currently, `DefaultResolver` and its user-specified parameters are wired together within `make_operation` on an operation input struct (such as `ListBucketsInput`). +```rust +// omitting unnecessary details + +pub async fn make_operation( + &self, + _config: &crate::config::Config, +) -> std::result::Result< + aws_smithy_http::operation::Operation< + crate::operation::list_buckets::ListBuckets, + aws_http::retry::AwsResponseRetryClassifier, + >, + aws_smithy_http::operation::error::BuildError, +> { + let params_result = crate::endpoint::Params::builder() + .set_region(_config.region.as_ref().map(|r| r.as_ref().to_owned())) + .set_use_fips(_config.use_fips) + .set_use_dual_stack(_config.use_dual_stack) + // --snip-- + .set_accelerate(_config.accelerate) + .build(); + + let (endpoint_result, params) = match params_result { + // `_config.endpoint_resolver` is a trait object whose underlying type + // is `DefaultResolver`. + Ok(params) => ( + _config.endpoint_resolver.resolve_endpoint(¶ms), + Some(params), + ), + Err(e) => (Err(e), None), + }; + + // --snip-- + + let mut properties = aws_smithy_http::property_bag::SharedPropertyBag::new(); + let body = aws_smithy_http::body::SdkBody::from(""); + let request = request.body(body).expect("should be valid request"); + let mut request = aws_smithy_http::operation::Request::from_parts(request, properties); + request.properties_mut().insert(endpoint_result); + if let Some(params) = params { + request.properties_mut().insert(params); + } + + // --snip-- +} +``` + +The `endpoint_result` and `params` stored in `SharedPropertyBag` are later used by the [`MapRequestLayer` for `SmithyEndpointStage`](https://github.com/awslabs/smithy-rs/blob/main/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs#L37-L83) to add to the request header as it is dispatched. + +With the transition to the orchestrator world, the functionality performed by `make_operation` and `SmithyEndpointStage::apply` will be moved to `DefaultResolver`. Specifically, `DefaultResolver` will implement the `aws_smithy_runtime_api::client::orchestrator::EndpointResolver trait`: +```rust +impl aws_smithy_runtime_api::client::orchestrator::EndpointResolver for DefaultResolver { + fn resolve_and_apply_endpoint( + &self, + request: &mut aws_smithy_runtime_api::client::orchestrator::HttpRequest, + cfg: &aws_smithy_runtime_api::config_bag::ConfigBag, + ) -> Result<(), aws_smithy_runtime_api::client::orchestrator::BoxError> { + /* + * Here, handles part of `make_operation` to yield `endpoint_result` and `params`. + * We may need to assume that the entire `Params` is already available + * in `cfg`, rather than extract every piece out of `cfg` necessary and + * build a `Params` here. + */ + + /* + * Later part of the method handles what SmithyEndpointStage does, + * modifying the passed-in `request`. + */ + } +} +``` + +Other runtime components have different requirements for their migration to the orchestrator world. A detailed roadmap for each of them is beyond the scope of this RFC. + +However, the general refactoring pattern involves moving logic out of `make_operation` and the associated `Tower` layer, and placing it in a struct that implements the relevant trait defined in `aws_smithy_runtime_api::client::orchestrator`. + +Changes checklist +----------------- + +- [ ] Put client configuration into `ConfigBag` by either of the two + - [ ] Implementing RuntimePlugin for service configs and service config builders + - [ ] Updating `ConfigLoader`, `SdkConfig`, and service configs to be backed by `ConfigBag` +- [ ] Decide whether `aws_smithy_runtime_api::client::orchestrator::TraceProbe` should be supported when users can already set up `tracing::Subscriber` in the SDK +- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::RetryStrategy` with the orchestrator +- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::EndpointResolver` with the orchestrator +- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::Connection` with the orchestrator +- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::IdentityResolvers` with the orchestrator +- [ ] Integrate the default implementations for `aws_smithy_runtime_api::client::orchestrator::HttpAuthSchemes` and `aws_smithy_runtime_api::client::orchestrator::AuthOptionResolver` with the orchestrator + +Appendix: Exposing `.runtime_plugin` through config types +--------------------------------------------------------- +Alternatively, a `runtime_plugin` method could be added to `ConfigLoader`, `SdkConfig`, and service configs. The method would look like this: +```rust +pub fn runtime_plugin( + mut self, + runtime_plugin: impl RuntimePlugin + 'static, +) -> Self +{ + todo!() +} +``` +However, this approach forces users to implement the `RuntimePlugin` trait for a given type, leading to the following issues: +- The `RuntimePlugin::configure` method takes a `&mut ConfigBag`, and it may not be immediately clear to users what they should put into or extract from the bag to implement the method. In contrast, methods like `endpoint_url` or `retry_config` are self-explanatory, and users are already familiar with them. +- With the `runtime_plugin` method, there could be two ways to configure the same runtime component. If a user specifies both `endpoint_url` and `runtime_plugin(/* a plugin for an endpoint */)` at the same time on `ConfigLoader`, it would be necessary to determine which one takes precedence. + +For these reasons, this solution was dismissed as such. From a2c6da30784196b63fa28fa5922d7410418ab350 Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Wed, 5 Apr 2023 09:25:19 -0500 Subject: [PATCH 02/10] Update design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md Co-authored-by: John DiSanti --- .../src/rfcs/rfc0035_client_configuration_for_orchestrator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md b/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md index c995d195ea..52afc9ebbd 100644 --- a/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md +++ b/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md @@ -16,7 +16,7 @@ As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/s - `TraceProbes`: Configures locations to which SDK metrics are published. - `EndpointProviders`: Configures which hostname an SDK will call when making a request. - `HTTPClients`: Configures how remote services are called. -- `IdentityProviders: Configures how users identify themselves to remote services. +- `IdentityProviders`: Configures how users identify themselves to remote services. - `HTTPAuthSchemes` & `AuthSchemeResolver`s: Configures how users authenticate themselves to remote services. - `Checksum Algorithms`: Configures how an SDK calculates request and response checksums. - `Interceptors`: Configures specific stages of the request execution pipeline. From d6ec7522ae732d8b4e95c95b004cb8388cc995a9 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Wed, 5 Apr 2023 22:03:16 -0500 Subject: [PATCH 03/10] Convert RFC to evolving design doc This commit converts RFC to an evolving design document. The document leaves out too many implementation details to be called an RFC. They will be discovered over time and the document will be updated accordingly rather than a snapshot of the decision record. --- design/src/SUMMARY.md | 1 - .../configuration.md} | 67 +++++-------------- design/src/rfcs/overview.md | 1 - 3 files changed, 17 insertions(+), 52 deletions(-) rename design/src/{rfcs/rfc0035_client_configuration_for_orchestrator.md => client/configuration.md} (76%) diff --git a/design/src/SUMMARY.md b/design/src/SUMMARY.md index 1b6fe6509b..ae41ea5246 100644 --- a/design/src/SUMMARY.md +++ b/design/src/SUMMARY.md @@ -57,7 +57,6 @@ - [RFC-0032: Better Constraint Violations](./rfcs/rfc0032_better_constraint_violations.md) - [RFC-0033: Improving access to request IDs in SDK clients](./rfcs/rfc0033_improve_sdk_request_id_access.md) - [RFC-0034: Smithy Orchestrator](./rfcs/rfc0034_smithy_orchestrator.md) - - [RFC-0035: Client configuration for the orchestrator](./rfcs/rfc0035_client_configuration_for_orchestrator.md) - [Contributing](./contributing/overview.md) - [Writing and debugging a low-level feature that relies on HTTP](./contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md) diff --git a/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md b/design/src/client/configuration.md similarity index 76% rename from design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md rename to design/src/client/configuration.md index c995d195ea..fa553cc42b 100644 --- a/design/src/rfcs/rfc0035_client_configuration_for_orchestrator.md +++ b/design/src/client/configuration.md @@ -1,11 +1,6 @@ -RFC: Client configuration for the orchestrator -======================= +Client configuration for the orchestrator -> Status: RFC -> -> Applies to: client - -This RFC proposes two areas of improvement for configuring an SDK client: +This document discusses two areas of improvement for configuring an SDK client: - Support for operation-level configuration - Support for runtime components required by the orchestrator @@ -16,37 +11,24 @@ As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/s - `TraceProbes`: Configures locations to which SDK metrics are published. - `EndpointProviders`: Configures which hostname an SDK will call when making a request. - `HTTPClients`: Configures how remote services are called. -- `IdentityProviders: Configures how users identify themselves to remote services. +- `IdentityProviders`: Configures how users identify themselves to remote services. - `HTTPAuthSchemes` & `AuthSchemeResolver`s: Configures how users authenticate themselves to remote services. - `Checksum Algorithms`: Configures how an SDK calculates request and response checksums. - `Interceptors`: Configures specific stages of the request execution pipeline. -However, not all of these components are covered by the public APIs for configuring them, and for those that are, they do not appear under exactly the same names, e.g. [endpoint_url](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.endpoint_url) and [retry_config](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.retry_config). This RFC proposes allowing users to configure the above components either by keeping current APIs or adding new ones. - -What this RFC does NOT cover: -- Interceptors: A future RFC will provide a detailed description of this feature -- Step-by-step instructions for how to migrate current default runtime components to the orchestrator - -Terminology ------------ - -- Component: An interface coupled with its default implementations to enable an SDK client functionality. -- Fluent Client: A code generated Client that has methods for each service operation on it. A fluent builder is generated alongside it to make construction easier. -- Operation: A high-level abstraction representing an interaction between an SDK Client and a remote service. -- Orchestrator: The code within an SDK client that handles the process of making requests and receiving responses from remote services. -- Remote Service: A remote API that a user wants to use. Communication with a remote service usually happens over HTTP. The remote service is usually, but not necessarily, an AWS service. -- SDK Client: A client generated for the AWS SDK, allowing users to make requests to remote services. +However, not all of these components are covered by the public APIs for configuring them, and for those that are, they do not appear under exactly the same names, e.g. [endpoint_url](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.endpoint_url) and [retry_config](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.retry_config). We will allow users to configure the above components either by keeping current APIs or adding new ones. +There are a lot of moving parts and this document is expected to evolve as we discover the implementation details. -The user experience if this RFC is implemented ----------------------------------------------- +The user experience if this design is implemented +------------------------------------------------- ### Operation-level configuration Currently, users are able to customize runtime configuration at multiple levels. `SdkConfig` is used to configure settings for all services, while a service config (e.g., `aws_sdk_s3::config::Config`) is used to configure settings for a specific service client. -With this RFC, users will be able to go one step further and override configuration for a single operation invocation: +With this design, users will be able to go one step further and override configuration for a single operation invocation: ```rust let sdk_config = aws_config::from_env().load().await; let s3_client = aws_sdk_s3::client::Client::new(&sdk_config); @@ -67,7 +49,7 @@ The main benefit of this approach is simplicity for users. The only change requi While `ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's `Tower`-based infrastructure, they do not cover all of the components required for the orchestrator to perform its job. -The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the RFC (the right column). +The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the design (the right column). | Runtime component | Today's builder method | Proposed builder method | | :-: | --- | --- | @@ -87,9 +69,9 @@ The proposed methods generally take a type that implements [the corresponding tr We also marked "No change" for `Checksum Algorithms` because it should not be arbitrarily configurable at the service level config. Today, operations that support a predefined set of checksum algorithms expose the `checksum_algorithm` method through their fluent builders. -How to actually implement this RFC ----------------------------------- -Implementing this RFC is tied to [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md). In that RFC, applying client configuration means putting runtime components and required function parameters into a type map for `aws_smithy_runtime::client::orchestrator::invoke` to use. +How to actually implement this design +------------------------------------- +Implementing this design is tied to [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md). In that RFC, applying client configuration means putting runtime components and required function parameters into a type map for `aws_smithy_runtime::client::orchestrator::invoke` to use. This section covers three parts: - Where we store operation-level config @@ -129,7 +111,7 @@ The relations between the types can be illustrated in the following diagram: The `aws_sdk_s3::config::Config` type on the left stores the service-level configuration, which implicitly includes/shadows the AWS-level configuration. `Config` is accessible via `Handle` from a fluent builder `ListBucketsFluentBuilder`, which holds the operation level config. -After the `send` method is called on the `fluent_builder` variable and before it internally calls `aws_smithy_runtime::client::orchestrator::invoke`, the fields in `self.handle.conf` and those in `self.config_override` will be put into a type map. How this is done exactly is an implementation detail outside the scope of the RFC, but we have been working on [send_v2](https://github.com/awslabs/smithy-rs/blob/b023426d1cfd05e1fd9eef2f92a21cad58b93b86/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L331-L347) to allow for a gradual transition. +After the `send` method is called on the `fluent_builder` variable and before it internally calls `aws_smithy_runtime::client::orchestrator::invoke`, the fields in `self.handle.conf` and those in `self.config_override` will be put into a type map (we have been working on [send_v2](https://github.com/awslabs/smithy-rs/blob/b023426d1cfd05e1fd9eef2f92a21cad58b93b86/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L331-L347) to allow for a gradual transition). We use the `aws_smithy_runtime_api::config_bag::ConfigBag` type as the type map, and we have defined a trait called `aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin` that allows trait implementors to add key-value pairs to `ConfigBag`. The `RuntimePlugin` trait is defined as follows: ```rust @@ -234,26 +216,11 @@ impl aws_smithy_runtime_api::client::orchestrator::EndpointResolver for DefaultR } ``` -Other runtime components have different requirements for their migration to the orchestrator world. A detailed roadmap for each of them is beyond the scope of this RFC. - -However, the general refactoring pattern involves moving logic out of `make_operation` and the associated `Tower` layer, and placing it in a struct that implements the relevant trait defined in `aws_smithy_runtime_api::client::orchestrator`. - -Changes checklist ------------------ - -- [ ] Put client configuration into `ConfigBag` by either of the two - - [ ] Implementing RuntimePlugin for service configs and service config builders - - [ ] Updating `ConfigLoader`, `SdkConfig`, and service configs to be backed by `ConfigBag` -- [ ] Decide whether `aws_smithy_runtime_api::client::orchestrator::TraceProbe` should be supported when users can already set up `tracing::Subscriber` in the SDK -- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::RetryStrategy` with the orchestrator -- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::EndpointResolver` with the orchestrator -- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::Connection` with the orchestrator -- [ ] Integrate the default implementation for `aws_smithy_runtime_api::client::orchestrator::IdentityResolvers` with the orchestrator -- [ ] Integrate the default implementations for `aws_smithy_runtime_api::client::orchestrator::HttpAuthSchemes` and `aws_smithy_runtime_api::client::orchestrator::AuthOptionResolver` with the orchestrator +Other runtime components have different requirements for their migration to the orchestrator world. The general refactoring pattern involves moving logic out of `make_operation` and the associated `Tower` layer, and placing it in a struct that implements the relevant trait defined in `aws_smithy_runtime_api::client::orchestrator`. -Appendix: Exposing `.runtime_plugin` through config types ---------------------------------------------------------- -Alternatively, a `runtime_plugin` method could be added to `ConfigLoader`, `SdkConfig`, and service configs. The method would look like this: +Alternative: Exposing `.runtime_plugin` through config types +------------------------------------------------------------ +A `runtime_plugin` method could be added to `ConfigLoader`, `SdkConfig`, and service configs. The method would look like this: ```rust pub fn runtime_plugin( mut self, diff --git a/design/src/rfcs/overview.md b/design/src/rfcs/overview.md index ae62cfab29..10ffde1db9 100644 --- a/design/src/rfcs/overview.md +++ b/design/src/rfcs/overview.md @@ -44,4 +44,3 @@ - [RFC-0032: Better Constraint Violations](./rfc0032_better_constraint_violations.md) - [RFC-0033: Improving access to request IDs in SDK clients](./rfc0033_improve_sdk_request_id_access.md) - [RFC-0034: Smithy Orchestrator](./rfc0034_smithy_orchestrator.md) -- [RFC-0035: Client configuration for the orchestrator](./rfc0035_client_configuration_for_orchestrator.md) From 5f34ee00ebbc786552e74745a255fc7e712b6aec Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 5 Apr 2023 22:18:55 -0500 Subject: [PATCH 04/10] Update configuration.md --- design/src/client/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index fa553cc42b..5b0c5ccab1 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -1,4 +1,5 @@ Client configuration for the orchestrator +----------------------------------------- This document discusses two areas of improvement for configuring an SDK client: - Support for operation-level configuration @@ -18,12 +19,11 @@ As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/s However, not all of these components are covered by the public APIs for configuring them, and for those that are, they do not appear under exactly the same names, e.g. [endpoint_url](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.endpoint_url) and [retry_config](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.retry_config). We will allow users to configure the above components either by keeping current APIs or adding new ones. -There are a lot of moving parts and this document is expected to evolve as we discover the implementation details. +There are still a lot of moving parts and this document is expected to evolve as we discover the implementation details. The user experience if this design is implemented ------------------------------------------------- - ### Operation-level configuration Currently, users are able to customize runtime configuration at multiple levels. `SdkConfig` is used to configure settings for all services, while a service config (e.g., `aws_sdk_s3::config::Config`) is used to configure settings for a specific service client. From 1832432b3e7efa0206bdeacbdcc15800594c9cb4 Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Tue, 11 Apr 2023 17:02:27 -0500 Subject: [PATCH 05/10] Update design/src/client/configuration.md Co-authored-by: Zelda Hessler --- design/src/client/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index 5b0c5ccab1..05e649c074 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -1,5 +1,5 @@ Client configuration for the orchestrator ------------------------------------------ +========================================= This document discusses two areas of improvement for configuring an SDK client: - Support for operation-level configuration From d4ebf339342fcceb9e197eff8c0e5cabdcc06d46 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Thu, 13 Apr 2023 17:47:15 -0500 Subject: [PATCH 06/10] Update configuration.md Address https://github.com/awslabs/smithy-rs/pull/2527#discussion_r1162073412 --- design/src/client/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index 05e649c074..c4c345849e 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -109,7 +109,7 @@ The relations between the types can be illustrated in the following diagram: client config at different levels -The `aws_sdk_s3::config::Config` type on the left stores the service-level configuration, which implicitly includes/shadows the AWS-level configuration. `Config` is accessible via `Handle` from a fluent builder `ListBucketsFluentBuilder`, which holds the operation level config. +The `aws_sdk_s3::config::Config` type on the middle left represents the service-level configuration, which implicitly includes/shadows the AWS-level configuration. `Config` is accessible via `Handle` from a fluent builder `ListBucketsFluentBuilder`, which holds the operation level config. After the `send` method is called on the `fluent_builder` variable and before it internally calls `aws_smithy_runtime::client::orchestrator::invoke`, the fields in `self.handle.conf` and those in `self.config_override` will be put into a type map (we have been working on [send_v2](https://github.com/awslabs/smithy-rs/blob/b023426d1cfd05e1fd9eef2f92a21cad58b93b86/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L331-L347) to allow for a gradual transition). From e4604b3854375f3b4b50e017d011f618c67afed6 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Fri, 14 Apr 2023 15:34:24 -0500 Subject: [PATCH 07/10] Bring back the terminology section This commit addresses https://github.com/awslabs/smithy-rs/pull/2527#discussion_r1161828555 --- design/src/client/configuration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index c4c345849e..2ceb2d29aa 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -21,6 +21,15 @@ However, not all of these components are covered by the public APIs for configur There are still a lot of moving parts and this document is expected to evolve as we discover the implementation details. +Terminology +----------- +- Component: An interface coupled with its default implementations to enable an SDK client functionality. +- Fluent Client: A code generated Client that has methods for each service operation on it. A fluent builder is generated alongside it to make construction easier. +- Operation: A high-level abstraction representing an interaction between an SDK Client and a remote service. +- Orchestrator: The code within an SDK client that handles the process of making requests and receiving responses from remote services. +- Remote Service: A remote API that a user wants to use. Communication with a remote service usually happens over HTTP. The remote service is usually, but not necessarily, an AWS service. +- SDK Client: A client generated for the AWS SDK, allowing users to make requests to remote services. + The user experience if this design is implemented ------------------------------------------------- From 87f98ea1f67543c879c48445fdc2f11353fa2a55 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Fri, 14 Apr 2023 15:42:20 -0500 Subject: [PATCH 08/10] Link to `Tower`-based infrastructure to `call_raw` This commit addresses https://github.com/awslabs/smithy-rs/pull/2527#discussion_r1161836383 --- design/src/client/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index 2ceb2d29aa..c037f62c69 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -56,7 +56,7 @@ The main benefit of this approach is simplicity for users. The only change requi ### Configuring runtime components required by the orchestrator -While `ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's `Tower`-based infrastructure, they do not cover all of the components required for the orchestrator to perform its job. +While `ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's [`Tower`-based infrastructure](https://github.com/awslabs/smithy-rs/blob/35f2f27a8380a1310c264a386e162cd9f2180137/rust-runtime/aws-smithy-client/src/lib.rs#L155-L245), they do not cover all of the components required for the orchestrator to perform its job. The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the design (the right column). From 9a8c78efc5038a2b89a838e8a6713cf7950c4b26 Mon Sep 17 00:00:00 2001 From: Yuki Saito Date: Fri, 14 Apr 2023 15:52:22 -0500 Subject: [PATCH 09/10] Link to their respective sections This commit addresses https://github.com/awslabs/smithy-rs/pull/2527#discussion_r1162071568 --- design/src/client/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index c037f62c69..fb43ed3451 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -83,9 +83,9 @@ How to actually implement this design Implementing this design is tied to [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md). In that RFC, applying client configuration means putting runtime components and required function parameters into a type map for `aws_smithy_runtime::client::orchestrator::invoke` to use. This section covers three parts: -- Where we store operation-level config -- How we put runtime components and required function parameters into a type map -- How we plan to migrate today's runtime components to the world of the orchestrator +- [Where we store operation-level config](#where-we-store-operation-level-config) +- [How we put runtime components and required function parameters into a type map](#how-we-put-runtime-components-and-required-function-parameters-into-a-type-map) +- [How we plan to migrate today's runtime components to the world of the orchestrator](#how-we-plan-to-migrate-todays-runtime-components-to-the-world-of-the-orchestrator) #### Where we store operation-level config From 0f6f94106be7aa1fb6c6a16d911e70f72ca97bda Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Wed, 10 May 2023 14:17:41 -0500 Subject: [PATCH 10/10] Update configuration.md based on today's codebase --- design/src/client/configuration.md | 244 ++++++++++++++++------------- 1 file changed, 139 insertions(+), 105 deletions(-) diff --git a/design/src/client/configuration.md b/design/src/client/configuration.md index fb43ed3451..eb87e1ad3a 100644 --- a/design/src/client/configuration.md +++ b/design/src/client/configuration.md @@ -1,13 +1,11 @@ -Client configuration for the orchestrator -========================================= +Configuring SDK client for the orchestrator +=========================================== -This document discusses two areas of improvement for configuring an SDK client: -- Support for operation-level configuration +This document describes how to configure an SDK client for the orchestrator, with a focus on the following: - Support for runtime components required by the orchestrator +- Support for operation-level configuration -We have encountered use cases where configuring a client for a single operation invocation is necessary ([example](https://github.com/awslabs/aws-sdk-rust/issues/696)). At the time of writing, this feature is not yet supported, but operation-level configuration will address that limitation. - -As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md), the orchestrator uses configured components for an SDK to handle messages between the client and a service. They include: +As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md), the orchestrator prescribes configured components for an SDK to handle messages between the client and a service. They include: - `RetryStrategy`: Configures how requests are retried. - `TraceProbes`: Configures locations to which SDK metrics are published. - `EndpointProviders`: Configures which hostname an SDK will call when making a request. @@ -17,25 +15,47 @@ As described in [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/s - `Checksum Algorithms`: Configures how an SDK calculates request and response checksums. - `Interceptors`: Configures specific stages of the request execution pipeline. -However, not all of these components are covered by the public APIs for configuring them, and for those that are, they do not appear under exactly the same names, e.g. [endpoint_url](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.endpoint_url) and [retry_config](https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.retry_config). We will allow users to configure the above components either by keeping current APIs or adding new ones. +From an interface perspective, the aim of client configuration for the SDK is to provide public APIs for customizing these components as needed. From an implementation perspective, the goal of client configuration is to store the components in a typed configuration map for later use by the orchestrator during execution. + +Configuring the client for the orchestrator will simplify support for operation-level configuration. There are use cases where configuring a client for a single operation invocation is necessary ([example](https://github.com/awslabs/aws-sdk-rust/issues/696)). At the time of writing, this feature is not yet supported, but operation-level configuration will address that limitation. -There are still a lot of moving parts and this document is expected to evolve as we discover the implementation details. +Please note that this document does not cover the configuration of the generic client in the orchestrator. That will be addressed in a separate design document. Terminology ----------- - Component: An interface coupled with its default implementations to enable an SDK client functionality. - Fluent Client: A code generated Client that has methods for each service operation on it. A fluent builder is generated alongside it to make construction easier. +- Generic Client: A `aws_smithy_client::Client` struct that is responsible for gluing together the connector, middleware, and retry policy. The concept still applies, but the struct will cease to exist with the introduction of the orchestrator. - Operation: A high-level abstraction representing an interaction between an SDK Client and a remote service. - Orchestrator: The code within an SDK client that handles the process of making requests and receiving responses from remote services. - Remote Service: A remote API that a user wants to use. Communication with a remote service usually happens over HTTP. The remote service is usually, but not necessarily, an AWS service. - SDK Client: A client generated for the AWS SDK, allowing users to make requests to remote services. -The user experience if this design is implemented -------------------------------------------------- +The user experience +------------------- + +### Configuring runtime components required by the orchestrator + +`ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's [`Tower`-based infrastructure](https://github.com/awslabs/smithy-rs/blob/35f2f27a8380a1310c264a386e162cd9f2180137/rust-runtime/aws-smithy-client/src/lib.rs#L155-L245). The degree to which these components can be configured in the orchestrator will remain largely unchanged. + +The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the design (the right column). + +| Runtime component | Today's builder method | Proposed builder method | +| :-: | --- | --- | +| RetryStrategy | `.retry_config` | No change | +| TraceProbes | None | No plan to implement before general availability (GA) | +| EndpointResolver | `.endpoint_url` | No change | +| HTTPClients | `.http_connector` | No change | +| IdentityProviders | `.credentials_provider` | `.credentials_provider` + `.token_provider` | +| HTTPAuthSchemes & AuthSchemeResolvers | None | Not configurable on SDK client | +| Checksum Algorithms | Not configurable on SDK client | No change | +| Interceptors | None | `.interceptor(&self, impl Interceptor + 'static)` | + +If components can be configured today, their builder methods continue to exist in the orchestrator. There are no plans to support `TraceProbes` prior to GA because users can still set up `tracing::Subscriber` to specify where metrics are published. For `IdentityProviders`, in addition to `.credentials_provider`, we will introduce `.token_provider` as part of [this PR](https://github.com/awslabs/smithy-rs/pull/2627). `HTTPAuthSchemes`, `AuthSchemeResolver`s, and `Checksum Algorithms` will only be configurable on the generic client, but not on the SDK client. Finally, customers will be able to configure interceptors to inject logic into specific stages of the request execution pipeline, which will be enabled by [this PR](https://github.com/awslabs/smithy-rs/pull/2669). ### Operation-level configuration -Currently, users are able to customize runtime configuration at multiple levels. `SdkConfig` is used to configure settings for all services, while a service config (e.g., `aws_sdk_s3::config::Config`) is used to configure settings for a specific service client. +Currently, users are able to customize runtime configuration at multiple levels. `SdkConfig` is used to configure settings for all services, while a service config (e.g., `aws_sdk_s3::config::Config`) is used to configure settings for a specific service client. However, there has been no support for configuring settings for a single operation invocation. With this design, users will be able to go one step further and override configuration for a single operation invocation: ```rust @@ -48,44 +68,26 @@ s3_client.create_bucket() .await; ``` -This is achieved through the `.config_override` method, which is added to fluent builders, such as `fluent_builders::CreateBucket`. This method sets the `us-west-1` region to override any region setting specified in the service level config. The operation level config takes the highest precedence, followed by the service level config, and then the AWS level config. +This can be achieved through the `.config_override` method, which is added to fluent builders, such as `fluent_builders::CreateBucket`. This method sets the `us-west-1` region to override any region setting specified in the service level config. The operation level config takes the highest precedence, followed by the service level config, and then the AWS level config. Here is an example of how a config is overwritten by another config: +``` +Config A overridden by Config B == Config C +field_1: None, field_1: Some(v2), field_1: Some(v2), +field_2: Some(v1), field_2: Some(v2), field_2: Some(v2), +field_3: Some(v1), field_3: None, field_3: Some(v1), +``` The `config_override` method takes a service config builder instead of a service config, allowing `None` values to be used for fields, so as not to override settings at a lower-precedence configuration. The main benefit of this approach is simplicity for users. The only change required is to call the `config_override` method on an operation input fluent builder. If users do not wish to specify the operation level config, their workflow will remain unaffected. -### Configuring runtime components required by the orchestrator - -While `ConfigLoader`, `SdkConfig`, and service configs allow users to configure the necessary runtime components for today's [`Tower`-based infrastructure](https://github.com/awslabs/smithy-rs/blob/35f2f27a8380a1310c264a386e162cd9f2180137/rust-runtime/aws-smithy-client/src/lib.rs#L155-L245), they do not cover all of the components required for the orchestrator to perform its job. - -The following table shows for each runtime component (the left column), what method on `ConfigLoader`, `sdk_config::Builder`, and service config builder (e.g. `aws_sdk_s3::config::Builder`) are currently available (the middle column) and what new method will be available on those types as proposed by the design (the right column). - -| Runtime component | Today's builder method | Proposed builder method | -| :-: | --- | --- | -| RetryStrategy | `.retry_config` | `.retry_strategy(&self, impl RetryStrategy + 'static)` | -| TraceProbes | None | (See the Changes checklist) | -| EndpointResolver | `.endpoint_url` | No change | -| HTTPClients | `.http_connector` | No change | -| IdentityProviders | `.credentials_cache` & `.credentials_provider` | No change | -| HTTPAuthSchemes & AuthSchemeResolvers | None | `.auth_scheme(&self, impl HttpAuthScheme + 'static)` | -| Checksum Algorithms | `.checksum_algorithm` only at the operation level for those that support it | No change | -| Interceptors | None | `.interceptor(&self, impl Interceptor + 'static)` | - -The proposed methods generally take a type that implements [the corresponding trait from RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md#traits). However, for backward compatibility reasons, we mark "No change" for `EndpointResolver`, `HttpClients`, and `IdentityProviders`: -- `EndpointResolver` - we [deprecated the previous the .endpoint_resolver method](https://github.com/awslabs/smithy-rs/pull/2464). Introducing a new `.endpoint_resolver` might run counter to Endpoints 2.0 migration. -- `HttpClients` - users have been able to specify custom connections using the existing method, and introducing a new method like `.connection(&self, impl Connection + 'static)` might require non-trivial upgrades. -- `IdentityProviders` - users have been able to specify credentials cache and provider in a flexible manner using the existing methods, and introducing a new method like `.identity_provider(&self, impl IdentityProvider + 'static)` might require non-trivial upgrades. - -We also marked "No change" for `Checksum Algorithms` because it should not be arbitrarily configurable at the service level config. Today, operations that support a predefined set of checksum algorithms expose the `checksum_algorithm` method through their fluent builders. - -How to actually implement this design -------------------------------------- -Implementing this design is tied to [RFC 34](https://github.com/awslabs/smithy-rs/blob/main/design/src/rfcs/rfc0034_smithy_orchestrator.md). In that RFC, applying client configuration means putting runtime components and required function parameters into a type map for `aws_smithy_runtime::client::orchestrator::invoke` to use. +How the design has been implemented +----------------------------------- +As stated, the goal of client configuration implementation is to store the components in a typed configuration map for `aws_smithy_runtime::client::orchestrator::invoke` for later use during execution. This section covers three parts: - [Where we store operation-level config](#where-we-store-operation-level-config) - [How we put runtime components and required function parameters into a type map](#how-we-put-runtime-components-and-required-function-parameters-into-a-type-map) -- [How we plan to migrate today's runtime components to the world of the orchestrator](#how-we-plan-to-migrate-todays-runtime-components-to-the-world-of-the-orchestrator) +- [How we have been migrating today's runtime components to the orchestrator](#how-we-have-been-migrating-todays-runtime-components-to-the-orchestrator) #### Where we store operation-level config @@ -116,44 +118,117 @@ let fluent_builder = s3_client.list_buckets() ``` The relations between the types can be illustrated in the following diagram: -client config at different levels +client config at different levels -The `aws_sdk_s3::config::Config` type on the middle left represents the service-level configuration, which implicitly includes/shadows the AWS-level configuration. `Config` is accessible via `Handle` from a fluent builder `ListBucketsFluentBuilder`, which holds the operation level config. +A fluent builder `ListBucketsFluentBuilder` has access to the service config via `self.handle.conf` and to the operation-level config, if any, via `self.config_override`. To configure a client, the appropriate components need to be created from the fields of those configs and stored in a typed map, `ConfigBag`. -After the `send` method is called on the `fluent_builder` variable and before it internally calls `aws_smithy_runtime::client::orchestrator::invoke`, the fields in `self.handle.conf` and those in `self.config_override` will be put into a type map (we have been working on [send_v2](https://github.com/awslabs/smithy-rs/blob/b023426d1cfd05e1fd9eef2f92a21cad58b93b86/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L331-L347) to allow for a gradual transition). +That can be accomplished in two steps: +1. When the fluent builder's [send_orchestrate](https://github.com/awslabs/smithy-rs/blob/a37b7382c14461709ec09b3a5a7c7fc6819e6173/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt#L367-L398) method is called, it creates `RuntimePlugins` and then calls `aws_smithy_runtime::client::orchestrator::invoke` with it. +2. The `invoke` method calls `RuntimePlugin::configure` for each of the passed-in runtime plugins to store the components in the `ConfigBag` -We use the `aws_smithy_runtime_api::config_bag::ConfigBag` type as the type map, and we have defined a trait called `aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin` that allows trait implementors to add key-value pairs to `ConfigBag`. The `RuntimePlugin` trait is defined as follows: +For 1., the `send_orchestrate` method looks as follows, up to a point where it calls `invoke`: ```rust -pub trait RuntimePlugin { - fn configure(&self, cfg: &mut ConfigBag) -> Result<(), BoxError>; +let mut runtime_plugins = + aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins::new() + .with_client_plugin(crate::config::ServiceRuntimePlugin::new( + self.handle.clone(), + )); +if let Some(config_override) = self.config_override { + runtime_plugins = runtime_plugins.with_operation_plugin(config_override); } -``` +runtime_plugins = runtime_plugins.with_operation_plugin( + crate::operation::::::new(), +); -With this in mind, we plan to -- Implement the `RuntimePlugin` trait for service configuration, which will put runtime components and required function parameters specified at the service level config into `ConfigBag` -- Implement the `RuntimePlugin` trait for service configuration builders, which will put runtime components and required function parameters specified at the operation level config into `ConfigBag`. +// --snip-- -Once these changes are made, fluent builders will be able to do something like this: +let input = self + .inner + .build() + .map_err(aws_smithy_http::result::SdkError::construction_failure)?; +let input = aws_smithy_runtime_api::type_erasure::TypedBox::new(input).erase(); +let output = aws_smithy_runtime::client::orchestrator::invoke(input, &runtime_plugins) + +// --snip-- +``` +For the client level config, `ServiceRuntimePlugin` is created with the service config (`self.handle.conf`) and added as a client plugin. Similarly, for the operation level config, two runtime plugins are registered as operation plugins, one of which uses the config builder passed-in via `.config_override` earlier. + +Adding plugins at this point will not do anything, but it sets things up so that when the `configure` method is called on a plugin, it creates components and stores them in `ConfigBag`. The following code snippet for `ServiceRuntimePlugin` shows that it creates components such as `EndpointResolver` and `IdentityResolver` and stores them in the `ConfigBag`. It also registers interceptors that will be invoked at specific stages of the request execution pipeline during `invoke`. ```rust -// pseudo-code, possibly somewhere down the line of the `send_v2` method +impl aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin for ServiceRuntimePlugin { + fn configure( + &self, + cfg: &mut aws_smithy_runtime_api::config_bag::ConfigBag, + _interceptors: &mut aws_smithy_runtime_api::client::interceptors::Interceptors, + ) -> Result<(), aws_smithy_runtime_api::client::runtime_plugin::BoxError> { + use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors; + + // --snip-- + + let http_auth_schemes = aws_smithy_runtime_api::client::auth::HttpAuthSchemes::builder() + .auth_scheme( + aws_runtime::auth::sigv4::SCHEME_ID, + aws_runtime::auth::sigv4::SigV4HttpAuthScheme::new(), + ) + .build(); + cfg.set_http_auth_schemes(http_auth_schemes); + + // Set an empty auth option resolver to be overridden by operations that need auth. + cfg.set_auth_option_resolver( + aws_smithy_runtime_api::client::auth::option_resolver::StaticAuthOptionResolver::new( + Vec::new(), + ), + ); + + let endpoint_resolver = + aws_smithy_runtime::client::orchestrator::endpoints::DefaultEndpointResolver::< + crate::endpoint::Params, + >::new(aws_smithy_http::endpoint::SharedEndpointResolver::from( + self.handle.conf.endpoint_resolver(), + )); + cfg.set_endpoint_resolver(endpoint_resolver); -let mut config_bag = /* create an empty config bag */; + // --snip-- -// first put into the bag things from the service level config -self.handle.conf.configure(&mut config_bag); + _interceptors.register_client_interceptor(std::sync::Arc::new( + aws_runtime::user_agent::UserAgentInterceptor::new(), + ) as _); + _interceptors.register_client_interceptor(std::sync::Arc::new( + aws_runtime::invocation_id::InvocationIdInterceptor::new(), + ) as _); + _interceptors.register_client_interceptor(std::sync::Arc::new( + aws_runtime::recursion_detection::RecursionDetectionInterceptor::new(), + ) as _); -// then put into the bag things from the operation level config -if let Some(config_override) = self.config_override { - self.config_override.configure(&mut config_bag); + // --snip-- + + cfg.set_identity_resolvers( + aws_smithy_runtime_api::client::identity::IdentityResolvers::builder() + .identity_resolver( + aws_runtime::auth::sigv4::SCHEME_ID, + aws_runtime::identity::credentials::CredentialsIdentityResolver::new( + self.handle.conf.credentials_cache(), + ), + ) + .build(), + ); + Ok(()) + } } ``` -Note that if `ConfigLoader`, `SdkConfig`, and service configurations, including all of their builders, are implemented in terms of `ConfigBag`, implementing the `RuntimePlugin` trait may become unnecessary because the fields have already been stored in the bag. In that case, however, `ConfigBag`, which is held by `Arc`, may need to be unshared so that it can be passed as `&mut` to `aws_smithy_runtime::client::orchestrator::invoke`. -#### How we plan to migrate today's runtime components to the world of the orchestrator +For 2., the first thing `invoke` does is to [apply client runtime plugins and operation plugins](https://github.com/awslabs/smithy-rs/blob/main/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs#L75-L80). That will actually execute the `configure` method as shown above to set up `ConfigBag` properly. It is crucial to apply client runtime plugins first and then operation runtime plugins because what's applied later will end up in a more recent layer in `ConfigBag` and the operation-level config should take precedence over the client-level config. -This part is a work in progress because we will learn more about the implementation details as we shift more and more runtime components to the world of the orchestrator. That said, here's our migration plan at a high level, using `DefaultResolver` as an example. +#### How we have been migrating today's runtime components to the orchestrator -Currently, `DefaultResolver` and its user-specified parameters are wired together within `make_operation` on an operation input struct (such as `ListBucketsInput`). +We have been porting the existing components designed to run in the Tower-based infrastructure over to the orchestrator. The following PRs demonstrate examples: +- [Add SigV4 support to the orchestrator](https://github.com/awslabs/smithy-rs/pull/2533) +- [Implement the UserAgentInterceptor for the SDK](https://github.com/awslabs/smithy-rs/pull/2550) +- [Add DefaultEndpointResolver to the orchestrator](https://github.com/awslabs/smithy-rs/pull/2577) + +While each PR tackles different part of runtime components, some of them share a common porting strategy, which involves moving logic out of `make_operation` on an operation input struct and logic out of the associated Tower layer, and placing them in a struct that implements the relevant trait defined in [aws_smithy_runtime_api::client::orchestrator](https://github.com/awslabs/smithy-rs/blob/2b165037fd785ce122c993c1a59d3c8d5a3e522c/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs#L81-L130). + +Taking the third PR, for instance, `DefaultResolver` today and its user-specified parameters are wired together within `make_operation` on an operation input struct (such as `ListBucketsInput`). ```rust // omitting unnecessary details @@ -202,45 +277,4 @@ pub async fn make_operation( The `endpoint_result` and `params` stored in `SharedPropertyBag` are later used by the [`MapRequestLayer` for `SmithyEndpointStage`](https://github.com/awslabs/smithy-rs/blob/main/rust-runtime/aws-smithy-http/src/endpoint/middleware.rs#L37-L83) to add to the request header as it is dispatched. -With the transition to the orchestrator world, the functionality performed by `make_operation` and `SmithyEndpointStage::apply` will be moved to `DefaultResolver`. Specifically, `DefaultResolver` will implement the `aws_smithy_runtime_api::client::orchestrator::EndpointResolver trait`: -```rust -impl aws_smithy_runtime_api::client::orchestrator::EndpointResolver for DefaultResolver { - fn resolve_and_apply_endpoint( - &self, - request: &mut aws_smithy_runtime_api::client::orchestrator::HttpRequest, - cfg: &aws_smithy_runtime_api::config_bag::ConfigBag, - ) -> Result<(), aws_smithy_runtime_api::client::orchestrator::BoxError> { - /* - * Here, handles part of `make_operation` to yield `endpoint_result` and `params`. - * We may need to assume that the entire `Params` is already available - * in `cfg`, rather than extract every piece out of `cfg` necessary and - * build a `Params` here. - */ - - /* - * Later part of the method handles what SmithyEndpointStage does, - * modifying the passed-in `request`. - */ - } -} -``` - -Other runtime components have different requirements for their migration to the orchestrator world. The general refactoring pattern involves moving logic out of `make_operation` and the associated `Tower` layer, and placing it in a struct that implements the relevant trait defined in `aws_smithy_runtime_api::client::orchestrator`. - -Alternative: Exposing `.runtime_plugin` through config types ------------------------------------------------------------- -A `runtime_plugin` method could be added to `ConfigLoader`, `SdkConfig`, and service configs. The method would look like this: -```rust -pub fn runtime_plugin( - mut self, - runtime_plugin: impl RuntimePlugin + 'static, -) -> Self -{ - todo!() -} -``` -However, this approach forces users to implement the `RuntimePlugin` trait for a given type, leading to the following issues: -- The `RuntimePlugin::configure` method takes a `&mut ConfigBag`, and it may not be immediately clear to users what they should put into or extract from the bag to implement the method. In contrast, methods like `endpoint_url` or `retry_config` are self-explanatory, and users are already familiar with them. -- With the `runtime_plugin` method, there could be two ways to configure the same runtime component. If a user specifies both `endpoint_url` and `runtime_plugin(/* a plugin for an endpoint */)` at the same time on `ConfigLoader`, it would be necessary to determine which one takes precedence. - -For these reasons, this solution was dismissed as such. +With the transition to the orchestrator, the functionality performed by `make_operation` and `SmithyEndpointStage::apply` has been ported to [`DefaultEndpointResolver`](https://github.com/awslabs/smithy-rs/blob/2b165037fd785ce122c993c1a59d3c8d5a3e522c/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs#L81-L130) which implements the `aws_smithy_runtime_api::client::orchestrator::EndpointResolver trait`.