From c9e6b9f2a6d85afb207d35459f1187c75fcc07cf Mon Sep 17 00:00:00 2001 From: Scott Beddall Date: Fri, 13 Dec 2024 16:09:38 -0800 Subject: [PATCH 1/3] first go with a hardcoded time. I want to be able to provide the timeout via cli arg, and that's difficult with the middleware etc --- .../CommandOptions/OptionsGenerator.cs | 8 +++- .../CommandOptions/StartOptions.cs | 7 ++- .../AutoShutdown/ShutdownConfiguration.cs | 7 +++ .../Common/AutoShutdown/ShutdownTimer.cs | 44 +++++++++++++++++++ .../AutoShutdown/ShutdownTimerMiddleware.cs | 28 ++++++++++++ .../Azure.Sdk.Tools.TestProxy/Startup.cs | 14 ++++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs create mode 100644 tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs create mode 100644 tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimerMiddleware.cs diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs index 3ad5748f5ca..54e6a800872 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs @@ -55,6 +55,11 @@ public static RootCommand GenerateCommandLineOptions(Func getDefaultValue: () => false); universalOption.AddAlias("-u"); + var autoShutdownOption = new Option( + name: "--shutdownInSeconds", + description: "Integer argument; When provided, the proxy will auto-shutdown after not being contacted over any HTTP route for this number of seconds.", + getDefaultValue: () => -1); + var breakGlassOption = new Option( name: "--break-glass", description: "Flag; Ignore secret push protection results when pushing.", @@ -88,10 +93,11 @@ public static RootCommand GenerateCommandLineOptions(Func startCommand.AddOption(insecureOption); startCommand.AddOption(dumpOption); startCommand.AddOption(universalOption); + startCommand.AddOption(autoShutdownOption); startCommand.AddArgument(collectedArgs); startCommand.SetHandler(async (startOpts) => await callback(startOpts), - new StartOptionsBinder(storageLocationOption, storagePluginOption, insecureOption, dumpOption, universalOption, collectedArgs) + new StartOptionsBinder(storageLocationOption, storagePluginOption, insecureOption, dumpOption, universalOption, autoShutdownOption, collectedArgs) ); root.Add(startCommand); diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/StartOptions.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/StartOptions.cs index 6014568e1bf..db8a44eeb5c 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/StartOptions.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/StartOptions.cs @@ -10,6 +10,8 @@ public class StartOptions : DefaultOptions public bool Dump { get; set; } public bool UniversalOutput { get; set; } + public int AutoShutdownTime { get; set; } + // On the command line, use -- and everything after that becomes arguments to Host.CreateDefaultBuilder // For example Test-Proxy start -i -d -- --urls https://localhost:8002 would set AdditionaArgs to a list containing // --urls and https://localhost:8002 as individual entries. This is converted to a string[] before being @@ -23,15 +25,17 @@ public class StartOptionsBinder : BinderBase private readonly Option _insecureOption; private readonly Option _dumpOption; private readonly Option _univeralOutputOption; + private readonly Option _autoShutdownTime; private readonly Argument _additionalArgs; - public StartOptionsBinder(Option storageLocationOption, Option storagePluginOption, Option insecureOption, Option dumpOption, Option universalOutput, Argument additionalArgs) + public StartOptionsBinder(Option storageLocationOption, Option storagePluginOption, Option insecureOption, Option dumpOption, Option universalOutput, Option autoShutdownTime, Argument additionalArgs) { _storageLocationOption = storageLocationOption; _storagePluginOption = storagePluginOption; _insecureOption = insecureOption; _dumpOption = dumpOption; _univeralOutputOption = universalOutput; + _autoShutdownTime = autoShutdownTime; _additionalArgs = additionalArgs; } @@ -43,6 +47,7 @@ protected override StartOptions GetBoundValue(BindingContext bindingContext) => Insecure = bindingContext.ParseResult.GetValueForOption(_insecureOption), Dump = bindingContext.ParseResult.GetValueForOption(_dumpOption), UniversalOutput = bindingContext.ParseResult.GetValueForOption(_univeralOutputOption), + AutoShutdownTime = bindingContext.ParseResult.GetValueForOption(_autoShutdownTime), AdditionalArgs = bindingContext.ParseResult.GetValueForArgument(_additionalArgs) }; } diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs new file mode 100644 index 00000000000..864c038875f --- /dev/null +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs @@ -0,0 +1,7 @@ +namespace Azure.Sdk.Tools.TestProxy.Common.AutoShutdown +{ + public class ShutdownConfiguration + { + public bool EnableAutoShutdown { get; set; } = false; + } +} diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs new file mode 100644 index 00000000000..4fe5d8cc3d4 --- /dev/null +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace Azure.Sdk.Tools.TestProxy.Common.AutoShutdown +{ + public class ShutdownTimer + { + private readonly int _timeoutSeconds; + private CancellationTokenSource _cts; + private Task _shutdownTask; + + public ShutdownTimer(int timeoutSeconds = 300) + { + _timeoutSeconds = timeoutSeconds; + } + + public void ResetTimer() + { + _cts?.Cancel(); + _cts = new CancellationTokenSource(); + + _shutdownTask = Task.Run(async () => + { + try + { + await Task.Delay(_timeoutSeconds * 1000, _cts.Token); + + System.Console.WriteLine("Server idle timeout reached. Shutting down..."); + Environment.Exit(0); + } + catch (TaskCanceledException) + { + // Timer was reset or canceled + } + }); + } + + public void StopTimer() + { + _cts?.Cancel(); + } + } +} diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimerMiddleware.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimerMiddleware.cs new file mode 100644 index 00000000000..369a04b798a --- /dev/null +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimerMiddleware.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; + +namespace Azure.Sdk.Tools.TestProxy.Common.AutoShutdown +{ + public class ShutdownTimerMiddleware + { + private readonly RequestDelegate _next; + private readonly ShutdownTimer _shutdownTimer; + private readonly ShutdownConfiguration _shutdownConfig; + + public ShutdownTimerMiddleware(RequestDelegate next, ShutdownTimer shutdownTimer, ShutdownConfiguration shutdownConfig) + { + _next = next; + _shutdownTimer = shutdownTimer; + _shutdownConfig = shutdownConfig; + } + + public async Task InvokeAsync(HttpContext context) + { + if (_shutdownConfig.EnableAutoShutdown) + { + _shutdownTimer.ResetTimer(); + } + await _next(context); + } + } +} diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs index eecdedab1c7..80450d3f9e0 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs @@ -24,6 +24,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; +using Azure.Sdk.Tools.TestProxy.Common.AutoShutdown; namespace Azure.Sdk.Tools.TestProxy { @@ -125,6 +126,7 @@ private static async Task Run(object commandObj) StorageLocation = defaultOpts.StorageLocation, StoragePlugin = defaultOpts.StoragePlugin, Insecure = false, + AutoShutdownTime = -1, Dump = false }); break; @@ -147,6 +149,7 @@ private static void StartServer(StartOptions startOptions) newLine: true, statusThreadCts.Token); var host = Host.CreateDefaultBuilder((startOptions.AdditionalArgs??new string[] { }).ToArray()); + host.ConfigureWebHostDefaults( builder => @@ -180,6 +183,14 @@ private static void StartServer(StartOptions startOptions) var app = host.Build(); + var shutdownService = app.Services.GetRequiredService(); + if (startOptions.AutoShutdownTime > -1) + { + shutdownService.EnableAutoShutdown = true; + // start the first iteration of the shutdown timer + app.Services.GetRequiredService().ResetTimer(); + } + if (startOptions.Dump) { var config = app.Services?.GetService(); @@ -227,6 +238,8 @@ public void ConfigureServices(IServiceCollection services) ); services.AddSingleton(singletonRecordingHandler); + services.AddSingleton(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) @@ -237,6 +250,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF } app.UseCors("DefaultPolicy"); app.UseMiddleware(); + app.UseMiddleware(); DebugLogger.ConfigureLogger(loggerFactory); From 1ccd48b0f01c88b06e616aa54ddeebc98110a06d Mon Sep 17 00:00:00 2001 From: Scott Beddall Date: Fri, 13 Dec 2024 16:16:19 -0800 Subject: [PATCH 2/3] for the first time ever I'm pretty happy with asp.net injection --- .../AutoShutdown/ShutdownConfiguration.cs | 1 + .../Common/AutoShutdown/ShutdownTimer.cs | 20 +++++++++---------- .../Azure.Sdk.Tools.TestProxy/Startup.cs | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs index 864c038875f..18f11245bea 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownConfiguration.cs @@ -3,5 +3,6 @@ namespace Azure.Sdk.Tools.TestProxy.Common.AutoShutdown public class ShutdownConfiguration { public bool EnableAutoShutdown { get; set; } = false; + public int TimeoutInSeconds { get; set; } = 300; } } diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs index 4fe5d8cc3d4..cf8ac998d96 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/AutoShutdown/ShutdownTimer.cs @@ -6,13 +6,13 @@ namespace Azure.Sdk.Tools.TestProxy.Common.AutoShutdown { public class ShutdownTimer { - private readonly int _timeoutSeconds; + private readonly ShutdownConfiguration _shutdownConfig; private CancellationTokenSource _cts; private Task _shutdownTask; - public ShutdownTimer(int timeoutSeconds = 300) + public ShutdownTimer(ShutdownConfiguration shutdownConfiguration) { - _timeoutSeconds = timeoutSeconds; + _shutdownConfig = shutdownConfiguration; } public void ResetTimer() @@ -24,10 +24,13 @@ public void ResetTimer() { try { - await Task.Delay(_timeoutSeconds * 1000, _cts.Token); + await Task.Delay(_shutdownConfig.TimeoutInSeconds * 1000, _cts.Token); - System.Console.WriteLine("Server idle timeout reached. Shutting down..."); - Environment.Exit(0); + if (_shutdownConfig.EnableAutoShutdown) + { + System.Console.WriteLine("Server idle timeout reached. Shutting down..."); + Environment.Exit(0); + } } catch (TaskCanceledException) { @@ -35,10 +38,5 @@ public void ResetTimer() } }); } - - public void StopTimer() - { - _cts?.Cancel(); - } } } diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs index 80450d3f9e0..d703dd7559e 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs @@ -150,7 +150,6 @@ private static void StartServer(StartOptions startOptions) var host = Host.CreateDefaultBuilder((startOptions.AdditionalArgs??new string[] { }).ToArray()); - host.ConfigureWebHostDefaults( builder => builder.UseStartup() @@ -187,6 +186,7 @@ private static void StartServer(StartOptions startOptions) if (startOptions.AutoShutdownTime > -1) { shutdownService.EnableAutoShutdown = true; + shutdownService.TimeoutInSeconds = startOptions.AutoShutdownTime; // start the first iteration of the shutdown timer app.Services.GetRequiredService().ResetTimer(); } From 10d1308d4075819a70b551313c47909355be167d Mon Sep 17 00:00:00 2001 From: Scott Beddall <45376673+scbedd@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:28:35 -0800 Subject: [PATCH 3/3] Update tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs --- .../CommandOptions/OptionsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs index 54e6a800872..fde07504ea3 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/CommandOptions/OptionsGenerator.cs @@ -56,7 +56,7 @@ public static RootCommand GenerateCommandLineOptions(Func universalOption.AddAlias("-u"); var autoShutdownOption = new Option( - name: "--shutdownInSeconds", + name: "--auto-shutdown-in-seconds", description: "Integer argument; When provided, the proxy will auto-shutdown after not being contacted over any HTTP route for this number of seconds.", getDefaultValue: () => -1);