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

Refactor all e2e tests to use fixture #49

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
307 changes: 302 additions & 5 deletions test/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,55 @@

import pytest
import boto3
import logging
import time
from dataclasses import dataclass

from acktest.aws.identity import get_region
from acktest import k8s
from acktest.resources import random_suffix_name
from acktest.aws.identity import get_region, get_account_id
from acktest import k8s as k8sclient
from acktest.k8s import resource as k8s

from e2e import CRD_GROUP, CRD_VERSION, load_lambda_resource
from e2e.service_bootstrap import LAMBDA_FUNCTION_FILE_ZIP
from e2e.replacement_values import REPLACEMENT_VALUES
from e2e.bootstrap_resources import get_bootstrap_resources

FunctionPackageTypeImage = "Image"
FunctionPackageTypeZip = "Zip"
FunctionTestingRoleBasic = "basic"
FunctionTestingRoleESM = "esm"

@dataclass
class ResourceWaitTimes:
Create: int
Update: int
Delete: int
Comment on lines +35 to +39
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be defined in the test-infra package. Also, we should probably provide sane defaults (say 5 seconds for each?)


@dataclass
class ServiceWaitTimes:
Function: ResourceWaitTimes
Alias: ResourceWaitTimes
CodeSigningConfig: ResourceWaitTimes
EventSourceMapping: ResourceWaitTimes
FunctionURLConfig: ResourceWaitTimes

Wait = ServiceWaitTimes(
Function = ResourceWaitTimes(30, 30, 30),
Alias = ResourceWaitTimes(10, 10, 10),
CodeSigningConfig = ResourceWaitTimes(10, 10, 10),
EventSourceMapping = ResourceWaitTimes(20, 20, 20),
FunctionURLConfig = ResourceWaitTimes(30, 10, 10),
)
Comment on lines +42 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I'd prefer to see each of these defined in their respective test files. They can be cross-imported if we need to access them in other tests.


def get_testing_image_url():
aws_region = get_region()
account_id = get_account_id()
return f"{account_id}.dkr.ecr.{aws_region}.amazonaws.com/ack-e2e-testing-lambda-controller:v1"

def pytest_addoption(parser):
parser.addoption("--runslow", action="store_true", default=False, help="run slow tests")


