Merge pull request #1173 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Pavel Krymets 2018-08-08 12:47:30 -07:00 committed by GitHub
commit 11514cd17f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 417 additions and 524 deletions

View File

@ -8,7 +8,7 @@ phases:
afterBuild: afterBuild:
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: Upload binlog displayName: Upload binlog
condition: always() condition: eq(variables['system.pullrequest.isfork'], false)
inputs: inputs:
artifactName: logs artifactName: logs
artifactType: Container artifactType: Container

View File

@ -6,7 +6,7 @@
#include "GlobalVersionUtility.h" #include "GlobalVersionUtility.h"
namespace fs = std::experimental::filesystem; namespace fs = std::filesystem;
// throws runtime error if no request handler versions are installed. // throws runtime error if no request handler versions are installed.
// Throw invalid_argument if any argument is null // Throw invalid_argument if any argument is null

View File

@ -43,7 +43,7 @@ PollingAppOfflineApplication::CheckAppOffline()
} }
std::experimental::filesystem::path PollingAppOfflineApplication::GetAppOfflineLocation(IHttpApplication& pApplication) std::filesystem::path PollingAppOfflineApplication::GetAppOfflineLocation(IHttpApplication& pApplication)
{ {
return std::experimental::filesystem::path(pApplication.GetApplicationPhysicalPath()) / "app_offline.htm"; return std::filesystem::path(pApplication.GetApplicationPhysicalPath()) / "app_offline.htm";
} }

View File

@ -30,8 +30,8 @@ public:
void StopInternal(bool fServerInitiated) override { UNREFERENCED_PARAMETER(fServerInitiated); } void StopInternal(bool fServerInitiated) override { UNREFERENCED_PARAMETER(fServerInitiated); }
protected: protected:
std::experimental::filesystem::path m_appOfflineLocation; std::filesystem::path m_appOfflineLocation;
static std::experimental::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication); static std::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication);
private: private:
static const int c_appOfflineRefreshIntervalMS = 200; static const int c_appOfflineRefreshIntervalMS = 200;

View File

@ -13,7 +13,7 @@
#include "HandleWrapper.h" #include "HandleWrapper.h"
#include "Environment.h" #include "Environment.h"
namespace fs = std::experimental::filesystem; namespace fs = std::filesystem;
// //
// Runs a standalone appliction. // Runs a standalone appliction.
@ -98,7 +98,7 @@ HOSTFXR_UTILITY::GetStandaloneHostfxrParameters(
} }
BOOL BOOL
HOSTFXR_UTILITY::IsDotnetExecutable(const std::experimental::filesystem::path & dotnetPath) HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
{ {
auto name = dotnetPath.filename(); auto name = dotnetPath.filename();
name.replace_extension(""); name.replace_extension("");

View File

@ -44,7 +44,7 @@ public:
static static
BOOL BOOL
IsDotnetExecutable( IsDotnetExecutable(
_In_ const std::experimental::filesystem::path & dotnetPath _In_ const std::filesystem::path & dotnetPath
); );
static static
@ -74,25 +74,25 @@ public:
); );
static static
std::optional<std::experimental::filesystem::path> std::optional<std::filesystem::path>
GetAbsolutePathToHostFxr( GetAbsolutePathToHostFxr(
_In_ const std::experimental::filesystem::path & dotnetPath, _In_ const std::filesystem::path & dotnetPath,
_In_ HANDLE hEventLog _In_ HANDLE hEventLog
); );
static static
std::optional<std::experimental::filesystem::path> std::optional<std::filesystem::path>
GetAbsolutePathToDotnetFromProgramFiles(); GetAbsolutePathToDotnetFromProgramFiles();
static static
std::optional<std::experimental::filesystem::path> std::optional<std::filesystem::path>
InvokeWhereToFindDotnet(); InvokeWhereToFindDotnet();
static static
std::optional<std::experimental::filesystem::path> std::optional<std::filesystem::path>
GetAbsolutePathToDotnet( GetAbsolutePathToDotnet(
_In_ const std::experimental::filesystem::path & applicationPath, _In_ const std::filesystem::path & applicationPath,
_In_ const std::experimental::filesystem::path & requestedPath _In_ const std::filesystem::path & requestedPath
); );
}; };

View File

@ -1,380 +0,0 @@
// 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.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Web.Administration;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
/// <summary>
/// Represents the IIS website registered in the global applicationHost.config
/// </summary>
internal class IISApplication
{
internal const int ERROR_OBJECT_NOT_FOUND = unchecked((int)0x800710D8);
internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200);
private readonly ServerManager _serverManager = new ServerManager();
private readonly DeploymentParameters _deploymentParameters;
private readonly ILogger _logger;
private readonly string _ancmVersion;
private readonly string _ancmDllName;
private readonly string _apphostConfigBackupPath;
private static readonly string _apphostConfigPath = Path.Combine(
Environment.SystemDirectory,
"inetsrv",
"config",
"applicationhost.config");
public Process HostProcess { get; set; }
public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
{
_deploymentParameters = deploymentParameters;
_logger = logger;
_ancmVersion = deploymentParameters.AncmVersion.ToString();
_ancmDllName = deploymentParameters.AncmVersion == AncmVersion.AspNetCoreModuleV2 ? "aspnetcorev2.dll" : "aspnetcore.dll";
WebSiteName = CreateTestSiteName();
AppPoolName = $"{WebSiteName}Pool";
_apphostConfigBackupPath = Path.Combine(
Environment.SystemDirectory,
"inetsrv",
"config",
$"applicationhost.config.{WebSiteName}backup");
}
public string WebSiteName { get; }
public string AppPoolName { get; }
public async Task StartIIS(Uri uri, string contentRoot)
{
// Backup currently deployed apphost.config file
using (_logger.BeginScope("StartIIS"))
{
var port = uri.Port;
if (port == 0)
{
throw new NotSupportedException("Cannot set port 0 for IIS.");
}
AddTemporaryAppHostConfig();
ConfigureAppPool(contentRoot);
ConfigureSite(contentRoot, port);
ConfigureAppHostConfig();
_serverManager.CommitChanges();
await WaitUntilSiteStarted();
}
}
private async Task WaitUntilSiteStarted()
{
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
var serverManager = new ServerManager();
var appPool = serverManager.ApplicationPools.FirstOrDefault(s => s.Name.Equals(AppPoolName));
var site = serverManager.Sites.FirstOrDefault(s => s.Name.Equals(WebSiteName));
if (site.State == ObjectState.Started)
{
var workerProcess = appPool.WorkerProcesses.SingleOrDefault();
if (workerProcess != null)
{
HostProcess = Process.GetProcessById(workerProcess.ProcessId);
_logger.LogInformation($"Site {WebSiteName} has started.");
return;
}
}
else if (site.State != ObjectState.Starting)
{
_logger.LogInformation($"Site hasn't started with state: {site.State.ToString()}");
var state = site.Start();
_logger.LogInformation($"Tried to start site, state: {state.ToString()}");
}
}
catch (Exception ex) when (
ex is DllNotFoundException ||
ex is COMException &&
(ex.HResult == ERROR_OBJECT_NOT_FOUND || ex.HResult == ERROR_SHARING_VIOLATION))
{
// Accessing the site.State property while the site
// is starting up returns the COMException
// The object identifier does not represent a valid object.
// (Exception from HRESULT: 0x800710D8)
// This also means the site is not started yet, so catch and retry
// after waiting.
}
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to start site.");
}
public async Task StopAndDeleteAppPool()
{
if (string.IsNullOrEmpty(WebSiteName))
{
return;
}
StopSite();
StopAppPool();
_serverManager.CommitChanges();
await WaitUntilSiteStopped();
RestoreAppHostConfig();
}
private async Task WaitUntilSiteStopped()
{
var site = _serverManager.Sites.Where(element => element.Name == WebSiteName).FirstOrDefault();
if (site == null)
{
return;
}
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
if (site.State == ObjectState.Stopped)
{
if (HostProcess.HasExited)
{
_logger.LogInformation($"Site {WebSiteName} has stopped successfully.");
return;
}
}
}
catch (COMException)
{
// Accessing the site.State property while the site
// is shutdown down returns the COMException
return;
}
_logger.LogWarning($"IIS has not stopped after {sw.Elapsed.TotalMilliseconds}");
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to stop site {site}.");
}
private void AddTemporaryAppHostConfig()
{
RetryFileOperation(() => File.Move(_apphostConfigPath, _apphostConfigBackupPath),
e => _logger.LogWarning($"Failed to backup apphost.config: {e.Message}"));
_logger.LogInformation($"Backed up {_apphostConfigPath} to {_apphostConfigBackupPath}");
RetryFileOperation(
() => File.WriteAllText(_apphostConfigPath, _deploymentParameters.ServerConfigTemplateContent ?? File.ReadAllText("IIS.config")),
e => _logger.LogWarning($"Failed to copy IIS.config to apphost.config: {e.Message}"));
_logger.LogInformation($"Copied contents of IIS.config to {_apphostConfigPath}");
}
private void RestoreAppHostConfig()
{
if (File.Exists(_apphostConfigPath))
{
RetryFileOperation(
() => File.Delete(_apphostConfigPath),
e => _logger.LogWarning($"Failed to delete file : {e.Message}"));
}
if (File.Exists(_apphostConfigBackupPath))
{
RetryFileOperation(
() => File.Move(_apphostConfigBackupPath, _apphostConfigPath),
e => _logger.LogError($"Failed to backup apphost.config: {e.Message}"));
}
else
{
// Test failed to create backup config file, put a default one from IIS.config there instead.
// An apphost.config file is required to be replaced because we use it for removing the app pool.
RetryFileOperation(
() => File.WriteAllText(_apphostConfigPath, File.ReadAllText("IIS.config")),
e => _logger.LogWarning($"Failed to copy IIS.config to apphost.config: {e.Message}"));
}
_logger.LogInformation($"Restored {_apphostConfigPath}.");
}
private void RetryFileOperation(Action retryBlock, Action<Exception> exceptionBlock)
{
RetryHelper.RetryOperation(retryBlock,
exceptionBlock,
retryCount: 10,
retryDelayMilliseconds: 100);
}
private ApplicationPool ConfigureAppPool(string contentRoot)
{
try
{
var pool = _serverManager.ApplicationPools.Add(AppPoolName);
pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
pool.ManagedRuntimeVersion = string.Empty;
pool.StartMode = StartMode.AlwaysRunning;
AddEnvironmentVariables(contentRoot, pool);
_logger.LogInformation($"Configured AppPool {AppPoolName}");
return pool;
}
catch (COMException comException)
{
_logger.LogError(File.ReadAllText(_apphostConfigPath));
throw comException;
}
}
private void AddEnvironmentVariables(string contentRoot, ApplicationPool pool)
{
try
{
var envCollection = pool.GetCollection("environmentVariables");
foreach (var tuple in _deploymentParameters.EnvironmentVariables)
{
AddEnvironmentVariableToAppPool(envCollection, tuple.Key, tuple.Value);
}
}
catch (COMException comException)
{
_logger.LogInformation($"Could not add environment variables to worker process: {comException.Message}");
}
}
private static void AddEnvironmentVariableToAppPool(ConfigurationElementCollection envCollection, string key, string value)
{
var addElement = envCollection.CreateElement("add");
addElement["name"] = key;
addElement["value"] = value;
envCollection.Add(addElement);
}
private Site ConfigureSite(string contentRoot, int port)
{
var site = _serverManager.Sites.Add(WebSiteName, contentRoot, port);
site.Applications.Single().ApplicationPoolName = AppPoolName;
_logger.LogInformation($"Configured Site {WebSiteName} with AppPool {AppPoolName}");
return site;
}
private Configuration ConfigureAppHostConfig()
{
var config = _serverManager.GetApplicationHostConfiguration();
SetGlobalModuleSection(config);
SetModulesSection(config);
return config;
}
private void StopSite()
{
var site = _serverManager.Sites.Where(sites => sites.Name == WebSiteName).SingleOrDefault();
site.Stop();
}
private void StopAppPool()
{
var appPool = _serverManager.ApplicationPools.Where(pool => pool.Name == AppPoolName).SingleOrDefault();
appPool.Stop();
}
private void SetGlobalModuleSection(Configuration config)
{
var ancmFile = GetAncmLocation();
var globalModulesSection = config.GetSection("system.webServer/globalModules");
var globalConfigElement = globalModulesSection
.GetCollection()
.Where(element => (string)element["name"] == _ancmVersion)
.FirstOrDefault();
if (globalConfigElement == null)
{
_logger.LogInformation($"Could not find {_ancmVersion} section in global modules; creating section.");
var addElement = globalModulesSection.GetCollection().CreateElement("add");
addElement["name"] = _ancmVersion;
addElement["image"] = ancmFile;
globalModulesSection.GetCollection().Add(addElement);
}
else
{
_logger.LogInformation($"Replacing {_ancmVersion} section in global modules with {ancmFile}");
globalConfigElement["image"] = ancmFile;
}
}
private void SetModulesSection(Configuration config)
{
var modulesSection = config.GetSection("system.webServer/modules");
var moduleConfigElement = modulesSection.GetCollection().Where(element => (string)element["name"] == _ancmVersion).FirstOrDefault();
if (moduleConfigElement == null)
{
_logger.LogInformation($"Could not find {_ancmVersion} section in modules; creating section.");
var moduleElement = modulesSection.GetCollection().CreateElement("add");
moduleElement["name"] = _ancmVersion;
modulesSection.GetCollection().Add(moduleElement);
}
}
private string CreateTestSiteName()
{
if (!string.IsNullOrEmpty(_deploymentParameters.SiteName))
{
return $"{_deploymentParameters.SiteName}{DateTime.Now.ToString("yyyyMMddHHmmss")}";
}
else
{
return $"testsite{DateTime.Now.ToString("yyyyMMddHHmmss")}";
}
}
private string GetAncmLocation()
{
var dllRoot = AppContext.BaseDirectory;
var arch = _deploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{_ancmDllName}" : $@"x86\{_ancmDllName}";
var ancmFile = Path.Combine(dllRoot, arch);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
ancmFile = Path.Combine(dllRoot, _ancmDllName);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
}
}
return ancmFile;
}
}
}

