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: e2e tests for s3 mrap #1193

Merged
merged 24 commits into from
Feb 22, 2024
Merged

misc: e2e tests for s3 mrap #1193

merged 24 commits into from
Feb 22, 2024

Conversation

0marperez
Copy link
Contributor

Issue #

Description of changes

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 Jan 24, 2024
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.

Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

Copy link

github-actions bot commented Feb 9, 2024

A new generated diff is ready to view.

Copy link

github-actions bot commented Feb 9, 2024

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.

Copy link

A new generated diff is ready to view.

@0marperez 0marperez marked this pull request as ready for review February 14, 2024 21:53
@0marperez 0marperez requested a review from a team as a code owner February 14, 2024 21:53
@@ -75,6 +75,10 @@ subprojects {
implementation(libraries.kotlin.test.junit5)
implementation(project(":tests:e2e-test-util"))
implementation(libraries.slf4j.simple)
implementation("aws.sdk.kotlin:s3control:+")
Copy link
Collaborator

Choose a reason for hiding this comment

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

fix: We can't just add these to every e2e test. For example sts has it's own e2e tests and taking a dependency on it will cause classpath conflicts (or more likely unpredictable class loading behavior). We're going to need to probably give this more thought.

return accountId ?: throw Exception("Unable to get AWS account ID")
}

internal suspend fun createS3Bucket(
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: Why can't we re-use the existing S3Utils.getBucket()?

@@ -116,3 +131,243 @@ object S3TestUtils {
return connection.responseCode
}
}

internal suspend fun getAccountId(): String {
Copy link
Collaborator

Choose a reason for hiding this comment

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

fix: Move to function on S3Utils so these are all grouped.

region = "us-west-2"
}

val accountId = sts.getCallerIdentity(
Copy link
Collaborator

Choose a reason for hiding this comment

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

style: this operation has a default parameter, no need to construct an empty request object

val accountId = sts.getCallerIdentity()

val createRequestToken = s3ControlClient.createMultiRegionAccessPoint(
CreateMultiRegionAccessPointRequest {
accountId = testAccountId
details =
Copy link
Collaborator

Choose a reason for hiding this comment

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

style: use the DSL builder

        s3control.createMultiRegionAccessPoint {
            accountId = "accont"
            details { 
                name = "access-point-name"
                regions = listOf(
                    Region { bucket = "one" },
                    Region { bucket = "two" }
                )
            }
        }

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not going to mark this everywhere but there are several other spots I'd have the same feedback

) {
val startTime = System.currentTimeMillis()

while (System.currentTimeMillis() - startTime < timeLimit) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

fix: You don't need this, just use withTimeout (or withTimeoutOrNull depending)

return
}

TimeUnit.SECONDS.sleep(10L) // Avoid constant status checks
Copy link
Collaborator

Choose a reason for hiding this comment

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

fix: It doesn't matter here since these are all runBlocking tests but this is not coroutine friendly, use delay instead

multiRegionAccessPointArn,
keyForObject,
)
} catch (exception: Throwable) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

style: This is unnecessary, any exception will fail the test with the given cause.

