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

Prerequisites to Enable smithy client #3317

Merged
merged 1 commit into from
Mar 7, 2025
Merged

Prerequisites to Enable smithy client #3317

merged 1 commit into from
Mar 7, 2025

Conversation

sbera87
Copy link
Contributor

@sbera87 sbera87 commented Feb 27, 2025

Issue #, if available:

Description of changes:
This set of changes are needed to enable Smithy client for S3 with applicable backwards compatible changes.

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@sbera87 sbera87 force-pushed the s3smithyon branch 2 times, most recently from dea11b7 to f0684a4 Compare February 27, 2025 19:50
@@ -290,18 +460,21 @@ void AwsSmithyClientBase::AttemptOneRequestAsync(std::shared_ptr<AwsSmithyClient
return;
}

pRequestCtx->m_interceptorContext->SetTransmitRequest(pRequestCtx->m_httpRequest);
for (const auto& interceptor : m_interceptors)
if (pRequestCtx->m_interceptorContext)
Copy link
Contributor Author

@sbera87 sbera87 Feb 27, 2025

Choose a reason for hiding this comment

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

just added code block under null guard since in requestless cases, this is currently not created in requestless call flow.
This is done to emulate legacy behavior:
with request case:

InterceptorContext context{request};

vs

request less case:

HttpResponseOutcome AWSClient::AttemptOneRequest(const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest,




void AwsSmithyClientBase::MakeRequestAsyncHelper(std::shared_ptr<AwsSmithyClientAsyncRequestContext>& pRequestCtx,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This helper abstracts endpoint resolution into a callback and is invoked contextually by different variations of APIs used for backwards compatibilty

@sbera87 sbera87 force-pushed the s3smithyon branch 3 times, most recently from 3547603 to f03166d Compare February 28, 2025 16:58
Aws::Map<Aws::String, Aws::String> params;
params.emplace("bucketName", bucket);
ServiceSpecificParameters serviceSpecificParameters{params};
auto serviceParams = Aws::MakeShared<ServiceSpecificParameters>(ALLOCATION_TAG, serviceSpecificParameters);
return AWSClient::GeneratePresignedUrl(endpoint, method, customizedHeaders, expirationInSeconds, Aws::Auth::SIGV4_SIGNER,
nullptr, nullptr, serviceParams);
EndpointUpdateCallback endpointCallback = [&](Aws::Endpoint::AWSEndpoint& endpoint){
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes to resolve endpoint after select auth scheme

Aws::Vector<AuthSchemeOption> authSchemeOptions = m_authSchemeResolver->resolveAuthScheme(identityParams);
Aws::Vector<AuthSchemeOption> authSchemeOptions;
/*for backwards compatibility, signer name override is used to filter auth schemes by id equivalent to legacy signer name*/
if(ctx.m_signerNameOverride.has_value())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is added to make the backwards compatible api functional.
In legacy client, signer name passed was used to fetch the signer object via
auto signer = GetSignerByName(signerName);
In smithy design, this name passed is simply re-mapped and used to filter desired auth scheme (with requested signer) from list of supported auth schemes

Copy link
Contributor

Choose a reason for hiding this comment

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

This is added to make the backwards compatible api functional.

This is really confusing me, you say we added this for backwards compatibility, but then we don't declare GetSignerByName so we break backwards incompatibility with that

i.e. this compiles before but is broken after. So why are we doing something for backwards compatibility, then we are actually breaking backwards compatibility?

#include <aws/core/Aws.h>
#include <aws/s3/S3Client.h>

using namespace Aws;
using namespace Aws::S3;

class MyS3Client : public Aws::S3::S3Client {
public:
  MyS3Client() = default;
  ~MyS3Client() override = default;
  void Foo() {
    std::cout << this->GetSignerByName("some_name") << std::endl;
  }
};

auto main() -> int {
  SDKOptions options{};
  options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
  InitAPI(options);
  {
    MyS3Client client{};
    client.Foo();
  }
  ShutdownAPI(options);
  return 0;
}

@@ -240,8 +314,141 @@ namespace client
}
return GeneratePresignedUrl(uri, method, signerRegionOverride, signerServiceNameOverride, expirationInSeconds, customizedHeaders, serviceSpecificParameters);
}

ResponseT MakeRequest(const Aws::Http::URI& uri,
Copy link
Contributor Author

@sbera87 sbera87 Feb 28, 2025

Choose a reason for hiding this comment

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

Legacy APIs that need to be supported as external clients found to use some variations.

Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this to a different class? its makes it hard to understand what is going on here if we add them in the smithy client, move it to a new class then have the smithy client just reference it. we do not want to copy and paste the old work flow into the new one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, The interface is old which we have to maintain for backwards compatibility, but internally, it follows the same design as smithy MakeRequestDeserialize API, i.e use executor to submit an Async API implementation with resolve endpoint action as a callback and then deserialize the outcome .

In this example the call flow is: MakeRequest -> MakeRequestWithUriAsync -> MakeRequestAsyncHelper

using ClientError = AWSError;
using SigningError = AWSError;
using AWSCoreError = Aws::Client::AWSError<CoreErrors>;
using ClientError = AWSCoreError;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed to ensure backwards compatibility . External clients use AwsError as a templated type

std::shared_ptr<Aws::Utils::Threading::Executor> pExecutor
) const;

const std::shared_ptr<Aws::Client::AWSErrorMarshaller>& GetErrorMarshaller() const
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed as External Users use this

@@ -200,7 +237,7 @@ namespace client
Aws::Vector<std::shared_ptr<smithy::interceptor::Interceptor>> m_interceptors{};
std::shared_ptr<smithy::client::UserAgentInterceptor> m_userAgentInterceptor;
private:
void UpdateAuthSchemeFromEndpoint(const Aws::Endpoint::AWSEndpoint& endpoint, AuthSchemeOption& authscheme) const;
void UpdateAuthSchemeFromEndpoint(std::shared_ptr<AwsSmithyClientAsyncRequestContext>& pRequestCtx) const;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interface updated to facilitate legacy API usage where endpoint may not be available

Copy link
Contributor

@sbiscigl sbiscigl left a comment

Choose a reason for hiding this comment

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

I think we've got a maintainibilty issue here. You've more or less copy and pasted AWSClient into the SmithyClient, which I don't think is the way forward here. If we want to maintain backward compatible interfaces for newer clients we should maintain the interface, but not copy and paste the code directly.

@@ -62,6 +62,10 @@ namespace Aws
IdentityProviderSupplier identityProviderSupplier = [](const S3Client &client) -> std::shared_ptr<S3ExpressIdentityProvider> {
return Aws::MakeShared<DefaultS3ExpressIdentityProvider>("S3ClientConfiguration", client);
};
using SmithyIdentityProviderSupplier = std::function<std::shared_ptr<SmithyS3ExpressIdentityProvider> (const S3Client &)>;
Copy link
Contributor

Choose a reason for hiding this comment

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

so SmithyS3ExpressIdentityProvider and S3ExpressIdentityProvider are more or less the same thing, and we really should only have one, could we make this a CRT Variant so that either could be provided and we internally can choose what do differently based on which type is present? having two of the same configuration seems misleading.

Copy link
Contributor Author

@sbera87 sbera87 Mar 3, 2025

Choose a reason for hiding this comment

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

Though the functionality is achieved the same, the interfaces are different and hence added to support both old and new
class AWS_S3_API S3ExpressIdentityProvider : public smithy::IdentityResolverBase<S3ExpressIdentity>
vs
class AWS_S3_API SmithyS3ExpressIdentityProvider : public smithy::IdentityResolverBase<smithy::AwsCredentialIdentityBase>

I have made changes in next commit to address this so that there is only one identity provider supplier as before.

}
}

