Skip to content

Commit

Permalink
Merge branch 'main' into properties-debug
Browse files Browse the repository at this point in the history
  • Loading branch information
rcoh authored Apr 25, 2023
2 parents 1b45b80 + ae995fb commit ff45b05
Show file tree
Hide file tree
Showing 67 changed files with 2,874 additions and 409 deletions.
1 change: 0 additions & 1 deletion aws/rust-runtime/aws-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" }
aws-smithy-protocol-test = { path = "../../../rust-runtime/aws-smithy-protocol-test" }
bytes-utils = "0.1.2"
env_logger = "0.9"
http = "0.2.3"
tokio = { version = "1.23.1", features = ["macros", "rt", "rt-multi-thread", "test-util", "time"] }
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
proptest = "1"
Expand Down
1 change: 1 addition & 0 deletions aws/rust-runtime/aws-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ aws-types = { path = "../aws-types" }
http = "0.2.3"
percent-encoding = "2.1.0"
tracing = "0.1"
uuid = { version = "1", features = ["v4", "fast-rng"] }

[dev-dependencies]
aws-smithy-protocol-test = { path = "../../../rust-runtime/aws-smithy-protocol-test" }
Expand Down
3 changes: 2 additions & 1 deletion aws/rust-runtime/aws-runtime/external-types.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
allowed_external_types = [
"aws_credential_types::*",
"aws_sigv4::*",
"aws_smithy_http::body::SdkBody",
"aws_smithy_http::*",
"aws_smithy_types::*",
"aws_smithy_runtime_api::*",
"aws_types::*",
"http::request::Request",
Expand Down
5 changes: 2 additions & 3 deletions aws/rust-runtime/aws-runtime/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ pub mod sigv4 {
UriPathNormalizationMode,
};
use aws_smithy_http::property_bag::PropertyBag;
use aws_smithy_runtime_api::client::identity::Identity;
use aws_smithy_runtime_api::client::identity::{Identity, IdentityResolver, IdentityResolvers};
use aws_smithy_runtime_api::client::orchestrator::{
BoxError, HttpAuthScheme, HttpRequest, HttpRequestSigner, IdentityResolver,
IdentityResolvers,
BoxError, HttpAuthScheme, HttpRequest, HttpRequestSigner,
};
use aws_types::region::SigningRegion;
use aws_types::SigningService;
Expand Down
12 changes: 5 additions & 7 deletions aws/rust-runtime/aws-runtime/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
pub mod credentials {
use aws_credential_types::cache::SharedCredentialsCache;
use aws_smithy_http::property_bag::PropertyBag;
use aws_smithy_runtime_api::client::identity::Identity;
use aws_smithy_runtime_api::client::orchestrator::{
BoxError, BoxFallibleFut, IdentityResolver,
};
use aws_smithy_runtime_api::client::identity::{Identity, IdentityResolver};
use aws_smithy_runtime_api::client::orchestrator::{BoxError, Future};

/// Smithy identity resolver for AWS credentials.
#[derive(Debug)]
Expand All @@ -26,13 +24,13 @@ pub mod credentials {
}

impl IdentityResolver for CredentialsIdentityResolver {
fn resolve_identity(&self, _identity_properties: &PropertyBag) -> BoxFallibleFut<Identity> {
fn resolve_identity(&self, _identity_properties: &PropertyBag) -> Future<Identity> {
let cache = self.credentials_cache.clone();
Box::pin(async move {
Future::new(Box::pin(async move {
let credentials = cache.as_ref().provide_cached_credentials().await?;
let expiration = credentials.expiry();
Result::<_, BoxError>::Ok(Identity::new(credentials, expiration))
})
}))
}
}
}
91 changes: 91 additions & 0 deletions aws/rust-runtime/aws-runtime/src/invocation_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_smithy_runtime_api::client::interceptors::error::BoxError;
use aws_smithy_runtime_api::client::interceptors::{Interceptor, InterceptorContext};
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse};
use aws_smithy_runtime_api::config_bag::ConfigBag;
use http::{HeaderName, HeaderValue};
use uuid::Uuid;

#[allow(clippy::declare_interior_mutable_const)] // we will never mutate this
const AMZ_SDK_INVOCATION_ID: HeaderName = HeaderName::from_static("amz-sdk-invocation-id");

/// This interceptor generates a UUID and attaches it to all request attempts made as part of this operation.
#[non_exhaustive]
#[derive(Debug)]
pub struct InvocationIdInterceptor {
id: HeaderValue,
}

impl InvocationIdInterceptor {
/// Creates a new `InvocationIdInterceptor`
pub fn new() -> Self {
Self::default()
}
}

impl Default for InvocationIdInterceptor {
fn default() -> Self {
let id = Uuid::new_v4();
let id = id
.to_string()
.parse()
.expect("UUIDs always produce a valid header value");
Self { id }
}
}

impl Interceptor<HttpRequest, HttpResponse> for InvocationIdInterceptor {
fn modify_before_retry_loop(
&self,
context: &mut InterceptorContext<HttpRequest, HttpResponse>,
_cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let headers = context.request_mut()?.headers_mut();
headers.append(AMZ_SDK_INVOCATION_ID, self.id.clone());
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::invocation_id::InvocationIdInterceptor;
use aws_smithy_http::body::SdkBody;
use aws_smithy_runtime_api::client::interceptors::{Interceptor, InterceptorContext};
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse};
use aws_smithy_runtime_api::config_bag::ConfigBag;
use aws_smithy_runtime_api::type_erasure::TypedBox;
use http::HeaderValue;

fn expect_header<'a>(
context: &'a InterceptorContext<HttpRequest, HttpResponse>,
header_name: &str,
) -> &'a HeaderValue {
context
.request()
.unwrap()
.headers()
.get(header_name)
.unwrap()
}

