예제 #1
0
    private async Task <(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(string contentRoot)
    {
        using (Logger.BeginScope("StartIISExpress"))
        {
            var iisExpressPath = GetIISExpressPath();

            for (var attempt = 0; attempt < MaximumAttempts; attempt++)
            {
                var uri  = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint);
                var port = uri.Port;
                if (port == 0)
                {
                    port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort();
                }

                Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
                PrepareConfig(contentRoot, port);

                var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ?
                                 string.Format(CultureInfo.InvariantCulture, "/port:{0} /path:\"{1}\" /trace:error /systray:false", uri.Port, contentRoot) :
                                 string.Format(CultureInfo.InvariantCulture, "/site:{0} /config:{1} /trace:error /systray:false", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);

                Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters);

                var startInfo = new ProcessStartInfo
                {
                    FileName               = iisExpressPath,
                    Arguments              = parameters,
                    UseShellExecute        = false,
                    CreateNoWindow         = true,
                    RedirectStandardError  = true,
                    RedirectStandardOutput = true,
                    // VS sets current directory to C:\Program Files\IIS Express
                    WorkingDirectory = Path.GetDirectoryName(iisExpressPath)
                };

                AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);

                Uri url     = null;
                var started = new TaskCompletionSource <bool>();

                var process = new Process()
                {
                    StartInfo = startInfo
                };
                process.OutputDataReceived += (sender, dataArgs) =>
                {
                    if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage, StringComparison.Ordinal))
                    {
                        // We completely failed to start and we don't really know why
                        started.TrySetException(new InvalidOperationException("Failed to start IIS Express"));
                    }
                    else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage, StringComparison.Ordinal))
                    {
                        started.TrySetResult(false);
                    }
                    else if (string.Equals(dataArgs.Data, IISExpressRunningMessage, StringComparison.Ordinal))
                    {
                        started.TrySetResult(true);
                    }
                    else if (!string.IsNullOrEmpty(dataArgs.Data))
                    {
                        var m = UrlDetectorRegex.Match(dataArgs.Data);
                        if (m.Success)
                        {
                            url = new Uri(m.Groups["url"].Value);
                        }
                    }
                };

                process.EnableRaisingEvents = true;
                var hostExitTokenSource = new CancellationTokenSource();
                process.Exited += (sender, e) =>
                {
                    Logger.LogInformation("iisexpress Process {pid} shut down", process.Id);

                    // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
                    started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}"));

                    TriggerHostShutdown(hostExitTokenSource);
                };
                process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger);
                Logger.LogInformation("iisexpress Process {pid} started", process.Id);

                if (process.HasExited)
                {
                    Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode);
                    throw new Exception("Failed to start host");
                }

                // Wait for the app to start
                // The timeout here is large, because we don't know how long the test could need. We cover a lot
                // of error cases above, but I want to make sure we eventually give up and don't hang the build
                // just in case we missed one -anurse
                if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(15)))
                {
                    Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port);

                    // Wait for the process to exit and try again
                    process.WaitForExit(30 * 1000);
                    await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up
                }
                else
                {
                    _hostProcess = process;

                    // Ensure iisexpress.exe is killed if test process termination is non-graceful.
                    // Prevents locked files when stop debugging unit test.
                    ProcessTracker.Add(_hostProcess);

                    // cache the process start time for verifying log file name.
                    var _ = _hostProcess.StartTime;

                    Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
                    return(url : url, hostExitToken : hostExitTokenSource.Token);
                }
            }

            var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
            Logger.LogError(message);
            throw new TimeoutException(message);
        }
    }
예제 #2
0
 public WebSocketMiddlewareTests()
 {
     ClientPort    = TestPortHelper.GetNextPort();
     ClientAddress = $"ws://localhost:{ClientPort}/";
 }
예제 #3
0
    public override async Task <DeploymentResult> DeployAsync()
    {
        using (Logger.BeginScope("Deploy"))
        {
            _configFile = Path.GetTempFileName();

            var uri = string.IsNullOrEmpty(DeploymentParameters.ApplicationBaseUriHint) ?
                      new Uri("http://localhost:0") :
                      new Uri(DeploymentParameters.ApplicationBaseUriHint);

            if (uri.Port == 0)
            {
                var builder = new UriBuilder(uri);
                if (OperatingSystem.IsLinux())
                {
                    // This works with nginx 1.9.1 and later using the reuseport flag, available on Ubuntu 16.04.
                    // Keep it open so nobody else claims the port
                    _portSelector = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _portSelector.Bind(new IPEndPoint(IPAddress.Loopback, 0));
                    builder.Port = ((IPEndPoint)_portSelector.LocalEndPoint).Port;
                }
                else
                {
                    builder.Port = TestPortHelper.GetNextPort();
                }
                uri = builder.Uri;
            }

            var redirectUri = TestUriHelper.BuildTestUri(ServerType.Nginx);

            if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr &&
                DeploymentParameters.ApplicationType == ApplicationType.Standalone)
            {
                // Publish is required to get the correct files in the output directory
                DeploymentParameters.PublishApplicationBeforeDeployment = true;
            }

            if (DeploymentParameters.PublishApplicationBeforeDeployment)
            {
                DotnetPublish();
            }

            var(appUri, exitToken) = await StartSelfHostAsync(redirectUri);

            SetupNginx(appUri.ToString(), uri);

            Logger.LogInformation("Application ready at URL: {appUrl}", uri);

            // Wait for App to be loaded since Nginx returns 502 instead of 503 when App isn't loaded
            // Target actual address to avoid going through Nginx proxy
            using (var httpClient = new HttpClient())
            {
                var response = await RetryHelper.RetryRequest(() =>
                {
                    return(httpClient.GetAsync(redirectUri));
                }, Logger, exitToken);

                if (!response.IsSuccessStatusCode)
                {
                    throw new InvalidOperationException("Deploy failed");
                }
            }

            return(new DeploymentResult(
                       LoggerFactory,
                       DeploymentParameters,
                       applicationBaseUri: uri.ToString(),
                       contentRoot: DeploymentParameters.ApplicationPath,
                       hostShutdownToken: exitToken));
        }
    }