View File

@ -1,22 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.Server.IntegrationTesting.Common; using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Web.Administration;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{ {
/// <summary> /// <summary>
/// Deployer for IIS. /// Deployer for IIS.
/// </summary> /// </summary>
public partial class IISDeployer : IISDeployerBase public class IISDeployer : IISDeployerBase
{ {
private IISApplication _application; internal const int ERROR_OBJECT_NOT_FOUND = unchecked((int)0x800710D8);
internal const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
internal const int ERROR_SERVICE_CANNOT_ACCEPT_CTRL = unchecked((int)0x80070425);
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10);
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200);
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource(); private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
private string _configPath;
private string _debugLogFile;
public Process HostProcess { get; set; }
public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(new IISDeploymentParameters(deploymentParameters), loggerFactory) : base(new IISDeploymentParameters(deploymentParameters), loggerFactory)
{ {
@ -29,14 +46,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public override void Dispose() public override void Dispose()
{ {
if (_application != null) StopAndDeleteAppPool().GetAwaiter().GetResult();
{
_application.StopAndDeleteAppPool().GetAwaiter().GetResult();
TriggerHostShutdown(_hostShutdownToken); TriggerHostShutdown(_hostShutdownToken);
}
GetLogsFromFile($"{_application.WebSiteName}.txt"); GetLogsFromFile();
CleanPublishedOutput(); CleanPublishedOutput();
InvokeUserApplicationCleanup(); InvokeUserApplicationCleanup();
@ -50,17 +64,23 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{ {
StartTimer(); StartTimer();
var contentRoot = string.Empty;
if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent))
{ {
DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config"); DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config");
} }
_application = new IISApplication(IISDeploymentParameters, Logger);
// For now, only support using published output // For now, only support using published output
DeploymentParameters.PublishApplicationBeforeDeployment = true; DeploymentParameters.PublishApplicationBeforeDeployment = true;
// Do not override settings set on parameters
if (!IISDeploymentParameters.HandlerSettings.ContainsKey("debugLevel") &&
!IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile"))
{
_debugLogFile = Path.GetTempFileName();
IISDeploymentParameters.HandlerSettings["debugLevel"] = "4";
IISDeploymentParameters.HandlerSettings["debugFile"] = _debugLogFile;
}
if (DeploymentParameters.ApplicationType == ApplicationType.Portable) if (DeploymentParameters.ApplicationType == ApplicationType.Portable)
{ {
DefaultWebConfigActions.Add( DefaultWebConfigActions.Add(
@ -69,28 +89,19 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture))); DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture)));
} }
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
DotnetPublish();
contentRoot = DeploymentParameters.PublishedApplicationRootPath;
// Do not override settings set on parameters
if (!IISDeploymentParameters.HandlerSettings.ContainsKey("debugLevel") &&
!IISDeploymentParameters.HandlerSettings.ContainsKey("debugFile"))
{
var logFile = Path.Combine(contentRoot, $"{_application.WebSiteName}.txt");
IISDeploymentParameters.HandlerSettings["debugLevel"] = "4";
IISDeploymentParameters.HandlerSettings["debugFile"] = logFile;
}
DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection( DotnetPublish();
key: "modules", var contentRoot = DeploymentParameters.PublishedApplicationRootPath;
value: DeploymentParameters.AncmVersion.ToString()));
RunWebConfigActions(contentRoot); DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection(
} key: "modules",
value: DeploymentParameters.AncmVersion.ToString()));
RunWebConfigActions(contentRoot);
var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint); var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint);
// To prevent modifying the IIS setup concurrently. // To prevent modifying the IIS setup concurrently.
await _application.StartIIS(uri, contentRoot); await StartIIS(uri, contentRoot);
// Warm up time for IIS setup. // Warm up time for IIS setup.
Logger.LogInformation("Successfully finished IIS application directory setup."); Logger.LogInformation("Successfully finished IIS application directory setup.");
@ -100,29 +111,254 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
applicationBaseUri: uri.ToString(), applicationBaseUri: uri.ToString(),
contentRoot: contentRoot, contentRoot: contentRoot,
hostShutdownToken: _hostShutdownToken.Token, hostShutdownToken: _hostShutdownToken.Token,
hostProcess: _application.HostProcess hostProcess: HostProcess
); );
} }
} }
private void GetLogsFromFile(string file) private void GetLogsFromFile()
{ {
var arr = new string[0]; var arr = new string[0];
RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)), RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, _debugLogFile)),
(ex) => Logger.LogWarning("Could not read log file"), (ex) => Logger.LogWarning(ex, "Could not read log file"),
5, 5,
200); 200);
if (arr.Length == 0)
{
Logger.LogWarning($"{file} is empty.");
}
foreach (var line in arr) foreach (var line in arr)
{ {
Logger.LogInformation(line); Logger.LogInformation(line);
} }
if (File.Exists(_debugLogFile))
{
File.Delete(_debugLogFile);
}
}
public async Task StartIIS(Uri uri, string contentRoot)
{
// Backup currently deployed apphost.config file
using (Logger.BeginScope("StartIIS"))
{
var port = uri.Port;
if (port == 0)
{
throw new NotSupportedException("Cannot set port 0 for IIS.");
}
AddTemporaryAppHostConfig(contentRoot, port);
await WaitUntilSiteStarted();
}
}
private async Task WaitUntilSiteStarted()
{
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
using (var serverManager = new ServerManager())
{
var site = serverManager.Sites.Single();
var appPool = serverManager.ApplicationPools.Single();
if (site.State == ObjectState.Started)
{
var workerProcess = appPool.WorkerProcesses.SingleOrDefault();
if (workerProcess != null)
{
HostProcess = Process.GetProcessById(workerProcess.ProcessId);
Logger.LogInformation("Site has started.");
return;
}
}
else
{
if (appPool.State != ObjectState.Started && appPool.State != ObjectState.Starting)
{
var state = appPool.Start();
Logger.LogInformation($"Starting pool, state: {state.ToString()}");
}
if (site.State != ObjectState.Starting)
{
var state = site.Start();
Logger.LogInformation($"Starting site, state: {state.ToString()}");
}
}
}
}
catch (Exception ex) when (IsExpectedException(ex))
{
// Accessing the site.State property while the site
// is starting up returns the COMException
// The object identifier does not represent a valid object.
// (Exception from HRESULT: 0x800710D8)
// This also means the site is not started yet, so catch and retry
// after waiting.
}
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to start site.");
}
public async Task StopAndDeleteAppPool()
{
Stop();
await WaitUntilSiteStopped();
RestoreAppHostConfig();
}
private async Task WaitUntilSiteStopped()
{
using (var serverManager = new ServerManager())
{
var site = serverManager.Sites.SingleOrDefault();
if (site == null)
{
return;
}
var sw = Stopwatch.StartNew();
while (sw.Elapsed < _timeout)
{
try
{
if (site.State == ObjectState.Stopped)
{
if (HostProcess.HasExited)
{
Logger.LogInformation($"Site has stopped successfully.");
return;
}
}
}
catch (Exception ex) when (IsExpectedException(ex))
{
// Accessing the site.State property while the site
// is shutdown down returns the COMException
return;
}
Logger.LogWarning($"IIS has not stopped after {sw.Elapsed.TotalMilliseconds}");
await Task.Delay(_retryDelay);
}
throw new TimeoutException($"IIS failed to stop site {site}.");
}
}
private static bool IsExpectedException(Exception ex)
{
return ex is DllNotFoundException ||
ex is COMException &&
(ex.HResult == ERROR_OBJECT_NOT_FOUND || ex.HResult == ERROR_SHARING_VIOLATION || ex.HResult == ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
}
private void AddTemporaryAppHostConfig(string contentRoot, int port)
{
_configPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
var appHostConfigPath = Path.Combine(_configPath, "applicationHost.config");
Directory.CreateDirectory(_configPath);
var config = XDocument.Parse(DeploymentParameters.ServerConfigTemplateContent ?? File.ReadAllText("IIS.config"));
ConfigureAppHostConfig(config.Root, contentRoot, port);
config.Save(appHostConfigPath);
using (var serverManager = new ServerManager())
{
var redirectionConfiguration = serverManager.GetRedirectionConfiguration();
var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection");
redirectionSection.Attributes["enabled"].Value = true;
redirectionSection.Attributes["path"].Value = _configPath;
serverManager.CommitChanges();
}
}
private void RestoreAppHostConfig()
{
using (var serverManager = new ServerManager())
{
var redirectionConfiguration = serverManager.GetRedirectionConfiguration();
var redirectionSection = redirectionConfiguration.GetSection("configurationRedirection");
redirectionSection.Attributes["enabled"].Value = false;
serverManager.CommitChanges();
Directory.Delete(_configPath, true);
}
}
private void ConfigureAppHostConfig(XElement config, string contentRoot, int port)
{
var siteElement = config
.RequiredElement("system.applicationHost")
.RequiredElement("sites")
.RequiredElement("site");
siteElement
.RequiredElement("application")
.RequiredElement("virtualDirectory")
.SetAttributeValue("physicalPath", contentRoot);
siteElement
.RequiredElement("bindings")
.RequiredElement("binding")
.SetAttributeValue("bindingInformation", $"*:{port}:");
var ancmVersion = DeploymentParameters.AncmVersion.ToString();
config
.RequiredElement("system.webServer")
.RequiredElement("globalModules")
.GetOrAdd("add", "name", ancmVersion)
.SetAttributeValue("image", GetAncmLocation(DeploymentParameters.AncmVersion));
config
.RequiredElement("system.webServer")
.RequiredElement("modules")
.GetOrAdd("add", "name", ancmVersion);
var pool = config
.RequiredElement("system.applicationHost")
.RequiredElement("applicationPools")
.RequiredElement("add");
var environmentVariables = pool
.GetOrAdd("environmentVariables");
foreach (var tuple in DeploymentParameters.EnvironmentVariables)
{
environmentVariables
.GetOrAdd("add", "name", tuple.Key)
.SetAttributeValue("value", tuple.Value);
}
RunServerConfigActions(config, contentRoot);
}
private void Stop()
{
using (var serverManager = new ServerManager())
{
var site = serverManager.Sites.SingleOrDefault();
site.Stop();
var appPool = serverManager.ApplicationPools.SingleOrDefault();
appPool.Stop();
serverManager.CommitChanges();
}
} }
} }
} }

View File

@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
protected List<Action<XElement, string>> DefaultWebConfigActions { get; } = new List<Action<XElement, string>>(); protected List<Action<XElement, string>> DefaultWebConfigActions { get; } = new List<Action<XElement, string>>();
protected List<Action<XElement, string>> DefaultServerConfigActions { get; } = new List<Action<XElement, string>>();
public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(deploymentParameters, loggerFactory) : base(deploymentParameters, loggerFactory)
{ {
@ -26,11 +28,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public void RunWebConfigActions(string contentRoot) public void RunWebConfigActions(string contentRoot)
{ {
if (IISDeploymentParameters == null)
{
return;
}
if (!DeploymentParameters.PublishApplicationBeforeDeployment) if (!DeploymentParameters.PublishApplicationBeforeDeployment)
{ {
throw new InvalidOperationException("Cannot modify web.config file if no published output."); throw new InvalidOperationException("Cannot modify web.config file if no published output.");
@ -38,37 +35,55 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
var path = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); var path = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config");
var webconfig = XDocument.Load(path); var webconfig = XDocument.Load(path);
var xElement = webconfig.Descendants("system.webServer").Single();
foreach (var action in DefaultWebConfigActions) foreach (var action in DefaultWebConfigActions)
{ {
action.Invoke(xElement, contentRoot); action.Invoke(webconfig.Root, contentRoot);
} }
foreach (var action in IISDeploymentParameters.WebConfigActionList) if (IISDeploymentParameters != null)
{ {
action.Invoke(xElement, contentRoot); foreach (var action in IISDeploymentParameters.WebConfigActionList)
{
action.Invoke(webconfig.Root, contentRoot);
}
} }
webconfig.Save(path); webconfig.Save(path);
} }
public string RunServerConfigActions(string serverConfigString, string contentRoot) public void RunServerConfigActions(XElement config, string contentRoot)
{ {
if (IISDeploymentParameters == null) foreach (var action in DefaultServerConfigActions)
{ {
return serverConfigString; action.Invoke(config, contentRoot);
} }
var serverConfig = XDocument.Parse(serverConfigString); if (IISDeploymentParameters != null)
var xElement = serverConfig.Descendants("configuration").FirstOrDefault();
foreach (var action in IISDeploymentParameters.ServerConfigActionList)
{ {
action.Invoke(xElement, contentRoot); foreach (var action in IISDeploymentParameters.ServerConfigActionList)
{
action.Invoke(config, contentRoot);
}
} }
return xElement.ToString(); }
protected string GetAncmLocation(AncmVersion version)
{
var ancmDllName = version == AncmVersion.AspNetCoreModuleV2 ? "aspnetcorev2.dll" : "aspnetcore.dll";
var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{ancmDllName}" : $@"x86\{ancmDllName}";
var ancmFile = Path.Combine(AppContext.BaseDirectory, arch);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
ancmFile = Path.Combine(AppContext.BaseDirectory, ancmDllName);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
}
}
return ancmFile;
} }
} }
} }

