diff --git a/.vsts-pipelines/templates/build-steps.yml b/.vsts-pipelines/templates/build-steps.yml index 2ebd7b36d1..a5983be46b 100644 --- a/.vsts-pipelines/templates/build-steps.yml +++ b/.vsts-pipelines/templates/build-steps.yml @@ -8,7 +8,7 @@ phases: afterBuild: - task: PublishBuildArtifacts@1 displayName: Upload binlog - condition: always() + condition: eq(variables['system.pullrequest.isfork'], false) inputs: artifactName: logs artifactType: Container diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp index b2c6b69871..7cb185b0c3 100644 --- a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp @@ -6,7 +6,7 @@ #include "GlobalVersionUtility.h" -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; // throws runtime error if no request handler versions are installed. // Throw invalid_argument if any argument is null diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp index 08491b0d76..e9bbb42383 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp @@ -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"; } diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h index 98d32ae8d3..95097bb3ac 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h @@ -30,8 +30,8 @@ public: void StopInternal(bool fServerInitiated) override { UNREFERENCED_PARAMETER(fServerInitiated); } protected: - std::experimental::filesystem::path m_appOfflineLocation; - static std::experimental::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication); + std::filesystem::path m_appOfflineLocation; + static std::filesystem::path GetAppOfflineLocation(IHttpApplication& pApplication); private: static const int c_appOfflineRefreshIntervalMS = 200; diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index d0cb70dbb2..792cd4e411 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -13,7 +13,7 @@ #include "HandleWrapper.h" #include "Environment.h" -namespace fs = std::experimental::filesystem; +namespace fs = std::filesystem; // // Runs a standalone appliction. @@ -98,7 +98,7 @@ HOSTFXR_UTILITY::GetStandaloneHostfxrParameters( } BOOL -HOSTFXR_UTILITY::IsDotnetExecutable(const std::experimental::filesystem::path & dotnetPath) +HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath) { auto name = dotnetPath.filename(); name.replace_extension(""); diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index 1315865803..9e56fb4e28 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -44,7 +44,7 @@ public: static BOOL IsDotnetExecutable( - _In_ const std::experimental::filesystem::path & dotnetPath + _In_ const std::filesystem::path & dotnetPath ); static @@ -74,25 +74,25 @@ public: ); static - std::optional + std::optional GetAbsolutePathToHostFxr( - _In_ const std::experimental::filesystem::path & dotnetPath, + _In_ const std::filesystem::path & dotnetPath, _In_ HANDLE hEventLog ); static - std::optional + std::optional GetAbsolutePathToDotnetFromProgramFiles(); static - std::optional + std::optional InvokeWhereToFindDotnet(); static - std::optional + std::optional GetAbsolutePathToDotnet( - _In_ const std::experimental::filesystem::path & applicationPath, - _In_ const std::experimental::filesystem::path & requestedPath + _In_ const std::filesystem::path & applicationPath, + _In_ const std::filesystem::path & requestedPath ); }; diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs deleted file mode 100644 index 81cebf1568..0000000000 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs +++ /dev/null @@ -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 -{ - /// - /// Represents the IIS website registered in the global applicationHost.config - /// - 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 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; - } - } -} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs index acc4ae4cfb..d76f5f48d7 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs @@ -1,22 +1,39 @@ // 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.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.AspNetCore.Server.IntegrationTesting.Common; using Microsoft.Extensions.Logging; +using Microsoft.Web.Administration; namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { /// /// Deployer for IIS. /// - 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 string _configPath; + private string _debugLogFile; + + public Process HostProcess { get; set; } + public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) : base(new IISDeploymentParameters(deploymentParameters), loggerFactory) { @@ -29,14 +46,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS public override void Dispose() { - if (_application != null) - { - _application.StopAndDeleteAppPool().GetAwaiter().GetResult(); + StopAndDeleteAppPool().GetAwaiter().GetResult(); - TriggerHostShutdown(_hostShutdownToken); - } + TriggerHostShutdown(_hostShutdownToken); - GetLogsFromFile($"{_application.WebSiteName}.txt"); + GetLogsFromFile(); CleanPublishedOutput(); InvokeUserApplicationCleanup(); @@ -50,17 +64,23 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { StartTimer(); - var contentRoot = string.Empty; if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent)) { DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config"); } - _application = new IISApplication(IISDeploymentParameters, Logger); - // For now, only support using published output 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) { DefaultWebConfigActions.Add( @@ -69,28 +89,19 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS 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( - key: "modules", - value: DeploymentParameters.AncmVersion.ToString())); - RunWebConfigActions(contentRoot); - } + DotnetPublish(); + var contentRoot = DeploymentParameters.PublishedApplicationRootPath; + + DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection( + key: "modules", + value: DeploymentParameters.AncmVersion.ToString())); + + RunWebConfigActions(contentRoot); var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint); // To prevent modifying the IIS setup concurrently. - await _application.StartIIS(uri, contentRoot); + await StartIIS(uri, contentRoot); // Warm up time for IIS setup. Logger.LogInformation("Successfully finished IIS application directory setup."); @@ -100,29 +111,254 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS applicationBaseUri: uri.ToString(), contentRoot: contentRoot, hostShutdownToken: _hostShutdownToken.Token, - hostProcess: _application.HostProcess + hostProcess: HostProcess ); } } - private void GetLogsFromFile(string file) + private void GetLogsFromFile() { var arr = new string[0]; - RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)), - (ex) => Logger.LogWarning("Could not read log file"), + RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, _debugLogFile)), + (ex) => Logger.LogWarning(ex, "Could not read log file"), 5, 200); - if (arr.Length == 0) - { - Logger.LogWarning($"{file} is empty."); - } - foreach (var line in arr) { 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(); + } } } } diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs index 8dcc3e135e..f511f9f5f1 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS protected List> DefaultWebConfigActions { get; } = new List>(); + protected List> DefaultServerConfigActions { get; } = new List>(); + public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) : base(deploymentParameters, loggerFactory) { @@ -26,11 +28,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS public void RunWebConfigActions(string contentRoot) { - if (IISDeploymentParameters == null) - { - return; - } - if (!DeploymentParameters.PublishApplicationBeforeDeployment) { 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 webconfig = XDocument.Load(path); - var xElement = webconfig.Descendants("system.webServer").Single(); 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); } - 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); - var xElement = serverConfig.Descendants("configuration").FirstOrDefault(); - - foreach (var action in IISDeploymentParameters.ServerConfigActionList) + if (IISDeploymentParameters != null) { - 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; } } } diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs index 8d96236295..fc8fd99efc 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Xml.Linq; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { @@ -65,63 +63,47 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS private Action AddWebConfigEnvironmentVariables() { - return (xElement, _) => + return (element, _) => { if (WebConfigBasedEnvironmentVariables.Count == 0) { return; } - var element = xElement.Descendants("environmentVariables").SingleOrDefault(); - if (element == null) - { - element = new XElement("environmentVariables"); - xElement.Descendants("aspNetCore").SingleOrDefault().Add(element); - } + var environmentVariables = element + .RequiredElement("system.webServer") + .RequiredElement("aspNetCore") + .GetOrAdd("environmentVariables"); + foreach (var envVar in WebConfigBasedEnvironmentVariables) { - CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable"); + environmentVariables.GetOrAdd("environmentVariable", "name", envVar.Key) + .SetAttributeValue("value", envVar.Value); } }; } private Action AddHandlerSettings() { - return (xElement, _) => + return (element, _) => { if (HandlerSettings.Count == 0) { return; } - var element = xElement.Descendants("handlerSettings").SingleOrDefault(); - if (element == null) - { - element = new XElement("handlerSettings"); - xElement.Descendants("aspNetCore").SingleOrDefault().Add(element); - } + var handlerSettings = element + .RequiredElement("system.webServer") + .RequiredElement("aspNetCore") + .GetOrAdd("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); - } } } diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs index ff02771ec0..cd1fbc69f2 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs @@ -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 // 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: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig); + serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", AncmVersion.AspNetCoreModule, serverConfig); + serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", AncmVersion.AspNetCoreModuleV2, serverConfig); serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture)); 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, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); } - serverConfig = RunServerConfigActions(serverConfig, contentRoot); + + var config = XDocument.Parse(serverConfig); + RunServerConfigActions(config.Root, contentRoot); + serverConfig = config.ToString(); DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); @@ -317,21 +320,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS 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)) { - var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{dllName}" : $@"x86\{dllName}"; - 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); - } - } + var ancmFile = GetAncmLocation(version); Logger.LogDebug($"Writing '{replaceFlag}' '{ancmFile}' to config"); return serverConfig.Replace(replaceFlag, ancmFile); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs index 31f103fee2..589907657f 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs @@ -2,7 +2,6 @@ // 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 @@ -10,13 +9,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS public static class WebConfigHelpers { public static Action AddOrModifyAspNetCoreSection(string key, string value) - => AddAction(key, value, section: "aspNetCore"); - - public static Action AddAction(string key, string value, string section) { - return (element, _) => - { - element.Descendants(section).SingleOrDefault().SetAttributeValue(key, value); + return (element, _) => { + element + .GetOrAdd("system.webServer") + .GetOrAdd("aspNetCore") + .SetAttributeValue(key, value); }; } @@ -24,11 +22,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { return (element, _) => { - element.Descendants("handlers") - .FirstOrDefault() - .Descendants("add") - .FirstOrDefault() - .SetAttributeValue(key, value); + element + .GetOrAdd("system.webServer") + .GetOrAdd("handlers") + .GetOrAdd("add") + .SetAttributeValue(key, value); }; } } diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs new file mode 100644 index 0000000000..45ed897019 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/XElementExtensions.cs @@ -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; + } + } +} diff --git a/test/Common.FunctionalTests/AppHostConfig/IIS.config b/test/Common.FunctionalTests/AppHostConfig/IIS.config index 624edf91ca..bc24ef9639 100644 --- a/test/Common.FunctionalTests/AppHostConfig/IIS.config +++ b/test/Common.FunctionalTests/AppHostConfig/IIS.config @@ -5,7 +5,7 @@ For schema documentation, see %windir%\system32\inetsrv\config\schema\IIS_schema.xml. - + Please make a backup of this file before making any changes to it. --> @@ -17,20 +17,20 @@ The section controls the registration of sections. Section is the basic unit of deployment, locking, searching and containment for configuration settings. - + Every section belongs to one section group. A section group is a container of logically-related sections. - + Sections cannot be nested. Section groups may be nested. - +
- + The recommended way to unlock sections is by using a location tag: @@ -131,7 +131,9 @@ - + + + diff --git a/test/CommonLibTests/FileOutputManagerTests.cpp b/test/CommonLibTests/FileOutputManagerTests.cpp index d0caa524a8..ae1085408c 100644 --- a/test/CommonLibTests/FileOutputManagerTests.cpp +++ b/test/CommonLibTests/FileOutputManagerTests.cpp @@ -41,7 +41,7 @@ namespace FileOutManagerStartupTests 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()); ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix); diff --git a/test/CommonLibTests/GlobalVersionTests.cpp b/test/CommonLibTests/GlobalVersionTests.cpp index 9c74b6720e..f38c9361d2 100644 --- a/test/CommonLibTests/GlobalVersionTests.cpp +++ b/test/CommonLibTests/GlobalVersionTests.cpp @@ -7,7 +7,7 @@ namespace GlobalVersionTests { using ::testing::Test; - namespace fs = std::experimental::filesystem; + namespace fs = std::filesystem; class GlobalVersionTest : public Test { diff --git a/test/CommonLibTests/Helpers.cpp b/test/CommonLibTests/Helpers.cpp index 3e429a6665..ccca6cad5b 100644 --- a/test/CommonLibTests/Helpers.cpp +++ b/test/CommonLibTests/Helpers.cpp @@ -28,7 +28,7 @@ TempDirectory::TempDirectory() RPC_CSTR szUuid = NULL; 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(szUuid); RpcStringFreeA(&szUuid); return; } @@ -37,5 +37,5 @@ TempDirectory::TempDirectory() TempDirectory::~TempDirectory() { - std::experimental::filesystem::remove_all(m_path); + std::filesystem::remove_all(m_path); } diff --git a/test/CommonLibTests/Helpers.h b/test/CommonLibTests/Helpers.h index 657766dd43..67256966bb 100644 --- a/test/CommonLibTests/Helpers.h +++ b/test/CommonLibTests/Helpers.h @@ -18,11 +18,11 @@ public: ~TempDirectory(); - std::experimental::filesystem::path path() const + std::filesystem::path path() const { return m_path; } private: - std::experimental::filesystem::path m_path; + std::filesystem::path m_path; };