Files
o3de/Tests/ly_shared/gamelift_utils.py
T
Brian Herrera e8bbb5a0d5 Set scripts to be executable
This is required to build on linux/mac
2021-03-26 09:26:12 -07:00

746 lines
31 KiB
Python
Executable File

"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Requires boto3 >= 1.12.4
"""
import configparser
import shutil
import os
import logging
import pytest
import six
pytest.importorskip("botocore")
from botocore.exceptions import ClientError
pytest.importorskip("boto3")
import boto3
import ly_test_tools.environment.process_utils as process_utils
import ly_test_tools.environment.file_system as file_system
from . import s3_utils
logger = logging.getLogger(__name__)
DEFAULT_REGION = 'us-west-2'
class GameliftUtils(object):
"""
Stores a boto3 gamelift client to use for AWS Gamelift functionalities. Static methods are provided when no client
is needed for the function.
"""
def __init__(self, session=None):
# type: (boto3.Session) -> None
"""
The gamelift client can be set during init, or a default one will be created.
:param client: A boto3 gamelift client
"""
self._client = None
self._session = session
@property
def client(self):
if self._client is None:
if self._session:
self._client = self._session.client('gamelift')
else:
logger.info(f"No client provided, using default credentials with default region: {DEFAULT_REGION}")
self._client = boto3.Session(region_name=DEFAULT_REGION).client('gamelift')
return self._client
@staticmethod
def get_aws_credentials(profile):
# type: () -> dict
"""
Gets credentials from .aws/credentials file. QA will setup our nodes using one default set for Isengard
:profile: The profile name found in the credentials file. ex. [default]
:return: Dictionary of AWS Credentials gathered
"""
aws_credentials = {}
home = os.path.expanduser("~")
IniRead = configparser.ConfigParser()
IniRead.read(os.path.join(home, '.aws', 'credentials'))
aws_credentials['access'] = IniRead.get(profile, 'aws_access_key_id')
aws_credentials['secret'] = IniRead.get(profile, 'aws_secret_access_key')
return aws_credentials
# This method is static because it uses the AWS CLI to upload a build locally. Boto3 only provides a way to create
# a gamelift build from s3.
@staticmethod
def upload_build_to_gamelift(source_folder, name, operating_system, region, build_version='1.0.0'):
# type: (str, str, str, str, str) -> str
"""
Function to upload a build to gamelift. We use AWS CLI because boto3 can only create builds from s3
:param source_folder: Path to the build zip file
:param name: Name of the Gamelift Build
:param operating_system: Operating system parameter for the aws gamelift upload-build command
:param region: AWS region, such as us-east-1
:param build_version: Build version as a string
Example command:
aws gamelift upload-build --name "1.11 pc-47 (profile)" E:/lyengine/dev --build-version 1.11 --build-root
"F:\\lumberyard-1.11-472772-pc-47\\dev\\multiplayersample_pc_paks_dedicated" --operating-system WINDOWS_2012
--region us-west-2
"""
upload_build_cmd = ['aws', 'gamelift', 'upload-build',
'--build-root', source_folder,
'--name', name,
'--operating-system', operating_system,
'--region', region,
'--build-version', build_version,
]
# We have to use AWS CLI to upload build from local machine, boto3 only uploads builds from S3
# shell=True only works on Windows.
output = process_utils.check_output(upload_build_cmd, shell=True)
# Grab the line containing the Build ID
build_output_line = ''
for output_line in output.splitlines():
if "Build ID" in output_line:
build_output_line = output_line
break
# Check to see if the build was uploaded successfully
assert build_output_line, f"Gamelift build was not successfully uploaded: {output}"
# Find the build id and strip it for escape characters
build_id = build_output_line[build_output_line.index('build-'):].rstrip()
logger.info(f"Build: {build_id} was successfully uploaded.")
return build_id
def create_gamelift_queue(self, queue_name, destination_fleet_arn, timeout=120):
"""
Sends request to create a gamelift queue.
:param queue_name: Name of the queue to create
:param destination_fleet_arn: GameLift fleet the queue will place game sessions on.
:param timeout: Placement request timeout
:return: Queue Arn of queue created
"""
try:
response = self.client.create_game_session_queue(
Name=queue_name,
TimeoutInSeconds=timeout,
Destinations=[
{
'DestinationArn': destination_fleet_arn
},
]
)
created_game_session_queue = response['GameSessionQueue']
queue_arn = created_game_session_queue['GameSessionQueueArn']
except Exception as e:
raise AssertionError('GameLift queue:{} creation failed for fleetArn:{} Error:{}'.format(queue_name,
destination_fleet_arn,
e))
logger.info('GameLift queue:{} created Arn:{}'.format(queue_name, queue_arn))
return queue_arn
def create_gamelift_matchmaking_rule_set(self, rule_set_name):
"""
Creates GameLift matchmaking rule set
:param rule_set_name: Name of the rule set
:return:
"""
try:
rule_set = '{"name": "player_vs_asteriods", "ruleLanguageVersion": "1.0", "teams": [{"name": "Players",' \
'"maxPlayers": 4,"minPlayers": 2}]} '
self.client.create_matchmaking_rule_set(
Name=rule_set_name,
RuleSetBody=rule_set
)
except ClientError as e:
raise AssertionError('GameLift matchmaking rule set:{} creation failed. Error {}'.format(rule_set_name, e))
logger.info('GameLift matchmaking rule set:{}'.format(rule_set_name))
def create_gamelift_matchmaking_config(self, config_name, queue_arn, rules_set_name, backfill_mode):
"""
Creates GameLift matchmaking config
:param config_name: Name of the matchmaking config
:return:
"""
try:
response = self.client.create_matchmaking_configuration(
Name=config_name,
Description='MS Test config',
GameSessionQueueArns=[
queue_arn
],
RequestTimeoutSeconds=120,
AcceptanceRequired=False,
RuleSetName=rules_set_name,
GameProperties=[
{
'Key': 'sv_name',
'Value': 'MSTestServer'
},
{
'Key': 'sv_map',
'Value': 'multiplayersample'
}
],
BackfillMode= backfill_mode
)
except ClientError as e:
raise AssertionError('GameLift matchmaking config :{} creation failed {}'.format(config_name, e))
logger.info('GameLift matchmaking config:{}'.format(config_name))
def create_gamelift_fleet(self, name, build_id, ec2_instance_type, runtime_configuration,
ec2_inbound_permissions):
"""
Function to create a fleet within Gamelift
:param name: Name of the Gamelift Fleet
:param build_id: The id of the build given back to us from Gamelift when we uploaded a build
:param ec2_instance_type: AWS instance type, such as 'c3.large'
:param runtime_configuration: A dictionary of LaunchPath, Parameters, and ConcurrentExecutions
:param ec2_inbound_permissions: A list of port settings for the fleet
:return: ID,ARN of the fleet that was created
"""
for port_settings in ec2_inbound_permissions:
if port_settings['IpRange'] == '0.0.0.0/0':
logger.warning('WARNING - Creating fleet with IpRange 0.0.0.0/0 is a security risk. This setting can be'
'changed in the networking_constants.py file')
output = self.client.create_fleet(Name=name,
Description='Automated Test',
BuildId=build_id,
EC2InstanceType=ec2_instance_type,
EC2InboundPermissions=ec2_inbound_permissions,
RuntimeConfiguration=runtime_configuration
)
logger.info(output)
try:
fleet_id = output['FleetAttributes']['FleetId']
fleet_arn = output['FleetAttributes']['FleetArn']
return fleet_id, fleet_arn
except ValueError as e:
problem = ValueError(f'Error when trying to return Fleet Id from output: {output}.')
six.raise_from(problem, e)
def is_build_status_ready(self, build_id):
# type: (str) -> bool
"""
Returns True if the build status is 'READY', otherwise returns False
:param build_id: The AWS build-id to be checked
:return: bool
"""
build_status = self.get_build_status(build_id)
return build_status == 'READY'
def get_build_status(self, build_id):
# type: (str) -> str
"""
Returns the status of the build. Possible statuses include the following: 'INITIALIZED', 'READY', 'FAILED'
:param build_id: The AWS build-id to be checked
:return: The status of the build as a string
"""
output = self.client.describe_build(BuildId=build_id)
logger.debug(output)
try:
build_status = output['Build']['Status']
except ValueError as e:
problem = ValueError(f'Cannot find Build Status in output: {output}.')
six.raise_from(problem, e)
return build_status
def check_instance_state(self, fleet_id):
# type: (str) -> bool
"""
Checked the state of an instance. Returns true when the instance is in an Active state. Asserts when the fleet
has an ERROR state, otherwise false
:param fleet_id: the Id of the fleet we are checking the state of
:return: True when fleet is in the active state
"""
fleet_attribute = self.get_fleet_list_attributes([fleet_id])
# We expect fleet_attribute to be a list of one element
try:
if len(fleet_attribute) == 0:
logger.debug(f'Fleet attribute was empty for fleet: {fleet_id}')
return False
fleet_status = fleet_attribute[0]['Status']
if fleet_status == 'ACTIVE':
return True
elif fleet_status == 'ERROR':
raise AssertionError(f'Fleet: {fleet_id} has status ERROR!')
except ValueError as e:
problem = ValueError(f'Error occurred when checking fleet status for fleet: {fleet_id} from attribute: '
f'{fleet_attribute}')
six.raise_from(problem, e)
logger.debug(f'Fleet attribute was expected to be ACTIVE, but was: {fleet_status} for fleet {fleet_id}')
return False
def get_fleet_list_attributes(self, fleet_ids=None):
# type: (list[str]) -> list[str]
"""
Function to retrieve the fleet attribute for a given fleet. Will return all fleet attributes if no fleet_id is
given
:param fleet_ids: a list of fleet ids (can be a list of one) to get attributes for
:return: list
"""
fleet_attribute_list = []
# Get paginator
try:
fleet_attribute_paginator = self.client.get_paginator('describe_fleet_attributes')
except ClientError as e:
problem = ClientError('Error occurred when getting paginator describe_fleet_attributes. Check to see if the'
'botocore version is compatible')
six.raise_from(problem, e)
# Get iterator
try:
if fleet_ids:
fleet_attribute_iterator = fleet_attribute_paginator.paginate(FleetIds=fleet_ids)
else:
fleet_attribute_iterator = fleet_attribute_paginator.paginate()
except ClientError as e:
problem = ClientError(f'Error occurred when getting fleet attributes for fleet: {fleet_ids}')
six.raise_from(problem, e)
# Iterate through the fleet responses
try:
for fleet_response in fleet_attribute_iterator:
for fleet_attribute in fleet_response['FleetAttributes']:
fleet_attribute_list.append(fleet_attribute)
except ValueError as e:
problem = ValueError(f'Cannot find FleetAttributes in output: {fleet_response}')
six.raise_from(problem, e)
return fleet_attribute_list
def check_all_current_player_sessions(self, fleet_id, number_of_players):
# type: (str, int) -> bool
"""
Checks for the number of players inside a game session against the expected value.
:param fleet_id: The ID of the gamelift fleet
:param number_of_players: Number of players to be expecting for this function to verify
:return: Returns true of the number of players match the number of connected clients
"""
game_sessions = self.get_game_sessions(fleet_id)
if len(game_sessions) == 0:
return False
for game_session in game_sessions:
if game_session['CurrentPlayerSessionCount'] != number_of_players:
return False
return True
def check_total_players_in_all_game_sessions(self, fleet_id, number_of_players):
"""
Checks the number of players passed in is same as the total players in all game sessions on a Gamelift fleet.
The reason to perform a check for total players instead of checking individual game sessions is the current
MultiplayerSample implementation connects to the first game session it finds. In addition to that the response
from GameLift describing active game sessions in a fleet is non-deterministic (order). So the best check is to
verify total number of player sessions count
:param fleet_id:
:param number_of_players:
:return:
"""
game_sessions = self.get_game_sessions(fleet_id)
total_players = 0
for game_session in game_sessions:
total_players += game_session['CurrentPlayerSessionCount']
return number_of_players == total_players
def get_game_sessions(self, fleet_id=None):
# type: (str) -> dict
"""
Gets a gamelift game session from the given fleet. Returns None if there is no active game_session for the fleet.
If no fleet is provided, will return all game sessions.
:param fleet_id: The ID of the gamelift fleet
:return: The gamelift game session response
"""
game_sessions_list = []
if fleet_id:
logger.debug(f"Getting game session for fleet: {fleet_id}")
# Get paginator
try:
game_sessions_paginator = self.client.get_paginator('describe_game_sessions')
except ClientError as e:
problem = ClientError('Error occurred when getting paginator describe_game_sessions. Check to see if the '
'botocore version is compatible')
six.raise_from(problem, e)
# Get iterator
try:
if fleet_id:
game_sessions_iterator = game_sessions_paginator.paginate(FleetId=fleet_id)
else:
game_sessions_iterator = game_sessions_paginator.paginate()
except ClientError as e:
problem = ClientError('Error occurred when getting game sessions')
six.raise_from(problem, e)
# Iterate through the fleet responses
try:
for sessions_response in game_sessions_iterator:
for game_session in sessions_response['GameSessions']:
game_sessions_list.append(game_session)
except ValueError as e:
problem = ValueError(f'Cannot find GameSessions in output: {sessions_response}')
six.raise_from(problem, e)
return game_sessions_list
@staticmethod
def run_multiplayer_sample_paks_pc_dedicated(dev_path):
# type: (str) -> None
"""
Runs a bat script which will package up the items needed for Gamelift
:param dev_path: Path to the dev folder of Lumberyard
"""
run_paks_cmd = [dev_path + r'\BuildMultiplayerSample_Paks_PC_dedicated.bat']
logger.info(process_utils.check_output(run_paks_cmd))
@staticmethod
def run_multiplayer_sample_linux_packer(dev_path):
# type: (str) -> str
"""
Runs a bat script which will create a .tar Gamelift package that can be transferred to a Linux machine
:param dev_path: Path to the dev folder of Lumberyard
:return: The full path of the tar package
"""
run_packer_cmd = os.path.join(dev_path, 'MultiplayerSample_LinuxPacker.bat')
output = process_utils.check_output(run_packer_cmd)
validation_text = 'Uncompressed archive successfully generated at '
for line in output.decode().split('\r'):
if validation_text in line:
tar_filepath = line[line.index(validation_text)+len(validation_text):].rstrip()
return tar_filepath
logger.info(output)
raise AssertionError('tar package not found!')
@staticmethod
def copy_bin_folder(bin_directory, dev_path):
# type: (str, str) -> None
"""
Copies bin folder into the MultiplayerSamples_pc_Paks_Dedicated folder for Windows Gamelift Builds
:param bin_directory: The bin directory to copy (usually the Dedicated Bin folder)
:param dev_path: The path to the ~/dev directory
:return: None
"""
to_folder = dev_path + '\\MultiplayerSample_pc_Paks_Dedicated\\' + bin_directory
if os.path.exists(to_folder):
file_system.delete([to_folder], True, True)
shutil.copytree(os.path.join(dev_path, bin_directory), to_folder)
def download_required_gamelift_files(self, destination_dir):
# type: (str) -> None
"""
Downloads required vc redistributables, their installation script, and debug libs from S3. Not all these files are
required by Gamelift, but is for organizing our builds.
:param destination_dir: Path on disk to be checked.
"""
s3_util = s3_utils.S3Utils(self._session)
gamelift_files_bucket = 'ly-net-gamelift-required-files'
keys = ['vc_redist.x64.exe', 'VC_redist2017.x64.exe', 'vcredist_x64.exe', 'install.bat',
'msvcp140d.dll', 'ucrtbased.dll', 'vcruntime140d.dll']
for file_key in keys:
s3_util.download_from_bucket(gamelift_files_bucket, file_key, destination_dir)
def create_gamelift_package(self, dev_path, bin_directory):
# type: (str, str) -> None
"""
Creates a package ready to be deployed to gamelift via aws cli
:param dev_path: Path to dev directory
:param bin_directory: path to bin directory
:return: None
"""
paks_dir = os.path.join(dev_path, 'MultiplayerSample_pc_Paks_Dedicated')
if os.path.exists(paks_dir):
file_system.delete([paks_dir], True, True)
GameliftUtils.run_multiplayer_sample_paks_pc_dedicated(dev_path)
self.download_required_gamelift_files(paks_dir)
GameliftUtils.copy_bin_folder(f"{bin_directory}.Dedicated", dev_path)
def get_build_id_from_fleet_id(self, fleet_id):
# type: (str) -> str
"""
Retreives the build id that a fleet was created from.
:param fleet_id: The ID from a gamelift fleet
:return: A gamelift build id
"""
fleet_attributes = self.get_fleet_list_attributes([fleet_id])
# We expect a list of one element
try:
build_id = fleet_attributes[0]['BuildId']
except ValueError as e:
problem = ValueError(f'Cannot find build id in fleet attributes: {fleet_attributes} for fleet: {fleet_id}')
six.raise_from(problem, e)
return build_id
def get_in_use_build_list_from_gamelift(self):
# type: () -> list[str]
"""
Function that returns a list of in use builds. 'In use builds' include those that are being initalized and if an
'in use fleet' is being created from that build. This function is used to target stale builds that are either idle
or have errored out.
:return: A list of build id's that are in use
"""
exclude_build_list = []
# Gather in use fleets and their builds
for fleet in self.get_in_use_fleet_list():
exclude_build_list.append(self.get_build_id_from_fleet_id(fleet))
# Gather any build that is being initialized
build_list = self.get_build_id_list(status='INITIALIZED')
exclude_build_list.extend(build_list)
return exclude_build_list
def get_build_id_list(self, status=None):
# type: (str) -> list[str]
"""
Gets all build id's from the boto3 client. An optional status filter is provided to gather all builds with a
given status
:param status: A build status to filter. Can be 'READY', 'INITIALIZED', or 'FAILED'
:return: a list of build id's
"""
build_list = []
# Get paginator
try:
build_paginator = self.client.get_paginator('list_builds')
except ClientError as e:
problem = ClientError('Error occurred when getting paginator list_builds. Check to see if the '
'botocore version is compatible')
six.raise_from(problem, e)
# Get iterator
try:
if status:
build_iterator = build_paginator.paginate(Status=status)
else:
build_iterator = build_paginator.paginate()
except ClientError as e:
problem = ClientError('Error occurred when getting build list')
six.raise_from(problem, e)
# Iterate over the build responses
try:
for build_response in build_iterator:
for build_attributes in build_response['Builds']:
build_list.append(build_attributes['BuildId'])
except ValueError as e:
problem = ValueError(f'Cannot find Builds in output: {build_response}')
six.raise_from(problem, e)
return build_list
def get_in_use_fleet_list(self):
# type: () -> list[str]
"""
Function that returns a list of in use fleets. 'In use fleets' include those that are being initialized or
currently in use in an active game session. This function is used to target stale fleets that are either idle
or have errored out.
:return: a list of fleet id's
"""
in_use_fleet_list = set()
# Gather all fleets
fleet_list = self.get_fleet_list()
# Exclude any fleets that have an "in use" status
fleet_attribute_list = self.get_fleet_list_attributes()
for fleet_attrib in fleet_attribute_list:
if fleet_attrib['Status'] in ["DELETING", "DOWNLOADING", "ACTIVATING", "NEW", "VALIDATING", "BUILDING"]:
in_use_fleet_list.add(fleet_attrib['FleetId'])
# Exclude any fleets that are active, but have players connected to them
for fleet_id in fleet_list:
game_sessions = self.get_game_sessions(fleet_id)
for game_session in game_sessions:
if game_session['CurrentPlayerSessionCount'] > 0:
in_use_fleet_list.add(fleet_id)
return list(in_use_fleet_list)
def get_fleet_list(self):
# type: () -> list[str]
"""
Function to retrieve all fleet Id's from gamelift.
:return: A list of fleet ids
"""
fleet_list = []
# Get paginator
try:
fleet_paginator = self.client.get_paginator('list_fleets')
except ClientError as e:
problem = ClientError('Error occurred when getting paginator list_fleets. Check to see if the '
'botocore version is compatible')
six.raise_from(problem, e)
# Get iterator
try:
fleet_iterator = fleet_paginator.paginate()
except ClientError as e:
problem = ClientError('Error occurred when getting fleet list')
six.raise_from(problem, e)
# Iterate over the fleet responses
try:
for fleet_response in fleet_iterator:
for fleet_id in fleet_response['FleetIds']:
fleet_list.append(fleet_id)
except ValueError as e:
problem = ValueError(f'Cannot find FleetIds in output: {fleet_response}')
six.raise_from(problem, e)
return fleet_list
def delete_build_from_gamelift(self, build_id):
# type: (str) -> None
"""
Function to delete a build from gamelift
:param build_id: The id of the build given back to us from Gamelift when we uploaded a build
:return: None
"""
try:
self.client.delete_build(BuildId=build_id)
logger.debug(f'Deleting build: {build_id}')
except ClientError as e:
problem = ClientError(f'Cannot delete build: {build_id}')
six.raise_from(problem, e)
def delete_fleet_from_gamelift(self, fleet_id):
# type: (str) -> None
"""
Function to delete a fleet from gamelift
:param fleet_id: The id of the fleet given back to us from Gamelift when we created a fleet
:return: None
"""
# Update fleet capacity command must be ran before deleting the fleet
try:
self.check_instance_state(fleet_id)
self.update_fleet_capacity(fleet_id, 0)
# Don't update fleet capacity when fleet status is in ERROR state
except AssertionError:
logger.debug('Cannot update fleet capacity because fleet is in an ERROR state')
try:
self.client.delete_fleet(FleetId=fleet_id)
logger.debug(f"Deleting fleet: {fleet_id}")
except ClientError as e:
problem = f"Error occurred while attempting to delete fleet: {fleet_id}"
six.raise_from(problem, e)
def update_fleet_capacity(self, fleet_id, desired_instances):
# type: (str, int) -> None
"""
Function to update a fleet capacity in gamelift.
:param fleet_id: The id of the fleet given back to us from Gamelift when we created a fleet
:param desired_instances: The capacity to set
:return: None
"""
try:
self.client.update_fleet_capacity(FleetId=fleet_id, DesiredInstances=desired_instances)
except ClientError as e:
problem = f"Error occurred while trying to update fleet: {fleet_id} to capacity: {desired_instances}"
six.raise_from(problem, e)
def get_gamelift_queue_names(self):
"""
Calls describe game session queues to list current active queues.
:return: list of queue names found
"""
try:
response = self.client.describe_game_session_queues()
queue_names = []
for queue in response['GameSessionQueues']:
queue_names.append(queue['Name'])
return queue_names
except ClientError as e:
raise AssertionError('Failed describing gamelift queues, Error:{}'.format(e))
def delete_gamelift_queue(self, queue_name):
"""
Deletes a gamelift queue
:param queue_name:
:return:
"""
try:
self.client.delete_game_session_queue(
Name=queue_name
)
except ClientError as e:
raise AssertionError('Failed deleting queue, Name:{} Error:{}'.format(queue_name, e))
def get_gamelift_matchmaking_rule_set(self):
"""
Calls describe matchmaking rule set to list current active rule sets
:return: list of rule sets found
"""
try:
response = self.client.describe_matchmaking_rule_sets()
rule_set_names = []
for rule_set in response['RuleSets']:
rule_set_names.append(rule_set['RuleSetName'])
return rule_set_names
except ClientError as e:
raise AssertionError('Failed describing gamelift matchmaking rule sets, Error:{}'.format(e))
def delete_gamelift_matchmaking_rule_set(self, rule_set_name):
"""
Deletes a gamelift matchmaking rule set
:param rule_set_name:
:return:
"""
try:
self.client.delete_matchmaking_rule_set(
Name=rule_set_name
)
except ClientError as e:
raise AssertionError(
'Failed deleting gamelift matchmaking rule set, Name:{} Error:{}'.format(rule_set_name, e))
def get_gamelift_matchmaking_configuration(self):
"""
Calls describe matchmaking configuration to list current active rule sets
:return: list of rule sets found
"""
try:
response = self.client.describe_matchmaking_configurations()
configuration_names = []
for configuration in response['Configurations']:
configuration_names.append(configuration['Name'])
return configuration_names
except ClientError as e:
raise AssertionError('Failed describing gamelift matchmaking configurations, Error:{}'.format(e))
def delete_gamelift_matchmaking_configurations(self, configuration_name):
"""
Deletes a gamelift matchmaking configuration
:param configuration_name:
:return:
"""
try:
self.client.delete_matchmaking_configuration(
Name=configuration_name
)
except ClientError as e:
raise AssertionError(
'Failed deleting gamelift matchmaking configuration, Name:{} Error:{}'.format(configuration_name, e))