def pytest_configure(config):
config.addinivalue_line(
"markers", "canary: mark test to also run in canary tests"
Expand All @@ -32,6 +72,12 @@ def pytest_configure(config):
config.addinivalue_line(
"markers", "slow: mark test as slow to run"
)
config.addinivalue_line(
"markers", "function_overrides: function parameters to override when creating fixture"
Copy link
Contributor

Choose a reason for hiding this comment

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

PyTest markers are used to label a test - not to provide a parameter to tests. For instance, we have a marker for "slow" tests, in case you want to skip them. I would prefer if we just hardcode the test values, or use another mechanism for parameterising these

)
config.addinivalue_line(
"markers", "esm_overrides: event source mapping parameters to override when creating fixture"
)

def pytest_collection_modifyitems(config, items):
if config.getoption("--runslow"):
Expand All @@ -44,8 +90,259 @@ def pytest_collection_modifyitems(config, items):
# Provide a k8s client to interact with the integration test cluster
@pytest.fixture(scope='class')
def k8s_client():
return k8s._get_k8s_api_client()
return k8sclient._get_k8s_api_client()
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for the sake of consistency with the other tests, would you mind changing this back to k8s. In conjunction with my comments below, this shouldn't conflict.


@pytest.fixture(scope='module')
def lambda_client():
return boto3.client('lambda', region_name=get_region())
return boto3.client('lambda', region_name=get_region())

@pytest.fixture
Copy link
Contributor

Choose a reason for hiding this comment

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

Pytest fixtures should be defined in whichever scope they're used. I defined lambda_client and k8s_client in this file because they can be used by every/any test. These fixtures below are specific to individual tests (function, code signing, etc.). I'd prefer if we moved these to the test_*.py files. If we need to re-use the fixture as part of another test, it would be better to just import them across files.

def function(request, code_signing_config):
resource_name = random_suffix_name("function", 24)

resources = get_bootstrap_resources()
replacements = REPLACEMENT_VALUES.copy()
replacements["FUNCTION_NAME"] = resource_name
replacements["IMAGE_URL"] = get_testing_image_url()
replacements["CODE_SIGNING_CONFIG_ARN"] = ""
replacements["RESERVED_CONCURRENT_EXECUTIONS"] = ""

resource_file = ""

marker = request.node.get_closest_marker("function_overrides")
if marker is not None:
data = marker.args[0]
if 'package_type' in data:
if data['package_type'] == FunctionPackageTypeZip:
resource_file = "function"
resource_name = random_suffix_name("function-zip", 32)
replacements["FUNCTION_NAME"] = resource_name
replacements["BUCKET_NAME"] = resources.FunctionsBucket.name
replacements["LAMBDA_FILE_NAME"] = LAMBDA_FUNCTION_FILE_ZIP
replacements["RESERVED_CONCURRENT_EXECUTIONS"] = "0"
replacements["CODE_SIGNING_CONFIG_ARN"] = ""

elif data['package_type'] == FunctionPackageTypeImage:
resource_file = "function_package_type_image"
resource_name = random_suffix_name("function-image", 32)
replacements["FUNCTION_NAME"] = resource_name
replacements["IMAGE_URL"] = get_testing_image_url()
replacements["LAMBDA_ROLE"] = resources.BasicRole.arn

if 'role_type' in data:
if data['role_type'] == FunctionTestingRoleBasic:
replacements["LAMBDA_ROLE"] = resources.BasicRole.arn
elif data['role_type'] == FunctionTestingRoleESM:
replacements["LAMBDA_ROLE"] = resources.ESMRole.arn

if 'reserved_concurrent_executions' in data:
replacements["RESERVED_CONCURRENT_EXECUTIONS"] = str(data["reserved_concurrent_executions"])

if 'code_signing_config_arn' in data:
replacements["CODE_SIGNING_CONFIG_ARN"] = data['code_signing_config_arn']
elif 'create_code_signing_config' in data and data["create_code_signing_config"]:
(_, cr) = code_signing_config
replacements["CODE_SIGNING_CONFIG_ARN"] = cr["status"]["ackResourceMetadata"]["arn"]

# Load Function CR
resource_data = load_lambda_resource(
resource_file,
additional_replacements=replacements,
)
logging.debug(resource_data)

# Create k8s resource
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, "functions",
resource_name, namespace="default",
)
k8s.create_custom_resource(ref, resource_data)
time.sleep(Wait.Function.Create)

cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
assert k8s.get_resource_exists(ref)

yield (ref, cr)

# Try to delete, if doesn't already exist
try:
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
assert deleted
except:
pass

@pytest.fixture
def code_signing_config():
resource_name = random_suffix_name("csc", 24)
resources = get_bootstrap_resources()

replacements = REPLACEMENT_VALUES.copy()
replacements["CODE_SIGNING_CONFIG_NAME"] = resource_name
replacements["SIGNING_PROFILE_VERSION_ARN"] = resources.SigningProfile.signing_profile_arn

# Load CodeSigningConfig CR
resource_data = load_lambda_resource(
"code_signing_config",
additional_replacements=replacements,
)
logging.debug(resource_data)

# Create k8s resource
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, "codesigningconfigs",
resource_name, namespace="default",
)
k8s.create_custom_resource(ref, resource_data)
time.sleep(Wait.CodeSigningConfig.Create)

cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
assert k8s.get_resource_exists(ref)

yield (ref, cr)

# Try to delete, if doesn't already exist
try:
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
assert deleted
except:
pass

@pytest.fixture
def alias(function):
(_, function_resource) = function
lambda_function_name = function_resource["spec"]["name"]

