From 8ac8f9309835d0d496c231d646333dc8dbf510ed Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 2 Apr 2020 02:02:12 -0700 Subject: [PATCH] Add test for container -> host communication. (#290) * Add test for container -> host communication. * Small tweaks - Modify frontend-backend app to print backend url - Fix log in docker runner - Rename test to use docker instead of container * More clean up - Don't set container port if the service isn't a container * Bind to all interfaces on linux only --- samples/frontend-backend/frontend/Startup.cs | 9 +++- src/Microsoft.Tye.Hosting/DockerRunner.cs | 2 +- src/Microsoft.Tye.Hosting/PortAssigner.cs | 24 ++++++----- src/Microsoft.Tye.Hosting/ProcessRunner.cs | 7 +++- test/E2ETest/TyeRunTests.cs | 44 ++++++++++++++++++++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/samples/frontend-backend/frontend/Startup.cs b/samples/frontend-backend/frontend/Startup.cs index 11845f35..5699af64 100644 --- a/samples/frontend-backend/frontend/Startup.cs +++ b/samples/frontend-backend/frontend/Startup.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Frontend { @@ -37,7 +38,7 @@ namespace Frontend services.AddHealthChecks(); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) { if (env.IsDevelopment()) { @@ -48,9 +49,13 @@ namespace Frontend app.UseEndpoints(endpoints => { + var uri = Configuration.GetServiceUri("backend"); + + logger.LogInformation("Backend URL: {BackendUrl}", uri); + var httpClient = new HttpClient() { - BaseAddress = Configuration.GetServiceUri("backend"), + BaseAddress = uri }; endpoints.MapGet("/", async context => diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index 0a6c20e5..acd5b234 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -214,7 +214,7 @@ namespace Microsoft.Tye.Hosting var command = $"run -d {workingDirectory} {volumes} {environmentArguments} {portString} --name {replica} --restart=unless-stopped {docker.Image} {docker.Args ?? ""}"; - _logger.LogInformation("Running command image {Image} for {Replica}", docker.Image, replica); + _logger.LogInformation("Running image {Image} for {Replica}", docker.Image, replica); service.Logs.OnNext($"[{replica}]: docker {command}"); diff --git a/src/Microsoft.Tye.Hosting/PortAssigner.cs b/src/Microsoft.Tye.Hosting/PortAssigner.cs index 3ef5b0af..e2778041 100644 --- a/src/Microsoft.Tye.Hosting/PortAssigner.cs +++ b/src/Microsoft.Tye.Hosting/PortAssigner.cs @@ -78,18 +78,22 @@ namespace Microsoft.Tye.Hosting binding.Name ?? binding.Protocol); } - var httpBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "http"); - var httpsBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "https"); - - // Default the first http and https port to 80 and 443 - if (httpBinding != null) + // Only set the container port if we're running in a container + if (service.Description.RunInfo is DockerRunInfo) { - httpBinding.ContainerPort ??= 80; - } + var httpBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "http"); + var httpsBinding = service.Description.Bindings.FirstOrDefault(b => b.Protocol == "https"); - if (httpsBinding != null) - { - httpsBinding.ContainerPort ??= 443; + // Default the first http and https port to 80 and 443 + if (httpBinding != null) + { + httpBinding.ContainerPort ??= 80; + } + + if (httpsBinding != null) + { + httpsBinding.ContainerPort ??= 443; + } } } diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 4842b68a..5957dd2a 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -153,10 +153,15 @@ namespace Microsoft.Tye.Hosting if (hasPorts) { + // We need to bind to all interfaces on linux since the container -> host communication won't work + // if we use the IP address to reach out of the host. This works fine on osx and windows + // but doesn't work on linux. + var host = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "*" : "localhost"; + // These are the ports that the application should use for binding // 1. Configure ASP.NET Core to bind to those same ports - environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}")); + environment["ASPNETCORE_URLS"] = string.Join(";", ports.Select(p => $"{p.Protocol ?? "http"}://{host}:{p.Port}")); // Set the HTTPS port for the redirect middleware foreach (var p in ports) diff --git a/test/E2ETest/TyeRunTests.cs b/test/E2ETest/TyeRunTests.cs index 08c34c1f..ba9b531e 100644 --- a/test/E2ETest/TyeRunTests.cs +++ b/test/E2ETest/TyeRunTests.cs @@ -177,6 +177,50 @@ namespace E2ETest }); } + [ConditionalFact] + [SkipIfDockerNotRunning] + public async Task FrontendDockerBackendProject() + { + using var projectDirectory = CopySampleProjectDirectory("frontend-backend"); + + var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml")); + var outputContext = new OutputContext(_sink, Verbosity.Debug); + var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); + + // Transform the backend into a docker image for testing + var project = (ProjectServiceBuilder)application.Services.First(s => s.Name == "frontend"); + application.Services.Remove(project); + + var outputFileName = project.AssemblyName + ".dll"; + var container = new ContainerServiceBuilder(project.Name, $"mcr.microsoft.com/dotnet/core/sdk:{project.TargetFrameworkVersion}"); + container.Volumes.Add(new VolumeBuilder(project.PublishDir, name: null, target: "/app")); + container.Args = $"dotnet /app/{outputFileName} {project.Args}"; + container.Bindings.AddRange(project.Bindings); + + await ProcessUtil.RunAsync("dotnet", $"publish \"{project.ProjectFile.FullName}\" /nologo", outputDataReceived: _sink.WriteLine, errorDataReceived: _sink.WriteLine); + application.Services.Add(container); + + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (a, b, c, d) => true, + AllowAutoRedirect = false + }; + + var client = new HttpClient(new RetryHandler(handler)); + + await RunHostingApplication(application, Array.Empty(), async (app, uri) => + { + var frontendUri = await GetServiceUrl(client, uri, "frontend"); + var backendUri = await GetServiceUrl(client, uri, "backend"); + + var backendResponse = await client.GetAsync(backendUri); + var frontendResponse = await client.GetAsync(frontendUri); + + Assert.True(backendResponse.IsSuccessStatusCode); + Assert.True(frontendResponse.IsSuccessStatusCode); + }); + } + [ConditionalFact] [SkipIfDockerNotRunning] public async Task DockerNamedVolumeTest()