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

misc: credentials business metrics #1442

Merged
merged 20 commits into from
Nov 4, 2024

Conversation

0marperez
Copy link
Contributor

Issue #

Description of changes

-Emitting credentials related business metrics
-Tests for credentials related business metrics

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@0marperez 0marperez added the no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly. label Oct 16, 2024
Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

@0marperez 0marperez marked this pull request as ready for review October 16, 2024 17:09
@0marperez 0marperez requested a review from a team as a code owner October 16, 2024 17:09
Copy link

A new generated diff is ready to view.

@@ -7,7 +7,8 @@
"secret_access_key": "secretkeycorrect",
"session_token": "tokencorrect",
"expiry": 1632249686,
"accountId": "130633740322"
"accountId": "130633740322",
"business_metrics": ["p","0", "i"]
Copy link
Member

Choose a reason for hiding this comment

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

If this is just meant to resolve credentials from IMDS, why are there three credentials provider metrics emitted?

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 test actually uses this config file. So p is for the named provider, 0 is for IMDS, and i is for the assume role call with the credentials resolved from IMDS. I think it might be a good idea to change the docs for this test because I made the same assumption when I first added the business metrics.

/**
* The source used to create the [RoleArn]
*/
val roleArnSource: RoleArnSource? = null,
Copy link
Member

Choose a reason for hiding this comment

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

naming: source makes this more readable, roleArn.source vs. roleArn.roleArnSource

@@ -318,4 +326,286 @@ class ProfileCredentialsProviderTest {
val expected = credentials("AKID-Default", "Default-Secret", accountId = "12345")
assertEquals(expected, actual)
}