private fun cleanUpMrapTest(): Unit = runBlocking {
println("Cleaning up MutliRegionAccessPointTest tests")

val accountId = getAccountId()
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: You could get this once in setupMrapTest and share it across tests.

e.g.

private lateinit var accountId: String

@BeforeAll
private fun setupMrapTest() : Unit = runBlocking { 
    ...
    accountId = getAccountId()
}

private val keyForObject = "test.txt"

@BeforeAll
private fun setUpMrapTest(): Unit = runBlocking {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: setup and cleanup are one word.

Copy link
Contributor

Choose a reason for hiding this comment

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

Counter-nit: "setup" and "cleanup" are nouns. "Set up" and "Clean up" are verbs, which is the preferred form of method names.

Copy link
Member

Choose a reason for hiding this comment

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

nit: My thought on this is to just call it setup, the test class is enough of a hint of what you are setting up

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.

The tests look good. I would recommend keeping new functions out of S3TestUtils unless you think other S3 tests will use it. For MRAP-specific things, it makes more sense to define them in your specific test file

import aws.sdk.kotlin.gradle.kmp.*
import aws.sdk.kotlin.gradle.kmp.kotlin
Copy link
Member

Choose a reason for hiding this comment

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

question: why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IntelliJ does that, it doesn't like the * imports. I'll leave it how it was


@BeforeAll
private fun setUpMrapTest(): Unit = runBlocking {
println("Setting up MutliRegionAccessPointTest tests")
Copy link
Member

Choose a reason for hiding this comment

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

nit: unnecessary print


@AfterAll
private fun cleanUpMrapTest(): Unit = runBlocking {
println("Cleaning up MutliRegionAccessPointTest tests")
Copy link
Member

Choose a reason for hiding this comment

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

nit: unnecessary print

Comment on lines 135 to 149
internal suspend fun getAccountId(): String {
println("Getting account ID")

val sts = StsClient {
region = "us-west-2"
}

val accountId = sts.getCallerIdentity(
GetCallerIdentityRequest { },
).account

sts.close()

return accountId ?: throw Exception("Unable to get AWS account ID")
}
Copy link
Member

Choose a reason for hiding this comment

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

correctness: because this uses StsClient it's not an "S3 test util", I'd recommend moving it

Comment on lines 151 to 167
internal suspend fun createS3Bucket(
s3Client: S3Client,
bucketName: String,
location: BucketLocationConstraint,
) {
println("Creating S3 bucket: $bucketName")

s3Client.createBucket(
CreateBucketRequest {
bucket = bucketName
createBucketConfiguration =
CreateBucketConfiguration {
locationConstraint = location
}
},
)
}
Copy link
Member

Choose a reason for hiding this comment

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

question: This seems really similar to getBucketWithPrefix. Can that function be adapted to fit your requirements instead of making a new function that does roughly the same thing?

Comment on lines 309 to 320
internal suspend fun deleteS3Bucket(
s3Client: S3Client,
bucketName: String,
) {
println("Deleting S3 bucket: $bucketName")

s3Client.deleteBucket(
DeleteBucketRequest {
bucket = bucketName
},
)
}
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here, this function is simple enough, I recommend removing it

Comment on lines 330 to 332
ListObjectsV2Request {
bucket = bucketName
},
Copy link
Member

Choose a reason for hiding this comment

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

simplification: pass prefix = keyName to this call and S3 will do the searching for you

Comment on lines 367 to 373
internal fun closeClients(
vararg clients: SdkClient,
) {
clients.forEach { client ->
client.close()
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Another function that's simple enough to remove

keyForObject,
)
} catch (exception: Throwable) {
println("Test failed with exception: ${exception.cause}")
Copy link
Member

Choose a reason for hiding this comment

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

nit: unnecessary print

)
}.message.shouldContain("SIGV4_ASYMMETRIC support is not yet implemented for the default signer.")
} catch (exception: Throwable) {
println("Test failed with exception: ${exception.cause}")
Copy link
Member

Choose a reason for hiding this comment

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

nit: unnecessary print

Copy link

A new generated diff is ready to view.

Copy link

A new generated diff is ready to view.

}
}

internal suspend fun multiRegionAccessPointWasCreated(
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 could be done using a get operation and then looking at the error we get if the MRAP doesn't exist. The only issue is it returns a rather generic S3ControlException and all we would have to differentiate a non existent MRAP from a failure would be the error message. Which doesn't seem right as it could change. Either way I can take that approach if that's what everyone thinks

@@ -115,4 +145,126 @@ object S3TestUtils {

return connection.responseCode
}

internal suspend fun getAccountId(): String {
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 couldn't think of a better place for this function. It seemed out of place in the tests class instead of here with the other utils. Placing it in a STS test utils folder could be another option but it seems a little odd since it's used for S3

Copy link
Member

Choose a reason for hiding this comment

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

Yeah this seems fine, especially now that S3 E2E tests are guaranteed to have a dependency on STS

Copy link

A new generated diff is ready to view.

private suspend fun waitUntilMultiRegionAccessPointOperationCompletes(
s3ControlClient: S3ControlClient,
request: String,
duration: Int,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: withTimeout also accepts a Duration, instead of passing this as an integer you could just be writing 5.minutes in the calling code and accept a Duration. Also probably rename to something like timeoutAfter

return@withTimeout
}

if (status == "FAILED") throw Exception("$operation operation failed.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

style:

check(status != "FAILED") { "$operation operation failed")

@@ -88,6 +88,26 @@ subprojects {
description = "Run e2e service tests"
group = "verification"

if (project.name == "s3") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suppose this works but this is getting ugly. I'm thinking we'll be revisiting the structure around e2e tests soon.

Copy link
Contributor Author

@0marperez 0marperez Feb 22, 2024

Choose a reason for hiding this comment

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

I agree,I think we could refactor some of this into functions if it starts getting used more.

private val keyForObject = "test.txt"

@BeforeAll
private fun setUpMrapTest(): Unit = runBlocking {
Copy link
Member

Choose a reason for hiding this comment

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

nit: My thought on this is to just call it setup, the test class is enough of a hint of what you are setting up

Comment on lines 53 to 62
var testBucket = if (region != null) {
buckets?.firstOrNull {
client.getBucketLocation {
bucket = it
expectedBucketOwner = accountId
}.locationConstraint?.value == region && it.startsWith(prefix)
}
} else {
buckets?.firstOrNull { it.startsWith(prefix) }
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: possible simplification

var testBucket = buckets?.firstOrNull { bucketName -> 
    bucketName.startsWith(prefix) && 
    region?.let { 
        client.getBucketLocation { 
            bucket = bucketName
            expectedBucketOwner = accountId
        }.locationConstraint?.value == region 
    } ?: true 
}

@@ -115,4 +145,126 @@ object S3TestUtils {

return connection.responseCode
}

internal suspend fun getAccountId(): String {
println("Getting account ID")
Copy link
Member

Choose a reason for hiding this comment

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

nit: seems like an unnecessary print to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a couple of other print statements in the utils, why does this one seem unnecessary?

Copy link
Member

Choose a reason for hiding this comment

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

Because all the other prints have debuggable information like the bucket name, operation, etc. This one has nothing, so I think it's not super useful. It's totally not a blocking comment, just something I noticed

Comment on lines 264 to 268
val search = s3Control.listMultiRegionAccessPoints {
accountId = testAccountId
}.accessPoints?.find { it.name == multiRegionAccessPointName }

return search != null
Copy link
Member

Choose a reason for hiding this comment

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

nit: simplification

return s3Control.listMultiRegionAccessPoints {
    accountId = testAccountId
}.accessPoints?.any { it.name == multiRegionAccessPointName }

@@ -115,4 +145,126 @@ object S3TestUtils {

return connection.responseCode
}

internal suspend fun getAccountId(): String {
Copy link
Member

Choose a reason for hiding this comment

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

Yeah this seems fine, especially now that S3 E2E tests are guaranteed to have a dependency on STS

Copy link

Quality Gate Passed Quality Gate passed

Issues
1 New issue

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarCloud

Copy link

A new generated diff is ready to view.

@0marperez 0marperez merged commit d16f9f7 into main Feb 22, 2024
16 of 17 checks passed
@0marperez 0marperez deleted the e2e-tests branch February 22, 2024 21:17
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.

4 participants