From 194f41ac6cd34e8aa1a6568c078af7b7e80fa216 Mon Sep 17 00:00:00 2001 From: tculotta <60450686+tculotta@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:00:44 -0700 Subject: [PATCH] add configurable maintenance event to local agent (#180) --- AgentInterfaces/MaintenanceSchedule.cs | 2 ++ .../ControllerTests.cs | 3 ++ .../Config/MultiplayerSettings.cs | 8 +++++ .../Config/MultiplayerSettingsValidator.cs | 6 ++++ .../Controllers/SessionHostController.cs | 34 +++++++++++++++++-- .../MultiplayerSettings.json | 5 +++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/AgentInterfaces/MaintenanceSchedule.cs b/AgentInterfaces/MaintenanceSchedule.cs index 7f038be..dad0795 100644 --- a/AgentInterfaces/MaintenanceSchedule.cs +++ b/AgentInterfaces/MaintenanceSchedule.cs @@ -50,6 +50,8 @@ public class MaintenanceEvent public DateTime? NotBefore { get; set; } + public string Description { get; set; } + public string EventSource { get; set; } public int DurationInSeconds { get; set; } diff --git a/LocalMultiplayerAgent.UnitTest/ControllerTests.cs b/LocalMultiplayerAgent.UnitTest/ControllerTests.cs index 3e01c07..089e6c7 100644 --- a/LocalMultiplayerAgent.UnitTest/ControllerTests.cs +++ b/LocalMultiplayerAgent.UnitTest/ControllerTests.cs @@ -30,6 +30,9 @@ public async Task ValidateStandingByToInitializingThrows() string sessionHostId = "testSessionHostId"; + // Settings is accessed in the heartbeat endpoint + Globals.Settings = new(); + // send the first heartbeat with "StandingBy" SessionHostHeartbeatInfo heartbeat = new SessionHostHeartbeatInfo() { diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettings.cs b/LocalMultiplayerAgent/Config/MultiplayerSettings.cs index 978ddb3..ae20561 100644 --- a/LocalMultiplayerAgent/Config/MultiplayerSettings.cs +++ b/LocalMultiplayerAgent/Config/MultiplayerSettings.cs @@ -30,6 +30,8 @@ public class MultiplayerSettings public int NumHeartBeatsForTerminateResponse { get; set; } + public int NumHeartBeatsForMaintenanceEventResponse { get; set; } + public bool RunContainer { get; set; } public string OutputFolder { get; set; } @@ -49,6 +51,12 @@ public class MultiplayerSettings public IDictionary DeploymentMetadata { get; set; } + public string MaintenanceEventType { get; set; } + + public string MaintenanceEventStatus { get; set; } + + public string MaintenanceEventSource { get; set; } + public SessionHostsStartInfo ToSessionHostsStartInfo() { // Clear mount path in process based, otherwise, agent will kick into back-compat mode and try to strip the diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs index 6477a02..cb3b2b2 100644 --- a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs +++ b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs @@ -104,6 +104,12 @@ public bool IsValid(bool createBuild = false) Console.WriteLine("NumHeartBeatsForTerminateResponse must be greater than NumHeartBeatsForActivateResponse. Ideally more than 1, since you would like the server to be alive for a few seconds"); isSuccess = false; } + + if (_settings.NumHeartBeatsForTerminateResponse < _settings.NumHeartBeatsForMaintenanceEventResponse) + { + Console.WriteLine("NumHeartBeatsForMaintenanceEventResponse must be less than or equal to NumHeartBeatsForTerminateResponse."); + isSuccess = false; + } } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) diff --git a/LocalMultiplayerAgent/Controllers/SessionHostController.cs b/LocalMultiplayerAgent/Controllers/SessionHostController.cs index ed8ed1b..a5e2ba3 100644 --- a/LocalMultiplayerAgent/Controllers/SessionHostController.cs +++ b/LocalMultiplayerAgent/Controllers/SessionHostController.cs @@ -12,6 +12,7 @@ namespace LocalMultiplayerAgent.Controllers using Microsoft.Azure.Gaming.VmAgent.Model; using Microsoft.Extensions.Hosting; using Instrumentation; + using System.Collections.Generic; public class SessionHostController : Controller { @@ -54,6 +55,7 @@ public async Task ProcessHeartbeat(string sessionHostId, Operation op = Operation.Continue; SessionConfig config = null; Console.WriteLine($"CurrentGameState: {heartbeatRequest.CurrentGameState}"); + bool sendMaintenanceEvent = false; if(!ValidateSessionHostStatusTransition(previousSessionHostStatus, currentGameState)) { @@ -78,22 +80,50 @@ public async Task ProcessHeartbeat(string sessionHostId, config = Globals.SessionConfig; } + sendMaintenanceEvent = (numHeartBeats == Globals.Settings.NumHeartBeatsForMaintenanceEventResponse); + HeartBeatsCount[sessionHostId]++; } else { + sendMaintenanceEvent = (Globals.Settings.NumHeartBeatsForMaintenanceEventResponse == 1); HeartBeatsCount.TryAdd(sessionHostId, 1); } previousSessionHostStatus = currentGameState; - return Ok(new SessionHostHeartbeatInfo + SessionHostHeartbeatInfo heartbeatResponse = new SessionHostHeartbeatInfo { CurrentGameState = currentGameState, NextHeartbeatIntervalMs = DefaultHeartbeatIntervalMs, Operation = op, SessionConfig = config - }); + }; + + if (_wasGsdkVersionLogged && sendMaintenanceEvent) + { + heartbeatResponse.MaintenanceSchedule = new() + { + DocumentIncarnation = "1", + MaintenanceEvents = new List() + { + new() + { + EventId = Guid.NewGuid().ToString(), + EventType = Globals.Settings.MaintenanceEventType, + ResourceType = "VirtualMachine", + AffectedResources = new List() { "vmId" }, + EventStatus = Globals.Settings.MaintenanceEventStatus, + NotBefore = DateTime.UtcNow.AddMinutes(5), + Description = $"Scheduled {Globals.Settings.MaintenanceEventType} event for VM", + EventSource = Globals.Settings.MaintenanceEventSource, + DurationInSeconds = 300 + } + } + }; + } + + return Ok(heartbeatResponse); } private static bool _wasGsdkVersionLogged = false; diff --git a/LocalMultiplayerAgent/MultiplayerSettings.json b/LocalMultiplayerAgent/MultiplayerSettings.json index 979fc53..2a6060b 100644 --- a/LocalMultiplayerAgent/MultiplayerSettings.json +++ b/LocalMultiplayerAgent/MultiplayerSettings.json @@ -2,8 +2,13 @@ "RunContainer": false, "OutputFolder": "", "NumHeartBeatsForActivateResponse": 10, + "NumHeartBeatsForMaintenanceEventResponse": 0, // a value < 1 will disable the maintenance event "NumHeartBeatsForTerminateResponse": 60, "AgentListeningPort": 56001, + // Valid maintenance event values are explained here: https://learn.microsoft.com/azure/virtual-machines/windows/scheduled-events#event-properties + "MaintenanceEventType": "Reboot", + "MaintenanceEventStatus": "Scheduled", + "MaintenanceEventSource": "Platform", "AssetDetails": [ { "MountPath": "C:\\Assets",