From afba2d05aec0f56db287eecd3711e264db501ecc Mon Sep 17 00:00:00 2001 From: jhkimnew Date: Wed, 12 Jul 2017 16:48:47 -0700 Subject: [PATCH] Jhkim/add test gracefulshutdown (#120) Add new test for Graceful shutdown and Filtering MS request headers --- .../Framework/InitializeTestMachine.cs | 17 ++- test/AspNetCoreModule.Test/FunctionalTest.cs | 39 +++++-- .../FunctionalTestHelper.cs | 109 +++++++++++++++--- .../Program.cs | 32 ++++- .../Startup.cs | 19 ++- 5 files changed, 184 insertions(+), 32 deletions(-) diff --git a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs index 2f960f45e8..f64a1ed317 100644 --- a/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs +++ b/test/AspNetCoreModule.Test/Framework/InitializeTestMachine.cs @@ -13,8 +13,9 @@ namespace AspNetCoreModule.Test.Framework public const string ANCMTestFlagsEnvironmentVariable = "%ANCMTestFlags%"; public const string ANCMTestFlagsDefaultContext = "AdminAnd64Bit"; public const string ANCMTestFlagsTestSkipContext = "SkipTest"; - public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivateAspNetCoreFile"; - + public const string ANCMTestFlagsUsePrivateAspNetCoreFileContext = "UsePrivate"; + public const string ANCMTestFlagsUseIISExpressContext = "UseIISExpress"; + private static bool? _usePrivateAspNetCoreFile = null; public static bool? UsePrivateAspNetCoreFile { @@ -115,11 +116,17 @@ namespace AspNetCoreModule.Test.Framework IISConfigUtility.IsIISReady = false; if (IISConfigUtility.IsIISInstalled == true) { - if (Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS") != null && Environment.GetEnvironmentVariable("ANCMTEST_USE_IISEXPRESS").Equals("true", StringComparison.InvariantCultureIgnoreCase)) - { + var envValue = Environment.ExpandEnvironmentVariables(ANCMTestFlagsEnvironmentVariable); + if (envValue.ToLower().Contains(ANCMTestFlagsUseIISExpressContext.ToLower())) + { + TestUtility.LogInformation("UseIISExpress is set"); throw new System.ApplicationException("'ANCMTestServerType' environment variable is set to 'true'"); } - + else + { + TestUtility.LogInformation("UseIISExpress is not set"); + } + // check websocket is installed if (File.Exists(Path.Combine(IISConfigUtility.Strings.IIS64BitPath, "iiswsock.dll"))) { diff --git a/test/AspNetCoreModule.Test/FunctionalTest.cs b/test/AspNetCoreModule.Test/FunctionalTest.cs index 7488278f03..93d0df1f1e 100644 --- a/test/AspNetCoreModule.Test/FunctionalTest.cs +++ b/test/AspNetCoreModule.Test/FunctionalTest.cs @@ -39,15 +39,21 @@ namespace AspNetCoreModule.Test [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4)] - [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0)] - [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0)] - public Task ShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 25, 19, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 25, 19, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4, true)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 5, 4, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 5, 4, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0, false)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, 0, 0, true)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0, false)] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, 0, 0, true)] + public Task ShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime, bool isGraceFullShutdownEnabled) { - return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime); + return DoShutdownTimeLimitTest(appPoolBitness, valueOfshutdownTimeLimit, expectedClosingTime, isGraceFullShutdownEnabled); } [ConditionalTheory] @@ -274,7 +280,22 @@ namespace AspNetCoreModule.Test { return DoSendHTTPSRequestTest(appPoolBitness); } - + + [ConditionalTheory] + [ANCMTestSkipCondition("%ANCMTestFlags%")] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE", "f")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "mS-ASPNETCORE", "fo")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE-", "foo")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "mS-ASPNETCORE-f", "fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")] + [InlineData(IISConfigUtility.AppPoolBitness.enable32Bit, "MS-ASPNETCORE-foo", "foo")] + [InlineData(IISConfigUtility.AppPoolBitness.noChange, "MS-ASPNETCORE-foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", "bar")] + public Task FilterOutMSRequestHeadersTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestHeader, string requestHeaderValue) + { + return DoFilterOutMSRequestHeadersTest(appPoolBitness, requestHeader, requestHeaderValue); + } + [ConditionalTheory] [ANCMTestSkipCondition("%ANCMTestFlags%")] [OSSkipCondition(OperatingSystems.Linux)] diff --git a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs index 0e9c0fcd0a..f20551586e 100644 --- a/test/AspNetCoreModule.Test/FunctionalTestHelper.cs +++ b/test/AspNetCoreModule.Test/FunctionalTestHelper.cs @@ -711,37 +711,54 @@ namespace AspNetCoreModule.Test } } - public static async Task DoShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime) + public static async Task DoShutdownTimeLimitTest(IISConfigUtility.AppPoolBitness appPoolBitness, int valueOfshutdownTimeLimit, int expectedClosingTime, bool isGraceFullShutdownEnabled) { using (var testSite = new TestWebSite(appPoolBitness, "DoShutdownTimeLimitTest")) { using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) { - // Set new value (10 second) to make the backend process get the Ctrl-C signal and measure when the recycle happens - iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); - iisConfig.SetANCMConfig( - testSite.SiteName, - testSite.AspNetCoreApp.Name, - "environmentVariable", - new string[] { "ANCMTestShutdownDelay", "20000" } - ); + DateTime startTime = DateTime.Now; - await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); + // Make shutdownDelay time with hard coded value such as 20 seconds and test vairious shutdonwTimeLimit, either less than 20 seconds or bigger then 20 seconds + int shutdownDelayTime = 20000; + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", valueOfshutdownTimeLimit); + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "ANCMTestShutdownDelay", shutdownDelayTime.ToString() }); + string expectedGracefulShutdownResponseStatusCode = "202"; + if (!isGraceFullShutdownEnabled) + { + iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "environmentVariable", new string[] { "GracefulShutdown", "disabled" }); + expectedGracefulShutdownResponseStatusCode = "200"; + } + + string response = await GetResponse(testSite.AspNetCoreApp.GetUri(""), HttpStatusCode.OK); + Assert.True(response == "Running"); + string backendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); var backendProcess = Process.GetProcessById(Convert.ToInt32(backendProcessId)); - // Set a new value such as 100 to make the backend process being recycled - DateTime startTime = DateTime.Now; + // Set a new configuration value to make the backend process being recycled + DateTime startTime2 = DateTime.Now; iisConfig.SetANCMConfig(testSite.SiteName, testSite.AspNetCoreApp.Name, "shutdownTimeLimit", 100); backendProcess.WaitForExit(30000); DateTime endTime = DateTime.Now; - var difference = endTime - startTime; + var difference = endTime - startTime2; Assert.True(difference.Seconds >= expectedClosingTime); Assert.True(difference.Seconds < expectedClosingTime + 3); - Assert.True(backendProcessId != await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK)); + string newBackendProcessId = await GetResponse(testSite.AspNetCoreApp.GetUri("GetProcessId"), HttpStatusCode.OK); + Assert.True(backendProcessId != newBackendProcessId); await VerifyResponseBody(testSite.AspNetCoreApp.GetUri(), "Running", HttpStatusCode.OK); - } + // if expectedClosing time is less than the shutdownDelay time, gracefulshutdown is supposed to fail and failure event is expected + if (expectedClosingTime*1000 + 1000 == shutdownDelayTime) + { + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownEvent(arg1, arg2), startTime, backendProcessId)); + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownEvent(arg1, arg2), startTime, expectedGracefulShutdownResponseStatusCode)); + } + else + { + Assert.True(TestUtility.RetryHelper((arg1, arg2) => VerifyANCMGracefulShutdownFailureEvent(arg1, arg2), startTime, backendProcessId)); + } + } testSite.AspNetCoreApp.RestoreFile("web.config"); } } @@ -1180,6 +1197,58 @@ namespace AspNetCoreModule.Test } } + public static async Task DoFilterOutMSRequestHeadersTest(IISConfigUtility.AppPoolBitness appPoolBitness, string requestHeader, string requestHeaderValue) + { + using (var testSite = new TestWebSite(appPoolBitness, "DoSendHTTPSRequestTest", startIISExpress: false)) + { + using (var iisConfig = new IISConfigUtility(testSite.IisServerType, testSite.IisExpressConfigPath)) + { + string hostName = ""; + string subjectName = "localhost"; + string ipAddress = "*"; + string hexIPAddress = "0x00"; + int sslPort = InitializeTestMachine.SiteId + 6300; + + // Add https binding and get https uri information + iisConfig.AddBindingToSite(testSite.SiteName, ipAddress, sslPort, hostName, "https"); + + // Create a self signed certificate + string thumbPrint = iisConfig.CreateSelfSignedCertificate(subjectName); + + // Export the self signed certificate to rootCA + iisConfig.ExportCertificateTo(thumbPrint, sslStoreTo: @"Cert:\LocalMachine\Root"); + + // Configure http.sys ssl certificate mapping to IP:Port endpoint with the newly created self signed certificage + iisConfig.SetSSLCertificate(sslPort, hexIPAddress, thumbPrint); + + // starting IISExpress was deffered after creating test applications and now it is ready to start it + testSite.StartIISExpress(); + + // Verify http request + string requestHeaders = string.Empty; + requestHeaders = await GetResponseAndHeaders(testSite.AspNetCoreApp.GetUri("DumpRequestHeaders"), new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); + requestHeaders = requestHeaders.Replace(" ", ""); + Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower()+":")); + + // Verify https request + Uri targetHttpsUri = testSite.AspNetCoreApp.GetUri(null, sslPort, protocol: "https"); + requestHeader = await GetResponseAndHeaders(targetHttpsUri, new string[] { "Accept-Encoding", "gzip", requestHeader, requestHeaderValue }, HttpStatusCode.OK); + requestHeaders = requestHeaders.Replace(" ", ""); + Assert.False(requestHeaders.ToUpper().Contains(requestHeader.ToLower() + ":")); + + // Remove the SSL Certificate mapping + iisConfig.RemoveSSLCertificate(sslPort, hexIPAddress); + + // Remove the newly created self signed certificate + iisConfig.DeleteCertificate(thumbPrint); + + // Remove the exported self signed certificate on rootCA + iisConfig.DeleteCertificate(thumbPrint, @"Cert:\LocalMachine\Root"); + } + testSite.AspNetCoreApp.RestoreFile("web.config"); + } + } + public static async Task DoClientCertificateMappingTest(IISConfigUtility.AppPoolBitness appPoolBitness, bool useHTTPSMiddleWare) { using (var testSite = new TestWebSite(appPoolBitness, "DoClientCertificateMappingTest", startIISExpress: false)) @@ -1512,6 +1581,16 @@ namespace AspNetCoreModule.Test return VerifyEventLog(1001, startFrom, includeThis); } + private static bool VerifyANCMGracefulShutdownEvent(DateTime startFrom, string includeThis) + { + return VerifyEventLog(1006, startFrom, includeThis); + } + + private static bool VerifyANCMGracefulShutdownFailureEvent(DateTime startFrom, string includeThis) + { + return VerifyEventLog(1005, startFrom, includeThis); + } + private static bool VerifyApplicationEventLog(int eventID, DateTime startFrom, string includeThis) { return VerifyEventLog(eventID, startFrom, includeThis); diff --git a/test/AspNetCoreModule.TestSites.Standard/Program.cs b/test/AspNetCoreModule.TestSites.Standard/Program.cs index e02eeabd3b..35e934da14 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Program.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Program.cs @@ -14,6 +14,9 @@ namespace AspnetCoreModule.TestSites.Standard { public static class Program { + public static IApplicationLifetime AappLifetime; + public static int GracefulShutdownDelayTime = 0; + private static X509Certificate2 _x509Certificate2; public static void Main(string[] args) @@ -141,7 +144,34 @@ namespace AspnetCoreModule.TestSites.Standard } var host = builder.Build(); - host.Run(); + AappLifetime = (IApplicationLifetime)host.Services.GetService(typeof(IApplicationLifetime)); + + string gracefulShutdownDelay = Environment.GetEnvironmentVariable("GracefulShutdownDelayTime"); + if (!string.IsNullOrEmpty(gracefulShutdownDelay)) + { + GracefulShutdownDelayTime = Convert.ToInt32(gracefulShutdownDelay); + } + + AappLifetime.ApplicationStarted.Register( + () => Thread.Sleep(1000) + ); + AappLifetime.ApplicationStopping.Register( + () => Thread.Sleep(Startup.SleeptimeWhileClosing / 2) + ); + AappLifetime.ApplicationStopped.Register( + () => { + Thread.Sleep(Startup.SleeptimeWhileClosing / 2); + Startup.SleeptimeWhileClosing = 0; // All of SleeptimeWhileClosing is used now + } + ); + try + { + host.Run(); + } + catch + { + // ignore + } if (Startup.SleeptimeWhileClosing != 0) { diff --git a/test/AspNetCoreModule.TestSites.Standard/Startup.cs b/test/AspNetCoreModule.TestSites.Standard/Startup.cs index 149b4b5429..4a69fc9fe9 100644 --- a/test/AspNetCoreModule.TestSites.Standard/Startup.cs +++ b/test/AspNetCoreModule.TestSites.Standard/Startup.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -199,7 +200,7 @@ namespace AspnetCoreModule.TestSites.Standard app.Map("/ImpersonateMiddleware", subApp => { - subApp.UseMiddleware(); + subApp.UseMiddleware(); subApp.Run(context => { return context.Response.WriteAsync(""); @@ -302,7 +303,21 @@ namespace AspnetCoreModule.TestSites.Standard { response += de.Key + ":" + de.Value + "
"; } - } + } + } + + // Handle shutdown event from ANCM + if (HttpMethods.IsPost(context.Request.Method) && + //context.Request.Path.Equals(ANCMRequestPath) && + string.Equals("shutdown", context.Request.Headers["MS-ASPNETCORE-EVENT"], StringComparison.OrdinalIgnoreCase)) + { + response = "shutdown"; + string shutdownMode = Environment.GetEnvironmentVariable("GracefulShutdown"); + if (String.IsNullOrEmpty(shutdownMode) || !shutdownMode.ToLower().StartsWith("disabled")) + { + context.Response.StatusCode = StatusCodes.Status202Accepted; + Program.AappLifetime.StopApplication(); + } } return context.Response.WriteAsync(response); });