View File

@ -3,9 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{ {
@ -65,63 +63,47 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
private Action<XElement, string> AddWebConfigEnvironmentVariables() private Action<XElement, string> AddWebConfigEnvironmentVariables()
{ {
return (xElement, _) => return (element, _) =>
{ {
if (WebConfigBasedEnvironmentVariables.Count == 0) if (WebConfigBasedEnvironmentVariables.Count == 0)
{ {
return; return;
} }
var element = xElement.Descendants("environmentVariables").SingleOrDefault(); var environmentVariables = element
if (element == null) .RequiredElement("system.webServer")
{ .RequiredElement("aspNetCore")
element = new XElement("environmentVariables"); .GetOrAdd("environmentVariables");
xElement.Descendants("aspNetCore").SingleOrDefault().Add(element);
}
foreach (var envVar in WebConfigBasedEnvironmentVariables) foreach (var envVar in WebConfigBasedEnvironmentVariables)
{ {
CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable"); environmentVariables.GetOrAdd("environmentVariable", "name", envVar.Key)
.SetAttributeValue("value", envVar.Value);
} }
}; };
} }
private Action<XElement, string> AddHandlerSettings() private Action<XElement, string> AddHandlerSettings()
{ {
return (xElement, _) => return (element, _) =>
{ {
if (HandlerSettings.Count == 0) if (HandlerSettings.Count == 0)
{ {
return; return;
} }
var element = xElement.Descendants("handlerSettings").SingleOrDefault(); var handlerSettings = element
if (element == null) .RequiredElement("system.webServer")
{ .RequiredElement("aspNetCore")
element = new XElement("handlerSettings"); .GetOrAdd("handlerSettings");
xElement.Descendants("aspNetCore").SingleOrDefault().Add(element);
}
foreach (var handlerSetting in HandlerSettings) foreach (var handlerSetting in HandlerSettings)
{ {
CreateOrSetElement(element, handlerSetting.Key, handlerSetting.Value, "handlerSetting"); handlerSettings.GetOrAdd("handlerSetting", "name", handlerSetting.Key)
.SetAttributeValue("value", handlerSetting.Value);
} }
}; };
} }
private static void CreateOrSetElement(XElement rootElement, string name, string value, string elementName)
{
if (rootElement.Descendants()
.Attributes()
.Where(attribute => attribute.Value == name)
.Any())
{
return;
}
var element = new XElement(elementName);
element.SetAttributeValue("name", name);
element.SetAttributeValue("value", value);
rootElement.Add(element);
}
} }
} }

