Skip to content

Commit 562e7d3

Browse files
authored
add life cycle to s3 express to test helper (#494)
1 parent f92f250 commit 562e7d3

2 files changed

Lines changed: 56 additions & 35 deletions

File tree

tests/mock_s3_server/mock_s3_server.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ def resolve_response(self, wrapper, request_type, chunked=False, head_request=Fa
9090

9191
# If request_headers is present, validate that the request contains all required headers
9292
if 'request_headers' in data:
93-
for header in data['request_headers']:
94-
header_bytes = header.encode('utf-8')
95-
if not any(header_bytes == h[0] for h in self.request_headers):
96-
response = Response(status_code=500, delay=0, headers=headers,
97-
data=json.dumps({'error': f"Missing required header: {header}"}), chunked=chunked, head_request=head_request)
98-
return response
93+
for header in data['request_headers']:
94+
header_bytes = header.encode('utf-8')
95+
if not any(header_bytes == h[0] for h in self.request_headers):
96+
response = Response(status_code=500, delay=0, headers=headers,
97+
data=json.dumps({'error': f"Missing required header: {header}"}), chunked=chunked, head_request=head_request)
98+
return response
9999

100100
# if response has delay, then sleep before sending it
101101
delay = data.get('delay', 0)
@@ -411,7 +411,6 @@ def handle_get_object_modified(start_range, end_range, request):
411411
else:
412412
# Check the request header to make sure "If-Match" is set
413413
etag = get_request_header_value(request, "if-match")
414-
print(etag)
415414
# fetch Etag from the first_part response file
416415
response_file = os.path.join(
417416
base_dir, S3Opts.GetObject.name, f"get_object_modified_first_part.json")
@@ -436,7 +435,7 @@ def handle_get_object(wrapper, request, parsed_path, head_request=False):
436435

437436
if (parsed_path.path == "/get_object_invalid_response_missing_content_range" or
438437
parsed_path.path == "/get_object_invalid_response_missing_etags" or
439-
parsed_path.path == "/get_object_long_error"):
438+
parsed_path.path == "/get_object_long_error"):
440439
# Don't generate the body for those requests
441440
return response_config
442441

tests/test_helper/test_helper.py

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
44
# SPDX-License-Identifier: Apache-2.0.
55
import argparse
6+
import json
67
import boto3
78
import botocore
89
import sys
@@ -114,7 +115,7 @@ def create_bucket(client, **kwargs):
114115
raise e
115116

116117

117-
def create_bucket_with_lifecycle(availability_zone=None, client=s3_client):
118+
def create_bucket_with_lifecycle(availability_zone=None, client=s3_client, region=REGION):
118119
if availability_zone is not None:
119120
bucket_config = {
120121
'Location': {
@@ -128,37 +129,58 @@ def create_bucket_with_lifecycle(availability_zone=None, client=s3_client):
128129
}
129130
bucket_name = BUCKET_NAME_BASE+f"--{availability_zone}--x-s3"
130131
else:
131-
bucket_config = {'LocationConstraint': REGION}
132+
bucket_config = {'LocationConstraint': region}
132133
bucket_name = BUCKET_NAME_BASE
133134

134135
create_bucket(client,
135136
Bucket=bucket_name,
136137
CreateBucketConfiguration=bucket_config)
137-
if availability_zone is None:
138-
print(f"s3://{bucket_name} - Configuring bucket...")
139-
client.put_bucket_lifecycle_configuration(
140-
Bucket=bucket_name,
141-
LifecycleConfiguration={
142-
'Rules': [
143-
{
144-
'ID': 'clean up non-pre-existing objects',
145-
'Expiration': {
146-
'Days': 1,
147-
},
148-
'Filter': {
149-
'Prefix': 'upload/',
150-
},
151-
'Status': 'Enabled',
152-
'NoncurrentVersionExpiration': {
153-
'NoncurrentDays': 1,
154-
},
155-
'AbortIncompleteMultipartUpload': {
156-
'DaysAfterInitiation': 1,
157-
},
138+
print(f"s3://{bucket_name} - Configuring bucket...")
139+
if availability_zone is not None:
140+
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-objects-lifecycle.html#directory-bucket-lifecycle-differences
141+
# S3 express requires a bucket policy to allow session-based access to perform lifecycle actions
142+
account_id = boto3.client(
143+
'sts').get_caller_identity().get('Account')
144+
bucket_policy = {
145+
"Version": "2008-10-17",
146+
"Statement": [
147+
{
148+
"Effect": "Allow",
149+
"Principal": {
150+
"Service": "lifecycle.s3.amazonaws.com"
158151
},
159-
],
160-
},
161-
)
152+
"Action": "s3express:CreateSession",
153+
"Condition": {
154+
"StringEquals": {
155+
"s3express:SessionMode": "ReadWrite"
156+
}
157+
},
158+
"Resource": [
159+
f"arn:aws:s3express:{region}:{account_id}:bucket/{bucket_name}"
160+
]
161+
}
162+
]
163+
}
164+
client.put_bucket_policy(
165+
Bucket=bucket_name, Policy=json.dumps(bucket_policy))
166+
167+
client.put_bucket_lifecycle_configuration(
168+
Bucket=bucket_name,
169+
LifecycleConfiguration={
170+
'Rules': [
171+
{
172+
'ID': 'Abort all incomplete multipart uploads after 1 day',
173+
'Status': 'Enabled',
174+
'Filter': {'Prefix': ''}, # blank string means all
175+
'AbortIncompleteMultipartUpload': {'DaysAfterInitiation': 1},
176+
},
177+
{
178+
'ID': 'Objects under upload directory expire after 1 day',
179+
'Status': 'Enabled',
180+
'Filter': {'Prefix': 'upload/'},
181+
'Expiration': {'Days': 1},
182+
},
183+
]})
162184

163185
put_pre_existing_objects(
164186
10*MB, 'pre-existing-10MB', bucket=bucket_name, client=client)
@@ -231,7 +253,7 @@ def cleanup(bucket_name, availability_zone=None, client=s3_client):
231253

232254

233255
if args.action == 'init':
234-
create_bucket_with_lifecycle("use1-az4", s3_client_east1)
256+
create_bucket_with_lifecycle("use1-az4", s3_client_east1, "us-east-1")
235257
create_bucket_with_lifecycle("usw2-az1")
236258
create_bucket_with_lifecycle()
237259
create_bucket_with_public_object()

0 commit comments

Comments
 (0)