Lesson 35 - Get Compute Auth Token Working
This commit is contained in:
@@ -0,0 +1,481 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
|
||||
Description: >
|
||||
This CloudFormation template sets up a game backend service with a single Amazon GameLift fleet. After player
|
||||
authenticates and start a game via POST /start_game, a lambda handler searches for an existing viable game session
|
||||
with open player slot on the fleet, and if not found, creates a new game session. The game client is then expected
|
||||
to poll POST /get_game_connection to receive a viable game session.
|
||||
|
||||
Parameters:
|
||||
ApiGatewayStageNameParameter:
|
||||
Type: String
|
||||
Default: v1
|
||||
Description: Name of the Api Gateway stage
|
||||
|
||||
ContainerGroupDefinitionNameParameter:
|
||||
Type: String
|
||||
Default: SampleCGDName
|
||||
Description: Name of the Api Gateway stage
|
||||
|
||||
ContainerImageNameParameter:
|
||||
Type: String
|
||||
Default: SampleContainerImageName
|
||||
Description: Name for the Container Image
|
||||
|
||||
ContainerImageUriParameter:
|
||||
Type: String
|
||||
Description: URI pointing to a Container Image in ECR
|
||||
|
||||
FleetDescriptionParameter:
|
||||
Type: String
|
||||
Default: Deployed by the Amazon GameLift Plug-in for Unreal.
|
||||
Description: Description of the fleet
|
||||
|
||||
TotalMemoryLimitParameter:
|
||||
Type: Number
|
||||
Default: 4000
|
||||
Description: The maximum amount of memory (in MiB) to allocate to the container group
|
||||
|
||||
TotalVcpuLimitParameter:
|
||||
Type: Number
|
||||
Default: 2
|
||||
Description: The maximum amount of CPU units to allocate to the container group
|
||||
|
||||
FleetUdpFromPortParameter:
|
||||
Type: Number
|
||||
Default: 33430
|
||||
Description: Starting port number for UDP ports to be opened
|
||||
|
||||
FleetUdpToPortParameter:
|
||||
Type: Number
|
||||
Default: 33440
|
||||
Description: Ending port number for UDP ports to be opened
|
||||
|
||||
GameNameParameter:
|
||||
Type: String
|
||||
Default: MyGame
|
||||
Description: Game name to prepend before resource names
|
||||
MaxLength: 30
|
||||
|
||||
LambdaZipS3BucketParameter:
|
||||
Type: String
|
||||
Description: S3 bucket that stores the lambda function zip
|
||||
|
||||
LambdaZipS3KeyParameter:
|
||||
Type: String
|
||||
Description: S3 key that stores the lambda function zip
|
||||
|
||||
LaunchPathParameter:
|
||||
Type: String
|
||||
Description: Location of the game server executable in the build
|
||||
|
||||
MaxPlayersPerGameParameter:
|
||||
Type: Number
|
||||
Default: 10
|
||||
Description: Maximum number of players per game session
|
||||
|
||||
MaxTransactionsPerFiveMinutesPerIpParameter:
|
||||
Type: Number
|
||||
Default: 100
|
||||
MaxValue: 20000000
|
||||
MinValue: 100
|
||||
|
||||
UnrealEngineVersionParameter:
|
||||
Type: String
|
||||
Description: "Unreal Engine Version being used by the plugin"
|
||||
|
||||
EnableMetricsParameter:
|
||||
Type: String
|
||||
Default: "false"
|
||||
AllowedValues: ["true", "false"]
|
||||
Description: "Enable telemetry metrics collection using OTEL collector"
|
||||
|
||||
Conditions:
|
||||
ShouldCreateMetricsResources: !Equals [!Ref EnableMetricsParameter, "true"]
|
||||
|
||||
Resources:
|
||||
ApiGatewayCloudWatchRole:
|
||||
Type: "AWS::IAM::Role"
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- apigateway.amazonaws.com
|
||||
Action: "sts:AssumeRole"
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
|
||||
|
||||
Account:
|
||||
Type: "AWS::ApiGateway::Account"
|
||||
Properties:
|
||||
CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn
|
||||
|
||||
GameRequestLambdaFunctionExecutionRole:
|
||||
Type: "AWS::IAM::Role"
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- lambda.amazonaws.com
|
||||
Action:
|
||||
- "sts:AssumeRole"
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||
Policies:
|
||||
- PolicyName: !Sub ${GameNameParameter}GameRequestLambdaFunctionGameLiftPolicies
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- "gamelift:CreateGameSession"
|
||||
- "gamelift:CreatePlayerSession"
|
||||
- "gamelift:SearchGameSessions"
|
||||
Resource: "*"
|
||||
|
||||
RestApi:
|
||||
Type: "AWS::ApiGateway::RestApi"
|
||||
Properties:
|
||||
Name: !Sub ${GameNameParameter}RestApi
|
||||
|
||||
ResultsRequestLambdaFunctionExecutionRole:
|
||||
Type: "AWS::IAM::Role"
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- lambda.amazonaws.com
|
||||
Action:
|
||||
- "sts:AssumeRole"
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||
Policies:
|
||||
- PolicyName: !Sub ${GameNameParameter}ResultsRequestLambdaFunctionGameLiftPolicies
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- "gamelift:CreateGameSession"
|
||||
- "gamelift:CreatePlayerSession"
|
||||
- "gamelift:SearchGameSessions"
|
||||
Resource: "*"
|
||||
|
||||
UserPool:
|
||||
Type: "AWS::Cognito::UserPool"
|
||||
Properties:
|
||||
AdminCreateUserConfig:
|
||||
AllowAdminCreateUserOnly: false
|
||||
AutoVerifiedAttributes:
|
||||
- email
|
||||
EmailConfiguration:
|
||||
EmailSendingAccount: COGNITO_DEFAULT
|
||||
EmailVerificationMessage: "Please verify your email to complete account registration for the GameLift Plugin Single-fleet deployment scenario. Confirmation Code {####}."
|
||||
EmailVerificationSubject: GameLift Plugin - Deployment Scenario Account Verification
|
||||
Policies:
|
||||
PasswordPolicy:
|
||||
MinimumLength: 8
|
||||
RequireLowercase: true
|
||||
RequireNumbers: true
|
||||
RequireSymbols: true
|
||||
RequireUppercase: true
|
||||
Schema:
|
||||
- Name: email
|
||||
AttributeDataType: String
|
||||
Mutable: false
|
||||
Required: true
|
||||
UserPoolName: !Sub ${GameNameParameter}UserPool
|
||||
UsernameAttributes:
|
||||
- email
|
||||
|
||||
GameRequestApiResource:
|
||||
Type: "AWS::ApiGateway::Resource"
|
||||
Properties:
|
||||
ParentId: !GetAtt RestApi.RootResourceId
|
||||
PathPart: start_game
|
||||
RestApiId: !Ref RestApi
|
||||
|
||||
ResultsRequestApiResource:
|
||||
Type: "AWS::ApiGateway::Resource"
|
||||
Properties:
|
||||
ParentId: !GetAtt RestApi.RootResourceId
|
||||
PathPart: get_game_connection
|
||||
RestApiId: !Ref RestApi
|
||||
|
||||
UserPoolClient:
|
||||
Type: "AWS::Cognito::UserPoolClient"
|
||||
Properties:
|
||||
AccessTokenValidity: 1
|
||||
ClientName: !Sub ${GameNameParameter}UserPoolClient
|
||||
ExplicitAuthFlows:
|
||||
- ALLOW_USER_PASSWORD_AUTH
|
||||
- ALLOW_REFRESH_TOKEN_AUTH
|
||||
GenerateSecret: false
|
||||
IdTokenValidity: 1
|
||||
PreventUserExistenceErrors: ENABLED
|
||||
ReadAttributes:
|
||||
- email
|
||||
- preferred_username
|
||||
RefreshTokenValidity: 30
|
||||
SupportedIdentityProviders:
|
||||
- COGNITO
|
||||
UserPoolId: !Ref UserPool
|
||||
|
||||
WebACL:
|
||||
Type: "AWS::WAFv2::WebACL"
|
||||
DependsOn:
|
||||
- ApiDeployment
|
||||
Properties:
|
||||
DefaultAction:
|
||||
Allow:
|
||||
{}
|
||||
Description: !Sub "WebACL for game: ${GameNameParameter}"
|
||||
Name: !Sub ${GameNameParameter}WebACL
|
||||
Rules:
|
||||
- Name: !Sub ${GameNameParameter}WebACLPerIpThrottleRule
|
||||
Action:
|
||||
Block:
|
||||
{}
|
||||
Priority: 0
|
||||
Statement:
|
||||
RateBasedStatement:
|
||||
AggregateKeyType: IP
|
||||
Limit: !Ref MaxTransactionsPerFiveMinutesPerIpParameter
|
||||
VisibilityConfig:
|
||||
CloudWatchMetricsEnabled: true
|
||||
MetricName: !Sub ${GameNameParameter}WebACLPerIpThrottleRuleMetrics
|
||||
SampledRequestsEnabled: true
|
||||
Scope: REGIONAL
|
||||
VisibilityConfig:
|
||||
CloudWatchMetricsEnabled: true
|
||||
MetricName: !Sub ${GameNameParameter}WebACLMetrics
|
||||
SampledRequestsEnabled: true
|
||||
|
||||
ApiDeployment:
|
||||
Type: "AWS::ApiGateway::Deployment"
|
||||
DependsOn:
|
||||
- GameRequestApiMethod
|
||||
- ResultsRequestApiMethod
|
||||
Properties:
|
||||
RestApiId: !Ref RestApi
|
||||
StageDescription:
|
||||
DataTraceEnabled: true
|
||||
LoggingLevel: INFO
|
||||
MetricsEnabled: true
|
||||
StageName: !Ref ApiGatewayStageNameParameter
|
||||
|
||||
Authorizer:
|
||||
Type: "AWS::ApiGateway::Authorizer"
|
||||
Properties:
|
||||
IdentitySource: method.request.header.Auth
|
||||
Name: CognitoAuthorizer
|
||||
ProviderARNs:
|
||||
- "Fn::GetAtt":
|
||||
- UserPool
|
||||
- Arn
|
||||
RestApiId: !Ref RestApi
|
||||
Type: COGNITO_USER_POOLS
|
||||
|
||||
GameRequestApiMethod:
|
||||
Type: "AWS::ApiGateway::Method"
|
||||
Properties:
|
||||
AuthorizationType: COGNITO_USER_POOLS
|
||||
AuthorizerId: !Ref Authorizer
|
||||
HttpMethod: POST
|
||||
Integration:
|
||||
Type: AWS_PROXY
|
||||
IntegrationHttpMethod: POST
|
||||
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GameRequestLambdaFunction.Arn}/invocations"
|
||||
OperationName: GameRequest
|
||||
ResourceId: !Ref GameRequestApiResource
|
||||
RestApiId: !Ref RestApi
|
||||
|
||||
ResultsRequestApiMethod:
|
||||
Type: "AWS::ApiGateway::Method"
|
||||
Properties:
|
||||
AuthorizationType: COGNITO_USER_POOLS
|
||||
AuthorizerId: !Ref Authorizer
|
||||
HttpMethod: POST
|
||||
Integration:
|
||||
Type: AWS_PROXY
|
||||
IntegrationHttpMethod: POST
|
||||
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ResultsRequestLambdaFunction.Arn}/invocations"
|
||||
OperationName: ResultsRequest
|
||||
ResourceId: !Ref ResultsRequestApiResource
|
||||
RestApiId: !Ref RestApi
|
||||
|
||||
WebACLAssociation:
|
||||
Type: "AWS::WAFv2::WebACLAssociation"
|
||||
DependsOn:
|
||||
- ApiDeployment
|
||||
- WebACL
|
||||
Properties:
|
||||
ResourceArn: !Sub
|
||||
- "arn:aws:apigateway:${REGION}::/restapis/${REST_API_ID}/stages/${STAGE_NAME}"
|
||||
- REGION: !Ref "AWS::Region"
|
||||
REST_API_ID: !Ref RestApi
|
||||
STAGE_NAME: !Ref ApiGatewayStageNameParameter
|
||||
WebACLArn: !GetAtt WebACL.Arn
|
||||
|
||||
ContainerFleetRole:
|
||||
Type: "AWS::IAM::Role"
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- cloudformation.amazonaws.com
|
||||
- gamelift.amazonaws.com
|
||||
Action:
|
||||
- "sts:AssumeRole"
|
||||
ManagedPolicyArns:
|
||||
- "arn:aws:iam::aws:policy/GameLiftContainerFleetPolicy"
|
||||
Policies: !If
|
||||
- ShouldCreateMetricsResources
|
||||
- - PolicyName: AMPRemoteWriteAccess
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- aps:RemoteWrite
|
||||
Resource: !Sub 'arn:aws:aps:*:${AWS::AccountId}:workspace/*'
|
||||
- PolicyName: OTelCollectorEMFExporter
|
||||
PolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- logs:PutLogEvents
|
||||
- logs:CreateLogStream
|
||||
- logs:CreateLogGroup
|
||||
- logs:PutRetentionPolicy
|
||||
Resource: !Sub 'arn:aws:logs:*:${AWS::AccountId}:log-group:*:log-stream:*'
|
||||
- []
|
||||
|
||||
ContainerGroupResource:
|
||||
Type: "AWS::GameLift::ContainerGroupDefinition"
|
||||
Properties:
|
||||
GameServerContainerDefinition:
|
||||
ContainerName: !Ref ContainerImageNameParameter
|
||||
ImageUri: !Ref ContainerImageUriParameter
|
||||
ServerSdkVersion: "5.4.0"
|
||||
PortConfiguration:
|
||||
ContainerPortRanges:
|
||||
- FromPort: !Ref FleetUdpFromPortParameter
|
||||
Protocol: "UDP"
|
||||
ToPort: !Ref FleetUdpToPortParameter
|
||||
Name: !Ref ContainerGroupDefinitionNameParameter
|
||||
OperatingSystem: "AMAZON_LINUX_2023"
|
||||
TotalVcpuLimit: !Ref TotalVcpuLimitParameter
|
||||
TotalMemoryLimitMebibytes: !Ref TotalMemoryLimitParameter
|
||||
|
||||
ContainerFleetResource:
|
||||
DependsOn:
|
||||
- ContainerGroupResource
|
||||
Type: "AWS::GameLift::ContainerFleet"
|
||||
Properties:
|
||||
GameServerContainerGroupDefinitionName: !Ref ContainerGroupDefinitionNameParameter
|
||||
InstanceConnectionPortRange:
|
||||
FromPort: !Ref FleetUdpFromPortParameter
|
||||
ToPort: !Ref FleetUdpToPortParameter
|
||||
Description: !Sub
|
||||
- "${FleetDescriptionParameter} Using Unreal Engine Version ${UnrealEngineVersionParameter}${MetricsText}"
|
||||
- MetricsText: !If [ShouldCreateMetricsResources, " with telemetry metrics enabled", ""]
|
||||
InstanceInboundPermissions:
|
||||
- FromPort: !Ref FleetUdpFromPortParameter
|
||||
IpRange: "0.0.0.0/0"
|
||||
Protocol: UDP
|
||||
ToPort: !Ref FleetUdpToPortParameter
|
||||
InstanceType: c4.xlarge
|
||||
BillingType: ON_DEMAND
|
||||
FleetRoleArn: !GetAtt ContainerFleetRole.Arn
|
||||
NewGameSessionProtectionPolicy: FullProtection
|
||||
GameSessionCreationLimitPolicy:
|
||||
NewGameSessionsPerCreator: 5
|
||||
PolicyPeriodInMinutes: 2
|
||||
|
||||
AliasResource:
|
||||
Type: "AWS::GameLift::Alias"
|
||||
Properties:
|
||||
Description: !Sub Alias to access ${GameNameParameter} fleet
|
||||
Name: !Sub ${GameNameParameter}FleetAlias
|
||||
RoutingStrategy:
|
||||
Type: SIMPLE
|
||||
FleetId: !Ref ContainerFleetResource
|
||||
|
||||
ResultsRequestLambdaFunction:
|
||||
Type: "AWS::Lambda::Function"
|
||||
Properties:
|
||||
Code:
|
||||
S3Bucket: !Ref LambdaZipS3BucketParameter
|
||||
S3Key: !Ref LambdaZipS3KeyParameter
|
||||
Description: Lambda function to handle game requests
|
||||
Environment:
|
||||
Variables:
|
||||
FleetAlias: !Ref AliasResource
|
||||
FunctionName: !Sub ${GameNameParameter}ResultsRequestLambda
|
||||
Handler: results_request.handler
|
||||
MemorySize: 128
|
||||
Role: !GetAtt ResultsRequestLambdaFunctionExecutionRole.Arn
|
||||
Runtime: python3.14
|
||||
|
||||
GameRequestLambdaFunction:
|
||||
Type: "AWS::Lambda::Function"
|
||||
Properties:
|
||||
Code:
|
||||
S3Bucket: !Ref LambdaZipS3BucketParameter
|
||||
S3Key: !Ref LambdaZipS3KeyParameter
|
||||
Description: Lambda function to handle game requests
|
||||
Environment:
|
||||
Variables:
|
||||
FleetAlias: !Ref AliasResource
|
||||
MaxPlayersPerGame: !Ref MaxPlayersPerGameParameter
|
||||
FunctionName: !Sub ${GameNameParameter}GameRequestLambda
|
||||
Handler: game_request.handler
|
||||
MemorySize: 128
|
||||
Role: !GetAtt GameRequestLambdaFunctionExecutionRole.Arn
|
||||
Runtime: python3.14
|
||||
|
||||
ResultsRequestLambdaFunctionApiGatewayPermission:
|
||||
Type: "AWS::Lambda::Permission"
|
||||
Properties:
|
||||
Action: "lambda:InvokeFunction"
|
||||
FunctionName: !GetAtt ResultsRequestLambdaFunction.Arn
|
||||
Principal: apigateway.amazonaws.com
|
||||
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*"
|
||||
|
||||
GameRequestLambdaFunctionApiGatewayPermission:
|
||||
Type: "AWS::Lambda::Permission"
|
||||
Properties:
|
||||
Action: "lambda:InvokeFunction"
|
||||
FunctionName: !GetAtt GameRequestLambdaFunction.Arn
|
||||
Principal: apigateway.amazonaws.com
|
||||
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*"
|
||||
|
||||
Outputs:
|
||||
ApiGatewayEndpoint:
|
||||
Description: Url of ApiGateway Endpoint
|
||||
Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageNameParameter}/"
|
||||
|
||||
UserPoolClientId:
|
||||
Description: Id of UserPoolClient
|
||||
Value: !Ref UserPoolClient
|
||||
|
||||
IdentityRegion:
|
||||
Description: Region name
|
||||
Value: !Ref "AWS::Region"
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# THIS IS A SAMPLE CLOUDFORMATION PARAMETERS FILE
|
||||
GameNameParameter:
|
||||
value: "{{AWSGAMELIFT::SYS::GAMENAME}}"
|
||||
LambdaZipS3BucketParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::LambdaZipS3BucketParameter}}"
|
||||
LambdaZipS3KeyParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::LambdaZipS3KeyParameter}}"
|
||||
ApiGatewayStageNameParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::ApiGatewayStageNameParameter}}"
|
||||
ContainerGroupDefinitionNameParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::ContainerGroupDefinitionNameParameter}}"
|
||||
ContainerImageNameParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::ContainerImageNameParameter}}"
|
||||
ContainerImageUriParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::ContainerImageUriParameter}}"
|
||||
LaunchPathParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::LaunchPathParameter}}"
|
||||
TotalVcpuLimitParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::TotalVcpuLimitParameter}}"
|
||||
TotalMemoryLimitParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::TotalMemoryLimitParameter}}"
|
||||
FleetUdpFromPortParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::FleetUdpFromPortParameter}}"
|
||||
FleetUdpToPortParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::FleetUdpToPortParameter}}"
|
||||
UnrealEngineVersionParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::UnrealEngineVersionParameter}}"
|
||||
EnableMetricsParameter:
|
||||
value: "{{AWSGAMELIFT::VARS::EnableMetricsParameter}}"
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# Key/value pairs to be added to the game's awsGameLiftClientConfig.yml file
|
||||
# These values will be replaced at the end of create/update of
|
||||
# the feature's CloudFormation stack.
|
||||
|
||||
user_pool_client_id: "{{AWSGAMELIFT::CFNOUTPUT::UserPoolClientId}}"
|
||||
identity_api_gateway_base_url: "{{AWSGAMELIFT::CFNOUTPUT::ApiGatewayEndpoint}}"
|
||||
identity_region: "{{AWSGAMELIFT::CFNOUTPUT::IdentityRegion}}"
|
||||
@@ -0,0 +1,79 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import boto3
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
"""
|
||||
Handles requests to start games from the game client.
|
||||
This function first looks for any game session
|
||||
:param event: lambda event, contains the region to player latency mapping in `regionToLatencyMapping` key, as well
|
||||
as the player information from the Cognito id tokens.
|
||||
:param context: lambda context, not used by this function
|
||||
:return:
|
||||
- 202 (Accepted) if the matchmaking request is accepted and is now being processed
|
||||
- 409 (Conflict) if the another matchmaking request is in progress
|
||||
- 500 (Internal Error) if error occurred when processing the matchmaking request
|
||||
"""
|
||||
|
||||
gamelift = boto3.client('gamelift')
|
||||
fleet_alias = os.environ['FleetAlias']
|
||||
max_players_per_game = int(os.environ['MaxPlayersPerGame'])
|
||||
|
||||
player_id = event["requestContext"]["authorizer"]["claims"]["sub"]
|
||||
print(f'Handling start game request. PlayerId: {player_id}')
|
||||
|
||||
# NOTE: latency mapping is not used in this deployment scenario
|
||||
region_to_latency_mapping = get_region_to_latency_mapping(event)
|
||||
if region_to_latency_mapping:
|
||||
print(f"Region to latency mapping: {region_to_latency_mapping}")
|
||||
else:
|
||||
print("No regionToLatencyMapping mapping provided")
|
||||
|
||||
if not has_viable_game_sessions(gamelift, fleet_alias):
|
||||
create_game_session(gamelift, fleet_alias, max_players_per_game)
|
||||
|
||||
return {
|
||||
'headers': {
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
'statusCode': 202
|
||||
}
|
||||
|
||||
|
||||
def has_viable_game_sessions(gamelift, fleet_alias):
|
||||
print(f"Checking for viable game sessions: {fleet_alias}")
|
||||
|
||||
# NOTE: SortExpression="creationTimeMillis ASC" is not needed because we are looking for any viable game sessions,
|
||||
# hence the order does not matter.
|
||||
search_game_sessions_response = gamelift.search_game_sessions(
|
||||
AliasId=fleet_alias,
|
||||
FilterExpression="hasAvailablePlayerSessions=true",
|
||||
)
|
||||
return len(search_game_sessions_response['GameSessions']) != 0
|
||||
|
||||
|
||||
def create_game_session(gamelift, fleet_alias, max_players_per_game):
|
||||
print(f"Creating game session: {fleet_alias}")
|
||||
gamelift.create_game_session(
|
||||
AliasId=fleet_alias,
|
||||
MaximumPlayerSessionCount=max_players_per_game,
|
||||
)
|
||||
|
||||
|
||||
def get_region_to_latency_mapping(event):
|
||||
request_body = event.get("body")
|
||||
if not request_body:
|
||||
return None
|
||||
|
||||
try:
|
||||
request_body_json = json.loads(request_body)
|
||||
except ValueError:
|
||||
print(f"Error parsing request body: {request_body}")
|
||||
return None
|
||||
|
||||
if request_body_json and request_body_json.get('regionToLatencyMapping'):
|
||||
return request_body_json.get('regionToLatencyMapping')
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import boto3
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
"""
|
||||
Handles requests to describe the game session connection information after a StartGame request.
|
||||
This function will look up the MatchmakingRequest table to find a pending matchmaking request by
|
||||
the player, and if it is QUEUED, look up the GameSessionPlacement table to find the game's
|
||||
connection information.
|
||||
:param event: lambda event, contains the region to player latency mapping in `regionToLatencyMapping` key, as well
|
||||
as the player information from the Cognito id tokens. Cognito provides a `sub` user attribute that we use as a
|
||||
Player ID. Unlike `username` value `sub` is UUID for a user which is never reassigned to another user.
|
||||
:param context: lambda context, not used by this function
|
||||
:return:
|
||||
- 200 (OK) if the game connection is ready, along with server info: "IpAddress", "Port", "DnsName", "PlayerSessionId", "PlayerId"
|
||||
- 204 (No Content) if the requested game is still in progress of matchmaking
|
||||
- 404 (Not Found) if no game has been started by the player, or if all started game were expired
|
||||
- 500 (Internal Error) if errors occurred during matchmaking or placement
|
||||
"""
|
||||
|
||||
gamelift = boto3.client('gamelift')
|
||||
fleet_alias = os.environ['FleetAlias']
|
||||
|
||||
player_id = event["requestContext"]["authorizer"]["claims"]["sub"]
|
||||
print(f'Handling request result request. PlayerId: {player_id}')
|
||||
|
||||
oldest_viable_game_session = get_oldest_viable_game_session(gamelift, fleet_alias)
|
||||
if oldest_viable_game_session:
|
||||
player_session = create_player_session(gamelift, oldest_viable_game_session['GameSessionId'], player_id)
|
||||
|
||||
game_session_connection_info = dict((k, player_session[k]) for k in ('IpAddress', 'Port', 'DnsName', 'PlayerSessionId', 'PlayerId'))
|
||||
game_session_connection_info['GameSessionArn'] = player_session['GameSessionId']
|
||||
print(f"Connection info: {game_session_connection_info}")
|
||||
|
||||
return {
|
||||
'body': json.dumps(game_session_connection_info),
|
||||
'headers': {
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
'statusCode': 200
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'headers': {
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
'statusCode': 404
|
||||
}
|
||||
|
||||
|
||||
def get_oldest_viable_game_session(gamelift, fleet_alias):
|
||||
print("Checking for viable game sessions:", fleet_alias)
|
||||
search_game_sessions_response = gamelift.search_game_sessions(
|
||||
AliasId=fleet_alias,
|
||||
FilterExpression="hasAvailablePlayerSessions=true",
|
||||
SortExpression="creationTimeMillis ASC",
|
||||
)
|
||||
print(f"Received search game session response: {search_game_sessions_response}")
|
||||
return next(iter(search_game_sessions_response['GameSessions']), None)
|
||||
|
||||
def create_player_session(gamelift, game_session_id, player_id):
|
||||
print(f"Creating PlayerSession session on GameSession: {game_session_id}, PlayerId: {player_id}")
|
||||
create_player_session_response = gamelift.create_player_session(
|
||||
GameSessionId=game_session_id,
|
||||
PlayerId=player_id
|
||||
)
|
||||
print(f"Received create player session response: {create_player_session_response}")
|
||||
return create_player_session_response['PlayerSession']
|
||||
@@ -0,0 +1,43 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# GameLift Region Mappings
|
||||
# Keep synchronized with: https://docs.aws.amazon.com/general/latest/gr/gamelift.html
|
||||
#
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# Region Name Region Endpoint Protocol
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# US East (Ohio) us-east-2 gamelift.us-east-2.amazonaws.com HTTPS
|
||||
# US East (N. Virginia) us-east-1 gamelift.us-east-1.amazonaws.com HTTPS
|
||||
# US West (N. California) us-west-1 gamelift.us-west-1.amazonaws.com HTTPS
|
||||
# US West (Oregon) us-west-2 gamelift.us-west-2.amazonaws.com HTTPS
|
||||
# Asia Pacific (Mumbai) ap-south-1 gamelift.ap-south-1.amazonaws.com HTTPS
|
||||
# Asia Pacific (Seoul) ap-northeast-2 gamelift.ap-northeast-2.amazonaws.com HTTPS
|
||||
# Asia Pacific (Singapore) ap-southeast-1 gamelift.ap-southeast-1.amazonaws.com HTTPS
|
||||
# Asia Pacific (Sydney) ap-southeast-2 gamelift.ap-southeast-2.amazonaws.com HTTPS
|
||||
# Asia Pacific (Tokyo) ap-northeast-1 gamelift.ap-northeast-1.amazonaws.com HTTPS
|
||||
# Canada (Central) ca-central-1 gamelift.ca-central-1.amazonaws.com HTTPS
|
||||
# Europe (Frankfurt) eu-central-1 gamelift.eu-central-1.amazonaws.com HTTPS
|
||||
# Europe (Ireland) eu-west-1 gamelift.eu-west-1.amazonaws.com HTTPS
|
||||
# Europe (London) eu-west-2 gamelift.eu-west-2.amazonaws.com HTTPS
|
||||
# South America (São Paulo) sa-east-1 gamelift.sa-east-1.amazonaws.com HTTPS
|
||||
# ----------------------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
five_letter_region_codes: {
|
||||
us-east-2 : usea2,
|
||||
us-east-1 : usea1,
|
||||
us-west-1 : uswe1,
|
||||
us-west-2 : uswe2,
|
||||
ap-south-1 : apso1,
|
||||
ap-northeast-2 : apne2,
|
||||
ap-southeast-1 : apse1,
|
||||
ap-southeast-2 : apse2,
|
||||
ap-northeast-1 : apne1,
|
||||
ca-central-1 : cace1,
|
||||
eu-central-1 : euce1,
|
||||
eu-west-1 : euwe1,
|
||||
eu-west-2 : euwe2,
|
||||
sa-east-1 : saea1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import boto3
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-g", "--game", help="game name", type=str, required=True)
|
||||
parser.add_argument("-r", "--region", help="region name, e.g. eu-west-1", type=str, required=True)
|
||||
parser.add_argument("-p", "--profile", help="profile name in the AWS shared credentials file ~/.aws/credentials", type=str, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
GAME_NAME = args.game.lower() # e.g. 'GameLiftSampleGame2ue4'
|
||||
REGION = args.region # e.g. 'eu-west-1'
|
||||
PROFILE_NAME = args.profile
|
||||
|
||||
USER_POOL_NAME = GAME_NAME + 'UserPool'
|
||||
USER_POOL_CLIENT_NAME = GAME_NAME + 'UserPoolClient'
|
||||
USERNAME = 'testuser@example.com'
|
||||
PASSWORD = 'TestPassw0rd.'
|
||||
REST_API_NAME = GAME_NAME + 'RestApi'
|
||||
REST_API_STAGE = 'dev'
|
||||
GAME_REQUEST_PATH = 'start_game'
|
||||
RESULTS_REQUEST_PATH = 'get_game_connection'
|
||||
session = boto3.Session(profile_name=PROFILE_NAME)
|
||||
cognito_idp = session.client('cognito-idp', region_name=REGION)
|
||||
apig = session.client('apigateway', region_name=REGION)
|
||||
REGION_TO_LATENCY_MAPPING = {
|
||||
"regionToLatencyMapping": {
|
||||
"us-west-2": 50,
|
||||
"us-east-1": 100,
|
||||
"eu-west-1": 150,
|
||||
"ap-northeast-1": 300
|
||||
}
|
||||
}
|
||||
GAME_REQUEST_PAYLOAD = json.dumps(REGION_TO_LATENCY_MAPPING)
|
||||
|
||||
|
||||
def main():
|
||||
user_pool = find_user_pool(USER_POOL_NAME)
|
||||
user_pool_id = user_pool['Id']
|
||||
print("User Pool Id:", user_pool_id)
|
||||
|
||||
user_pool_client = find_user_pool_client(user_pool_id, USER_POOL_CLIENT_NAME)
|
||||
user_pool_client_id = user_pool_client['ClientId']
|
||||
print("User Pool Client Id:", user_pool_client_id)
|
||||
|
||||
try:
|
||||
cognito_idp.sign_up(
|
||||
ClientId=user_pool_client_id,
|
||||
Username=USERNAME,
|
||||
Password=PASSWORD,
|
||||
)
|
||||
|
||||
print("Created user:", USERNAME)
|
||||
|
||||
cognito_idp.admin_confirm_sign_up(
|
||||
UserPoolId=user_pool_id,
|
||||
Username=USERNAME,
|
||||
)
|
||||
|
||||
init_auth_result = cognito_idp.initiate_auth(
|
||||
AuthFlow='USER_PASSWORD_AUTH',
|
||||
AuthParameters={
|
||||
'USERNAME': USERNAME,
|
||||
'PASSWORD': PASSWORD,
|
||||
},
|
||||
ClientId=user_pool_client_id
|
||||
)
|
||||
|
||||
assert init_auth_result['ResponseMetadata']['HTTPStatusCode'] == 200, "Unsuccessful init_auth"
|
||||
print("Authenticated via username and password")
|
||||
|
||||
id_token = init_auth_result['AuthenticationResult']['IdToken']
|
||||
headers = {
|
||||
'Auth': id_token
|
||||
}
|
||||
results_request_url = get_rest_api_endpoint(REST_API_NAME, REGION, REST_API_STAGE, RESULTS_REQUEST_PATH)
|
||||
game_request_url = get_rest_api_endpoint(REST_API_NAME, REGION, REST_API_STAGE, GAME_REQUEST_PATH)
|
||||
|
||||
print ("results_request_url: " + results_request_url)
|
||||
print ("game_request_url: " + game_request_url)
|
||||
|
||||
results_request_response = requests.post(url=results_request_url, headers=headers)
|
||||
assert results_request_response.status_code == 204 or results_request_response.status_code == 200, \
|
||||
"Expect 'POST /get_game_info' status code to be 200 (Success) or 204 (No Content). Actual: " \
|
||||
f"{str(results_request_response.status_code)}"
|
||||
print("Verified mock ResultsRequest response", results_request_response)
|
||||
|
||||
game_request_response = requests.post(url=game_request_url, headers=headers, data=GAME_REQUEST_PAYLOAD)
|
||||
assert game_request_response.status_code == 202, "Expect 'POST /start_game' status code to be 202 (Accepted)/ Actual: " \
|
||||
f"{str(results_request_response.status_code)}"
|
||||
print("Verified lambda GameRequest response", game_request_response)
|
||||
|
||||
#game_request_info = json.loads(game_request_response.content)
|
||||
print(f"Received start game info: {game_request_response}")
|
||||
|
||||
print("Waiting for game session to be created...")
|
||||
time.sleep(10) # 10 seconds
|
||||
|
||||
results_request_response = requests.post(url=results_request_url, headers=headers)
|
||||
assert results_request_response.status_code == 200, "Expect 'POST /get_game_info' status code to be 200 (Success). Actual: " \
|
||||
f"{str(results_request_response.status_code)}"
|
||||
print("Verified lambda ResultsRequest response", results_request_response.content)
|
||||
|
||||
game_connection_info = json.loads(results_request_response.content)
|
||||
print(f"Received game connection info: {game_connection_info}")
|
||||
assert game_connection_info['IpAddress'] != ''
|
||||
assert game_connection_info['Port'] > 0
|
||||
assert REGION in game_connection_info['DnsName'], \
|
||||
f"Expect {game_connection_info['DnsName']} to contain '{REGION}'"
|
||||
assert "psess-" in game_connection_info['PlayerSessionId'], \
|
||||
f"Expect {game_connection_info['PlayerSessionId']} to contain 'psess-'"
|
||||
assert REGION in game_connection_info['GameSessionArn'], \
|
||||
f"Expect {game_connection_info['GameSessionArn']} to contain '{REGION}'"
|
||||
print("Verified game connection info:", game_connection_info)
|
||||
|
||||
finally:
|
||||
cognito_idp.admin_delete_user(
|
||||
UserPoolId=user_pool_id,
|
||||
Username=USERNAME,
|
||||
)
|
||||
|
||||
print("Deleted user:", USERNAME)
|
||||
|
||||
print("Test Succeeded!")
|
||||
|
||||
|
||||
def find_user_pool(user_pool_name):
|
||||
print("Finding user pool:", user_pool_name)
|
||||
result = cognito_idp.list_user_pools(MaxResults=50)
|
||||
pools = result['UserPools']
|
||||
return next(x for x in pools if x['Name'] == user_pool_name)
|
||||
|
||||
|
||||
def find_user_pool_client(user_pool_id, user_pool_client_name):
|
||||
print("Finding user pool client:", user_pool_client_name)
|
||||
results = cognito_idp.list_user_pool_clients(UserPoolId=user_pool_id)
|
||||
clients = results['UserPoolClients']
|
||||
return next(x for x in clients if x['ClientName'] == user_pool_client_name)
|
||||
|
||||
|
||||
def find_rest_api(rest_api_name):
|
||||
print("Finding rest api:", rest_api_name)
|
||||
results = apig.get_rest_apis()
|
||||
rest_apis = results['items']
|
||||
return next(x for x in rest_apis if x['name'] == rest_api_name)
|
||||
|
||||
|
||||
def get_rest_api_endpoint(rest_api_name, region, stage, path):
|
||||
print("Getting rest api endpoint", rest_api_name)
|
||||
rest_api = find_rest_api(rest_api_name)
|
||||
rest_api_id = rest_api['id']
|
||||
return f'https://{rest_api_id}.execute-api.{region}.amazonaws.com/{stage}/{path}'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user