return {smithy::SigV4AuthSchemeOption::sigV4AuthSchemeOption};
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we returning sigv4 instead of a empty list?

Copy link
Contributor Author

@sbera87 sbera87 Mar 4, 2025

Choose a reason for hiding this comment

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

This is to address existing test cases like
TEST_F(BucketAndObjectOperationTest, TestS3AccessPointARN)

S3Client s3Client(config); GetObjectRequest getObjectRequest; getObjectRequest.SetBucket(bucketArn); getObjectRequest.SetKey("fakeObjectKey"); auto getObjectOutcome = s3Client.GetObject(getObjectRequest); ASSERT_FALSE(getObjectOutcome.IsSuccess());

when auth scheme is not found we currently have an assertion for builds which don't strip away asserts.
assert(authSchemeOptionIt != authSchemeOptions.end());
The fallback is added to not hit this assertion. without this it exits with a different core error and not the expected CoreErrors::ENDPOINT_RESOLUTION_FAILURE in the legacy test case.
This core error is only returned when resolve endpoint fails which in smithy workflow happens after select auth schemes.

Copy link
Contributor

Choose a reason for hiding this comment

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

that doesnt make sense, this should return a empty list. doing something because "it makes a test pass" is not a good reason for a implementation detail

get object shuold be using a auth scheme that is recognized, why isnt it.

auto authSchemeName = endpoint.GetAttributes()->authScheme.GetName();
if(strcmp(authSchemeName.c_str(),Aws::Auth::ASYMMETRIC_SIGV4_SIGNER) == 0)
{
return {smithy::SigV4aAuthSchemeOption::sigV4aAuthSchemeOption};
Copy link
Contributor

Choose a reason for hiding this comment

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

per the SRA

The result is a list of authentication schemes, in order of preference, that should be used for the request. The SDK will choose the first scheme that it supports, or fail the request if no schemes are supported.

so why are we returning a single auth scheme option? we should be returning the list of auth scheme options that are supported, then we choose the first one after we call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, its a vector of one for the appropriate scheme id. If you see smithy v4, v4a authscheme resolvers they return respective vector of one authscheme options. Since this is a child of multi auth scheme resolver it simply uses if else condition block for the different scheme ids.

Copy link
Contributor

Choose a reason for hiding this comment

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

So, its a vector of one for the appropriate scheme id.

no you should be returning a vector of supported auth schemes, and using the first supported one. i.e. if you are using v4a it should return [v4a, v4] then the caller gets to decide which one is supported, thats the way the interface is supposed to wok

return Aws::MakeShared<ServiceSpecificParameters>(ALLOCATION_TAG, serviceSpecificParameters);
}());
return DeleteBucketOutcome(MakeRequest(request, endpointResolutionOutcome.GetResult(), Aws::Http::HttpMethod::HTTP_DELETE));
[&]() -> std::shared_ptr<Http::ServiceSpecificParameters> {
Copy link
Contributor

Choose a reason for hiding this comment

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

your code change is changing formatting on unchanged lines -- please fix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I double checked. This honors the indentation used in the new smithy code gen template. Changing this will impact other smithy clients too.

Copy link
Contributor

Choose a reason for hiding this comment

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

well if it was a problem before we should fix it, it doesnt honor the correct indentation, please fix it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed, this is just the change smithy codegen brings with own consistent indentation that it is consistent with
.

@@ -190,13 +210,68 @@ namespace client
return m_serializer->Deserialize(std::move(httpResponseOutcome), GetServiceClientName(), requestName);
}

Aws::String GeneratePresignedUrl(
const Aws::String& bucket,
Copy link
Contributor

Choose a reason for hiding this comment

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

why is the string "bucket" here? the client is not aware of what a bucket is, there are several other presigned operations that exist and do not fit into this paradigm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The above example is already handled in another PR (where other rest xml clients use the API you mentioned.) This PR deals with S3 related changes only

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have made changes to this to eliminate bucket from the interface

Aws::Vector<AuthSchemeOption> authSchemeOptions = m_authSchemeResolver->resolveAuthScheme(identityParams);
Aws::Vector<AuthSchemeOption> authSchemeOptions;
/*for backwards compatibility, signer name override is used to filter auth schemes by id equivalent to legacy signer name*/
if(ctx.m_signerNameOverride.has_value())
Copy link
Contributor

Choose a reason for hiding this comment

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

This is added to make the backwards compatible api functional.

This is really confusing me, you say we added this for backwards compatibility, but then we don't declare GetSignerByName so we break backwards incompatibility with that

i.e. this compiles before but is broken after. So why are we doing something for backwards compatibility, then we are actually breaking backwards compatibility?

#include <aws/core/Aws.h>
#include <aws/s3/S3Client.h>

using namespace Aws;
using namespace Aws::S3;

class MyS3Client : public Aws::S3::S3Client {
public:
  MyS3Client() = default;
  ~MyS3Client() override = default;
  void Foo() {
    std::cout << this->GetSignerByName("some_name") << std::endl;
  }
};

auto main() -> int {
  SDKOptions options{};
  options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
  InitAPI(options);
  {
    MyS3Client client{};
    client.Foo();
  }
  ShutdownAPI(options);
  return 0;
}

@@ -70,6 +70,10 @@ namespace smithy
ResponseHandlerFunc m_responseHandler;
std::shared_ptr<Aws::Utils::Threading::Executor> m_pExecutor;
std::shared_ptr<interceptor::InterceptorContext> m_interceptorContext;
//For Backwards Compatible APIs
Copy link
Contributor

Choose a reason for hiding this comment

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

what backwards compatible APIs?

@@ -240,8 +314,141 @@ namespace client
}
return GeneratePresignedUrl(uri, method, signerRegionOverride, signerServiceNameOverride, expirationInSeconds, customizedHeaders, serviceSpecificParameters);
}

ResponseT MakeRequest(const Aws::Http::URI& uri,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this to a different class? its makes it hard to understand what is going on here if we add them in the smithy client, move it to a new class then have the smithy client just reference it. we do not want to copy and paste the old work flow into the new one.

const char* signerRegionOverride = nullptr,
const char* signerServiceNameOverride = nullptr) const
{
std::shared_ptr<Aws::Utils::Threading::Executor> pExecutor = Aws::MakeShared<Aws::Utils::Threading::SameThreadExecutor>(ServiceNameT);
Copy link
Contributor

Choose a reason for hiding this comment

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

you've more or less copy and pasted the old code into the new class, this is not what we want. this diverges the flow significantly. these functions should_call MakeRequestDeserialize and should not diverge the call path. This is just creating both clients in a a single class. we should defer calling to the new methods and emit a warning log about calling these methods directly.

Copy link
Contributor Author

@sbera87 sbera87 Mar 4, 2025

Choose a reason for hiding this comment

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

Its actually new code in old interface. But, I have changed all the legacy APIs to call MakeRequestDeserialize and moved them to a different class using CRTP pattern in smithy client

@sbera87 sbera87 force-pushed the s3smithyon branch 2 times, most recently from dfa0208 to 16c4b73 Compare March 4, 2025 14:48
@sbera87 sbera87 requested a review from sbiscigl March 4, 2025 14:53
auto authSchemeName = endpoint.GetAttributes()->authScheme.GetName();
if(strcmp(authSchemeName.c_str(),Aws::Auth::ASYMMETRIC_SIGV4_SIGNER) == 0)
{
return {smithy::SigV4aAuthSchemeOption::sigV4aAuthSchemeOption};
Copy link
Contributor

Choose a reason for hiding this comment

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

So, its a vector of one for the appropriate scheme id.

no you should be returning a vector of supported auth schemes, and using the first supported one. i.e. if you are using v4a it should return [v4a, v4] then the caller gets to decide which one is supported, thats the way the interface is supposed to wok

}
}

return {smithy::SigV4AuthSchemeOption::sigV4AuthSchemeOption};
Copy link
Contributor

Choose a reason for hiding this comment

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

that doesnt make sense, this should return a empty list. doing something because "it makes a test pass" is not a good reason for a implementation detail

get object shuold be using a auth scheme that is recognized, why isnt it.

return Aws::MakeShared<ServiceSpecificParameters>(ALLOCATION_TAG, serviceSpecificParameters);
}());
return DeleteBucketOutcome(MakeRequest(request, endpointResolutionOutcome.GetResult(), Aws::Http::HttpMethod::HTTP_DELETE));
[&]() -> std::shared_ptr<Http::ServiceSpecificParameters> {
Copy link
Contributor

Choose a reason for hiding this comment

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

well if it was a problem before we should fix it, it doesnt honor the correct indentation, please fix it

@@ -21,12 +21,12 @@ Aws::Auth::S3ExpressSignerProvider::S3ExpressSignerProvider(
region,
signingPolicy,
urlEscapePath) {
m_signers.emplace_back(Aws::MakeShared<Aws::S3::S3ExpressSigner>(CLASS_TAG,
m_signers.emplace_back(std::static_pointer_cast<Aws::Client::AWSAuthSigner>(Aws::MakeShared<Aws::S3::S3ExpressSigner>(CLASS_TAG,
Copy link
Contributor

Choose a reason for hiding this comment

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

remove the static pointer class it should not be needed

Copy link
Contributor Author

@sbera87 sbera87 Mar 5, 2025

Choose a reason for hiding this comment

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

For the return auth schemes list comment, we can't return all the auth schemes indiscriminately in a list for multi auth case. For the multi auth support, we do a resolve endpoint twice, the first time happens in select auth scheme.

Based on the auth scheme from endpoint, we build the list as aforementioned. What you are referring to as pick the first supported auth scheme actually happens here inside the resolver. This was done based on previous PR comments to move this auth scheme vector selection inside the auth scheme resolver class from smithy client class (else it would end up doing double endpoint resolution for all clients and not just S3).

So, for the negative test case I mentioned, epResolutionOutcome is not a success and hence we have empty auth schemes list which would trigger the assertion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the cast in next commit

namespace client
{
/*
This Class exists only to provide a way to access the legacy client APIs
Copy link
Contributor

Choose a reason for hiding this comment

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

niiiiiiiiiiiice, i like this a lot

@sbera87 sbera87 force-pushed the s3smithyon branch 4 times, most recently from 5e42f3e to c6089cf Compare March 5, 2025 20:26
@sbera87 sbera87 requested a review from sbiscigl March 5, 2025 20:28
@sbera87 sbera87 force-pushed the s3smithyon branch 3 times, most recently from 2e8a268 to b5c604b Compare March 6, 2025 16:10
@sbera87 sbera87 force-pushed the s3smithyon branch 2 times, most recently from 8de0047 to 3686975 Compare March 6, 2025 19:51
@sbera87 sbera87 changed the title Enable S3 smithy client Prerequisites to Enable smithy client Mar 6, 2025
@sbera87 sbera87 merged commit ea08eb7 into main Mar 7, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants