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:
commit
11514cd17f
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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("");
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue