-
Notifications
You must be signed in to change notification settings - Fork 733
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
React to SIGTERM exit code change (#1289)
- Loading branch information
Showing
30 changed files
with
1,784 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
src/Hosting/IntegrationTesting/src/ApplicationPublisher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.Extensions.Hosting.IntegrationTesting | ||
{ | ||
public class ApplicationPublisher | ||
{ | ||
public string ApplicationPath { get; } | ||
|
||
public ApplicationPublisher(string applicationPath) | ||
{ | ||
ApplicationPath = applicationPath; | ||
} | ||
|
||
public static readonly string DotnetCommandName = "dotnet"; | ||
|
||
public virtual Task<PublishedApplication> Publish(DeploymentParameters deploymentParameters, ILogger logger) | ||
{ | ||
var publishDirectory = CreateTempDirectory(); | ||
using (logger.BeginScope("dotnet-publish")) | ||
{ | ||
if (string.IsNullOrEmpty(deploymentParameters.TargetFramework)) | ||
{ | ||
throw new Exception($"A target framework must be specified in the deployment parameters for applications that require publishing before deployment"); | ||
} | ||
|
||
var parameters = $"publish " | ||
+ $" --output \"{publishDirectory.FullName}\"" | ||
+ $" --framework {deploymentParameters.TargetFramework}" | ||
+ $" --configuration {deploymentParameters.Configuration}" | ||
+ $" /p:TargetArchitecture={deploymentParameters.RuntimeArchitecture}" | ||
+ " --no-restore"; | ||
|
||
if (deploymentParameters.ApplicationType == ApplicationType.Standalone) | ||
{ | ||
parameters += $" --runtime {GetRuntimeIdentifier(deploymentParameters)}"; | ||
} | ||
else | ||
{ | ||
// Workaround for https://github.com/aspnet/websdk/issues/422 | ||
parameters += " -p:UseAppHost=false"; | ||
} | ||
|
||
parameters += $" {deploymentParameters.AdditionalPublishParameters}"; | ||
|
||
var startInfo = new ProcessStartInfo | ||
{ | ||
FileName = DotnetCommandName, | ||
Arguments = parameters, | ||
UseShellExecute = false, | ||
CreateNoWindow = true, | ||
RedirectStandardError = true, | ||
RedirectStandardOutput = true, | ||
WorkingDirectory = deploymentParameters.ApplicationPath, | ||
}; | ||
|
||
ProcessHelpers.AddEnvironmentVariablesToProcess(startInfo, deploymentParameters.PublishEnvironmentVariables, logger); | ||
|
||
var hostProcess = new Process() { StartInfo = startInfo }; | ||
|
||
logger.LogInformation($"Executing command {DotnetCommandName} {parameters}"); | ||
|
||
hostProcess.StartAndCaptureOutAndErrToLogger("dotnet-publish", logger); | ||
|
||
// A timeout is passed to Process.WaitForExit() for two reasons: | ||
// | ||
// 1. When process output is read asynchronously, WaitForExit() without a timeout blocks until child processes | ||
// are killed, which can cause hangs due to MSBuild NodeReuse child processes started by dotnet.exe. | ||
// With a timeout, WaitForExit() returns when the parent process is killed and ignores child processes. | ||
// https://stackoverflow.com/a/37983587/102052 | ||
// | ||
// 2. If "dotnet publish" does hang indefinitely for some reason, tests should fail fast with an error message. | ||
const int timeoutMinutes = 5; | ||
if (hostProcess.WaitForExit(milliseconds: timeoutMinutes * 60 * 1000)) | ||
{ | ||
if (hostProcess.ExitCode != 0) | ||
{ | ||
var message = $"{DotnetCommandName} publish exited with exit code : {hostProcess.ExitCode}"; | ||
logger.LogError(message); | ||
throw new Exception(message); | ||
} | ||
} | ||
else | ||
{ | ||
var message = $"{DotnetCommandName} publish failed to exit after {timeoutMinutes} minutes"; | ||
logger.LogError(message); | ||
throw new Exception(message); | ||
} | ||
|
||
logger.LogInformation($"{DotnetCommandName} publish finished with exit code : {hostProcess.ExitCode}"); | ||
} | ||
|
||
return Task.FromResult(new PublishedApplication(publishDirectory.FullName, logger)); | ||
} | ||
|
||
private static string GetRuntimeIdentifier(DeploymentParameters deploymentParameters) | ||
{ | ||
var architecture = deploymentParameters.RuntimeArchitecture; | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
{ | ||
return "win-" + architecture; | ||
} | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||
{ | ||
return "linux-" + architecture; | ||
} | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
{ | ||
return "osx-" + architecture; | ||
} | ||
throw new InvalidOperationException("Unrecognized operation system platform"); | ||
} | ||
|
||
protected static DirectoryInfo CreateTempDirectory() | ||
{ | ||
var tempPath = Path.GetTempPath() + Guid.NewGuid().ToString("N"); | ||
var target = new DirectoryInfo(tempPath); | ||
target.Create(); | ||
return target; | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/Hosting/IntegrationTesting/src/Common/ApplicationType.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Extensions.Hosting.IntegrationTesting | ||
{ | ||
public enum ApplicationType | ||
{ | ||
/// <summary> | ||
/// Does not target a specific platform. Requires the matching runtime to be installed. | ||
/// </summary> | ||
Portable, | ||
|
||
/// <summary> | ||
/// All dlls are published with the app for x-copy deploy. Net461 requires this because ASP.NET Core is not in the GAC. | ||
/// </summary> | ||
Standalone | ||
} | ||
} |
157 changes: 157 additions & 0 deletions
157
src/Hosting/IntegrationTesting/src/Common/DeploymentParameters.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Reflection; | ||
|
||
namespace Microsoft.Extensions.Hosting.IntegrationTesting | ||
{ | ||
/// <summary> | ||
/// Parameters to control application deployment. | ||
/// </summary> | ||
public class DeploymentParameters | ||
{ | ||
public DeploymentParameters() | ||
{ | ||
var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute<AssemblyConfigurationAttribute>(); | ||
if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration)) | ||
{ | ||
Configuration = configAttribute.Configuration; | ||
} | ||
} | ||
|
||
public DeploymentParameters(TestVariant variant) | ||
{ | ||
var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute<AssemblyConfigurationAttribute>(); | ||
if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration)) | ||
{ | ||
Configuration = configAttribute.Configuration; | ||
} | ||
|
||
TargetFramework = variant.Tfm; | ||
ApplicationType = variant.ApplicationType; | ||
RuntimeArchitecture = variant.Architecture; | ||
} | ||
|
||
/// <summary> | ||
/// Creates an instance of <see cref="DeploymentParameters"/>. | ||
/// </summary> | ||
/// <param name="applicationPath">Source code location of the target location to be deployed.</param> | ||
/// <param name="runtimeFlavor">Flavor of the clr to run against.</param> | ||
/// <param name="runtimeArchitecture">Architecture of the runtime to be used.</param> | ||
public DeploymentParameters( | ||
string applicationPath, | ||
RuntimeFlavor runtimeFlavor, | ||
RuntimeArchitecture runtimeArchitecture) | ||
{ | ||
if (string.IsNullOrEmpty(applicationPath)) | ||
{ | ||
throw new ArgumentException("Value cannot be null.", nameof(applicationPath)); | ||
} | ||
|
||
if (!Directory.Exists(applicationPath)) | ||
{ | ||
throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", applicationPath)); | ||
} | ||
|
||
ApplicationPath = applicationPath; | ||
ApplicationName = new DirectoryInfo(ApplicationPath).Name; | ||
RuntimeFlavor = runtimeFlavor; | ||
|
||
var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute<AssemblyConfigurationAttribute>(); | ||
if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration)) | ||
{ | ||
Configuration = configAttribute.Configuration; | ||
} | ||
} | ||
|
||
public DeploymentParameters(DeploymentParameters parameters) | ||
{ | ||
foreach (var propertyInfo in typeof(DeploymentParameters).GetProperties()) | ||
{ | ||
if (propertyInfo.CanWrite) | ||
{ | ||
propertyInfo.SetValue(this, propertyInfo.GetValue(parameters)); | ||
} | ||
} | ||
|
||
foreach (var kvp in parameters.EnvironmentVariables) | ||
{ | ||
EnvironmentVariables.Add(kvp); | ||
} | ||
|
||
foreach (var kvp in parameters.PublishEnvironmentVariables) | ||
{ | ||
PublishEnvironmentVariables.Add(kvp); | ||
} | ||
} | ||
|
||
public ApplicationPublisher ApplicationPublisher { get; set; } | ||
|
||
public RuntimeFlavor RuntimeFlavor { get; set; } | ||
|
||
public RuntimeArchitecture RuntimeArchitecture { get; set; } = RuntimeArchitecture.x64; | ||
|
||
public string EnvironmentName { get; set; } | ||
|
||
public string ApplicationPath { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the name of the application. This is used to execute the application when deployed. | ||
/// Defaults to the file name of <see cref="ApplicationPath"/>. | ||
/// </summary> | ||
public string ApplicationName { get; set; } | ||
|
||
public string TargetFramework { get; set; } | ||
|
||
/// <summary> | ||
/// Configuration under which to build (ex: Release or Debug) | ||
/// </summary> | ||
public string Configuration { get; set; } = "Debug"; | ||
|
||
/// <summary> | ||
/// Space separated command line arguments to be passed to dotnet-publish | ||
/// </summary> | ||
public string AdditionalPublishParameters { get; set; } | ||
|
||
/// <summary> | ||
/// To publish the application before deployment. | ||
/// </summary> | ||
public bool PublishApplicationBeforeDeployment { get; set; } | ||
|
||
public bool PreservePublishedApplicationForDebugging { get; set; } = false; | ||
|
||
public bool StatusMessagesEnabled { get; set; } = true; | ||
|
||
public ApplicationType ApplicationType { get; set; } | ||
|
||
public string PublishedApplicationRootPath { get; set; } | ||
|
||
/// <summary> | ||
/// Environment variables to be set before starting the host. | ||
/// Not applicable for IIS Scenarios. | ||
/// </summary> | ||
public IDictionary<string, string> EnvironmentVariables { get; } = new Dictionary<string, string>(); | ||
|
||
/// <summary> | ||
/// Environment variables used when invoking dotnet publish. | ||
/// </summary> | ||
public IDictionary<string, string> PublishEnvironmentVariables { get; } = new Dictionary<string, string>(); | ||
|
||
/// <summary> | ||
/// For any application level cleanup to be invoked after performing host cleanup. | ||
/// </summary> | ||
public Action<DeploymentParameters> UserAdditionalCleanup { get; set; } | ||
|
||
public override string ToString() | ||
{ | ||
return string.Format( | ||
"[Variation] :: Runtime={0}, Arch={1}, Publish={2}", | ||
RuntimeFlavor, | ||
RuntimeArchitecture, | ||
PublishApplicationBeforeDeployment); | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/Hosting/IntegrationTesting/src/Common/DeploymentResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.Extensions.Hosting.IntegrationTesting | ||
{ | ||
/// <summary> | ||
/// Result of a deployment. | ||
/// </summary> | ||
public class DeploymentResult | ||
{ | ||
private readonly ILoggerFactory _loggerFactory; | ||
|
||
/// <summary> | ||
/// The folder where the application is hosted. This path can be different from the | ||
/// original application source location if published before deployment. | ||
/// </summary> | ||
public string ContentRoot { get; } | ||
|
||
/// <summary> | ||
/// Original deployment parameters used for this deployment. | ||
/// </summary> | ||
public DeploymentParameters DeploymentParameters { get; } | ||
|
||
/// <summary> | ||
/// Triggered when the host process dies or pulled down. | ||
/// </summary> | ||
public CancellationToken HostShutdownToken { get; } | ||
|
||
public DeploymentResult(ILoggerFactory loggerFactory, DeploymentParameters deploymentParameters) | ||
: this(loggerFactory, deploymentParameters: deploymentParameters, contentRoot: string.Empty, hostShutdownToken: CancellationToken.None) | ||
{ } | ||
|
||
public DeploymentResult(ILoggerFactory loggerFactory, DeploymentParameters deploymentParameters, string contentRoot, CancellationToken hostShutdownToken) | ||
{ | ||
_loggerFactory = loggerFactory; | ||
|
||
ContentRoot = contentRoot; | ||
DeploymentParameters = deploymentParameters; | ||
HostShutdownToken = hostShutdownToken; | ||
} | ||
} | ||
} |
Oops, something went wrong.