#[test]
fn test_id_is_generated_and_set() {
let mut context = InterceptorContext::new(TypedBox::new("doesntmatter").erase());
context.set_request(http::Request::builder().body(SdkBody::empty()).unwrap());

let mut config = ConfigBag::base();
let interceptor = InvocationIdInterceptor::new();
interceptor
.modify_before_retry_loop(&mut context, &mut config)
.unwrap();

let header = expect_header(&context, "amz-sdk-invocation-id");
assert_eq!(&interceptor.id, header);
// UUID should include 32 chars and 4 dashes
assert_eq!(interceptor.id.len(), 36);
}
}
6 changes: 6 additions & 0 deletions aws/rust-runtime/aws-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ pub mod recursion_detection;

/// Supporting code for user agent headers in the AWS SDK.
pub mod user_agent;

/// Supporting code for retry behavior specific to the AWS SDK.
pub mod retries;

/// Supporting code for invocation ID headers in the AWS SDK.
pub mod invocation_id;
7 changes: 7 additions & 0 deletions aws/rust-runtime/aws-runtime/src/retries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/// Classifiers that can inspect a response and determine if it should be retried.
pub mod classifier;
174 changes: 174 additions & 0 deletions aws/rust-runtime/aws-runtime/src/retries/classifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_smithy_http::http::HttpHeaders;
use aws_smithy_http::result::SdkError;
use aws_smithy_runtime_api::client::retries::RetryReason;
use aws_smithy_types::error::metadata::ProvideErrorMetadata;
use aws_smithy_types::retry::ErrorKind;

/// AWS error codes that represent throttling errors.
pub const THROTTLING_ERRORS: &[&str] = &[
"Throttling",
"ThrottlingException",
"ThrottledException",
"RequestThrottledException",
"TooManyRequestsException",
"ProvisionedThroughputExceededException",
"TransactionInProgressException",
"RequestLimitExceeded",
"BandwidthLimitExceeded",
"LimitExceededException",
"RequestThrottled",
"SlowDown",
"PriorRequestNotComplete",
"EC2ThrottledException",
];

/// AWS error codes that represent transient errors.
pub const TRANSIENT_ERRORS: &[&str] = &["RequestTimeout", "RequestTimeoutException"];

/// A retry classifier for determining if the response sent by an AWS service requires a retry.
#[derive(Debug)]
pub struct AwsErrorCodeClassifier;

