Integrating github/staging through commit ab87ed9

This commit is contained in:
alexpete
2021-04-09 11:27:37 -07:00
parent ae62a97894
commit 1044dc3da1
1582 changed files with 29374 additions and 519051 deletions
-745
View File
@@ -1,745 +0,0 @@
"""
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))
-84
View File
@@ -1,84 +0,0 @@
"""
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.
"""
import logging
import winreg
logger = logging.getLogger(__name__)
LUMBERYARD_SETTINGS_PATH = r'Software\Amazon\Lumberyard\Settings'
def set_ly_registry_value(reg_path, value_name, new_value, value_type=winreg.REG_DWORD):
"""
Sets the specified value for the specified value_name in the LY registry key.
:param reg_path: A string that identifies the registry path to the desired key (e.g. Software\Amazon\Lumberyard\Settings)
:param value_name: A string that identifies the value name (e.g. UndoLevels, ViewportInteractionModel)
:param new_value: Value to set on the specified value_name
:param value_type: The type of value set. Defaults to a 32-bit number.
:return: None
"""
# Open LY Registry key
try:
key = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, reg_path, 0, access=winreg.KEY_ALL_ACCESS)
except OSError as err:
logger.error(err)
# Set value_name to the specified value
winreg.SetValueEx(key, value_name, 0, value_type, new_value)
# Verify the value set to the specified value_name
value = winreg.QueryValueEx(key, value_name)
if new_value == value[0]:
logger.debug(f'Successfully set {value_name} to {value[0]}')
else:
logger.debug(f'Failed to set {value_name} to {new_value}. Current value is {value[0]}.')
def get_ly_registry_value(reg_path, value_name):
"""
Gets the current value for an existing value_name in the LY registry key.
:param reg_path: A string that identifies the registry path to the desired key (e.g. Software\Amazon\Lumberyard\Settings)
:param value_name: A string that identifies the value name (e.g. UndoLevels, ViewportInteractionModel)
:return: Value set for the specified value_name
"""
# Open LY Registry key
try:
key = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, reg_path, 0, access=winreg.KEY_ALL_ACCESS)
except OSError as err:
logger.error(err)
# Check if value name is present and return current value
try:
value_name_value = winreg.QueryValueEx(key, value_name)
logger.debug(f'{value_name} is {value_name_value[0]}')
return value_name_value[0]
except OSError as err:
logger.error(err)
def delete_ly_registry_value(reg_path, value_name):
"""
Deletes the specific registry value_name found in the reg_path key.
:param reg_path: A string that identifies the registry path to the desired key (e.g. Software\Amazon\Lumberyard\Settings)
:param value_name: A string that identifies the value name (e.g. UndoLevels, ViewportInteractionModel)
:return: None
"""
# Open LY Registry key
try:
key = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, reg_path, 0, access=winreg.KEY_ALL_ACCESS)
except OSError as err:
logger.error(err)
# Attempt to delete the specified key/value
try:
winreg.DeleteValue(key, value_name)
except OSError as err:
logger.error(err)
-10
View File
@@ -1,10 +0,0 @@
"""
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.
"""
@@ -1,156 +0,0 @@
"""
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.
PreRequisites:
-AWS Credentials are set using AWS
configure
To Run:
cd ~/dev/Tests
~/Python/python3.cmd -m networking.clean_gamelift_builds_and_fleets
"""
import logging
import boto3
from ly_shared import gamelift_utils
from .test_lib import networking_constants
logger = logging.basicConfig(filename='test.log', filemode='w', level=logging.DEBUG)
AWS_PROFILE = networking_constants.DEFAULT_PROFILE
REGION = networking_constants.DEFAULT_REGION
BOTO3_SESSION = boto3.session.Session(profile_name=AWS_PROFILE, region_name=REGION)
GAMELIFT_UTILS = gamelift_utils.GameliftUtils(BOTO3_SESSION)
def main():
"""
This script cleans up the builds and fleets from gamelift. The main method retrieves a list of build and fleet
ids, and then sends a console command to delete them. The script will exclude any builds and fleets that are
currently in use. It will then validate to make sure all non excluded builds and fleets are deleted.
"""
print("Starting Gamelift cleanup script")
fleet_list = _get_unused_fleets_to_delete()
build_list = _get_builds_to_delete()
queue_list = GAMELIFT_UTILS.get_gamelift_queue_names()
configuration_list = GAMELIFT_UTILS.get_gamelift_matchmaking_configuration()
for configuration in configuration_list:
GAMELIFT_UTILS.delete_gamelift_matchmaking_configurations(configuration)
assert validate_matchmaking_configuration_list_empty(), 'Matchmaking configuration list was not deleted properly!'
rule_set_list = GAMELIFT_UTILS.get_gamelift_matchmaking_rule_set()
for rule_set in rule_set_list:
GAMELIFT_UTILS.delete_gamelift_matchmaking_rule_set(rule_set)
assert validate_matchmaking_rule_set_list_empty(), 'Matchmaking rule set list was not deleted properly!'
for queue in queue_list:
GAMELIFT_UTILS.delete_gamelift_queue(queue)
assert validate_queue_list_empty(), 'queue list was not deleted properly!'
for fleet in fleet_list:
GAMELIFT_UTILS.delete_fleet_from_gamelift(fleet)
assert validate_fleet_list_deleted(fleet_list), 'Fleet list was not deleted properly!'
for build_id in build_list:
GAMELIFT_UTILS.delete_build_from_gamelift(build_id)
assert validate_build_list_deleted(build_list), 'Build list was not deleted properly!'
def _get_unused_fleets_to_delete():
"""
Gathers all fleets and removes any in use fleets from the list. This results in a list of fleet id's to delete
:return: a list of fleet ids
"""
fleet_id_list = GAMELIFT_UTILS.get_fleet_list()
for fleet_id in GAMELIFT_UTILS.get_in_use_fleet_list():
try:
fleet_id_list.remove(fleet_id)
except ValueError:
pass # Try to remove any excluded fleets from our list
return fleet_id_list
def _get_builds_to_delete():
"""
Gathers all builds and removes any in use builds from the list. This results in a list of build id's to delete
:return: a list of build ids
"""
build_id_list = GAMELIFT_UTILS.get_build_id_list()
for build_id in GAMELIFT_UTILS.get_in_use_build_list_from_gamelift():
try:
build_id_list.remove(build_id)
except ValueError:
pass # Try to remove any excluded builds from our list
return build_id_list
def validate_build_list_deleted(exclude_build_list):
"""
Validates whether the builds were deleted correctly
:param exclude_build_list: a list of builds that should not be deleted
:return: returns True if the builds are deleted correctly
"""
build_list = GAMELIFT_UTILS.get_build_id_list()
for build in build_list:
if build in exclude_build_list:
return False
return True
def validate_fleet_list_deleted(exclude_fleet_list):
"""
Validates whether the fleets were deleted correctly
:param exclude_fleet_list: a list of fleets that should not be deleted
:return: returns True if the fleets are deleted correctly
"""
fleet_attribute_list = GAMELIFT_UTILS.get_fleet_list_attributes()
for fleet_attrib in fleet_attribute_list:
if (fleet_attrib['Status'] != "DELETING") and (fleet_attrib['FleetId'] in exclude_fleet_list):
return False
return True
def validate_queue_list_empty():
"""
Verifies there are no gamelift queues.
:return: bool True: If no queues.
"""
queue_list = GAMELIFT_UTILS.get_gamelift_queue_names()
return len(queue_list) == 0
def validate_matchmaking_configuration_list_empty():
"""
Verifies there are no gamelift matchmaking configurations.
:return: bool True: If no queues.
"""
configuration_list = GAMELIFT_UTILS.get_gamelift_matchmaking_configuration()
return len(configuration_list) == 0
def validate_matchmaking_rule_set_list_empty():
"""
Verifies there are no gamelift matchmaking rule sets.
:return: bool True: If no queues.
"""
rule_set_list = GAMELIFT_UTILS.get_gamelift_matchmaking_rule_set()
return len(rule_set_list) == 0
if __name__ == '__main__':
main()
@@ -1,10 +0,0 @@
"""
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.
"""
@@ -1,215 +0,0 @@
"""
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.
PRE-REQUISITES:
-machine is configured with AWS credentials (run aws configure)
-AWS default output format is set to 'json'
-MultiplayerSample is built for the given platforms
-Dedicated servers are also built for the respective platforms
-Have LYTestTools installed - run in dev 'lmbr_test pysetup install ly_test_tools'
-A GameLift fleet is ready with its fleet id provided in the config.ini file
Part 3 of 3 for Linux GameLift Automation:
Launches 4 MultiplayerSample clients and connects to a given GameLift fleet
"""
import os
import pytest
import configparser
import logging
import shutil
pytest.importorskip('dateutil')
import boto3
pytest.importorskip("ly_test_tools")
# ly_test_tools dependencies
import ly_test_tools.builtin.helpers as helpers
import ly_test_tools.environment.waiter as waiter
import ly_test_tools.launchers.launcher_helper as launcher_helper
import ly_remote_console.remote_console_commands as remote_console
# test level dependencies
from ...ly_shared import file_utils, network_utils
from ...ly_shared.gamelift_utils import GameliftUtils
from ..test_lib import networking_constants
logger = logging.getLogger(__name__)
INTERVAL_TIME = 10 # in seconds, longer interval times to reduce api call spam
TIMEOUT = 30 # in seconds, time to wait for asyncronous api calls
CONFIG_SECTION = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_SECTION
CONFIG_FILENAME = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_FILENAME
CONFIG_FLEET_KEY = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_FLEET_KEY
CONFIG_DIR = os.path.dirname(__file__) # assumes CONFIG_FILENAME is a sibling to this file
CONFIG_FILE = os.path.join(CONFIG_DIR, CONFIG_FILENAME)
AWS_PROFILE = networking_constants.DEFAULT_PROFILE
REGION = networking_constants.DEFAULT_REGION
BOTO3_SESSION = boto3.session.Session(profile_name=AWS_PROFILE, region_name=REGION)
GAMELIFTUTILS = GameliftUtils(BOTO3_SESSION)
REMOTE_CONSOLE_PORT = 4600
LAUNCHER_PORT_DICTS = []
@pytest.fixture
def multiplayer_client_launcher_list(request, workspace, level, number_of_launchers):
# type: (ly_test_tools.managers.workspace.AbstractWorkspaceManager, str, int) -> test_tools.launchers.platforms.base.Launcher
"""
Create a list of simple launchers. Returns a list of dicts that contain the launcher and its remote console port.
ex.
for launcher_dict in launcher_list:
game_launcher = launcher_dict['launcher']
game_port = launcher_dict['port']
:param request: Pytest request object
:param workspace: The workspace where the launcher is located
:param level: The level name to load
:param number_of_launchers: The number of launcher instances
:return: A fully configured list of dicts containing launchers and their respective port
"""
launcher_list = []
def teardown():
for launcher_dict in launcher_list:
if launcher_dict['launcher'].is_alive():
launcher_dict['launcher'].stop()
if launcher_dict['remote_console'].connected:
launcher_dict['remote_console'].stop()
request.addfinalizer(teardown)
# Create a list of launchers
for index in range(0, number_of_launchers):
port = REMOTE_CONSOLE_PORT + index
launcher_list.append({'launcher': launcher_helper.create_launcher(workspace),
'rc_port': port,
'remote_console': remote_console.RemoteConsole(port=port)})
return launcher_list
@pytest.mark.parametrize('platform', ['win_x64_vs2017'])
@pytest.mark.parametrize('configuration', ['profile'])
@pytest.mark.parametrize('project', ['MultiplayerSample'])
@pytest.mark.parametrize('spec', ['all'])
@pytest.mark.parametrize('level', ['multiplayersample'])
@pytest.mark.parametrize('number_of_launchers', [4])
class TestRunWindowsGameliftOnLinuxFleet(object):
"""
This test will connect 4 MultiplayerSample clients to a given fleet.
"""
def test_Gamelift_WindowsClientsConnectLinuxFleet_Workflow(self, request, multiplayer_client_launcher_list,
workspace):
def teardown():
# Gather all log files in cache/user/log
for file_name in os.listdir(workspace.paths.project_log()):
file_path = os.path.join(workspace.paths.project_log(), file_name)
try:
workspace.artifact_manager.save_artifact(file_path)
except(IOError, WindowsError):
logger.debug(f'A log could not be saved: {file_path}')
# If error or crash logs exist, move them to the artifact
file_utils.gather_error_logs(workspace)
# Required to use custom teardown()
request.addfinalizer(teardown)
# Delete previous logs
if os.path.exists(workspace.paths.project_log()):
shutil.rmtree(workspace.paths.project_log())
# Test setup
current_launcher_instance = 0
gamelift_credentials = GameliftUtils.get_aws_credentials(AWS_PROFILE)
# Get the fleet id
config = configparser.ConfigParser()
config.read(CONFIG_FILE)
try:
fleet_id = config[CONFIG_SECTION][CONFIG_FLEET_KEY]
except KeyError as e:
raise AssertionError(f'Cannot find section: {CONFIG_SECTION} or fleet key: {CONFIG_FLEET_KEY} in file: '
f'{CONFIG_FILE}. {e}')
host_launcher = multiplayer_client_launcher_list[0]
host_launcher['launcher'].args = ['+sv_port', '33435', '+gamelift_fleet_id', fleet_id,
'+gamelift_aws_access_key', gamelift_credentials['access'],
'+gamelift_aws_secret_key', gamelift_credentials['secret'],
'+gamelift_aws_region', REGION, '+gamelift_endpoint',
f'gamelift.{REGION}.amazonaws.com', '+map', 'gamelobby']
# Process Assets
assert workspace.asset_processor.batch_process(), 'Assets did not process correctly when calling AP Batch'
# Normally we start a launcher using with: which utilizes context managers to ensure teardown
# However we are using multiple launchers so we ensure launcher.stop() is called in custom teardown
host_launcher['launcher'].start()
# Start the remote console
host_launcher['remote_console'].start()
# Wait for the launcher to load
validate_level_loaded = "SetGlobalState 13->2 'LEVEL_LOAD_COMPLETE' -> 'RUNNING'"
host_launcher['remote_console'].expect_log_line(validate_level_loaded, TIMEOUT)
# Checks if the remote console port is listening
waiter.wait_for(lambda: network_utils.check_for_listening_port(host_launcher['rc_port']),
timeout=TIMEOUT,
exc=AssertionError(f'Port {host_launcher["rc_port"]} not listening.'))
# Set expected log line to assert
gamelift_host_event = host_launcher['remote_console'].\
expect_log_line('(GameLift) - Initialized GameLift client successfully.', TIMEOUT)
# Send the remote console command to host a multiplayersample game
host_launcher['remote_console'].\
send_command('gamelift_host AutomationTest multiplayersample 8')
assert gamelift_host_event(), 'Console never stated gamelift hosting was completed properly'
# Wait for the game to load
host_launcher['remote_console'].expect_log_line(validate_level_loaded, TIMEOUT)
host_launcher['remote_console'].stop()
current_launcher_instance += 1
# Assert on if our client connected
waiter.wait_for(lambda: GAMELIFTUTILS.check_all_current_player_sessions(fleet_id, current_launcher_instance),
timeout=TIMEOUT, interval=INTERVAL_TIME,
exc=AssertionError(f"Client {current_launcher_instance} never connected to GameLift"))
# Repeat with all other clients, except the console command is different
for launcher_dict in multiplayer_client_launcher_list[1:]:
launcher_dict['launcher'].args = ['+sv_port', '33435', '+gamelift_fleet_id', fleet_id,
'+gamelift_aws_access_key', gamelift_credentials['access'],
'+gamelift_aws_secret_key', gamelift_credentials['secret'],
'+gamelift_aws_region', REGION, '+gamelift_endpoint',
f'gamelift.{REGION}.amazonaws.com', '+gm_enableMetrics']
launcher_dict['launcher'].start()
launcher_dict['remote_console'].start()
launcher_dict['remote_console'].expect_log_line(validate_level_loaded, TIMEOUT)
launcher_dict['remote_console'].send_command('gamelift_join 1')
launcher_dict['remote_console'].expect_log_line(validate_level_loaded, TIMEOUT)
waiter.wait_for(
lambda: GAMELIFTUTILS.check_all_current_player_sessions(fleet_id, current_launcher_instance + 1),
timeout=TIMEOUT, interval=INTERVAL_TIME,
exc=AssertionError(f"Client {current_launcher_instance} never connected to GameLift"))
waiter.wait_for(lambda: network_utils.check_for_listening_port(launcher_dict['rc_port']),
timeout=TIMEOUT, exc=AssertionError(f'Port {launcher_dict["rc_port"]} not listening.'))
launcher_dict['remote_console'].stop()
current_launcher_instance += 1
current_launcher_instance = 0
for launcher_dict in multiplayer_client_launcher_list:
assert launcher_dict['launcher'].is_alive(), f"Client number: {current_launcher_instance} was closed " \
f"unexpectedly"
current_launcher_instance += 1
@@ -1,81 +0,0 @@
"""
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.
PRE-REQUISITES:
-machine is configured with AWS credentials (run aws configure)
-MultiplayerSample is built for the given platforms
-Dedicated servers are also built for the respective platforms
-Have LYTestTools installed - run in dev 'lmbr_test pysetup install ly_test_tools'
Part 1 of 3 for Linux Gamelift Automation:
Prepares a Linux Gamelift package and uploads it to S3
"""
import os
import pytest
import logging
import configparser
pytest.importorskip('boto3')
import boto3
pytest.importorskip("ly_test_tools")
# ly_test_tools dependencies
import ly_test_tools.builtin.helpers as helpers
# test level dependencies
from ...ly_shared import s3_utils, gamelift_utils, file_utils
from ..test_lib import networking_constants
logger = logging.getLogger(__name__)
AWS_PROFILE = networking_constants.DEFAULT_PROFILE
REGION = networking_constants.DEFAULT_REGION
CONFIG_SECTION = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_SECTION
CONFIG_FILENAME = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_FILENAME
CONFIG_TARFILE_KEY = networking_constants.LINUX_GAMELIFT_WORKFLOW_CONFIG_TARFILE_KEY
CONFIG_DIR = os.path.dirname(__file__) # assumes CONFIG_FILENAME is a sibling to this file
CONFIG_FILE = os.path.join(CONFIG_DIR, CONFIG_FILENAME)
BOTO3_SESSION = boto3.session.Session(profile_name=AWS_PROFILE, region_name=REGION)
S3_UTIL = s3_utils.S3Utils(BOTO3_SESSION)
@pytest.mark.parametrize('platform', ['win_x64_vs2017'])
@pytest.mark.parametrize('configuration', ['profile'])
@pytest.mark.parametrize('project', ['MultiplayerSample'])
@pytest.mark.parametrize('spec', ['all'])
class TestGameliftLinuxSetup(object):
"""
This test will create a Linux Gamelift package and upload it to s3
"""
def test_Gamelift_PrepareLinuxPackage_Workflow(self, request, workspace):
def teardown():
# Delete the locally generated tar file
if os.path.exists(tar_filepath):
os.remove(tar_filepath)
# Required to use custom teardown()
request.addfinalizer(teardown)
# Run paks script
gamelift_utils.GameliftUtils.run_multiplayer_sample_paks_pc_dedicated(workspace.paths.dev())
# Run linux packer script
tar_filepath = gamelift_utils.GameliftUtils.run_multiplayer_sample_linux_packer(workspace.paths.dev())
tar_filename = os.path.basename(tar_filepath)
# Upload to s3
S3_UTIL.upload_to_bucket(networking_constants.LINUX_TAR_BUCKET, tar_filepath)
# Write filename to .ini file to be utilized in next script
file_utils.clear_out_file(CONFIG_FILE)
config = configparser.ConfigParser()
config[CONFIG_SECTION] = {}
config[CONFIG_SECTION][CONFIG_TARFILE_KEY] = tar_filename
with open(CONFIG_FILE, 'w') as config_to_write:
config.write(config_to_write)
-10
View File
@@ -1,10 +0,0 @@
"""
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.
"""
@@ -1,99 +0,0 @@
"""
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.
"""
import os
DEFAULT_REGION = 'us-west-2'
DEFAULT_PROFILE = 'LYQA-Gamelift'
LINUX_TAR_BUCKET = 'ly-gamelift-linux-packages'
DEFAULT_LINUX_WORKSPACE = os.path.join('/home', 'ubuntu', 'workspace')
LINUX_GAMELIFT_WORKFLOW_CONFIG_FILENAME = 'gamelift_workflow_config.ini'
LINUX_GAMELIFT_WORKFLOW_CONFIG_SECTION = 'LinuxGameLiftTest'
LINUX_GAMELIFT_WORKFLOW_CONFIG_TARFILE_KEY = 'TAR_FILENAME'
LINUX_GAMELIFT_WORKFLOW_CONFIG_FLEET_KEY = 'FLEET_ID'
WINDOWS_GAMELIFT_FLEET = {
'fleet_name': 'WindowsGameliftFleet',
'ec2_instance_type': 'c4.large',
'runtime_configuration': {
'ServerProcesses': [
{
'LaunchPath': None, # Override this value with os.path.join("C:", "game", <BIN.DEDICATED_DIR>,
# "MultiplayerSampleLauncher_Server.exe")
'Parameters': '+sv_port 33435 +gamelift_start_server',
'ConcurrentExecutions': 1,
},
{
'LaunchPath': None,
'Parameters': '+sv_port 33436 +gamelift_start_server',
'ConcurrentExecutions': 1,
},
{
'LaunchPath': None,
'Parameters': '+sv_port 33437 +gamelift_start_server',
'ConcurrentExecutions': 1,
}
]
},
'ec2_inbound_permissions': [ # Ports & IP's for clients to connect
{
'FromPort': 33435,
'ToPort': 33437,
'IpRange': '0.0.0.0/0', # Override this value with client IP
'Protocol': 'UDP',
}
]
}
WINDOWS_CUSTOMBACKFILL_GAMELIFT_FLEET = {
'fleet_name': 'WindowsGameliftFleetCustomBackfill',
'ec2_instance_type': 'c4.large',
'runtime_configuration': {
'ServerProcesses': [
{
'LaunchPath': None, # Override this value with os.path.join("C:", "game", <BIN.DEDICATED_DIR>,
# "MultiplayerSampleLauncher_Server.exe")
'Parameters': '+sv_port 33435 +gamelift_start_server +gamelift_flexmatch_enable 1 '
'+gamelift_flexmatch_onplayerremoved_enable 1 '
'+gamelift_flexmatch_minimumplayersessioncount 2',
'ConcurrentExecutions': 1,
}
]
},
'ec2_inbound_permissions': [ # Ports & IP's for clients to connect
{
'FromPort': 33435,
'ToPort': 33435,
'IpRange': '0.0.0.0/0', # Override this value with client IP
'Protocol': 'UDP',
}
]
}
LINUX_GAMELIFT_FLEET = {
'fleet_name': 'LinuxGameliftFleet',
'ec2_instance_type': 'c5.large',
'runtime_configuration': {
"GameSessionActivationTimeoutSeconds": 300,
"MaxConcurrentGameSessionActivations": 3,
"ServerProcesses": [
{
"LaunchPath": "/local/game/./MultiplayerSampleLauncher_Server",
"Parameters": "+sv_port 33435 +map multiplayersample +gamelift_start_server true",
"ConcurrentExecutions": 1
}
]
},
'ec2_inbound_permissions': [ # Ports & IP's for clients to connect
{
'FromPort': 33435,
'ToPort': 33437,
'IpRange': '0.0.0.0/0', # Override this value with client IP
'Protocol': 'UDP',
}
]
}
-10
View File
@@ -1,10 +0,0 @@
"""
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.
"""
-547
View File
@@ -1,547 +0,0 @@
"""
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.
PRE-REQUISITES:
-machine is configured with AWS credentials (run aws configure)
-AWS default output format is set to 'json'
-MultiplayerSample is built for the given platforms
-Dedicated servers are also built for the respective platforms
-Have LYTestTools installed - run in dev 'lmbr_test pysetup install ly_test_tools'
All of these tests will test the ability for 4 clients ability to attach to a Gamelift server instance. We will also
check that all of the game clients are alive and connected by the end of the tests. If an assertion happens we will
gather all of the artifacts and move them into an artifact folder which will eventually be consumed by Flume.
"""
import os
import pytest
import shutil
import time
import logging
pytest.importorskip("boto3")
import boto3
pytest.importorskip("ly_test_tools")
# ly_test_tools dependencies
import ly_test_tools.builtin.helpers as helpers
import ly_test_tools.environment.waiter as waiter
import ly_test_tools.launchers.launcher_helper as launcher_helper
import ly_remote_console.remote_console_commands
# test level dependencies
from ...ly_shared import file_utils, network_utils
from ...ly_shared.gamelift_utils import GameliftUtils
from ..test_lib import networking_constants
import uuid
logger = logging.getLogger(__name__)
INTERVAL_TIME = 10 # seconds for api calls against aws
AWS_PROFILE = networking_constants.DEFAULT_PROFILE
REGION = networking_constants.DEFAULT_REGION
BOTO3_SESSION = boto3.session.Session(profile_name=AWS_PROFILE, region_name=REGION)
FLEET_TIMEOUT = 3600
gamelift_utils = GameliftUtils(BOTO3_SESSION)
AWS_REGION = 'us-west-2'
@pytest.fixture
def remote_console_instances(request,
number_of_launchers # type: int
):
"""
Creates a list of RemoteConsole objects
:param number_of_launchers: The number of launcher instances
:return: A list of RemoteConsole objects
"""
# Each launcher has its own remote console
remote_console_list = []
for x in range(0, number_of_launchers):
remote_console_list.append(ly_remote_console.remote_console_commands.RemoteConsole(port=4600+x))
def teardown():
for console in remote_console_list:
if console.connected:
console.stop()
request.addfinalizer(teardown)
return remote_console_list
@pytest.fixture
def multiplayer_client_launcher_list(
request,
workspace, # type: test_tools.managers.workspace.AbstractWorkspaceManager
level, # type: str
number_of_launchers, # type: int
):
# type: (...) -> test_tools.launchers.platforms.base.Launcher
"""
Create a list of simple launchers.
:param workspace: The workspace where the launcher is located
:param level: The level name to load
:param number_of_launchers: The number of launcher instances
:return: A fully configured list of launchers
"""
launcher_list = []
# Create a list of launchers
for _ in range(0, number_of_launchers):
launcher_list.append(launcher_helper.create_launcher(workspace))
def teardown():
# Gather all log files in cache/user/log
try:
for file_name in os.listdir(workspace.paths.project_log()):
file_path = os.path.join(workspace.paths.project_log(), file_name)
try:
workspace.artifact_manager.save_artifact(file_path)
except(IOError, WindowsError):
logger.info('A log could not be saved: {}'.format(file_path))
except:
logger.info("No log folder found for path: {}".format(workspace.paths.project_log()))
# If error or crash logs exist, move them to the artifact
file_utils.gather_error_logs(workspace)
# Custom teardown of multiple launchers because we cannot use context manager
for client in launcher_list:
try:
client.stop()
except AssertionError:
logger.info("A launcher could not be shut down!")
request.addfinalizer(teardown)
return launcher_list
def add_game_lift_creds_and_endpoint_args(launcher, gamelift_credentials):
launcher.args.extend(['+gamelift_aws_access_key', gamelift_credentials['access'],
'+gamelift_aws_secret_key', gamelift_credentials['secret'],
'+gamelift_aws_region', AWS_REGION,
'+gamelift_endpoint', 'gamelift.{}.amazonaws.com'.format(AWS_REGION)])
def create_game_lift_player_sessions(fleet_id, gamelift_credentials, timeout,
remote_console_instances, multiplayer_client_launcher_list):
"""
Creates Gamelift player sessions. Connects the MultiplayerSample game client to game sessions using remote console
gamelift_join command. The command supports joining first game session found in the list of game sessions from
GameLift
:param fleet_id: Fleet to create player sessions
:param gamelift_credentials: Credentials to use to make GameLift calls
:param timeout: Timeout for waiter calls
:param remote_console_instances: Remote console instances to work with
:param multiplayer_client_launcher_list: Client launcher instances to work with
:return:
"""
# First 2 instances are reserved for creating game sessions. 3 and 4 instance of the launcher and remote console instances
# are used for player sessions
current_launcher_instance = 2
while current_launcher_instance < 4:
launcher = multiplayer_client_launcher_list[current_launcher_instance]
launcher.args = []
add_game_lift_creds_and_endpoint_args(launcher, gamelift_credentials)
launcher.args.extend(['+gamelift_fleet_id', fleet_id, '+gm_enableMetrics'])
launcher.start()
# Hard wait for the clients to initialize. Hoping 10secs is enough on the worst machines as well.
time.sleep(10)
join_game_session_index = current_launcher_instance % 2 + 1
remote_console_instances[current_launcher_instance].start()
remote_console_instances[current_launcher_instance].send_command('gamelift_join {}'
.format(join_game_session_index))
waiter.wait_for(lambda: network_utils.check_for_listening_port(4600 + current_launcher_instance),
timeout=timeout,
exc=AssertionError('Port 460{} not listening.'.format(current_launcher_instance)))
remote_console_instances[current_launcher_instance].stop()
current_launcher_instance += 1
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, current_launcher_instance),
timeout=timeout, interval=5,
exc=AssertionError("Client {} never connected".format(current_launcher_instance)))
def create_gamelift_flexmatch_game_and_player_sessions(matchmaking_config_name, fleet_id, gamelift_credentials, timeout,
remote_console_instances, multiplayer_client_launcher_list):
# First 2 instances are reserved for creating game sessions. 3 and 4 for player sessions.
# 5 and 6 instance are used for flex match.
# are used for player sessions
current_launcher_instance = 4
multiplayer_client_launcher_count = len(multiplayer_client_launcher_list)
while current_launcher_instance < multiplayer_client_launcher_count:
launcher = multiplayer_client_launcher_list[current_launcher_instance]
launcher.args = []
add_game_lift_creds_and_endpoint_args(launcher, gamelift_credentials)
launcher.start()
# Hard wait for the clients to initialize. Hoping 10secs is enough on the worst machines as well.
time.sleep(10)
remote_console_instances[current_launcher_instance].start()
waiter.wait_for(lambda: network_utils.check_for_listening_port(4600 + current_launcher_instance),
timeout=timeout,
exc=AssertionError('Port 460{} not listening.'.format(current_launcher_instance)))
current_launcher_instance += 1
current_launcher_instance = 4
while current_launcher_instance < multiplayer_client_launcher_count:
remote_console_instances[current_launcher_instance].send_command('gamelift_flexmatch {}'
.format(matchmaking_config_name))
remote_console_instances[current_launcher_instance].stop()
current_launcher_instance += 1
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, multiplayer_client_launcher_count),
timeout=timeout, interval=5,
exc=AssertionError("Client 5 and 6 never connected"))
def create_gamelift_custom_flexmatch_game_and_player_sessions(matchmaking_config_name, fleet_id, gamelift_credentials, timeout,
remote_console_instances, multiplayer_client_launcher_list):
current_launcher_instance = 0
multiplayer_client_launcher_count = len(multiplayer_client_launcher_list)
# Initialized all 4 clients
while current_launcher_instance < multiplayer_client_launcher_count:
launcher = multiplayer_client_launcher_list[current_launcher_instance]
launcher.args = []
add_game_lift_creds_and_endpoint_args(launcher, gamelift_credentials)
launcher.start()
# Hard wait for the clients to initialize. Setting to 10s to allow slowest machines to connect
time.sleep(10)
remote_console_instances[current_launcher_instance].start()
waiter.wait_for(lambda: network_utils.check_for_listening_port(4600 + current_launcher_instance),
timeout=timeout,
exc=AssertionError('Port 460{} not listening.'.format(current_launcher_instance)))
current_launcher_instance += 1
# 2 clients request matchmaking
current_launcher_instance = 0
while current_launcher_instance < 2:
remote_console_instances[current_launcher_instance].send_command('gamelift_flexmatch {}'
.format(matchmaking_config_name))
current_launcher_instance += 1
# for initial delay
time.sleep(timeout)
# Add 1 more client
remote_console_instances[current_launcher_instance].send_command('gamelift_flexmatch {}'
.format(matchmaking_config_name))
# for initial delay
time.sleep(timeout)
current_launcher_instance += 1
# Add 1 more client
remote_console_instances[current_launcher_instance].send_command('gamelift_flexmatch {}'
.format(matchmaking_config_name))
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, multiplayer_client_launcher_count),
timeout=timeout, interval=5,
exc=AssertionError('Client never connected {}'.format(multiplayer_client_launcher_count)))
# Disconnect 1 client
current_launcher_instance = 0
remote_console_instances[current_launcher_instance].send_command('mpdisconnect')
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, multiplayer_client_launcher_count-1),
timeout=timeout, interval=5,
exc=AssertionError("Client failed to disconnect"))
time.sleep(20)
# Reconnect disconnected client as new player
remote_console_instances[current_launcher_instance].send_command('gamelift_flexmatch {}'
.format(matchmaking_config_name))
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, multiplayer_client_launcher_count),
timeout=timeout, interval=5,
exc=AssertionError("Client never reconnected"))
# Disconnect 3 clients. Game Session should end.
current_launcher_instance = 1
while current_launcher_instance < multiplayer_client_launcher_count:
remote_console_instances[current_launcher_instance].send_command('mpdisconnect')
current_launcher_instance += 1
waiter.wait_for(
lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id, 0),
timeout=timeout, interval=5,
exc=AssertionError("Client did not disconnect"))
def create_gamelift_game_sessions(fleet_id, queue_name, gamelift_credentials, timeout,
remote_console_instances, multiplayer_client_launcher_list):
"""
Creates Gamelift game sessions. Connects the current clients to the created game sessions.
:param fleet_id: Fleet to create game sessions on
:param gamelift_credentials: Credentials to use to make GameLift calls
:param timeout: Timeout for waiter calls
:param remote_console_instances: Remote console instances to work with
:param multiplayer_client_launcher_list: Client launcher instances to work with
:return:
"""
# First 2 launcher and remote console instances are reserved for creating game sessions
current_launcher_instance = 0
while current_launcher_instance < 2:
launcher = multiplayer_client_launcher_list[current_launcher_instance]
launcher.args = []
add_game_lift_creds_and_endpoint_args(launcher, gamelift_credentials)
launcher.args.extend(['+map', 'gamelobby'])
if current_launcher_instance == 0:
launcher.args.extend(['+gamelift_fleet_id', fleet_id, '+sv_port', '33435'])
else:
launcher.args.extend(['+gamelift_queue_name', queue_name, '+sv_port', '33436'])
# Normally we start a launcher using with: which utilizes context managers to ensure teardown
# However we are using multiple launchers so we ensure launcher.stop() is called in custom teardown
launcher.start()
# Hard wait for the clients to initialize. Hoping 10secs is enough on the worst machines as well.
time.sleep(10)
# Checks if the port is listening
waiter.wait_for(lambda: network_utils.check_for_listening_port(4600 + current_launcher_instance),
timeout=timeout,
exc=AssertionError('Port 460{} not listening.'.format(current_launcher_instance)))
# Start the remote console
remote_console_instances[current_launcher_instance].start()
# Set expected log line to assert
gamelift_host_event = remote_console_instances[current_launcher_instance].\
expect_log_line('(GameLift) - Initialized GameLift client successfully.', 90)
# Send the remote console command to host a multiplayersample game
remote_console_instances[current_launcher_instance].\
send_command('gamelift_host AutomationTest multiplayersample 8')
assert gamelift_host_event(), 'Console never stated gamelift hosting was completed properly'
current_launcher_instance += 1
# Assert on if our client connected
waiter.wait_for(lambda: gamelift_utils.check_total_players_in_all_game_sessions(fleet_id,
current_launcher_instance),
timeout=timeout,
interval=5,
exc=AssertionError("Client {} never connected".format(current_launcher_instance)))
@pytest.mark.parametrize('platform', ['win_x64_vs2017'])
@pytest.mark.parametrize('configuration', ['profile'])
@pytest.mark.parametrize('project', ['MultiplayerSample'])
@pytest.mark.parametrize('spec', ['all'])
@pytest.mark.parametrize('level', ['multiplayersample'])
@pytest.mark.parametrize('number_of_launchers', [6])
class TestRunWindowsGamelift(object):
"""
This test will upload a build to Gamelift, creates a GameLift fleet and queue.
Deploys 2 game sessions one directly on the fleet using fleetId and other using the queue (Queue name).
1 game session created using Automatic backfill mode.
It then connects clients between these 2 sessions. Half clients are connected using fleetId and
other half using queueName
Since MultiplayerSample is implemented on the client side to connect to first game session found from the list
returned by GameLift describe game sessions and additionally GameLift response is non-deterministic(order),
what is being verified is that across 2 game sessions total number of players can be tallied.
"""
def test_RunWindowsGamelift(self, request, configuration, remote_console_instances,
multiplayer_client_launcher_list, workspace):
# debug takes longer to load
TIMEOUT = 60
if configuration == 'debug':
TIMEOUT = 120
# Delete previous logs
if os.path.exists(workspace.paths.project_log()):
try:
shutil.rmtree(workspace.paths.project_log())
except Exception as e:
logger.error(e)
# Test setup
gamelift_credentials = GameliftUtils.get_aws_credentials(AWS_PROFILE)
file_utils.clear_out_file(os.path.join(workspace.paths.project(), 'initialmap.cfg'))
workspace.configuration = configuration
# Process Assets
process_assets_success = workspace.asset_processor.batch_process()
assert process_assets_success, 'Assets did not process correctly'
# Create PC Gamelift package
gamelift_utils.create_gamelift_package(workspace.paths.dev(), workspace.paths._find_bin_dir())
# Upload Gamelift build
build_root = os.path.join(workspace.paths.dev(), 'MultiplayerSample_pc_Paks_Dedicated')
gamelift_build_id = GameliftUtils.upload_build_to_gamelift(build_root, 'WindowsGameliftTest', 'WINDOWS_2012',
REGION)
# Assert that the build uploaded successfully
waiter.wait_for(lambda: gamelift_utils.is_build_status_ready(gamelift_build_id),
timeout=TIMEOUT, interval=INTERVAL_TIME,
exc=AssertionError("Gamelift build never became active"))
# Create a Gamelift fleet
runtime_configuration = networking_constants.WINDOWS_GAMELIFT_FLEET['runtime_configuration']
ec2_instance_type = networking_constants.WINDOWS_GAMELIFT_FLEET['ec2_instance_type']
fleet_name = networking_constants.WINDOWS_GAMELIFT_FLEET['fleet_name']
build_dir = "{}.Dedicated".format(workspace.paths._find_bin_dir())
for server_process in runtime_configuration['ServerProcesses']:
server_process['LaunchPath'] = os.path.join("C:\\", "game", build_dir,
"MultiplayerSampleLauncher_Server.exe")
# IPs should be manually set when testing locally, or set to clients' IPs when automated.
ec2_inbound_permissions = networking_constants.WINDOWS_GAMELIFT_FLEET['ec2_inbound_permissions']
fleet_id, fleet_arn = gamelift_utils.create_gamelift_fleet(name=fleet_name,
build_id=gamelift_build_id,
ec2_instance_type=ec2_instance_type,
runtime_configuration=runtime_configuration,
ec2_inbound_permissions=ec2_inbound_permissions
)
# Assert that the fleet starts successfully
waiter.wait_for(lambda: gamelift_utils.check_instance_state(fleet_id), timeout=FLEET_TIMEOUT,
interval=INTERVAL_TIME, exc=AssertionError("Gamelift server never became active"))
queue_name = 'MSTest-{}'.format(str(uuid.uuid4()))
# Create GameLift queue
queue_arn = gamelift_utils.create_gamelift_queue(queue_name, fleet_arn)
# Create GameLift matchmaking ruleset
rule_set_name = 'MSTest-{}'.format(str(uuid.uuid4()))
gamelift_utils.create_gamelift_matchmaking_rule_set(rule_set_name)
# Create GameLift matchmaking configuration
matchmaking_config_name = 'MSTest-{}'.format(str(uuid.uuid4()))
gamelift_utils.create_gamelift_matchmaking_config(matchmaking_config_name, queue_arn, rule_set_name,
'AUTOMATIC')
create_gamelift_game_sessions(fleet_id, queue_name, gamelift_credentials, TIMEOUT,
remote_console_instances, multiplayer_client_launcher_list)
# Test clients connecting to game lift servers.
create_game_lift_player_sessions(fleet_id, gamelift_credentials, TIMEOUT,
remote_console_instances, multiplayer_client_launcher_list)
# Test flex match creating game session and players connecting to the created flex match game session
create_gamelift_flexmatch_game_and_player_sessions(matchmaking_config_name, fleet_id, gamelift_credentials,
TIMEOUT, remote_console_instances,
multiplayer_client_launcher_list)
# Verify all clients are alive.
for launcher in multiplayer_client_launcher_list:
assert launcher.is_alive(), "A process was closed unexpectedly"
@pytest.mark.parametrize('platform', ['win_x64_vs2017'])
@pytest.mark.parametrize('configuration', ['profile'])
@pytest.mark.parametrize('project', ['MultiplayerSample'])
@pytest.mark.parametrize('spec', ['all'])
@pytest.mark.parametrize('level', ['multiplayersample'])
@pytest.mark.parametrize('number_of_launchers', [4])
class TestRunWindowsCustomBackfillGamelift(object):
"""
This test will upload a build to Gamelift, creates a GameLift fleet and queue. 1 game session is created using
custom match backfill.
Test players connecting and disconnecting the game session.
"""
def test_RunWindowsGamelift_Custombackfill(self, request, configuration, remote_console_instances,
multiplayer_client_launcher_list, workspace):
# debug takes longer to load
TIMEOUT = 60
if configuration == 'debug':
TIMEOUT = 120
# Test setup
gamelift_credentials = GameliftUtils.get_aws_credentials(AWS_PROFILE)
file_utils.clear_out_file(os.path.join(workspace.paths.project(), 'initialmap.cfg'))
workspace.configuration = configuration
# Process Assets
process_assets_success = workspace.asset_processor.batch_process()
assert process_assets_success, 'Assets did not process correctly'
# Create PC Gamelift package
gamelift_utils.create_gamelift_package(workspace.paths.dev(), workspace.paths._find_bin_dir())
# Upload Gamelift build
build_root = os.path.join(workspace.paths.dev(), 'MultiplayerSample_pc_Paks_Dedicated')
gamelift_build_id = GameliftUtils.upload_build_to_gamelift(build_root, 'WindowsGameliftTest', 'WINDOWS_2012',
REGION)
# Assert that the build uploaded successfully
waiter.wait_for(lambda: gamelift_utils.is_build_status_ready(gamelift_build_id),
timeout=TIMEOUT, interval=INTERVAL_TIME,
exc=AssertionError("Gamelift build never became active"))
# Create a Gamelift fleet
runtime_configuration = networking_constants.WINDOWS_CUSTOMBACKFILL_GAMELIFT_FLEET['runtime_configuration']
ec2_instance_type = networking_constants.WINDOWS_CUSTOMBACKFILL_GAMELIFT_FLEET['ec2_instance_type']
fleet_name = networking_constants.WINDOWS_CUSTOMBACKFILL_GAMELIFT_FLEET['fleet_name']
build_dir = "{}.Dedicated".format(workspace.paths._find_bin_dir())
for server_process in runtime_configuration['ServerProcesses']:
server_process['LaunchPath'] = os.path.join("C:\\", "game", build_dir,
"MultiplayerSampleLauncher_Server.exe")
# IPs should be manually set when testing locally, or set to clients' IPs when automated.
ec2_inbound_permissions = networking_constants.WINDOWS_GAMELIFT_FLEET['ec2_inbound_permissions']
fleet_id, fleet_arn = gamelift_utils.create_gamelift_fleet(name=fleet_name,
build_id=gamelift_build_id,
ec2_instance_type=ec2_instance_type,
runtime_configuration=runtime_configuration,
ec2_inbound_permissions=ec2_inbound_permissions
)
# Assert that the fleet starts successfully
waiter.wait_for(lambda: gamelift_utils.check_instance_state(fleet_id), timeout=FLEET_TIMEOUT,
interval=INTERVAL_TIME, exc=AssertionError("Gamelift server never became active"))
queue_name = 'MSTest-{}'.format(str(uuid.uuid4()))
# Create GameLift queue
queue_arn = gamelift_utils.create_gamelift_queue(queue_name, fleet_arn)
# Create GameLift matchmaking ruleset
rule_set_name = 'MSTest-{}'.format(str(uuid.uuid4()))
gamelift_utils.create_gamelift_matchmaking_rule_set(rule_set_name)
# Create GameLift matchmaking configuration
matchmaking_config_name = 'MSTest-{}'.format(str(uuid.uuid4()))
gamelift_utils.create_gamelift_matchmaking_config(matchmaking_config_name, queue_arn, rule_set_name,
'MANUAL')
# Test flex match creating game session and players connecting to the created custome flex match game session
create_gamelift_custom_flexmatch_game_and_player_sessions(matchmaking_config_name, fleet_id, gamelift_credentials,
TIMEOUT, remote_console_instances,
multiplayer_client_launcher_list)
# Verify all clients are alive.
for launcher in multiplayer_client_launcher_list:
assert launcher.is_alive(), "A process was closed unexpectedly"
@@ -1,76 +0,0 @@
"""
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.
"""
import logging
import gamelift_utils
logger = logging.basicConfig(filename='test.log', filemode='w', level=logging.DEBUG)
def main():
"""
This script cleans up the builds and fleets from gamelift. The main method retrieves a list of build and fleet
ids, and then sends a console command to delete them. The script will exclude any builds and fleets that are
currently in use. It will then validate to make sure all non excluded builds and fleets are deleted.
"""
exclude_fleet_list = gamelift_utils.get_in_use_fleet_list_from_gamelift()
fleet_list = gamelift_utils.get_fleet_list_from_gamelift(exclude_fleet_list)
exclude_build_list = gamelift_utils.get_in_use_build_list_from_gamelift()
build_list = gamelift_utils.get_build_list_from_gamelift(exclude_build_list)
for fleet in fleet_list:
gamelift_utils.delete_fleet_from_gamelift(fleet)
assert validate_fleet_list_deleted(exclude_fleet_list), 'Fleet list was not deleted properly!'
for build in build_list:
gamelift_utils.delete_build_from_gamelift(build)
assert validate_build_list_deleted(exclude_build_list), 'Build list was not deleted properly!'
def validate_build_list_deleted(exclude_build_list):
"""
Validates whether the builds were deleted correctly
:param exclude_build_list: a list of builds that should not be deleted
:return: returns True if the builds are deleted correctly
"""
build_is_empty = True
build_list = gamelift_utils.get_build_list_from_gamelift()
for build in build_list:
if build not in exclude_build_list:
build_is_empty = False
break
return build_is_empty
def validate_fleet_list_deleted(exclude_fleet_list):
"""
Validates whether the fleets were deleted correctly
:param exclude_fleet_list: a list of fleets that should not be deleted
:return: returns True if the fleets are deleted correctly
"""
fleet_is_empty = True
fleet_attribute_list = gamelift_utils.get_fleet_list_attributes_from_gamelift()
for fleet_attrib in fleet_attribute_list:
if (fleet_attrib['Status'] not in ["DELETING"]) and (fleet_attrib['FleetId'] not in exclude_fleet_list):
fleet_is_empty = False
break
return fleet_is_empty
if __name__ == '__main__':
main()
-486
View File
@@ -1,486 +0,0 @@
"""
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.
"""
import ConfigParser
import json
import subprocess
import shutil
from s3_utils import *
logger = logging.getLogger(__name__)
def get_aws_credentials():
"""
Gets credentials from .aws/credentials file. QA will setup our nodes using one default set for Isengard
:return: Dictionary of AWS Credentials gathered
"""
aws_credentials = {}
home = os.path.expanduser("~")
IniRead = ConfigParser.ConfigParser()
IniRead.read('{}\.aws\credentials'.format(home))
aws_credentials['access'] = IniRead.get('default', 'aws_access_key_id')
aws_credentials['secret'] = IniRead.get('default', 'aws_secret_access_key')
return aws_credentials
def upload_build_to_gamelift(dev_path):
"""
Function to upload a build to gamelift
:param dev_path: Path to the dev folder of Lumberyard
Example command:
aws Gamelift upload-build --name "1.11 pc-47 (profile)" --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', '--name', dev_path, '--build-version', '1.0.0',
'--operating-system',
'WINDOWS_2012', '--build-root',
r'{}\MultiplayerSample_pc_Paks_Dedicated'.format(dev_path),
'--region', 'us-west-1']
proc = subprocess.Popen(upload_build_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=dev_path)
lines = []
for stdout_line in iter(proc.stdout.readline, ''):
lines.append(stdout_line)
proc.stdout.close()
assert ('Successfully uploaded' in lines[0]), "Not successfully uploaded"
build_id = lines[1].split(" ")[-1:][0].rstrip()
logger.info(build_id)
return build_id
def create_gamelift_fleet(dev_path, build_id, build_dir):
"""
Function to create a fleet within Gamelift
:param dev_path: Path to the dev folder of Lumberyard
:param build_id: The id of the build given back to us from Gamelift when we uploaded a build
:param build_dir: The directory where the build exists
:return: ID of the fleet that was created
"""
upload_build_cmd = ['aws', 'gamelift', 'create-fleet', '--name', dev_path, '--description',
'Automated Testing for Networking', '--build-id', build_id,
'--ec2-instance-type', 'c3.large', '--runtime-configuration',
'ServerProcesses=[{'
+ 'LaunchPath=C:\game\{}\MultiplayerSampleLauncher_Server.exe'.format(build_dir) +
',Parameters=+sv_port 33435 '
'+gamelift_start_server,ConcurrentExecutions=1}]',
'--ec2-inbound-permissions', 'FromPort=33435,ToPort=33436,IpRange=0.0.0.0/0,Protocol=UDP',
'FromPort=3389,ToPort=3389,IpRange=0.0.0.0/0,Protocol=TCP', '--log-paths', 'c:\game\user\log',
'--region', 'us-west-1']
output, _ = subprocess.Popen(upload_build_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=dev_path).communicate()
logger.info(output)
try:
fleet_id = json.loads(output)['FleetAttributes']['FleetId']
except ValueError:
raise AssertionError('Json value not returned when executing upload, build')
return fleet_id
def check_instance_state(dev_path, fleet_id):
"""
Checked the state of an instance. Returns true when the instance is in an Active state
:param dev_path: Path to the dev folder of Lumberyard
: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 = get_fleet_list_attributes_from_gamelift(fleet_id)
try:
if len(fleet_attribute) == 0:
return False
elif fleet_attribute[0]['Status'] == 'ACTIVE':
return True
except ValueError:
raise AssertionError('Json value not returned when executing upload, build')
return False
def check_current_player_sessions(fleet_id, number_of_players):
"""
Checks for the number of players inside a game session
:param dev_path: Path to the dev folder of Lumberyard
: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_session = get_game_session_from_gamelift(fleet_id)
if len(game_session) == 0:
return False
elif game_session[0]['CurrentPlayerSessionCount'] == number_of_players:
return True
def run_multiplayer_sample_paks_pc_dedicated(dev_path):
"""
Runs a bat script which will package up the items needed for Gamelift
:param dev_path: Path to the dev folder of Lumberyard
"""
cmd = [dev_path + r'\BuildMultiplayerSample_Paks_PC_dedicated.bat']
try:
proc = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=dev_path)
for stdout_line in iter(proc.stdout.readline, ''):
logger.info(stdout_line)
proc.stdout.close()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'Script to make PC Paks for Gamelift failed'
def copy_bin_folder(bin_directory, dev_path):
"""
Copies bin folder into the MultiplayerSamples_pc_Paks_Dedicated folder for Windows Gamelift Builds
:param bin_directory:
:param dev_path:
:return: None
"""
to_folder = dev_path + '\\MultiplayerSample_pc_Paks_Dedicated\\' + bin_directory
if os.path.exists(to_folder):
shutil.rmtree(to_folder, onerror=onerror)
shutil.copytree(os.path.join(dev_path, bin_directory), to_folder)
def download_required_gamelift_files(destination_dir):
"""
Downloads required vc redistributables and their installation script from S3.
:param destination_dir: Path on disk to be checked.
"""
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:
download_from_bucket(gamelift_files_bucket, file_key, destination_dir)
def create_gamelift_package(dev_path, bin_directory):
"""
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
"""
run_multiplayer_sample_paks_pc_dedicated(dev_path)
download_required_gamelift_files(dev_path + r'\MultiplayerSample_pc_Paks_Dedicated')
copy_bin_folder("{}.Dedicated".format(bin_directory), dev_path)
def onerror(func, path, exc_info):
"""
Error handler for ``shutil.rmtree``.
If the error is due to an access error (read only file)
it attempts to add write permission and then retries.
If the error is for another reason it re-raises the error.
"""
import stat
if not os.access(path, os.W_OK):
# Is the error an access error ?
os.chmod(path, stat.S_IWUSR)
func(path)
else:
raise
def get_game_session_from_gamelift(fleet_id):
get_game_session_cmd = ['aws', 'gamelift', 'describe-game-sessions', '--fleet-id', fleet_id]
try:
output, _ = subprocess.Popen(get_game_session_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_game_session_from_gamelift command failed'
logger.info(output)
try:
game_session = json.loads(output)['GameSessions']
except ValueError:
raise AssertionError('Json values not returned when executing get_game_session_cmd command')
# Return None if there is no active game_session for the fleet
return game_session
def get_build_id_from_fleet_id(fleet_id):
get_build_from_id_cmd = ['aws', 'gamelift', 'describe-fleet-attributes', '--fleet-id', fleet_id]
try:
output, _ = subprocess.Popen(get_build_from_id_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_build_id_from_fleet_id command failed'
logger.info(output)
try:
fleet_attribute = json.loads(output)['FleetAttributes']
build_id = fleet_attribute[0]['BuildId']
except ValueError:
raise AssertionError('Json values not returned when executing get_build_id_from_fleet_id command')
return build_id
def get_exclude_build_list_from_gamelift():
"""
Function to get the excluded builds and return them to a new list. Excluded builds include those that are being
initalized and if an excluded fleet is using that build
:return: list
"""
exclude_build_list = []
exclude_fleet_list = get_exclude_fleet_list_from_gamelift()
for fleet in exclude_fleet_list:
exclude_build_list.append(get_build_id_from_fleet_id(fleet))
build_list = get_build_list_from_gamelift()
for build_id in build_list:
get_describe_build_list_cmd = ['aws', 'gamelift', 'describe-build', '--build-id', build_id]
try:
output, error = subprocess.Popen(get_describe_build_list_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, ).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_describe_build_list_cmd command failed'
try:
build = json.loads(output)['Build']
if build['Status'] == 'INITIALIZED':
exclude_build_list.append(build['BuildId'])
except ValueError:
raise AssertionError('Json values not returned when executing get_describe_build_list_cmd command')
return exclude_build_list
def get_exclude_fleet_list_from_gamelift():
"""
Function to remove the excluded fleets and return them to a new list. Excluded fleets include those that are being
initialized or currently in use in an active game session.
:return: list
"""
exclude_fleet_list = []
fleet_list = get_fleet_list_from_gamelift()
fleet_attribute_list = get_fleet_list_attributes_from_gamelift()
for fleet_attrib in fleet_attribute_list:
if fleet_attrib['Status'] in ["DOWNLOADING", "ACTIVATING", "NEW", "VALIDATING", "BUILDING"]:
exclude_fleet_list.append(fleet_attrib['FleetId'])
for fleet_id in fleet_list:
game_session = get_game_session_from_gamelift(fleet_id)
if len(game_session) != 0 and game_session[0]['CurrentPlayerSessionCount'] > 0:
exclude_fleet_list.append(fleet_id)
return exclude_fleet_list
def get_fleet_list_from_gamelift(exclude_fleet_list=[]):
"""
Function to retrieve all fleet Id's from gamelift that are not in the excluded list. If no list is provided, it will
return all fleet Id's
:return: list
"""
get_fleet_list_cmd = ['aws', 'gamelift', 'list-fleets']
try:
output, error = subprocess.Popen(get_fleet_list_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_fleet_list_cmd command failed'
logger.info(output)
next_token = None
try:
next_token = json.loads(output)['NextToken']
except KeyError:
pass
# If the NextToken exists take it, otherwise ignore that we don't have one
try:
all_fleet_list = json.loads(output)['FleetIds']
if next_token is not None:
get_next_fleet_list_cmd = ['aws', 'gamelift', 'list-fleets', '--next-token', next_token]
try:
output, error = subprocess.Popen(get_next_fleet_list_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, ).communicate()
next_fleet_list = json.loads(output)['FleetIds']
all_fleet_list += next_fleet_list
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_next_fleet_list_cmd command failed'
logger.info(output)
except ValueError:
raise AssertionError('Json values not returned when executing get_fleet_list_cmd command')
fleet_list = []
for fleet in all_fleet_list:
if fleet not in exclude_fleet_list:
fleet_list.append(fleet)
return fleet_list
def get_build_list_from_gamelift(exclude_build_list=[]):
"""
Function to retrieve all build ID's from gamelift that are not in the excluded list. If no list is provided, it will
return all build Id's
:return: list
"""
get_build_list_cmd = ['aws', 'gamelift', 'list-builds']
try:
output, _ = subprocess.Popen(get_build_list_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_build_list_cmd command failed'
logger.info(output)
try:
build_list = json.loads(output)['Builds']
except ValueError:
raise AssertionError('Json values not returned when executing get_build_list_cmd command')
build_id_list = []
for build in build_list:
if build['BuildId'] not in exclude_build_list:
build_id_list.append(build['BuildId'])
return build_id_list
def get_fleet_list_attributes_from_gamelift(fleet_id=None):
"""
Function to retrieve the fleet attribute for a given fleet. Will return all fleet attributes if no fleet_id is given
:return: list
"""
if fleet_id is None:
get_fleet_list_attributes_cmd = ['aws', 'gamelift', 'describe-fleet-attributes']
else:
get_fleet_list_attributes_cmd = ['aws', 'gamelift', 'describe-fleet-attributes', '--fleet-id', fleet_id]
try:
output, _ = subprocess.Popen(get_fleet_list_attributes_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_fleet_list_attributes_cmd command failed'
logger.info(output)
next_token = None
try:
next_token = json.loads(output)['NextToken']
except KeyError:
pass
# If the NextToken exists take it, otherwise ignore that we don't have one
try:
fleet_attribute_list = json.loads(output)['FleetAttributes']
if next_token is not None:
get_next_fleet_list_cmd = ['aws', 'gamelift', 'describe-fleet-attributes', '--next-token', next_token]
try:
output, error = subprocess.Popen(get_next_fleet_list_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, ).communicate()
next_fleet_list = json.loads(output)['FleetAttributes']
fleet_attribute_list += next_fleet_list
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift get_next_fleet_list_cmd next token command failed'
logger.info(output)
except ValueError:
raise AssertionError('Json values not returned when executing get_fleet_list_attributes_cmd command')
return fleet_attribute_list
def delete_build_from_gamelift(build_id):
"""
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
Example command:
aws Gamelift delete-build --build-id build-f4t3gFDS32dfDa
"""
delete_build_cmd = ['aws', 'gamelift', 'delete-build', '--build-id', build_id]
try:
output, _ = subprocess.Popen(delete_build_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift delete_build_cmd command failed'
logger.info(output)
def delete_fleet_from_gamelift(fleet_id):
"""
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
Example command:
aws Gamelift delete-build --build-id build-f4t3gFDS32dfDa
"""
# Update fleet capacity command must be ran before deleting the fleet
update_fleet_capacity_cmd = ['aws', 'gamelift', 'update-fleet-capacity', '--fleet-id', fleet_id, '--desired-instances', '0']
try:
output_update, _ = subprocess.Popen(update_fleet_capacity_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift update_fleet_capacity_cmd command failed'
logger.info(output_update)
delete_fleet_cmd = ['aws', 'gamelift', 'delete-fleet', '--fleet-id', fleet_id]
try:
output_delete, _ = subprocess.Popen(delete_fleet_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).communicate()
except subprocess.CalledProcessError as e:
assert e.returncode > 0, 'AWS Gamelift delete_fleet_cmd command failed'
logger.info(output_delete)