Skip to content

Commit 855903f

Browse files
authored
Refactor controller end-to-end tests (#38)
Description of changes: This patch refactors the python end-to-end tests used to test the lambda-controller and fixes multiple tests and boosttrapping errors observed in recent PRs. Mainly this patch rewrites the bootstrapping logic and uses the latest bootstrappable resources (IAM Role, SQS Queue, DynamoDB Table, and Signer SigningProfile) introduced in `acktest` library. Also, this patch moves the testing asserting logic into a `LambdaValidator` class. Unblocks #37 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 7a525a0 commit 855903f

10 files changed

+279
-436
lines changed

test/e2e/bootstrap_resources.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,29 @@
1616
"""
1717

1818
from dataclasses import dataclass
19-
from acktest.resources import read_bootstrap_config
19+
20+
from acktest.bootstrapping import Resources
21+
from acktest.bootstrapping.s3 import Bucket
22+
from acktest.bootstrapping.dynamodb import Table
23+
from acktest.bootstrapping.signer import SigningProfile
24+
from acktest.bootstrapping.sqs import Queue
25+
from acktest.bootstrapping.iam import Role
26+
2027
from e2e import bootstrap_directory
2128

2229
@dataclass
23-
class TestBootstrapResources:
24-
FunctionsBucketName: str
25-
LambdaFunctionFilePath: str
26-
LambdaFunctionFileZip: str
27-
LambdaBasicRoleName: str
28-
LambdaBasicRoleARN: str
29-
LambdaESMRoleName: str
30-
LambdaESMRoleARN: str
31-
SigningProfileVersionArn: str
32-
SQSQueueARN: str
33-
SQSQueueURL: str
34-
DynamoDBTableName: str
35-
DynamoDBTableARN: str
36-
BasicExecutionRoleARNs: list
37-
ESMExecutionRoleARNs: list
30+
class BootstrapResources(Resources):
31+
FunctionsBucket: Bucket
32+
SigningProfile: SigningProfile
33+
BasicRole: Role
34+
ESMRole: Role
35+
ESMTable: Table
36+
ESMQueue: Queue
3837

3938
_bootstrap_resources = None
4039

41-
def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.yaml"):
40+
def get_bootstrap_resources(bootstrap_file_name: str = "bootstrap.pkl") -> BootstrapResources:
4241
global _bootstrap_resources
4342
if _bootstrap_resources is None:
44-
_bootstrap_resources = TestBootstrapResources(
45-
**read_bootstrap_config(bootstrap_directory, bootstrap_file_name=bootstrap_file_name),
46-
)
43+
_bootstrap_resources = BootstrapResources.deserialize(bootstrap_directory, bootstrap_file_name=bootstrap_file_name)
4744
return _bootstrap_resources

test/e2e/conftest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
# express or implied. See the License for the specific language governing
1212
# permissions and limitations under the License.
1313

14-
import os
1514
import pytest
15+
import boto3
1616

17+
from acktest.aws.identity import get_region
1718
from acktest import k8s
1819

1920

@@ -44,3 +45,7 @@ def pytest_collection_modifyitems(config, items):
4445
@pytest.fixture(scope='class')
4546
def k8s_client():
4647
return k8s._get_k8s_api_client()
48+
49+
@pytest.fixture(scope='module')
50+
def lambda_client():
51+
return boto3.client('lambda', region_name=get_region())

test/e2e/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@3d5e98f5960ac2ea8360c212141c4ec89cfcb668
1+
acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@58ffb067d7c400ffbd0d470e975056efdc9e4965

test/e2e/service_bootstrap.py

Lines changed: 73 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -16,126 +16,35 @@
1616
import os
1717
import boto3
1818
import logging
19-
from time import sleep
2019
from zipfile import ZipFile
21-
import random
22-
import string
2320

24-
from acktest import resources
25-
from acktest.aws.identity import get_region, get_account_id
2621
from e2e import bootstrap_directory
27-
from e2e.bootstrap_resources import TestBootstrapResources
22+
from e2e.bootstrap_resources import BootstrapResources
2823
from botocore.exceptions import ClientError
2924

30-
RAND_TEST_SUFFIX = (''.join(random.choice(string.ascii_lowercase) for _ in range(6)))
31-
32-
LAMBDA_BASIC_IAM_ROLE_NAME = 'ack-lambda-function-role-basic-' + RAND_TEST_SUFFIX
33-
LAMBDA_ESM_IAM_ROLE_NAME = 'ack-lambda-function-role-esm-' + RAND_TEST_SUFFIX
25+
from acktest.bootstrapping import Resources, BootstrapFailureException
26+
from acktest.bootstrapping.s3 import Bucket
27+
from acktest.bootstrapping.dynamodb import Table
28+
from acktest.bootstrapping.signer import SigningProfile
29+
from acktest.bootstrapping.sqs import Queue
30+
from acktest.bootstrapping.iam import Role
3431

3532
LAMBDA_IAM_ROLE_POLICY = '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": '\
3633
'"lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]} '
3734
LAMBDA_BASIC_EXECUTION_ARN = 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
3835
LAMBDA_DYNAMODB_EXECUTION_ROLE = 'arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole'
3936
LAMBDA_SQS_QUEUE_EXECUTION_ROLE = 'arn:aws:iam::aws:policy/AmazonSQSFullAccess'
4037

41-
BASIC_ROLES = [ LAMBDA_BASIC_EXECUTION_ARN ]
42-
ESM_ROLES = [ LAMBDA_BASIC_EXECUTION_ARN, LAMBDA_DYNAMODB_EXECUTION_ROLE, LAMBDA_SQS_QUEUE_EXECUTION_ROLE ]
43-
44-
FUNCTIONS_BUCKET_NAME = "ack-lambda-function-s3-bucket-" + RAND_TEST_SUFFIX
38+
BASIC_ROLE_POLICIES = [ LAMBDA_BASIC_EXECUTION_ARN ]
39+
ESM_ROLE_POLICIES = [ LAMBDA_BASIC_EXECUTION_ARN, LAMBDA_DYNAMODB_EXECUTION_ROLE, LAMBDA_SQS_QUEUE_EXECUTION_ROLE ]
4540

4641
LAMBDA_FUNCTION_FILE = "main.py"
4742
LAMBDA_FUNCTION_FILE_ZIP = "main.zip"
4843
LAMBDA_FUNCTION_FILE_PATH = f"./resources/lambda_function/{LAMBDA_FUNCTION_FILE}"
4944
LAMBDA_FUNCTION_FILE_PATH_ZIP = f"./resources/lambda_function/{LAMBDA_FUNCTION_FILE_ZIP}"
5045

51-
AWS_SIGNING_PROFILE_NAME = "ack_testing_lambda_signing_profile"
5246
AWS_SIGNING_PLATFORM_ID = "AWSLambda-SHA384-ECDSA"
5347

54-
SQS_QUEUE_NAME = "ack-lambda-sqs-queue-" + RAND_TEST_SUFFIX
55-
56-
DYNAMODB_TABLE_NAME = "ack-lambda-ddb-table-" + RAND_TEST_SUFFIX
57-
58-
def service_bootstrap() -> dict:
59-
logging.getLogger().setLevel(logging.INFO)
60-
lambda_esm_role_arn = create_lambda_function_esm_role(LAMBDA_ESM_IAM_ROLE_NAME)
61-
lambda_basic_role_arn = create_lambda_function_basic_role(LAMBDA_BASIC_IAM_ROLE_NAME)
62-
create_bucket(FUNCTIONS_BUCKET_NAME)
63-
zip_function_file(LAMBDA_FUNCTION_FILE_PATH, LAMBDA_FUNCTION_FILE_PATH_ZIP)
64-
upload_function_to_bucket(LAMBDA_FUNCTION_FILE_PATH_ZIP, FUNCTIONS_BUCKET_NAME)
65-
signing_profile_version_arn = ensure_signing_profile(AWS_SIGNING_PROFILE_NAME, AWS_SIGNING_PLATFORM_ID)
66-
sqs_queue_arn, sqs_queue_url = create_sqs_queue(SQS_QUEUE_NAME)
67-
dynamodb_table_stream_arn = create_dynamodb_table(DYNAMODB_TABLE_NAME)
68-
69-
return TestBootstrapResources(
70-
FUNCTIONS_BUCKET_NAME,
71-
LAMBDA_FUNCTION_FILE_PATH,
72-
LAMBDA_FUNCTION_FILE_ZIP,
73-
LAMBDA_BASIC_IAM_ROLE_NAME,
74-
lambda_basic_role_arn,
75-
LAMBDA_ESM_IAM_ROLE_NAME,
76-
lambda_esm_role_arn,
77-
signing_profile_version_arn,
78-
sqs_queue_arn,
79-
sqs_queue_url,
80-
DYNAMODB_TABLE_NAME,
81-
dynamodb_table_stream_arn,
82-
BASIC_ROLES,
83-
ESM_ROLES,
84-
).__dict__
85-
86-
87-
88-
def create_role_with_policies(iam_role_name: str, policies: list) -> str:
89-
region = get_region()
90-
iam_client = boto3.client("iam", region_name=region)
91-
92-
logging.debug(f"Creating iam role {iam_role_name}")
93-
try:
94-
iam_client.get_role(RoleName=iam_role_name)
95-
raise RuntimeError(f"Expected {iam_role_name} role to not exist."
96-
f" Did previous test cleanup successfully?")
97-
except iam_client.exceptions.NoSuchEntityException:
98-
pass
99-
100-
resp = iam_client.create_role(
101-
RoleName=iam_role_name,
102-
AssumeRolePolicyDocument=LAMBDA_IAM_ROLE_POLICY
103-
)
104-
105-
for policyARN in policies:
106-
iam_client.attach_role_policy(RoleName=iam_role_name, PolicyArn=policyARN)
107-
108-
logging.info(f"Created role {iam_role_name}")
109-
return resp['Role']['Arn']
110-
111-
112-
def create_lambda_function_basic_role(iam_role_name: str) -> str:
113-
return create_role_with_policies(
114-
iam_role_name,
115-
BASIC_ROLES,
116-
)
117-
118-
def create_lambda_function_esm_role(iam_role_name: str) -> str:
119-
return create_role_with_policies(
120-
iam_role_name,
121-
ESM_ROLES,
122-
)
123-
124-
def create_bucket(bucket_name: str):
125-
region = get_region()
126-
s3_client = boto3.resource('s3')
127-
logging.debug(f"Creating s3 data bucket {bucket_name}")
128-
try:
129-
s3_client.create_bucket(
130-
Bucket=bucket_name,
131-
CreateBucketConfiguration={"LocationConstraint": region}
132-
)
133-
except s3_client.exceptions.BucketAlreadyExists:
134-
raise RuntimeError(f"Expected {bucket_name} bucket to not exist."
135-
f" Did previous test cleanup successfully?")
136-
137-
logging.info(f"Created bucket {bucket_name}")
138-
13948
def zip_function_file(src: str, dst: str):
14049
with ZipFile(dst, 'w') as zipf:
14150
zipf.write(src, arcname=src)
@@ -155,73 +64,74 @@ def upload_function_to_bucket(file_path: str, bucket_name: str):
15564

15665
logging.info(f"Uploaded {file_path} to bucket {bucket_name}")
15766

158-
def ensure_signing_profile(signing_profile_name: str, platform_id: str) -> str:
159-
region = get_region()
160-
signer_client = boto3.client("signer", region_name=region)
161-
162-
# Signing profiles cannot be deleted. We just reuse the same signing profile
163-
# for ACK lambda controller e2e tests.
164-
try:
165-
resp = signer_client.get_signing_profile(
166-
profileName=signing_profile_name,
167-
)
168-
return resp['profileVersionArn']
169-
except:
170-
resp = signer_client.put_signing_profile(
171-
profileName=signing_profile_name,
172-
platformId=platform_id,
173-
)
174-
logging.info(f"Created signing profile {signing_profile_name}")
175-
return resp['profileVersionArn']
176-
177-
def create_sqs_queue(queue_name: str) -> str:
178-
region = get_region()
179-
sqs_client = boto3.resource('sqs', region_name=region)
180-
logging.debug(f"Creating SQS queue {queue_name}")
181-
resp = sqs_client.create_queue(
182-
QueueName=queue_name,
183-
)
184-
logging.info(f"Created SQS queue {queue_name}")
185-
return resp.attributes['QueueArn'], resp.url
186-
187-
def create_dynamodb_table(table_name: str) -> str:
188-
region = get_region()
189-
dynamodb_client = boto3.resource('dynamodb', region_name=region)
190-
resp = dynamodb_client.create_table(
191-
TableName=table_name,
192-
KeySchema=[
193-
{
194-
'AttributeName': 'id',
195-
'KeyType': 'HASH'
196-
},
197-
{
198-
'AttributeName': 'createdAt',
199-
'KeyType': 'RANGE'
200-
}
201-
],
202-
AttributeDefinitions=[
203-
{
204-
'AttributeName': 'id',
205-
'AttributeType': 'N'
67+
def service_bootstrap() -> Resources:
68+
logging.getLogger().setLevel(logging.INFO)
69+
resources = BootstrapResources(
70+
FunctionsBucket=Bucket(
71+
"ack-lambda-controller-tests",
72+
),
73+
SigningProfile=SigningProfile(
74+
"ack_testing_signer",
75+
signing_platform_id=AWS_SIGNING_PLATFORM_ID,
76+
),
77+
BasicRole=Role(
78+
"ack-lambda-controller-basic-role",
79+
principal_service="lambda.amazonaws.com",
80+
managed_policies=BASIC_ROLE_POLICIES,
81+
),
82+
ESMRole=Role(
83+
"ack-lambda-controller-esm-role",
84+
principal_service="lambda.amazonaws.com",
85+
managed_policies=ESM_ROLE_POLICIES,
86+
),
87+
ESMTable=Table(
88+
"ack-lambda-controller-table",
89+
attribute_definitions=[
90+
{
91+
'AttributeName': 'id',
92+
'AttributeType': 'N'
93+
},
94+
{
95+
'AttributeName': 'createdAt',
96+
'AttributeType': 'S'
97+
},
98+
],
99+
key_schema=[
100+
{
101+
'AttributeName': 'id',
102+
'KeyType': 'HASH'
103+
},
104+
{
105+
'AttributeName': 'createdAt',
106+
'KeyType': 'RANGE'
107+
}
108+
],
109+
stream_specification={
110+
'StreamEnabled': True,
111+
'StreamViewType': 'NEW_IMAGE'
206112
},
207-
{
208-
'AttributeName': 'createdAt',
209-
'AttributeType': 'S'
113+
provisioned_throughput={
114+
'ReadCapacityUnits': 5,
115+
'WriteCapacityUnits': 5
210116
},
211-
],
212-
ProvisionedThroughput={
213-
'ReadCapacityUnits': 5,
214-
'WriteCapacityUnits': 5
215-
},
216-
StreamSpecification={
217-
'StreamEnabled': True,
218-
'StreamViewType': 'NEW_IMAGE'
219-
}
117+
),
118+
ESMQueue=Queue(
119+
"ack-lambda-controller-queue"
120+
),
220121
)
221-
logging.info(f"Created Dynamodb table {table_name}")
222-
return resp.latest_stream_arn
122+
123+
try:
124+
resources.bootstrap()
125+
zip_function_file(LAMBDA_FUNCTION_FILE_PATH, LAMBDA_FUNCTION_FILE_PATH_ZIP)
126+
upload_function_to_bucket(
127+
LAMBDA_FUNCTION_FILE_PATH_ZIP,
128+
resources.FunctionsBucket.name,
129+
)
130+
except BootstrapFailureException as ex:
131+
exit(254)
132+
return resources
223133

224134
if __name__ == "__main__":
225135
config = service_bootstrap()
226136
# Write config to current directory by default
227-
resources.write_bootstrap_config(config, bootstrap_directory)
137+
config.serialize(bootstrap_directory)

0 commit comments

Comments
 (0)