View File

@ -273,8 +273,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
// Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config // Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config
// We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo. // We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", dllName: "aspnetcore.dll", serverConfig); serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", AncmVersion.AspNetCoreModule, serverConfig);
serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig); serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", AncmVersion.AspNetCoreModuleV2, serverConfig);
serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture)); serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture));
serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot); serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot);
@ -299,7 +299,10 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString()); serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString());
serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString());
} }
serverConfig = RunServerConfigActions(serverConfig, contentRoot);
var config = XDocument.Parse(serverConfig);
RunServerConfigActions(config.Root, contentRoot);
serverConfig = config.ToString();
DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);
@ -317,21 +320,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
return content; return content;
} }
private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig) private string ModifyANCMPathInConfig(string replaceFlag, AncmVersion version, string serverConfig)
{ {
var dllRoot = AppContext.BaseDirectory;
if (serverConfig.Contains(replaceFlag)) if (serverConfig.Contains(replaceFlag))
{ {
var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{dllName}" : $@"x86\{dllName}"; var ancmFile = GetAncmLocation(version);
var ancmFile = Path.Combine(dllRoot, arch);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
ancmFile = Path.Combine(dllRoot, dllName);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
}
}
Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config"); Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config");
return serverConfig.Replace(replaceFlag, ancmFile); return serverConfig.Replace(replaceFlag, ancmFile);

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
@ -10,13 +9,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
public static class WebConfigHelpers public static class WebConfigHelpers
{ {
public static Action<XElement, string> AddOrModifyAspNetCoreSection(string key, string value) public static Action<XElement, string> AddOrModifyAspNetCoreSection(string key, string value)
=> AddAction(key, value, section: "aspNetCore");
public static Action<XElement, string> AddAction(string key, string value, string section)
{ {
return (element, _) => return (element, _) => {
{ element
element.Descendants(section).SingleOrDefault().SetAttributeValue(key, value); .GetOrAdd("system.webServer")
.GetOrAdd("aspNetCore")
.SetAttributeValue(key, value);
}; };
} }
@ -24,11 +22,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{ {
return (element, _) => return (element, _) =>
{ {
element.Descendants("handlers") element
.FirstOrDefault() .GetOrAdd("system.webServer")
.Descendants("add") .GetOrAdd("handlers")
.FirstOrDefault() .GetOrAdd("add")
.SetAttributeValue(key, value); .SetAttributeValue(key, value);
}; };
} }
} }

View File

@ -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.Linq;
using System.Xml.Linq;
namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
{
internal static class XElementExtensions
{
public static XElement RequiredElement(this XElement element, string name)
{
var existing = element.Element(name);
if (existing == null)
{
throw new InvalidOperationException($"Element with name {name} not found in {element}");
}
return existing;
}
public static XElement GetOrAdd(this XElement element, string name)
{
var existing = element.Element(name);
if (existing == null)
{
existing = new XElement(name);
element.Add(existing);
}
return existing;
}
public static XElement GetOrAdd(this XElement element, string name, string attribute, string attributeValue)
{
var existing = element.Elements(name).FirstOrDefault(e => e.Attribute(attribute)?.Value == attributeValue);
if (existing == null)
{
existing = new XElement(name, new XAttribute(attribute, attributeValue));
element.Add(existing);
}
return existing;
}
}
}

View File

@ -5,7 +5,7 @@
For schema documentation, see For schema documentation, see
%windir%\system32\inetsrv\config\schema\IIS_schema.xml. %windir%\system32\inetsrv\config\schema\IIS_schema.xml.
Please make a backup of this file before making any changes to it. Please make a backup of this file before making any changes to it.
--> -->
@ -17,20 +17,20 @@
The <configSections> section controls the registration of sections. The <configSections> section controls the registration of sections.
Section is the basic unit of deployment, locking, searching and Section is the basic unit of deployment, locking, searching and
containment for configuration settings. containment for configuration settings.
Every section belongs to one section group. Every section belongs to one section group.
A section group is a container of logically-related sections. A section group is a container of logically-related sections.
Sections cannot be nested. Sections cannot be nested.
Section groups may be nested. Section groups may be nested.
<section <section
name="" [Required, Collection Key] [XML name of the section] name="" [Required, Collection Key] [XML name of the section]
allowDefinition="Everywhere" [MachineOnly|MachineToApplication|AppHostOnly|Everywhere] [Level where it can be set] allowDefinition="Everywhere" [MachineOnly|MachineToApplication|AppHostOnly|Everywhere] [Level where it can be set]
overrideModeDefault="Allow" [Allow|Deny] [Default delegation mode] overrideModeDefault="Allow" [Allow|Deny] [Default delegation mode]
allowLocation="true" [true|false] [Allowed in location tags] allowLocation="true" [true|false] [Allowed in location tags]
/> />
The recommended way to unlock sections is by using a location tag: The recommended way to unlock sections is by using a location tag:
<location path="Default Web Site" overrideMode="Allow"> <location path="Default Web Site" overrideMode="Allow">
<system.webServer> <system.webServer>
@ -131,7 +131,9 @@
<system.applicationHost> <system.applicationHost>
<applicationPools> <applicationPools>
<add name="DefaultAppPool" autoStart="true" /> <add name="DefaultAppPool" autoStart="true" startMode="AlwaysRunning">
<processModel identityType="LocalSystem" />
</add>
<applicationPoolDefaults managedRuntimeVersion="v4.0"> <applicationPoolDefaults managedRuntimeVersion="v4.0">
<processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="false" /> <processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="false" />
</applicationPoolDefaults> </applicationPoolDefaults>

View File

@ -41,7 +41,7 @@ namespace FileOutManagerStartupTests
wprintf(expected, out); wprintf(expected, out);
} }
for (auto & p : std::experimental::filesystem::directory_iterator(tempDirectory.path())) for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
{ {
std::wstring filename(p.path().filename()); std::wstring filename(p.path().filename());
ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix); ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);

