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

chore: Improve AWSS3 SigV4A integration tests to use preexisting MRAP if applicable. #1370

Merged
merged 2 commits into from
Feb 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 89 additions & 80 deletions IntegrationTests/Services/AWSS3IntegrationTests/S3SigV4ATests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,28 @@ class S3SigV4ATests: S3XCTestCase {
private var mrapArnFormat = "arn:aws:s3::%@:accesspoint/%@"
private var mrapArn: String!
private var mrapAlias: String!
private let mrapNamePrefix = "aws-sdk-s3-integration-test-"
private let mrapName = "aws-sdk-s3-integration-test-" + UUID().uuidString.split(separator: "-").first!.lowercased()
private var mrapConfig: S3ControlClientTypes.CreateMultiRegionAccessPointInput!

// The S3 control client used to create and delete MRAP
private var s3ControlClient: S3ControlClient!

// The STS client for fetching AWS account ID
private var stsClient: STSClient!
private var accountId: String!

// Key string used for putting object in tests
private let key = "text.txt"
private let key = UUID().uuidString.split(separator: "-").first!.lowercased()

private let NSEC_PER_SEC = 1_000_000_000

// MARK: - SETUP & TEARDOWN

override func setUp() async throws {
// Create a bucket
try await super.setUp()
s3ControlClient = try S3ControlClient(region: region)

// Create sigv4a-only S3 client
// Create sigv4a-only S3 client to be used for tests.
sigv4aConfig = try await S3Client.S3ClientConfiguration(region: region)
sigv4aConfig.authSchemes = [SigV4AAuthScheme()]
sigv4aClient = S3Client(config: sigv4aConfig)
Expand All @@ -54,84 +57,32 @@ class S3SigV4ATests: S3XCTestCase {
stsClient = try STSClient(region: region)
accountId = try await stsClient.getCallerIdentity(input: GetCallerIdentityInput()).account

// Construct MRAP config
let publicAccessBlock = S3ControlClientTypes.PublicAccessBlockConfiguration(
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false
)
let regions = [S3ControlClientTypes.Region(bucket: bucketName, bucketAccountId: accountId)]
let mrapConfig = S3ControlClientTypes.CreateMultiRegionAccessPointInput(
name: mrapName,
publicAccessBlock: publicAccessBlock,
regions: regions
)

// Create S3 Multi-Region Access Point (MRAP)
s3ControlClient = try S3ControlClient(region: region)
let createMRAPInput = CreateMultiRegionAccessPointInput(
accountId: accountId,
clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(),
details: mrapConfig
)
_ = try await s3ControlClient.createMultiRegionAccessPoint(input: createMRAPInput).requestTokenARN

// Wait until MRAP creation finishes
var status: S3ControlClientTypes.MultiRegionAccessPointStatus? = .creating
repeat {
let seconds = 20.0
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))

status = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput(
accountId: accountId,
name: mrapName
)).accessPoint?.status
} while status == .creating

// Fetch MRAP alias then format & save MRAP ARN
// This MRAP ARN is used in place of bucket name in subsequent calls
mrapAlias = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput(
accountId: accountId,
name: mrapName
)).accessPoint?.alias
mrapArn = String(format: mrapArnFormat, accountId, mrapAlias)
// Save mrapArn without creating new MRAP if it already exists.
if let preexistingMRAP = try await checkIfMRAPExists(), let alias = preexistingMRAP.alias {
mrapArn = String(format: mrapArnFormat, accountId, alias)
} else {
// Otherwise, create a new MRAP.
// Create a bucket to be linked to MRAP at construction.
try await super.setUp()
// Construct MRAP and save its ARN for use in tests.
_ = try await constructMRAP()
}
}

override func tearDown() async throws {
// MRAP must be deleted before deleting the linked bucket
try await deleteMRAP()
// Empty & delete the bucket
try await super.tearDown()
Copy link
Contributor

Choose a reason for hiding this comment

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

This test class inherits S3XCTestCase, which does have a tear-down... we do call super.setUp() at least in some cases, which creates a test bucket... do we need to call super.tearDown() as well?

Copy link
Contributor Author

@sichanyoo sichanyoo Feb 28, 2024

Choose a reason for hiding this comment

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

The super.tearDown() deletes the bucket that was created in super.setUp(), but since we're keeping the MRAP, we can't delete the bucket. To delete the bucket, we have to delete MRAP first. Otherwise it would result in an error.

}