resource_name = random_suffix_name("alias", 24)

replacements = REPLACEMENT_VALUES.copy()
replacements["ALIAS_NAME"] = resource_name
replacements["FUNCTION_NAME"] = lambda_function_name
replacements["FUNCTION_VERSION"] = "$LATEST"

# Load alias CR
resource_data = load_lambda_resource(
"alias",
additional_replacements=replacements,
)
logging.debug(resource_data)

# Create k8s resource
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, "aliases",
resource_name, namespace="default",
)
k8s.create_custom_resource(ref, resource_data)
time.sleep(Wait.Alias.Create)

cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
assert k8s.get_resource_exists(ref)

yield (ref, cr)

# Try to delete, if doesn't already exist
try:
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
assert deleted
except:
pass

@pytest.fixture
def event_source_mapping(request, function):
(_, function_resource) = function
lambda_function_name = function_resource["spec"]["name"]

resource_name = random_suffix_name("eventsourcemapping", 24)
resources = get_bootstrap_resources()

replacements = REPLACEMENT_VALUES.copy()
replacements["EVENT_SOURCE_MAPPING_NAME"] = resource_name
replacements["FUNCTION_NAME"] = lambda_function_name
replacements["BATCH_SIZE"] = "10"

resource_file = ""

marker = request.node.get_closest_marker("esm_overrides")
if marker is not None:
data = marker.args[0]
if 'source' in data:
if data['source'] == "sqsqueue":
resource_file = "event_source_mapping_sqs"
replacements["FUNCTION_NAME"] = lambda_function_name
replacements["EVENT_SOURCE_ARN"] = resources.ESMQueue.arn
replacements["MAXIMUM_BATCHING_WINDOW_IN_SECONDS"] = "1"

elif data['source'] == "ddbtable":
resource_file = "event_source_mapping_dynamodb"
replacements["EVENT_SOURCE_ARN"] = resources.ESMTable.latest_stream_arn
replacements["STARTING_POSITION"] = "LATEST"
replacements["MAXIMUM_RETRY_ATTEMPTS"] = "-1"

# Load EventSourceMapping CR
resource_data = load_lambda_resource(
resource_file,
additional_replacements=replacements,
)

# Create k8s resource
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, "eventsourcemappings",
resource_name, namespace="default",
)
k8s.create_custom_resource(ref, resource_data)
time.sleep(Wait.EventSourceMapping.Create)

cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
assert k8s.get_resource_exists(ref)

yield (ref, cr)

# Try to delete, if doesn't already exist
try:
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
assert deleted
except:
pass

@pytest.fixture
def function_url_config(function):
(_, function_resource) = function
lambda_function_name = function_resource["spec"]["name"]

resource_name = random_suffix_name("functionurlconfig", 24)

replacements = REPLACEMENT_VALUES.copy()
replacements["FUNCTION_URL_CONFIG_NAME"] = resource_name
replacements["FUNCTION_NAME"] = lambda_function_name
replacements["AUTH_TYPE"] = "NONE"

# Load FunctionURLConfig CR
resource_data = load_lambda_resource(
"function_url_config",
additional_replacements=replacements,
)
logging.debug(resource_data)

# Create k8s resource
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, "functionurlconfigs",
resource_name, namespace="default",
)
k8s.create_custom_resource(ref, resource_data)
time.sleep(Wait.FunctionURLConfig.Create)

cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
assert k8s.get_resource_exists(ref)

yield (ref, cr)

# Try to delete, if doesn't already exist
try:
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
assert deleted
except:
pass
4 changes: 3 additions & 1 deletion test/e2e/replacement_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
Lambda-specific test variables.
"""

from acktest.aws.identity import get_region

REPLACEMENT_VALUES = {

"AWS_REGION": get_region()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is globally defined, as part of acktest - no need to redefine it here. https://github.com/aws-controllers-k8s/test-infra/blob/main/src/acktest/resources.py#L32

}
Loading