Add full IIS tests (#979)
This commit is contained in:
parent
65d3787fc4
commit
dfd75e939d
|
|
@ -8,6 +8,7 @@ branches:
|
||||||
install:
|
install:
|
||||||
- ps: .\tools\update_schema.ps1
|
- ps: .\tools\update_schema.ps1
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
|
- net start w3svc
|
||||||
build_script:
|
build_script:
|
||||||
- ps: .\run.ps1 default-build
|
- ps: .\run.ps1 default-build
|
||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using IISIntegration.FunctionalTests.Utilities;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS.Performance
|
namespace Microsoft.AspNetCore.Server.IIS.Performance
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
@ -38,8 +38,10 @@
|
||||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
||||||
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34530</MicrosoftNetHttpHeadersPackageVersion>
|
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34530</MicrosoftNetHttpHeadersPackageVersion>
|
||||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||||
|
<MicrosoftWebAdministrationPackageVersion>11.1.0</MicrosoftWebAdministrationPackageVersion>
|
||||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||||
<SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion>
|
<SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion>
|
||||||
|
<SystemDiagnosticsEventLogPackageVersion>4.6.0-preview1-26617-01</SystemDiagnosticsEventLogPackageVersion>
|
||||||
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion>
|
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion>
|
||||||
<SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion>
|
<SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion>
|
||||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
||||||
const fs::path & requestedPath
|
const fs::path & requestedPath
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
WLOG_INFOF(L"Resolving absolute path do dotnet.exe from %s", requestedPath.c_str());
|
WLOG_INFOF(L"Resolving absolute path to dotnet.exe from %s", requestedPath.c_str());
|
||||||
|
|
||||||
//
|
//
|
||||||
// If we are given an absolute path to dotnet.exe, we are done
|
// If we are given an absolute path to dotnet.exe, we are done
|
||||||
|
|
@ -368,7 +368,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
||||||
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
|
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
|
||||||
if (dotnetViaWhere.has_value())
|
if (dotnetViaWhere.has_value())
|
||||||
{
|
{
|
||||||
WLOG_INFOF(L"Found dotnet.exe wia where.exe invocation at %s", dotnetViaWhere.value().c_str());
|
WLOG_INFOF(L"Found dotnet.exe via where.exe invocation at %s", dotnetViaWhere.value().c_str());
|
||||||
|
|
||||||
return dotnetViaWhere;
|
return dotnetViaWhere;
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +394,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
||||||
std::vector<std::wstring> versionFolders;
|
std::vector<std::wstring> versionFolders;
|
||||||
const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr";
|
const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr";
|
||||||
|
|
||||||
WLOG_INFOF(L"Resolving absolute path do hostfxr.dll from %s", dotnetPath.c_str());
|
WLOG_INFOF(L"Resolving absolute path to hostfxr.dll from %s", dotnetPath.c_str());
|
||||||
|
|
||||||
if (!is_directory(hostFxrBase))
|
if (!is_directory(hostFxrBase))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Web.Administration" Version="$(MicrosoftWebAdministrationPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||||
|
<PackageReference Include="System.Diagnostics.EventLog" Version="$(SystemDiagnosticsEventLogPackageVersion)" />
|
||||||
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
|
{
|
||||||
|
[SkipIfIISCannotRun]
|
||||||
|
public class IISTests : FunctionalTestsBase
|
||||||
|
{
|
||||||
|
[ConditionalFact]
|
||||||
|
public Task HelloWorld_IIS_CoreClr_X64_Standalone()
|
||||||
|
{
|
||||||
|
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Standalone);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public Task HelloWorld_IIS_CoreClr_X64_Portable()
|
||||||
|
{
|
||||||
|
return HelloWorld(RuntimeFlavor.CoreClr, ApplicationType.Portable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HelloWorld(RuntimeFlavor runtimeFlavor, ApplicationType applicationType)
|
||||||
|
{
|
||||||
|
var deploymentParameters = Helpers.GetBaseDeploymentParameters();
|
||||||
|
deploymentParameters.ServerType = ServerType.IIS;
|
||||||
|
deploymentParameters.ApplicationType = applicationType;
|
||||||
|
|
||||||
|
var deploymentResult = await DeployAsync(deploymentParameters);
|
||||||
|
|
||||||
|
var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld");
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
Assert.Equal("Hello World", responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// 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.Diagnostics;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using IISIntegration.FunctionalTests.Utilities;
|
using IISIntegration.FunctionalTests.Utilities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -11,7 +9,7 @@ using Microsoft.Extensions.Logging.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace IISIntegration.FunctionalTests.Inprocess
|
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
[SkipIfHostableWebCoreNotAvailible]
|
[SkipIfHostableWebCoreNotAvailible]
|
||||||
public class TestServerTest: LoggedTest
|
public class TestServerTest: LoggedTest
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,18 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
{
|
{
|
||||||
if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable))
|
if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable))
|
||||||
{
|
{
|
||||||
// enable debug output
|
|
||||||
parameters.EnvironmentVariables[DebugEnvironmentVariable] = "4";
|
parameters.EnvironmentVariables[DebugEnvironmentVariable] = "4";
|
||||||
}
|
}
|
||||||
_deployer = ApplicationDeployerFactory.Create(parameters, LoggerFactory);
|
|
||||||
|
// Currently hosting throws if the Servertype = IIS.
|
||||||
|
if (parameters.ServerType == ServerType.IIS)
|
||||||
|
{
|
||||||
|
_deployer = new IISDeployer(parameters, LoggerFactory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_deployer = ApplicationDeployerFactory.Create(parameters, LoggerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _deployer.DeployAsync();
|
var result = await _deployer.DeployAsync();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
// 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.Tasks;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(2);
|
||||||
|
private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(100);
|
||||||
|
private readonly ServerManager _serverManager = new ServerManager();
|
||||||
|
private readonly DeploymentParameters _deploymentParameters;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly string _ancmVersion;
|
||||||
|
private readonly object _syncLock = new object();
|
||||||
|
private readonly string _apphostConfigBackupPath;
|
||||||
|
private static readonly string _apphostConfigPath = Path.Combine(
|
||||||
|
Environment.SystemDirectory,
|
||||||
|
"inetsrv",
|
||||||
|
"config",
|
||||||
|
"applicationhost.config");
|
||||||
|
|
||||||
|
public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
|
||||||
|
{
|
||||||
|
_deploymentParameters = deploymentParameters;
|
||||||
|
_logger = logger;
|
||||||
|
_ancmVersion = deploymentParameters.AncmVersion.ToString();
|
||||||
|
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"))
|
||||||
|
{
|
||||||
|
AddTemporaryAppHostConfig();
|
||||||
|
var port = uri.Port;
|
||||||
|
if (port == 0)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Cannot set port 0 for IIS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureAppPool(contentRoot);
|
||||||
|
|
||||||
|
ConfigureSite(contentRoot, port);
|
||||||
|
|
||||||
|
ConfigureAppHostConfig(contentRoot);
|
||||||
|
|
||||||
|
if (_deploymentParameters.ApplicationType == ApplicationType.Portable)
|
||||||
|
{
|
||||||
|
ModifyAspNetCoreSectionInWebConfig("processPath", DotNetMuxer.MuxerPathOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
_serverManager.CommitChanges();
|
||||||
|
|
||||||
|
await WaitUntilSiteStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModifyAspNetCoreSectionInWebConfig(string key, string value)
|
||||||
|
{
|
||||||
|
var webConfigFile = Path.Combine(_deploymentParameters.PublishedApplicationRootPath, "web.config");
|
||||||
|
var config = XDocument.Load(webConfigFile);
|
||||||
|
var element = config.Descendants("aspNetCore").FirstOrDefault();
|
||||||
|
element.SetAttributeValue(key, value);
|
||||||
|
config.Save(webConfigFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitUntilSiteStarted()
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
while (sw.Elapsed < _timeout)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var site = _serverManager.Sites.FirstOrDefault(s => s.Name.Equals(WebSiteName));
|
||||||
|
if (site.State == ObjectState.Started)
|
||||||
|
{
|
||||||
|
_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 (COMException comException)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
_logger.LogWarning($"ComException: {comException.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(_retryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException($"IIS failed to start site.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAndDeleteAppPool()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(WebSiteName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreAppHostConfig();
|
||||||
|
|
||||||
|
_serverManager.CommitChanges();
|
||||||
|
|
||||||
|
await WaitUntilSiteStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_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()
|
||||||
|
{
|
||||||
|
File.Copy(_apphostConfigPath, _apphostConfigBackupPath);
|
||||||
|
_logger.LogInformation($"Backed up {_apphostConfigPath} to {_apphostConfigBackupPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreAppHostConfig()
|
||||||
|
{
|
||||||
|
RetryHelper.RetryOperation(
|
||||||
|
() => File.Delete(_apphostConfigPath),
|
||||||
|
e => _logger.LogWarning($"Failed to delete directory : {e.Message}"),
|
||||||
|
retryCount: 3,
|
||||||
|
retryDelayMilliseconds: 100);
|
||||||
|
|
||||||
|
File.Move(_apphostConfigBackupPath, _apphostConfigPath);
|
||||||
|
_logger.LogInformation($"Restored {_apphostConfigPath}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationPool ConfigureAppPool(string contentRoot)
|
||||||
|
{
|
||||||
|
var pool = _serverManager.ApplicationPools.Add(AppPoolName);
|
||||||
|
pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
|
||||||
|
pool.ManagedRuntimeVersion = string.Empty;
|
||||||
|
var envCollection = pool.GetCollection("environmentVariables");
|
||||||
|
|
||||||
|
AddEnvironmentVariables(contentRoot, envCollection);
|
||||||
|
|
||||||
|
_logger.LogInformation($"Configured AppPool {AppPoolName}");
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEnvironmentVariables(string contentRoot, ConfigurationElementCollection envCollection)
|
||||||
|
{
|
||||||
|
foreach (var tuple in _deploymentParameters.EnvironmentVariables)
|
||||||
|
{
|
||||||
|
AddEnvironmentVariableToAppPool(envCollection, tuple.Key, tuple.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(string dllRoot)
|
||||||
|
{
|
||||||
|
var config = _serverManager.GetApplicationHostConfiguration();
|
||||||
|
|
||||||
|
SetGlobalModuleSection(config, dllRoot);
|
||||||
|
|
||||||
|
SetModulesSection(config);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGlobalModuleSection(Configuration config, string dllRoot)
|
||||||
|
{
|
||||||
|
var ancmFile = GetAncmLocation(dllRoot);
|
||||||
|
|
||||||
|
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(string dllRoot)
|
||||||
|
{
|
||||||
|
var arch = _deploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? @"x64\aspnetcorev2.dll" : @"x86\aspnetcorev2.dll";
|
||||||
|
var ancmFile = Path.Combine(dllRoot, arch);
|
||||||
|
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
|
||||||
|
{
|
||||||
|
ancmFile = Path.Combine(dllRoot, "aspnetcorev2.dll");
|
||||||
|
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancmFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deployer for IIS.
|
||||||
|
/// </summary>
|
||||||
|
public partial class IISDeployer : ApplicationDeployer
|
||||||
|
{
|
||||||
|
private IISApplication _application;
|
||||||
|
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
|
||||||
|
: base(deploymentParameters, loggerFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (_application != null)
|
||||||
|
{
|
||||||
|
_application.StopAndDeleteAppPool().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
TriggerHostShutdown(_hostShutdownToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetLogsFromFile($"{_application.WebSiteName}.txt");
|
||||||
|
GetLogsFromFile("web.config");
|
||||||
|
|
||||||
|
CleanPublishedOutput();
|
||||||
|
InvokeUserApplicationCleanup();
|
||||||
|
|
||||||
|
StopTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<DeploymentResult> DeployAsync()
|
||||||
|
{
|
||||||
|
using (Logger.BeginScope("Deployment"))
|
||||||
|
{
|
||||||
|
StartTimer();
|
||||||
|
|
||||||
|
var contentRoot = string.Empty;
|
||||||
|
_application = new IISApplication(DeploymentParameters, Logger);
|
||||||
|
|
||||||
|
// For now, only support using published output
|
||||||
|
DeploymentParameters.PublishApplicationBeforeDeployment = true;
|
||||||
|
|
||||||
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
|
{
|
||||||
|
DotnetPublish();
|
||||||
|
contentRoot = DeploymentParameters.PublishedApplicationRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = TestIISUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint);
|
||||||
|
// To prevent modifying the IIS setup concurrently.
|
||||||
|
await _application.StartIIS(uri, contentRoot);
|
||||||
|
|
||||||
|
// Warm up time for IIS setup.
|
||||||
|
Logger.LogInformation("Successfully finished IIS application directory setup.");
|
||||||
|
|
||||||
|
return new DeploymentResult(
|
||||||
|
LoggerFactory,
|
||||||
|
DeploymentParameters,
|
||||||
|
applicationBaseUri: uri.ToString(),
|
||||||
|
contentRoot: contentRoot,
|
||||||
|
hostShutdownToken: _hostShutdownToken.Token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetLogsFromFile(string file)
|
||||||
|
{
|
||||||
|
var arr = new string[0];
|
||||||
|
|
||||||
|
RetryHelper.RetryOperation(() => arr = File.ReadAllLines(Path.Combine(DeploymentParameters.PublishedApplicationRootPath, file)),
|
||||||
|
(ex) => Logger.LogError("Could not read log file"),
|
||||||
|
5,
|
||||||
|
200);
|
||||||
|
foreach (var line in arr)
|
||||||
|
{
|
||||||
|
Logger.LogInformation(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// 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.Net.Http;
|
using System.Net.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
|
||||||
namespace IISIntegration.FunctionalTests.Utilities
|
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition
|
public sealed class SkipIfHostableWebCoreNotAvailibleAttribute : Attribute, ITestCondition
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
|
public sealed class SkipIfIISCannotRunAttribute : Attribute, ITestCondition
|
||||||
|
{
|
||||||
|
private static readonly bool _isMet;
|
||||||
|
public static readonly string _skipReason;
|
||||||
|
|
||||||
|
public bool IsMet => _isMet;
|
||||||
|
public string SkipReason => _skipReason;
|
||||||
|
|
||||||
|
static SkipIfIISCannotRunAttribute()
|
||||||
|
{
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
_skipReason = "IIS tests can only be run on Windows";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
var principal = new WindowsPrincipal(identity);
|
||||||
|
if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
|
||||||
|
{
|
||||||
|
_skipReason += "The current console is not running as admin.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(Path.Combine(Environment.SystemDirectory, "inetsrv", "w3wp.exe")))
|
||||||
|
{
|
||||||
|
_skipReason += "The machine does not have IIS installed.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml");
|
||||||
|
|
||||||
|
if (!File.Exists(ancmConfigPath))
|
||||||
|
{
|
||||||
|
_skipReason = "IIS Schema is not installed.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XDocument ancmConfig;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ancmConfig = XDocument.Load(ancmConfigPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_skipReason = "Could not read ANCM schema configuration";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isMet = ancmConfig
|
||||||
|
.Root
|
||||||
|
.Descendants("attribute")
|
||||||
|
.Any(n => "hostingModel".Equals(n.Attribute("name")?.Value, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
_skipReason = _isMet ? null : "IIS schema needs to be upgraded to support ANCM.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
{
|
||||||
|
// Copied from Hosting for now https://github.com/aspnet/Hosting/blob/970bc8a30d66dd6894f8f662e5fdab9e68d57777/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/TestUriHelper.cs
|
||||||
|
internal static class TestIISUriHelper
|
||||||
|
{
|
||||||
|
internal static Uri BuildTestUri(ServerType serverType)
|
||||||
|
{
|
||||||
|
return BuildTestUri(serverType, hint: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Uri BuildTestUri(ServerType serverType, string hint)
|
||||||
|
{
|
||||||
|
// Assume status messages are enabled for Kestrel and disabled for all other servers.
|
||||||
|
return BuildTestUri(serverType, hint, statusMessagesEnabled: serverType == ServerType.Kestrel);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Uri BuildTestUri(ServerType serverType, string hint, bool statusMessagesEnabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hint))
|
||||||
|
{
|
||||||
|
if (serverType == ServerType.Kestrel && statusMessagesEnabled)
|
||||||
|
{
|
||||||
|
// Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape
|
||||||
|
// the assigned port from the status message, which should be 100% reliable since the port is bound
|
||||||
|
// once and never released. Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
|
||||||
|
// supported, so the port is only bound on "127.0.0.1" (IPv4). If a test explicitly requires IPv6,
|
||||||
|
// it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
|
||||||
|
return new UriBuilder("http", "127.0.0.1", 0).Uri;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the server type is not Kestrel, or status messages are disabled, there is no status message
|
||||||
|
// from which to scrape the assigned port, so the less reliable GetNextPort() must be used. The
|
||||||
|
// port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific
|
||||||
|
// (non-zero) port.
|
||||||
|
return new UriBuilder("http", "localhost", GetNextPort()).Uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var uriHint = new Uri(hint);
|
||||||
|
if (uriHint.Port == 0)
|
||||||
|
{
|
||||||
|
// Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity.
|
||||||
|
// The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic
|
||||||
|
// port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath).
|
||||||
|
return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the hint contains a specific port, return it unchanged.
|
||||||
|
return uriHint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
|
||||||
|
//
|
||||||
|
// This method is an attempt to safely get a free port from the OS. Most of the time,
|
||||||
|
// when binding to dynamic port "0" the OS increments the assigned port, so it's safe
|
||||||
|
// to re-use the assigned port in another process. However, occasionally the OS will reuse
|
||||||
|
// a recently assigned port instead of incrementing, which causes flaky tests with AddressInUse
|
||||||
|
// exceptions. This method should only be used when the application itself cannot use
|
||||||
|
// dynamic port "0" (e.g. IISExpress). Most functional tests using raw Kestrel
|
||||||
|
// (with status messages enabled) should directly bind to dynamic port "0" and scrape
|
||||||
|
// the assigned port from the status message, which should be 100% reliable since the port
|
||||||
|
// is bound once and never released.
|
||||||
|
internal static int GetNextPort()
|
||||||
|
{
|
||||||
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
||||||
|
{
|
||||||
|
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
|
||||||
|
return ((IPEndPoint)socket.LocalEndPoint).Port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace IISIntegration.FunctionalTests.Utilities
|
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
public class TestServer: IDisposable, IStartup
|
public class TestServer: IDisposable, IStartup
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue