Add full IIS tests (#979)
This commit is contained in:
parent
65d3787fc4
commit
dfd75e939d
|
|
@ -8,6 +8,7 @@ branches:
|
|||
install:
|
||||
- ps: .\tools\update_schema.ps1
|
||||
- git submodule update --init --recursive
|
||||
- net start w3svc
|
||||
build_script:
|
||||
- ps: .\run.ps1 default-build
|
||||
clone_depth: 1
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using System.Net.Http;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using IISIntegration.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IIS.Performance
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
|
@ -38,8 +38,10 @@
|
|||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
||||
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview1-34530</MicrosoftNetHttpHeadersPackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<MicrosoftWebAdministrationPackageVersion>11.1.0</MicrosoftWebAdministrationPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<SystemBuffersPackageVersion>4.6.0-preview1-26617-01</SystemBuffersPackageVersion>
|
||||
<SystemDiagnosticsEventLogPackageVersion>4.6.0-preview1-26617-01</SystemDiagnosticsEventLogPackageVersion>
|
||||
<SystemIOPipelinesPackageVersion>4.6.0-preview1-26617-01</SystemIOPipelinesPackageVersion>
|
||||
<SystemMemoryPackageVersion>4.6.0-preview1-26617-01</SystemMemoryPackageVersion>
|
||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.6.0-preview1-26617-01</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
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
|
||||
|
|
@ -368,7 +368,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
|
|||
const auto dotnetViaWhere = InvokeWhereToFindDotnet();
|
||||
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;
|
||||
}
|
||||
|
|
@ -394,7 +394,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
|
|||
std::vector<std::wstring> versionFolders;
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@
|
|||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
<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="System.Diagnostics.EventLog" Version="$(SystemDiagnosticsEventLogPackageVersion)" />
|
||||
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<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.
|
||||
// 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 IISIntegration.FunctionalTests.Utilities;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -11,7 +9,7 @@ using Microsoft.Extensions.Logging.Testing;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Inprocess
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
[SkipIfHostableWebCoreNotAvailible]
|
||||
public class TestServerTest: LoggedTest
|
||||
|
|
|
|||
|
|
@ -21,10 +21,18 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
|||
{
|
||||
if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable))
|
||||
{
|
||||
// enable debug output
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Utilities
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
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.Logging;
|
||||
|
||||
namespace IISIntegration.FunctionalTests.Utilities
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||
{
|
||||
public class TestServer: IDisposable, IStartup
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue