Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable stalled stream protection for uploads behind new behavior version #3527

Merged
merged 9 commits into from
Apr 24, 2024
14 changes: 13 additions & 1 deletion CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,16 @@
# message = "Fix typos in module documentation for generated crates"
# references = ["smithy-rs#920"]
# meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"}
# author = "rcoh"
# author = "rcoh"

[[smithy-rs]]
message = "Stalled stream protection on uploads is now enabled by default behind `BehaviorVersion::v2024_03_28()`. If you're using `BehaviorVersion::latest()`, you will get this change automatically by running `cargo update`."
references = ["smithy-rs#3527"]
meta = { "breaking" = true, "tada" = true, "bug" = false }
authors = ["jdisanti"]

[[aws-sdk-rust]]
message = "Stalled stream protection on uploads is now enabled by default behind `BehaviorVersion::v2024_03_28()`. If you're using `BehaviorVersion::latest()`, you will get this change automatically by running `cargo update`. Updating your SDK is not necessary, this change will happen when a new version of the client libraries are consumed."
references = ["smithy-rs#3527"]
meta = { "breaking" = true, "tada" = true, "bug" = false }
author = "jdisanti"
2 changes: 1 addition & 1 deletion aws/rust-runtime/aws-inlineable/src/s3_express.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ pub(crate) mod identity_provider {
config_bag: &'a ConfigBag,
) -> Result<SessionCredentials, BoxError> {
let mut config_builder = crate::config::Builder::from_config_bag(config_bag)
.behavior_version(self.behavior_version.clone());
.behavior_version(self.behavior_version);

// inherits all runtime components from a current S3 operation but clears out
// out interceptors configured for that operation
Expand Down
2 changes: 1 addition & 1 deletion aws/rust-runtime/aws-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-types"
version = "1.2.0"
version = "1.2.1"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Russell Cohen <rcoh@amazon.com>"]
description = "Cross-service types for the AWS SDK."
edition = "2021"
Expand Down
4 changes: 2 additions & 2 deletions aws/rust-runtime/aws-types/src/sdk_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,9 +810,9 @@ impl SdkConfig {
self.stalled_stream_protection_config.clone()
}

/// Behavior major version configured for this client
/// Behavior version configured for this client
pub fn behavior_version(&self) -> Option<BehaviorVersion> {
self.behavior_version.clone()
self.behavior_version
}

/// Return an immutable reference to the service config provider configured for this client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private class S3ExpressServiceRuntimePluginCustomization(codegenContext: ClientC
rustTemplate(
"""
#{DefaultS3ExpressIdentityProvider}::builder()
.behavior_version(${section.serviceConfigName}.behavior_version.clone().expect(${behaviorVersionError.dq()}))
.behavior_version(${section.serviceConfigName}.behavior_version.expect(${behaviorVersionError.dq()}))
.time_source(${section.serviceConfigName}.time_source().unwrap_or_default())
.build()
""",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ async fn test_time_source_for_identity_cache() {
let _client = aws_sdk_s3::Client::from_conf(config);
}

#[allow(deprecated)] // intentionally testing an old behavior version
#[tokio::test]
async fn behavior_mv_from_aws_config() {
let (http_client, req) = capture_request(None);
Expand All @@ -177,6 +178,7 @@ async fn behavior_mv_from_aws_config() {
.starts_with("https://s3.us-west-2.amazonaws.com/"));
}

#[allow(deprecated)] // intentionally testing an old behavior version
#[tokio::test]
async fn behavior_mv_from_client_construction() {
let (http_client, req) = capture_request(None);
Expand Down
1 change: 1 addition & 0 deletions aws/sdk/integration-tests/s3/tests/identity-cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ async fn test_identity_cache_reused_by_default() {
// assert_eq!(2, provider.invoke_count.load(Ordering::SeqCst));
// }

#[allow(deprecated)] // intentionally testing an old behavior version
#[tokio::test]
async fn test_identity_cache_ga_behavior_version() {
let http_client =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,10 @@ async fn test_stalled_stream_protection_defaults_for_upload() {
let _ = tokio::spawn(server);

let conf = Config::builder()
// Stalled stream protection MUST BE enabled by default. Do not configure it explicitly.
.credentials_provider(Credentials::for_tests())
.region(Region::new("us-east-1"))
.endpoint_url(format!("http://{server_addr}"))
// TODO(https://github.com/smithy-lang/smithy-rs/issues/3510): make stalled stream protection enabled by default with BMV and remove this line
.stalled_stream_protection(StalledStreamProtectionConfig::enabled().build())
.build();
Comment on lines +95 to 99
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we copy one of these tests, use the older BMV, and validate that stalled stream upload protection is not enabled?

let client = Client::from_conf(conf);

Expand Down Expand Up @@ -255,6 +254,7 @@ async fn test_stalled_stream_protection_for_downloads_is_enabled_by_default() {

// Stalled stream protection should be enabled by default.
let sdk_config = aws_config::from_env()
// Stalled stream protection MUST BE enabled by default. Do not configure it explicitly.
.credentials_provider(Credentials::for_tests())
.region(Region::new("us-east-1"))
.endpoint_url(format!("http://{server_addr}"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ private fun baseClientRuntimePluginsFn(
.with_client_plugins(#{default_plugins}(
#{DefaultPluginParams}::new()
.with_retry_partition_name(${codegenContext.serviceShape.sdkId().dq()})
.with_behavior_version(config.behavior_version.clone().expect(${behaviorVersionError.dq()}))
.with_behavior_version(config.behavior_version.expect(${behaviorVersionError.dq()}))
))
// user config
.with_client_plugin(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ class ServiceConfigGenerator(
config: self.cloneable.clone(),
runtime_components: self.runtime_components.clone(),
runtime_plugins: self.runtime_plugins.clone(),
behavior_version: self.behavior_version.clone(),
behavior_version: self.behavior_version,
}
}
""",
Expand Down
2 changes: 1 addition & 1 deletion rust-runtime/aws-smithy-runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-smithy-runtime-api"
version = "1.4.0"
version = "1.5.0"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Zelda Hessler <zhessler@amazon.com>"]
description = "Smithy runtime types."
edition = "2021"
Expand Down
67 changes: 55 additions & 12 deletions rust-runtime/aws-smithy-runtime-api/src/client/behavior_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
* SPDX-License-Identifier: Apache-2.0
*/

//! Behavior Major version of the client
//! Behavior version of the client

/// Behavior major-version of the client
/// Behavior version of the client
///
/// Over time, new best-practice behaviors are introduced. However, these behaviors might not be
/// backwards compatible. For example, a change which introduces new default timeouts or a new
/// retry-mode for all operations might be the ideal behavior but could break existing applications.
#[derive(Clone)]
#[derive(Copy, Clone, PartialEq)]
pub struct BehaviorVersion {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add a section to the developer guide on behavior versions, what they are and guidance on how to choose, and what changed in each.

Copy link
Collaborator Author

@jdisanti jdisanti Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// currently there is only 1 MV so we don't actually need anything in here.
_private: (),
inner: Inner,
}

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
enum Inner {
// IMPORTANT: Order matters here for the `Ord` derive. Newer versions go to the bottom.
V2023_11_09,
V2024_03_28,
}

impl BehaviorVersion {
Expand All @@ -26,23 +32,60 @@ impl BehaviorVersion {
/// If, however, you're writing a service that is very latency sensitive, or that has written
/// code to tune Rust SDK behaviors, consider pinning to a specific major version.
///
/// The latest version is currently [`BehaviorVersion::v2023_11_09`]
/// The latest version is currently [`BehaviorVersion::v2024_03_28`]
pub fn latest() -> Self {
Self::v2023_11_09()
Self::v2024_03_28()
}

/// This method returns the behavior configuration for November 9th, 2023
/// Behavior version for March 28th, 2024.
///
/// This version enables stalled stream protection for uploads (request bodies) by default.
///
/// When a new behavior major version is released, this method will be deprecated.
pub fn v2024_03_28() -> Self {
Self {
inner: Inner::V2024_03_28,
}
}

/// Behavior version for November 9th, 2023.
#[deprecated(
since = "1.4.0",
note = "Superceded by v2024_03_28, which enabled stalled stream protection for uploads (request bodies) by default."
)]
pub fn v2023_11_09() -> Self {
Self { _private: () }
Self {
inner: Inner::V2023_11_09,
}
}

/// True if this version is newer or equal to the given `other` version.
pub fn is_at_least(&self, other: BehaviorVersion) -> bool {
self.inner >= other.inner
}
}

impl std::fmt::Debug for BehaviorVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BehaviorVersion")
.field("name", &"v2023_11_09")
.finish()
f.debug_tuple("BehaviorVersion").field(&self.inner).finish()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[allow(deprecated)]
fn version_comparison() {
assert!(BehaviorVersion::latest() == BehaviorVersion::latest());
assert!(BehaviorVersion::v2023_11_09() == BehaviorVersion::v2023_11_09());
assert!(BehaviorVersion::v2024_03_28() != BehaviorVersion::v2023_11_09());
assert!(BehaviorVersion::latest().is_at_least(BehaviorVersion::latest()));
assert!(BehaviorVersion::latest().is_at_least(BehaviorVersion::v2023_11_09()));
assert!(BehaviorVersion::latest().is_at_least(BehaviorVersion::v2024_03_28()));
assert!(!BehaviorVersion::v2023_11_09().is_at_least(BehaviorVersion::v2024_03_28()));
assert!(Inner::V2024_03_28 > Inner::V2023_11_09);
assert!(Inner::V2023_11_09 < Inner::V2024_03_28);
}
}
2 changes: 1 addition & 1 deletion rust-runtime/aws-smithy-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-smithy-runtime"
version = "1.3.1"
version = "1.4.0"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Zelda Hessler <zhessler@amazon.com>"]
description = "The new smithy runtime crate"
edition = "2021"
Expand Down
61 changes: 53 additions & 8 deletions rust-runtime/aws-smithy-runtime/src/client/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,11 @@ pub fn default_identity_cache_plugin() -> Option<SharedRuntimePlugin> {
note = "This function wasn't intended to be public, and didn't take the behavior major version as an argument, so it couldn't be evolved over time."
)]
pub fn default_stalled_stream_protection_config_plugin() -> Option<SharedRuntimePlugin> {
#[allow(deprecated)]
default_stalled_stream_protection_config_plugin_v2(BehaviorVersion::v2023_11_09())
}
fn default_stalled_stream_protection_config_plugin_v2(
_behavior_version: BehaviorVersion,
behavior_version: BehaviorVersion,
) -> Option<SharedRuntimePlugin> {
Some(
default_plugin(
Expand All @@ -191,13 +192,13 @@ fn default_stalled_stream_protection_config_plugin_v2(
},
)
.with_config(layer("default_stalled_stream_protection_config", |layer| {
layer.store_put(
StalledStreamProtectionConfig::enabled()
// TODO(https://github.com/smithy-lang/smithy-rs/issues/3510): enable behind new behavior version
.upload_enabled(false)
.grace_period(Duration::from_secs(5))
.build(),
);
let mut config =
StalledStreamProtectionConfig::enabled().grace_period(Duration::from_secs(5));
// Before v2024_03_28, upload streams did not have stalled stream protection by default
if !behavior_version.is_at_least(BehaviorVersion::v2024_03_28()) {
config = config.upload_enabled(false);
}
layer.store_put(config.build());
Comment on lines 194 to +201
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for debuggability, I wonder if we should alter the name of the plugin.

I would probably make two separate plugins that we select based on BMV instead, may be clearer and less likely to accidentally regress. That said, I don't have strong feelings.

}))
.into_shared(),
)
Expand Down Expand Up @@ -293,3 +294,47 @@ pub fn default_plugins(
.flatten()
.collect::<Vec<SharedRuntimePlugin>>()
}

#[cfg(test)]
mod tests {
use super::*;
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins;

fn test_plugin_params(version: BehaviorVersion) -> DefaultPluginParams {
DefaultPluginParams::new()
.with_behavior_version(version)
.with_retry_partition_name("dontcare")
}
fn config_for(plugins: impl IntoIterator<Item = SharedRuntimePlugin>) -> ConfigBag {
let mut config = ConfigBag::base();
let plugins = RuntimePlugins::new().with_client_plugins(plugins);
plugins.apply_client_configuration(&mut config).unwrap();
config
}

#[test]
#[allow(deprecated)]
fn v2024_03_28_stalled_stream_protection_difference() {
let latest = config_for(default_plugins(test_plugin_params(
BehaviorVersion::latest(),
)));
let v2023 = config_for(default_plugins(test_plugin_params(
BehaviorVersion::v2023_11_09(),
)));

assert!(
latest
.load::<StalledStreamProtectionConfig>()
.unwrap()
.upload_enabled(),
"stalled stream protection on uploads MUST be enabled after v2024_03_28"
);
assert!(
!v2023
.load::<StalledStreamProtectionConfig>()
.unwrap()
.upload_enabled(),
"stalled stream protection on uploads MUST NOT be enabled before v2024_03_28"
);
}
}