예제 #4
0
        private async Task <(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
        {
            using (Logger.BeginScope("StartIISExpress"))
            {
                var port = uri.Port;
                if (port == 0)
                {
                    port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort();
                }

                for (var attempt = 0; attempt < MaximumAttempts; attempt++)
                {
                    Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);

                    if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigTemplateContent))
                    {
                        var serverConfig = DeploymentParameters.ServerConfigTemplateContent;

                        // 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, contentRoot);

                        serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, contentRoot);

                        Logger.LogDebug("Writing ApplicationPhysicalPath '{applicationPhysicalPath}' to config", contentRoot);
                        Logger.LogDebug("Writing Port '{port}' to config", port);
                        serverConfig =
                            serverConfig
                            .Replace("[ApplicationPhysicalPath]", contentRoot)
                            .Replace("[PORT]", port.ToString());

                        DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();

                        if (serverConfig.Contains("[HostingModel]"))
                        {
                            var hostingModel = DeploymentParameters.HostingModel.ToString();
                            serverConfig.Replace("[HostingModel]", hostingModel);
                            Logger.LogDebug("Writing HostingModel '{hostingModel}' to config", hostingModel);
                        }

                        Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);

                        if (Logger.IsEnabled(LogLevel.Trace))
                        {
                            Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", serverConfig);
                        }

                        File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig);
                    }

                    if (DeploymentParameters.HostingModel == HostingModel.InProcess)
                    {
                        ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", value: "inprocess");
                    }

                    ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString());
                    ModifyDotNetExePathInWebConfig();

                    var parameters = string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation) ?
                                     string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) :
                                     string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);

                    var iisExpressPath = GetIISExpressPath();

                    Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters);

                    var startInfo = new ProcessStartInfo
                    {
                        FileName               = iisExpressPath,
                        Arguments              = parameters,
                        UseShellExecute        = false,
                        CreateNoWindow         = true,
                        RedirectStandardError  = true,
                        RedirectStandardOutput = true
                    };

                    AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);

                    Uri url     = null;
                    var started = new TaskCompletionSource <bool>();

                    var process = new Process()
                    {
                        StartInfo = startInfo
                    };
                    process.OutputDataReceived += (sender, dataArgs) =>
                    {
                        if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage))
                        {
                            // We completely failed to start and we don't really know why
                            started.TrySetException(new InvalidOperationException("Failed to start IIS Express"));
                        }
                        else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage))
                        {
                            started.TrySetResult(false);
                        }
                        else if (string.Equals(dataArgs.Data, IISExpressRunningMessage))
                        {
                            started.TrySetResult(true);
                        }
                        else if (!string.IsNullOrEmpty(dataArgs.Data))
                        {
                            var m = UrlDetectorRegex.Match(dataArgs.Data);
                            if (m.Success)
                            {
                                url = new Uri(m.Groups["url"].Value);
                            }
                        }
                    };

                    process.EnableRaisingEvents = true;
                    var hostExitTokenSource = new CancellationTokenSource();
                    process.Exited += (sender, e) =>
                    {
                        Logger.LogInformation("iisexpress Process {pid} shut down", process.Id);

                        // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
                        started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}"));

                        TriggerHostShutdown(hostExitTokenSource);
                    };
                    process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger);
                    Logger.LogInformation("iisexpress Process {pid} started", process.Id);

                    if (process.HasExited)
                    {
                        Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode);
                        throw new Exception("Failed to start host");
                    }

                    // Wait for the app to start
                    // The timeout here is large, because we don't know how long the test could need
                    // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
                    // just in case we missed one -anurse
                    if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10)))
                    {
                        Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", _hostProcess.Id, port);

                        // Wait for the process to exit and try again
                        process.WaitForExit(30 * 1000);
                        await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up
                    }
                    else
                    {
                        _hostProcess = process;
                        Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
                        return(url : url, hostExitToken : hostExitTokenSource.Token);
                    }
                }

                var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
                Logger.LogError(message);
                throw new TimeoutException(message);
            }
        }