View File

@ -7,7 +7,7 @@
namespace GlobalVersionTests namespace GlobalVersionTests
{ {
using ::testing::Test; using ::testing::Test;
namespace fs = std::experimental::filesystem; namespace fs = std::filesystem;
class GlobalVersionTest : public Test class GlobalVersionTest : public Test
{ {

View File

@ -28,7 +28,7 @@ TempDirectory::TempDirectory()
RPC_CSTR szUuid = NULL; RPC_CSTR szUuid = NULL;
if (UuidToStringA(&uuid, &szUuid) == RPC_S_OK) if (UuidToStringA(&uuid, &szUuid) == RPC_S_OK)
{ {
m_path = std::experimental::filesystem::temp_directory_path() / szUuid; m_path = std::filesystem::temp_directory_path() / reinterpret_cast<PCHAR>(szUuid);
RpcStringFreeA(&szUuid); RpcStringFreeA(&szUuid);
return; return;
} }
@ -37,5 +37,5 @@ TempDirectory::TempDirectory()
TempDirectory::~TempDirectory() TempDirectory::~TempDirectory()
{ {
std::experimental::filesystem::remove_all(m_path); std::filesystem::remove_all(m_path);
} }

View File

@ -18,11 +18,11 @@ public:
~TempDirectory(); ~TempDirectory();
std::experimental::filesystem::path path() const std::filesystem::path path() const
{ {
return m_path; return m_path;
} }
private: private:
std::experimental::filesystem::path m_path; std::filesystem::path m_path;
}; };