impl AwsErrorCodeClassifier {
/// Classify an error code to check if represents a retryable error. The codes of retryable
/// errors are defined [here](THROTTLING_ERRORS) and [here](TRANSIENT_ERRORS).
pub fn classify_error<E: ProvideErrorMetadata, R>(
&self,
error: &SdkError<E, R>,
) -> Option<RetryReason> {
if let Some(error_code) = error.code() {
if THROTTLING_ERRORS.contains(&error_code) {
return Some(RetryReason::Error(ErrorKind::ThrottlingError));
} else if TRANSIENT_ERRORS.contains(&error_code) {
return Some(RetryReason::Error(ErrorKind::TransientError));
}
};

None
}
}

/// A retry classifier that checks for `x-amz-retry-after` headers. If one is found, a
/// [`RetryReason::Explicit`] is returned containing the duration to wait before retrying.
#[derive(Debug)]
pub struct AmzRetryAfterHeaderClassifier;

impl AmzRetryAfterHeaderClassifier {
/// Classify an AWS responses error code to determine how (and if) it should be retried.
pub fn classify_error<E>(&self, error: &SdkError<E>) -> Option<RetryReason> {
error
.raw_response()
.and_then(|res| res.http_headers().get("x-amz-retry-after"))
.and_then(|header| header.to_str().ok())
.and_then(|header| header.parse::<u64>().ok())
.map(|retry_after_delay| {
RetryReason::Explicit(std::time::Duration::from_millis(retry_after_delay))
})
}
}

#[cfg(test)]
mod test {
use super::{AmzRetryAfterHeaderClassifier, AwsErrorCodeClassifier};
use aws_smithy_http::body::SdkBody;
use aws_smithy_http::operation;
use aws_smithy_http::result::SdkError;
use aws_smithy_runtime_api::client::retries::RetryReason;
use aws_smithy_types::error::metadata::ProvideErrorMetadata;
use aws_smithy_types::error::ErrorMetadata;
use aws_smithy_types::retry::{ErrorKind, ProvideErrorKind};
use std::fmt;
use std::time::Duration;

#[derive(Debug)]
struct UnmodeledError;

impl fmt::Display for UnmodeledError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UnmodeledError")
}
}

impl std::error::Error for UnmodeledError {}

struct CodedError {
metadata: ErrorMetadata,
}

impl CodedError {
fn new(code: &'static str) -> Self {
Self {
metadata: ErrorMetadata::builder().code(code).build(),
}
}
}

impl ProvideErrorKind for UnmodeledError {
fn retryable_error_kind(&self) -> Option<ErrorKind> {
None
}

fn code(&self) -> Option<&str> {
None
}
}

impl ProvideErrorMetadata for CodedError {
fn meta(&self) -> &ErrorMetadata {
&self.metadata
}
}

#[test]
fn classify_by_error_code() {
let policy = AwsErrorCodeClassifier;
let res = http::Response::new("OK");
let err = SdkError::service_error(CodedError::new("Throttling"), res);

assert_eq!(
policy.classify_error(&err),
Some(RetryReason::Error(ErrorKind::ThrottlingError))
);

let res = http::Response::new("OK");
let err = SdkError::service_error(CodedError::new("RequestTimeout"), res);
assert_eq!(
policy.classify_error(&err),
Some(RetryReason::Error(ErrorKind::TransientError))
)
}

#[test]
fn classify_generic() {
let policy = AwsErrorCodeClassifier;
let res = http::Response::new("OK");
let err = aws_smithy_types::Error::builder().code("SlowDown").build();
let err = SdkError::service_error(err, res);
assert_eq!(
policy.classify_error(&err),
Some(RetryReason::Error(ErrorKind::ThrottlingError))
);
}

#[test]
fn test_retry_after_header() {
let policy = AmzRetryAfterHeaderClassifier;
let res = http::Response::builder()
.header("x-amz-retry-after", "5000")
.body("retry later")
.unwrap()
.map(SdkBody::from);
let res = operation::Response::new(res);
let err = SdkError::service_error(UnmodeledError, res);

assert_eq!(
policy.classify_error(&err),
Some(RetryReason::Explicit(Duration::from_millis(5000))),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ val DECORATORS: List<ClientCodegenDecorator> = listOf(
AwsRequestIdDecorator(),
DisabledAuthDecorator(),
RecursionDetectionDecorator(),
InvocationIdDecorator(),
),

// Service specific decorators
Expand Down
Loading

0 comments on commit ff45b05

Please sign in to comment.