private func deleteMRAP() async throws {
// Delete the multi-region access point
_ = try await s3ControlClient.deleteMultiRegionAccessPoint(input: DeleteMultiRegionAccessPointInput(
accountId: accountId,
clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(),
details: S3ControlClientTypes.DeleteMultiRegionAccessPointInput(name: mrapName)
/*
* Note that MRAP is not deleted to be re-used in subsequent test runs.
* And because MRAP is not deleted, the bucket associated with it cannot
* be deleted either, hence the override with no-op.
*/
// Delete the previously added object from the bucket.
_ = try await sigv4aClient.deleteObject(input: DeleteObjectInput(
bucket: mrapArn,
key: key
))

// Wait until MRAP has been deleted before returning
var mrapExists = true
repeat {
let seconds = 20.0
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))

let mraps = try await s3ControlClient.listMultiRegionAccessPoints(input: ListMultiRegionAccessPointsInput(accountId: accountId)).accessPoints
mrapExists = checkMRAPExists(mraps ?? [])
} while mrapExists

// Wait some more before returning because
// deleting access point association with buckets takes more time
let seconds = 60.0
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
}

private func checkMRAPExists(_ mraps: [S3ControlClientTypes.MultiRegionAccessPointReport]) -> Bool {
return mraps.contains { $0.name == mrapName }
}
// MARK: - TEST CASES

func testS3MRAPSigV4A() async throws {
// Put an object to bucket via MRAP using SigV4A-only client
Expand All @@ -146,7 +97,7 @@ class S3SigV4ATests: S3XCTestCase {
let response = try await sigv4aClient.listObjectsV2(
input: ListObjectsV2Input(bucket: mrapArn)
)
XCTAssertEqual(response.keyCount, 1)
XCTAssertNotNil(response.contents?.first { $0.key == key })
}

func testS3MRAPSigV4APresignedRequest() async throws {
Expand Down Expand Up @@ -176,7 +127,7 @@ class S3SigV4ATests: S3XCTestCase {
let response = try await sigv4aClient.listObjectsV2(
input: ListObjectsV2Input(bucket: mrapArn)
)
XCTAssertEqual(response.keyCount, 1)
XCTAssertNotNil(response.contents?.first { $0.key == key })
}

func testS3MRAPSigV4APresignedURL() async throws {
Expand All @@ -197,6 +148,64 @@ class S3SigV4ATests: S3XCTestCase {
let response = try await sigv4aClient.listObjectsV2(
input: ListObjectsV2Input(bucket: mrapArn)
)
XCTAssertEqual(response.keyCount, 1)
XCTAssertNotNil(response.contents?.first { $0.key == key })
}

// MARK: - HELPER FUNCTIONS

// Check if MRAP exists by checking names of existing MRAPs against integ-test MRAP prefix.
private func checkIfMRAPExists() async throws -> S3ControlClientTypes.MultiRegionAccessPointReport? {
let mraps = try await s3ControlClient.listMultiRegionAccessPoints(input: ListMultiRegionAccessPointsInput(accountId: accountId)).accessPoints
return mraps?.first { $0.name?.hasPrefix(mrapNamePrefix) ?? false }
}

private func constructMRAPConfig() {
let publicAccessBlock = S3ControlClientTypes.PublicAccessBlockConfiguration(
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false
)
let regions = [S3ControlClientTypes.Region(bucket: bucketName, bucketAccountId: accountId)]
mrapConfig = S3ControlClientTypes.CreateMultiRegionAccessPointInput(
name: mrapName,
publicAccessBlock: publicAccessBlock,
regions: regions
)
}

private func constructMRAP() async throws {
// Construct MRAP config to use
constructMRAPConfig()
// Create S3 Multi-Region Access Point (MRAP)
let createMRAPInput = CreateMultiRegionAccessPointInput(
accountId: accountId,
clientToken: UUID().uuidString.split(separator: "-").first!.lowercased(),
details: mrapConfig
)
_ = try await s3ControlClient.createMultiRegionAccessPoint(input: createMRAPInput)

// Wait until MRAP creation finishes, with max polling count of 20.
let pollLimit = 20
var pollCount = 0
var status: S3ControlClientTypes.MultiRegionAccessPointStatus? = .creating
repeat {
let seconds = TimeInterval(20)
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))

status = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput(
accountId: accountId,
name: mrapName
)).accessPoint?.status
pollCount += 1
} while status == .creating && pollCount < pollLimit

// Fetch MRAP alias then format & save MRAP ARN
// This MRAP ARN is used in place of bucket name in subsequent calls
mrapAlias = try await s3ControlClient.getMultiRegionAccessPoint(input: GetMultiRegionAccessPointInput(
accountId: accountId,
name: mrapName
)).accessPoint?.alias
mrapArn = String(format: mrapArnFormat, accountId, mrapAlias)
}
}
Loading