// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.CloudWatch;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.GameLift;
using Amazon.GameLift.Model;
using Amazon.Lambda.Core;
using ManagementConsoleBackend.ManagementService.Data;
using Newtonsoft.Json;
namespace ManagementConsoleBackend.ManagementService.Lib
{
public static class GameLiftStateHandler
{
/// Makes multiple GameLift API requests to retrieve GameLift data and constructs a GameLiftStateEventDetail object
public static async Task GetStateEventDetail()
{
var gameLiftRequestHandler = new GameLiftRequestHandler(new AmazonGameLiftClient());
var cloudWatchRequestHandler = new CloudWatchRequestHandler(new AmazonCloudWatchClient());
var stateEvent = new GameLiftStateEventDetail();
var dynamoDbClient = new AmazonDynamoDBClient();
stateEvent.Aliases = await gameLiftRequestHandler.GetAliases();
stateEvent.GameSessionQueues = await gameLiftRequestHandler.GetGameSessionQueues();
var dynamoDbRequestHandler = new DynamoDbRequestHandler(dynamoDbClient);
var configDocument = await dynamoDbRequestHandler.GetManagementConfig("mainConfig");
var matchmakingConfigurations = await gameLiftRequestHandler.GetMatchmakingConfigurations();
LambdaLogger.Log("SIMULATOR ARN:" + configDocument.FlexMatchSimulatorArn);
// remove flexmatch simulator config as we don't want it in the state
var flexMatchSimulatorConfig = matchmakingConfigurations.Single(matchmakingConfig => matchmakingConfig.ConfigurationArn == configDocument.FlexMatchSimulatorArn);
matchmakingConfigurations.Remove(flexMatchSimulatorConfig);
stateEvent.MatchmakingSimulator = flexMatchSimulatorConfig;
stateEvent.MatchmakingConfigurations = matchmakingConfigurations;
try
{
var fleetCapacities = await gameLiftRequestHandler.GetFleetCapacities();
foreach (var fleetCapacity in fleetCapacities)
{
var fleetId = fleetCapacity.FleetId;
var metricData = new Dictionary();
var fleetMetricData = await cloudWatchRequestHandler.GetFleetMetricData(fleetId);
if (fleetMetricData != null)
{
foreach (var fleetMetric in fleetMetricData)
{
metricData[fleetMetric.Label] = -1;
if (fleetMetric.Values.Count>0)
{
metricData[fleetMetric.Label] = fleetMetric.Values[0];
}
}
}
var instances = new List();
var locationCapacities = new List();
var locationAttributes = await gameLiftRequestHandler.GetFleetLocationAttributes(fleetId);
if (locationAttributes != null)
{
foreach (var locationAttribute in locationAttributes)
{
var locationCapacity = await gameLiftRequestHandler.GetFleetLocationCapacity(fleetId, locationAttribute.LocationState.Location);
if (locationCapacity != null)
{
locationCapacities.Add(locationCapacity);
}
var locationInstances = await gameLiftRequestHandler.GetFleetInstances(fleetId, locationAttribute.LocationState.Location);
if (locationInstances != null)
{
instances.AddRange(locationInstances);
}
}
}
var fleetData = new FleetData
{
FleetId = fleetId,
LocationAttributes = locationAttributes,
FleetCapacity = fleetCapacity,
LocationCapacities = locationCapacities,
ScalingPolicies = await gameLiftRequestHandler.GetScalingPolicies(fleetId),
RuntimeConfiguration = await gameLiftRequestHandler.GetRuntimeConfiguration(fleetId),
FleetEvents = new List(),//await gameLiftRequestHandler.GetFleetEvents(fleetId, 300),
FleetUtilization = await gameLiftRequestHandler.GetFleetUtilization(fleetId),
FleetAttributes = await gameLiftRequestHandler.GetFleetAttributes(fleetId),
Instances = instances,
GameSessions = new List(),
Metrics = metricData,
};
stateEvent.FleetData.Add(fleetData);
}
}
catch (Exception e)
{
LambdaLogger.Log(e.ToString());
return null;
}
return stateEvent;
}
/// Polls active Game Sessions and updates non-active to be Terminated
public static async Task UpdateGameSessions()
{
var gameLiftRequestHandler = new GameLiftRequestHandler(new AmazonGameLiftClient());
var dynamoDbClient = new AmazonDynamoDBClient();
var dynamoDbRequestHandler = new DynamoDbRequestHandler(dynamoDbClient);
try
{
var fleetIds = await gameLiftRequestHandler.GetFleetIds();
if (fleetIds == null)
{
return false;
}
foreach (var fleetId in fleetIds)
{
var databaseActiveGameSessions = await dynamoDbRequestHandler.GetDatabaseGameSessionsByStatus(fleetId, "ACTIVE");
var activeGameSessions = await gameLiftRequestHandler.GetGameSessions(fleetId, "ACTIVE");
var gameSessionTable = Table.LoadTable(dynamoDbClient, Environment.GetEnvironmentVariable("GameSessionTableName"));
if (activeGameSessions != null)
{
foreach (var gameSession in activeGameSessions)
{
var item = Document.FromJson(JsonConvert.SerializeObject(gameSession));
item["StatusValue"] = gameSession.Status.Value;
await gameSessionTable.PutItemAsync(item);
}
}
if (databaseActiveGameSessions == null)
{
continue;
}
foreach (var dbGameSession in databaseActiveGameSessions)
{
var match = activeGameSessions.FirstOrDefault(gameSession => gameSession.GameSessionId == dbGameSession.GameSessionId);
if (match==null)
{
// This game session is no longer active
LambdaLogger.Log("SHOULD SET GAME SESSION " + dbGameSession.GameSessionId + " TO BE TERMINATED!");
var terminationTime = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
LambdaLogger.Log(terminationTime);
var updateRequest = new UpdateItemRequest
{
TableName = Environment.GetEnvironmentVariable("GameSessionTableName"),
Key = new Dictionary()
{{"GameSessionId", new Amazon.DynamoDBv2.Model.AttributeValue {S = dbGameSession.GameSessionId}}},
UpdateExpression = "SET #statusValue = :statusValue, #terminationTime = :terminationTime, #status.#value = :statusValue",
ExpressionAttributeNames = new Dictionary
{
{"#statusValue", "StatusValue"},
{"#status", "Status"},
{"#value", "Value"},
{"#terminationTime", "TerminationTime"},
},
ExpressionAttributeValues = new Dictionary()
{
{":statusValue", new Amazon.DynamoDBv2.Model.AttributeValue { S = "TERMINATED" }},
{":terminationTime", new Amazon.DynamoDBv2.Model.AttributeValue { S = terminationTime }},
}
};
try
{
await dynamoDbClient.UpdateItemAsync(updateRequest);
LambdaLogger.Log("Game session " + dbGameSession.GameSessionId + " UPDATED!");
}
catch (Exception e)
{
LambdaLogger.Log(e.Message);
}
}
else
{
LambdaLogger.Log(dbGameSession.GameSessionId + " IS STILL ACTIVE!");
}
}
}
}
catch (Exception e)
{
LambdaLogger.Log(e.ToString());
return false;
}
return true;
}
/// Retrieves the most recent state event from DDB, if it's <30 minutes old
public static async Task GetLatestStateDatabaseItem()
{
var dynamoDbClient = new AmazonDynamoDBClient();
try
{
var stateLogTable =
Table.LoadTable(dynamoDbClient, Environment.GetEnvironmentVariable("StateLogTableName"));
var thirtyMinutesAgo = DateTime.UtcNow - TimeSpan.FromMinutes(30);
var filter = new QueryFilter("Date", QueryOperator.Equal, thirtyMinutesAgo.Date);
filter.AddCondition("Time", QueryOperator.GreaterThan, thirtyMinutesAgo);
var queryConfig = new QueryOperationConfig
{
Filter = filter,
BackwardSearch = true,
Limit = 1,
};
var search = stateLogTable.Query(queryConfig);
var documentSet = await search.GetNextSetAsync();
foreach (var document in documentSet)
{
LambdaLogger.Log(document.ToJson());
return JsonConvert.DeserializeObject(document.ToJson());
}
}
catch (Exception e)
{
LambdaLogger.Log(e.ToString());
}
return null;
}
}
}