Merge AWSCore automation tests to the development branch (#2011)
* Create AWS Core automation for the interacting with AWS resources via the AWS Core gem. Signed-off-by: junbo <junbo@amazon.com> Co-authored-by: clujames <clujames@amazon.com>
This commit is contained in:
+5
-8
@@ -15,10 +15,7 @@ import ly_test_tools.log.log_monitor
|
||||
|
||||
# fixture imports
|
||||
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
|
||||
from AWS.Windows.cdk.cdk_utils import Cdk
|
||||
from AWS.common.aws_utils import AwsUtils
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
|
||||
from AWS.common.aws_credentials import aws_credentials
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
|
||||
from .aws_metrics_utils import aws_metrics_utils
|
||||
|
||||
AWS_METRICS_FEATURE_NAME = 'AWSMetrics'
|
||||
@@ -28,7 +25,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(launcher: ly_test_tools.launchers.Launcher,
|
||||
cdk: Cdk,
|
||||
cdk: pytest.fixture,
|
||||
asset_processor: asset_processor,
|
||||
resource_mappings: resource_mappings,
|
||||
context_variable: str = '') -> typing.Tuple[ly_test_tools.log.log_monitor.LogMonitor, str, str]:
|
||||
@@ -119,7 +116,7 @@ class TestAWSMetricsWindows(object):
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
aws_credentials: aws_credentials,
|
||||
aws_credentials: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
cdk: pytest.fixture,
|
||||
aws_metrics_utils: aws_metrics_utils,
|
||||
@@ -162,7 +159,7 @@ class TestAWSMetricsWindows(object):
|
||||
level: str,
|
||||
launcher: ly_test_tools.launchers.Launcher,
|
||||
cdk: pytest.fixture,
|
||||
aws_credentials: aws_credentials,
|
||||
aws_credentials: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
workspace: pytest.fixture):
|
||||
@@ -187,7 +184,7 @@ class TestAWSMetricsWindows(object):
|
||||
level: str,
|
||||
launcher: ly_test_tools.launchers.Launcher,
|
||||
cdk: pytest.fixture,
|
||||
aws_credentials: aws_credentials,
|
||||
aws_credentials: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import ly_test_tools
|
||||
import ly_test_tools.log.log_monitor
|
||||
import ly_test_tools.environment.process_utils as process_utils
|
||||
import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
|
||||
|
||||
AWS_CORE_FEATURE_NAME = 'AWSCore'
|
||||
AWS_RESOURCE_MAPPING_FILE_NAME = 'default_aws_resource_mappings.json'
|
||||
|
||||
process_utils.kill_processes_named("o3de", ignore_extensions=True) # Kill ProjectManager windows
|
||||
|
||||
GAME_LOG_NAME = 'Game.log'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(launcher: pytest.fixture, cdk: pytest.fixture, resource_mappings: pytest.fixture, asset_processor: pytest.fixture):
|
||||
asset_processor_utils.kill_asset_processor()
|
||||
logger.info(f'Cdk stack names:\n{cdk.list()}')
|
||||
stacks = cdk.deploy(additonal_params=['--all'])
|
||||
resource_mappings.populate_output_keys(stacks)
|
||||
asset_processor.start()
|
||||
asset_processor.wait_for_idle()
|
||||
|
||||
file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME)
|
||||
log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
|
||||
|
||||
return log_monitor
|
||||
|
||||
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.usefixtures('automatic_process_killer')
|
||||
@pytest.mark.usefixtures('asset_processor')
|
||||
@pytest.mark.usefixtures('cdk')
|
||||
@pytest.mark.parametrize('feature_name', [AWS_CORE_FEATURE_NAME])
|
||||
@pytest.mark.parametrize('region_name', ['us-west-2'])
|
||||
@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests'])
|
||||
@pytest.mark.parametrize('session_name', ['o3de-Automation-session'])
|
||||
@pytest.mark.usefixtures('workspace')
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('level', ['AWS/Core'])
|
||||
@pytest.mark.usefixtures('resource_mappings')
|
||||
@pytest.mark.parametrize('resource_mappings_filename', [AWS_RESOURCE_MAPPING_FILE_NAME])
|
||||
@pytest.mark.usefixtures('aws_credentials')
|
||||
@pytest.mark.parametrize('profile_name', ['AWSAutomationTest'])
|
||||
class TestAWSCoreAWSResourceInteraction(object):
|
||||
"""
|
||||
Test class to verify AWSCore can downloading a file from S3.
|
||||
"""
|
||||
def test_download_from_s3(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
cdk: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
resource_mappings: pytest.fixture
|
||||
):
|
||||
"""
|
||||
Setup: Deploys cdk and updates resource mapping file.
|
||||
Tests: Getting AWS credentials for no signed in user.
|
||||
Verification: Log monitor looks for success download. The existence and contents of the file are also verified.
|
||||
"""
|
||||
|
||||
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
|
||||
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
user_dir = os.path.join(workspace.paths.project(), 'user')
|
||||
download_dir = os.path.join(user_dir, 's3_download')
|
||||
if not os.path.exists(download_dir):
|
||||
os.makedirs(download_dir)
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=['(Script) - [S3] Head object request is done',
|
||||
'(Script) - [S3] Head object success: Object example.txt is found.',
|
||||
'(Script) - [S3] Get object success: Object example.txt is downloaded.'],
|
||||
unexpected_lines=['(Script) - [S3] Head object error: No response body.',
|
||||
'(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.'],
|
||||
halt_on_unexpected=True
|
||||
)
|
||||
|
||||
assert result, "Expected lines weren't found."
|
||||
|
||||
download_path = os.path.join(download_dir, 'output.txt')
|
||||
|
||||
file_was_downloaded = os.path.exists(download_path)
|
||||
# clean up the file directories.
|
||||
if file_was_downloaded:
|
||||
os.remove(download_path)
|
||||
os.rmdir(download_dir)
|
||||
|
||||
assert file_was_downloaded, 'The expected file wasn\'t successfully downloaded'
|
||||
|
||||
def test_invoke_lambda(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
cdk: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
asset_processor: pytest.fixture
|
||||
):
|
||||
"""
|
||||
Setup: Deploys the CDK.
|
||||
Tests: Runs the test level.
|
||||
Verification: Searches the logs for the expected output from the example lambda.
|
||||
"""
|
||||
|
||||
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
|
||||
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=['(Script) - [Lambda] Completed Invoke',
|
||||
'(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}'],
|
||||
unexpected_lines=['(Script) - Request validation failed, output file miss full path.',
|
||||
'(Script) - '],
|
||||
halt_on_unexpected=True
|
||||
)
|
||||
|
||||
assert result
|
||||
|
||||
def test_get_dynamodb_value(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
cdk: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
):
|
||||
"""
|
||||
Setup: Deploys the CDK application
|
||||
Test: Runs a launcher with a level that loads a scriptcanvas that pulls a DynamoDB table value.
|
||||
Verification: The value is output in the logs and verified by the test.
|
||||
"""
|
||||
|
||||
def write_test_table_data():
|
||||
client = aws_utils.client('dynamodb')
|
||||
table_name = resource_mappings.get_resource_name_id("AWSCore.ExampleDynamoTableOutput")
|
||||
try:
|
||||
client.put_item(
|
||||
TableName=table_name,
|
||||
Item={
|
||||
'id': {
|
||||
'S': 'Item1'
|
||||
}
|
||||
}
|
||||
)
|
||||
logger.info(f'Loaded data into table {table_name}')
|
||||
except ClientError:
|
||||
logger.exception(f'Failed to load data into table {table_name}')
|
||||
raise
|
||||
|
||||
log_monitor = setup(launcher, cdk, resource_mappings, asset_processor)
|
||||
write_test_table_data()
|
||||
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=['(Script) - [DynamoDB] Results finished'],
|
||||
unexpected_lines=['(Script) - Request validation failed, output file miss full path.',
|
||||
'(Script) - '],
|
||||
halt_on_unexpected=True
|
||||
)
|
||||
|
||||
assert result
|
||||
@@ -107,24 +107,3 @@ class AwsCredentials:
|
||||
else:
|
||||
self._credentials[self._profile_name][attribute_name] = attribute_value
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def aws_credentials(request: pytest.fixture, aws_utils: pytest.fixture, profile_name: str):
|
||||
"""
|
||||
Fixture for setting up temporary AWS credentials from assume role.
|
||||
|
||||
:param request: _pytest.fixtures.SubRequest class that handles getting
|
||||
a pytest fixture from a pytest function/fixture.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
:param profile_name: Named AWS profile to store temporary credentials.
|
||||
"""
|
||||
aws_credentials_obj = AwsCredentials(profile_name)
|
||||
original_access_key, original_secret_access_key, original_token = aws_credentials_obj.get_aws_credentials()
|
||||
aws_credentials_obj.set_aws_credentials_by_session(aws_utils.assume_session())
|
||||
|
||||
def teardown():
|
||||
# Reset to the named profile using the original AWS credentials
|
||||
aws_credentials_obj.set_aws_credentials(original_access_key, original_secret_access_key, original_token)
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return aws_credentials_obj
|
||||
|
||||
@@ -6,6 +6,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
import pytest
|
||||
import logging
|
||||
from AWS.common.aws_utils import AwsUtils
|
||||
from AWS.common.aws_credentials import AwsCredentials
|
||||
from AWS.Windows.cdk.cdk_utils import Cdk
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -81,3 +82,26 @@ def cdk(
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return pytest.cdk_obj
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def aws_credentials(request: pytest.fixture, aws_utils: pytest.fixture, profile_name: str):
|
||||
"""
|
||||
Fixture for setting up temporary AWS credentials from assume role.
|
||||
|
||||
:param request: _pytest.fixtures.SubRequest class that handles getting
|
||||
a pytest fixture from a pytest function/fixture.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
:param profile_name: Named AWS profile to store temporary credentials.
|
||||
"""
|
||||
aws_credentials_obj = AwsCredentials(profile_name)
|
||||
original_access_key, original_secret_access_key, original_token = aws_credentials_obj.get_aws_credentials()
|
||||
aws_credentials_obj.set_aws_credentials_by_session(aws_utils.assume_session())
|
||||
|
||||
def teardown():
|
||||
# Reset to the named profile using the original AWS credentials
|
||||
aws_credentials_obj.set_aws_credentials(original_access_key, original_secret_access_key, original_token)
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return aws_credentials_obj
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5242a9b598bc329ef2af2b114092e4e50c7c398cdde4605a0717b0b3ce66d797
|
||||
size 10030
|
||||
@@ -0,0 +1,6 @@
|
||||
<download name="Core" type="Map">
|
||||
<index src="filelist.xml" dest="filelist.xml"/>
|
||||
<files>
|
||||
<file src="level.pak" dest="level.pak" size="EBE" md5="2b5b16a34ef2c8f62956dfb5588c4578"/>
|
||||
</files>
|
||||
</download>
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4ba2f409fc974c72b8ee8b660d200ed1d013ee8408419b0e91d6d487e71e4997
|
||||
size 3774
|
||||
@@ -0,0 +1,12 @@
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
0,0,0,0,0,0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -72,7 +72,7 @@ namespace AWSCore
|
||||
{
|
||||
static constexpr const char AWSScriptBehaviorS3Name[] = "AWSScriptBehaviorS3";
|
||||
static constexpr const char OutputFileIsEmptyErrorMessage[] = "Request validation failed, output file is empty.";
|
||||
static constexpr const char OutputFileMissFullPathErrorMessage[] = "Request validation failed, output file miss full path.";
|
||||
static constexpr const char OutputFileFailedToResolveErrorMessage[] = "Request validation failed, cannot resolve the output file path.";
|
||||
static constexpr const char OutputFileIsDirectoryErrorMessage[] = "Request validation failed, output file is a directory.";
|
||||
static constexpr const char OutputFileDirectoryNotExistErrorMessage[] = "Request validation failed, output file directory doesn't exist.";
|
||||
static constexpr const char OutputFileIsReadOnlyErrorMessage[] = "Request validation failed, output file is read-only.";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
@@ -171,12 +172,16 @@ namespace AWSCore
|
||||
AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileIsEmptyErrorMessage);
|
||||
return false;
|
||||
}
|
||||
if (!AzFramework::StringFunc::Path::HasDrive(outFile.c_str()))
|
||||
|
||||
char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
|
||||
if (!AZ::IO::FileIOBase::GetInstance()->ResolvePath(outFile.c_str(), resolvedPath, AZ_MAX_PATH_LEN))
|
||||
{
|
||||
AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileMissFullPathErrorMessage);
|
||||
AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileMissFullPathErrorMessage);
|
||||
AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileFailedToResolveErrorMessage);
|
||||
AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileFailedToResolveErrorMessage);
|
||||
return false;
|
||||
}
|
||||
outFile = resolvedPath;
|
||||
|
||||
if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(outFile.c_str()))
|
||||
{
|
||||
AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileIsDirectoryErrorMessage);
|
||||
|
||||
@@ -122,11 +122,11 @@ TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithEmptyOutfileName_InvokeOnEr
|
||||
AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", "");
|
||||
}
|
||||
|
||||
TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileNameMissFullPath_InvokeOnError)
|
||||
TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileFailedToResolve_InvokeOnError)
|
||||
{
|
||||
AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock;
|
||||
EXPECT_CALL(s3HandlerMock, OnGetObjectError(::testing::_)).Times(1);
|
||||
AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", "dummyOut.txt");
|
||||
AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", "@dummy@/dummyOut.txt");
|
||||
}
|
||||
|
||||
TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileNameIsDirectory_InvokeOnError)
|
||||
|
||||
@@ -107,31 +107,31 @@ class ExampleResources(core.Stack):
|
||||
self._s3_output = core.CfnOutput(
|
||||
self,
|
||||
id=f'ExampleBucketOutput',
|
||||
description='An example S3 bucket to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"ExampleS3Bucket",
|
||||
value=self._s3_bucket.bucket_arn)
|
||||
description='An example S3 bucket name to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"{self.stack_name}:ExampleS3Bucket",
|
||||
value=self._s3_bucket.bucket_name)
|
||||
|
||||
# Define exports
|
||||
# Export resource group
|
||||
self._lambda_output = core.CfnOutput(
|
||||
self,
|
||||
id=f'ExampleLambdaOutput',
|
||||
description='An example Lambda to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"ExampleLambdaFunction",
|
||||
value=self._lambda.function_arn)
|
||||
description='An example Lambda name to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"{self.stack_name}::ExampleLambdaFunction",
|
||||
value=self._lambda.function_name)
|
||||
|
||||
# Export DynamoDB Table
|
||||
self._table_output = core.CfnOutput(
|
||||
self,
|
||||
id=f'ExampleDynamoTableOutput',
|
||||
description='An example DynamoDB Table to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"ExampleTable",
|
||||
value=self._table.table_arn)
|
||||
description='An example DynamoDB Table name to use with AWSCore ScriptBehaviors',
|
||||
export_name=f"{self.stack_name}:ExampleTable",
|
||||
value=self._table.table_name)
|
||||
|
||||
# Export user policy
|
||||
self._user_policy = core.CfnOutput(
|
||||
self,
|
||||
id=f'ExampleUserPolicyOutput',
|
||||
description='A User policy to invoke example resources',
|
||||
export_name=f"ExampleUserPolicy",
|
||||
export_name=f"{self.stack_name}:ExampleUserPolicy",
|
||||
value=self._policy.managed_policy_arn)
|
||||
|
||||
Reference in New Issue
Block a user