@Test
fun staticCredentialsBusinessMetrics() = runTest {
Copy link
Member

Choose a reason for hiding this comment

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

naming: static -> profile

override val name: String = "credentialsOverrideBusinessMetricsMiddleware"
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.withBlock(
"if (config.credentialsProvider.#T != #S ) {",
Copy link
Member

Choose a reason for hiding this comment

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

Compare classes directly rather than their simple names, if (config.credentialsProvider !is ManagedCredentialsProvider)

"ManagedCredentialsProvider",
) {
write(
"op.context.#T(#T.Credentials.CREDENTIALS_CODE)",
Copy link
Member

Choose a reason for hiding this comment

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

This middleware will emit CREDENTIALS_CODE any time a custom credentials provider is configured and used. I thought that this metric referred to using the StaticCredentialsProvider.

If someone configures a custom ProfileCredentialsProvider, should we really not be emitting the CREDENTIALS_PROFILE metric?

Copy link
Contributor Author

@0marperez 0marperez Oct 17, 2024

Choose a reason for hiding this comment

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

CREDENTIALS_CODE is meant for when there's any code to setup credentials, so configuring a custom / non-default credentials provider, the StaticCredentialsProvider, session objects for boto, etc. We don't have a business metric for StaticCredentialsProvider but it falls under CREDENTIALS_CODE. We could get more specific in the future though, we would just have to discuss adding a metric for something like the StaticCredentialsProvider that all SDKs share.

CREDENTIALS_PROFILE is confusing, it's meant for static credentials in a profile. It was called something else but I think the name changed during review. Same as StaticCredentialsProvider, we don't have a metric for ProfileCredentialsProvider. The focus for the initial metrics was more on the default credentials provider chain related metrics not all credentials providers but we can go back and add more metrics soon because I think they would be very useful.

We still capture most of what's going on, e.g. If a user sets the ProfileCredentialsProvider we would see that they set a non-default credentials provider, and then we could see where in the profile the credentials are resolved from because every profile option is captured in the metrics.

Copy link
Contributor Author

@0marperez 0marperez Oct 17, 2024

Choose a reason for hiding this comment

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

CREDENTIALS_CODE is now mapped to StaticCredentialsProvider. It's not in the provider itself because it's an implementation detail for other providers.

Copy link
Contributor

Choose a reason for hiding this comment

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

CREDENTIALS_CODE is now mapped to StaticCredentialsProvider. It's not in the provider itself because it's an implementation detail for other providers.

I don't think I understand the point being made here. Why can't we move this specialized logic into StaticCredentialsProvider? This custom middleware and the symbol references seem way more complex than just tracking the metric in static code the same way we do for all other providers.

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 issue is that it's used in the ProfileCredentialsProvider. When testing I saw the StaticCredentialsProvider business metrics being emitted when using ProfileCredentialsProvider.

Comment on lines 47 to 50
/**
* Indicates if the class was created using [fromEnvironment]
*/
private var createdFromEnvironment: Boolean = false
Copy link
Member

Choose a reason for hiding this comment

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

I understand why you needed to add this but it seems a little hacky.

We only call StsWebIdentityCredentialsProvider.fromEnvironment in one spot, the default chain, can the metrics emission for that be lifted to that point and then we can remove this createdFromEnvironment here?

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 originally had it in the default credentials provider chain but it wasn't working correctly so I moved it here. The default credentials provider chain doesn't have attributes so I was creating some and then merging them with the ones from the resolve call, but something about that wasn't working correctly. I can try again for a bit and see if I can make it work.

Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

@0marperez 0marperez force-pushed the credentials-business-metrics branch from 3f082bb to 6a7b812 Compare October 31, 2024 21:13
Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

Comment on lines 70 to 75
/**
* Emits a business metric into [Credentials.attributes]
* @param identifier The identifier of the [BusinessMetric] to be emitted.
*/
@InternalApi
public fun Credentials.emitBusinessMetric(identifier: String): Credentials =
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "Emit" feels like the wrong word here. If the credentials are never used then we won't have "emitted" a metric in the sense that it was included in the UA. Perhaps Credentials.withBusinessMetric?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment would go for every other use/variation of emitBusinessMetric . It's what we've been using so far.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with Ian, it's a "nit" comment but would improve readability

Comment on lines 100 to 105
public final class aws/sdk/kotlin/runtime/auth/credentials/LazilyInitializedCredentialsProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider {
public fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Laws/smithy/kotlin/runtime/businessmetrics/BusinessMetric;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Ljava/lang/String;Laws/smithy/kotlin/runtime/businessmetrics/BusinessMetric;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Correctness: Backwards-incompatible changes

Comment on lines 55 to 59
LazilyInitializedCredentialsProvider("EnvironmentStsWebIdentityCredentialsProvider") {
LazilyInitializedCredentialsProvider(
"EnvironmentStsWebIdentityCredentialsProvider",
AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN,
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Why should the LazilyInitializedCredentialsProvider care about its underlying delegate's metric? Can't StsWebIdentityCredentialsProvider record the metric itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was originally what I had, you can see it here. There's some conversation you can follow

Copy link
Member

Choose a reason for hiding this comment

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

This doesn't really address my initial concern of the business metrics seeming to be hacked into the credentials provider implementation. I agree with Ian, LazilyInitializedCredentialsProvider should not have to know about business metrics

Copy link
Contributor

Choose a reason for hiding this comment

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

Correctness: More backward-incompatible changes.

"ManagedCredentialsProvider",
) {
write(
"op.context.#T(#T.Credentials.CREDENTIALS_CODE)",
Copy link
Contributor

Choose a reason for hiding this comment

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

CREDENTIALS_CODE is now mapped to StaticCredentialsProvider. It's not in the provider itself because it's an implementation detail for other providers.

I don't think I understand the point being made here. Why can't we move this specialized logic into StaticCredentialsProvider? This custom middleware and the symbol references seem way more complex than just tracking the metric in static code the same way we do for all other providers.

Copy link

A new generated diff is ready to view.

Copy link

github-actions bot commented Nov 1, 2024

A new generated diff is ready to view.

Copy link

github-actions bot commented Nov 1, 2024

A new generated diff is ready to view.

Copy link

github-actions bot commented Nov 1, 2024

A new generated diff is ready to view.

Copy link
Member

@lauzadis lauzadis left a comment

Choose a reason for hiding this comment

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

Overall it looks good and getting close to merge. My last concern is around LazilyInitializedCredentialsProvider being too closely coupled with business metrics

@@ -86,6 +88,7 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor(
public val httpClient: HttpClientEngine? = null,
public val configurationSource: AwsConfigurationSource? = null,
) : CloseableCredentialsProvider {
private val credentialsBusinessMetrics: MutableSet<BusinessMetric> = mutableSetOf()
Copy link
Member

Choose a reason for hiding this comment

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

correctness: MutableSet does not guarantee order of elements

A generic unordered collection of elements that does not support duplicate elements, and supports adding and removing elements.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-set/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It actually does

From MutableSet:

LinkedHashSet
The implementation of the MutableSet interface, backed by a InternalMap implementation.

From LinkedHashSet:

The implementation of the MutableSet interface, backed by a InternalMap implementation.

The insertion order is preserved by the corresponding InternalMap implementation.

You can double check we're using LinkedHashSet by getting the ::class.simpleName of the business metrics

Copy link
Member

Choose a reason for hiding this comment

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

That's the current implementation, but it's not guaranteed and could change in a future version of Kotlin. It's unlikely, and I'm open to keeping this MutableSet but just wanted to call it out 🤷

@InternalApi
public fun Credentials.emitBusinessMetrics(metrics: Set<BusinessMetric>): Credentials {
var credentials = this
metrics.forEach { identifier ->
Copy link
Member

Choose a reason for hiding this comment

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

nit: this is no longer an identifier but the metric itself

creds = assumeProvider.resolve(attributes)
}

logger.debug { "Obtained credentials from profile; expiration=${creds.expiration?.format(TimestampFormat.ISO_8601)}" }
return creds
return creds.emitBusinessMetrics(credentialsBusinessMetrics)
Copy link
Member

Choose a reason for hiding this comment

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

note: We're calling emitBusinessMetrics on a creds which might already have business metrics applied, which would result in duplication if you weren't using a MutableSet (which you might not want to be, based on my other comment above)

Copy link

sonarqubecloud bot commented Nov 4, 2024

Copy link

github-actions bot commented Nov 4, 2024

A new generated diff is ready to view.

@0marperez 0marperez merged commit de5dc7f into credentials-chain-order Nov 4, 2024
11 checks passed
@0marperez 0marperez deleted the credentials-business-metrics branch November 4, 2024 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants