From 20c9bfeed22237530f1b159dcb048fe002e8bd87 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 23 Apr 2020 21:33:59 -0700 Subject: [PATCH] WIP --- .../ConfigModel/ConfigApplication.cs | 6 ++- .../{PortAssigner.cs => AddressAssigner.cs} | 54 ++++++++++++++++--- .../Dashboard/Pages/Index.razor | 13 +++-- src/Microsoft.Tye.Hosting/DockerRunner.cs | 3 +- src/Microsoft.Tye.Hosting/Model/Service.cs | 2 + src/Microsoft.Tye.Hosting/ProcessRunner.cs | 8 ++- src/Microsoft.Tye.Hosting/ProxyService.cs | 2 +- src/Microsoft.Tye.Hosting/TyeHost.cs | 2 +- 8 files changed, 70 insertions(+), 20 deletions(-) rename src/Microsoft.Tye.Hosting/{PortAssigner.cs => AddressAssigner.cs} (60%) diff --git a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs index aa06fec6..d070e9eb 100644 --- a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs +++ b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using Tye; using Tye.Serialization; using YamlDotNet.Serialization; @@ -106,7 +107,7 @@ namespace Microsoft.Tye.ConfigModel throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSameName("service")); } - if (service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1) { throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("service")); } @@ -166,7 +167,8 @@ namespace Microsoft.Tye.ConfigModel { throw new TyeYamlException(CoreStrings.IngressBindingMustBeHttpOrHttps); } - if (ingress.Bindings.Count(o => o.Port == binding.Port) > 1) + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ingress.Bindings.Count(o => o.Port == binding.Port) > 1) { throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("ingress")); } diff --git a/src/Microsoft.Tye.Hosting/PortAssigner.cs b/src/Microsoft.Tye.Hosting/AddressAssigner.cs similarity index 60% rename from src/Microsoft.Tye.Hosting/PortAssigner.cs rename to src/Microsoft.Tye.Hosting/AddressAssigner.cs index d5a1c670..767cc0b7 100644 --- a/src/Microsoft.Tye.Hosting/PortAssigner.cs +++ b/src/Microsoft.Tye.Hosting/AddressAssigner.cs @@ -9,20 +9,23 @@ using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.Tye.Hosting.Model; using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; namespace Microsoft.Tye.Hosting { - public class PortAssigner : IApplicationProcessor + public class AddressAssigner : IApplicationProcessor { private readonly ILogger _logger; - public PortAssigner(ILogger logger) + public AddressAssigner(ILogger logger) { _logger = logger; } public Task StartAsync(Application application) { + // Nobody's going to have > 255 services right? + var octect = 0; foreach (var service in application.Services.Values) { if (service.Description.RunInfo == null) @@ -30,7 +33,9 @@ namespace Microsoft.Tye.Hosting continue; } - static int GetNextPort() + octect++; + + static int GetNextPort(IPAddress address) { // Let the OS assign the next available port. Unless we cycle through all ports // on a test run, the OS will always increment the port number when making these calls. @@ -38,16 +43,53 @@ namespace Microsoft.Tye.Hosting // a given port, and a new test is able to bind to the same port due to port // reuse being enabled by default by the OS. using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + socket.Bind(new IPEndPoint(address, 0)); return ((IPEndPoint)socket.LocalEndPoint).Port; } + static bool IsPortAlreadyInUse(IPAddress address, int port) + { + var endpoint = new IPEndPoint(address, port); + try + { + using var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + socket.Bind(endpoint); + return false; + } + catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) + { + return true; + } + } + + var httpUsed = false; + var httpsUsed = false; + + // 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 address = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? IPAddress.Any : IPAddress.Parse($"127.0.0.{octect}"); + service.Address = address; + foreach (var binding in service.Description.Bindings) { // Auto assign a port if (binding.Port == null) { - binding.Port = GetNextPort(); + if (!httpUsed && binding.Protocol == "http" && !IsPortAlreadyInUse(address, 80)) + { + binding.Port = 80; + httpUsed = true; + } + else if (!httpsUsed && binding.Protocol == "https" && !IsPortAlreadyInUse(address, 443)) + { + binding.Port = 443; + httpsUsed = true; + } + else + { + binding.Port = GetNextPort(address); + } } if (service.Description.Replicas == 1) @@ -60,7 +102,7 @@ namespace Microsoft.Tye.Hosting for (var i = 0; i < service.Description.Replicas; i++) { // Reserve a port for each replica - var port = GetNextPort(); + var port = GetNextPort(address); binding.ReplicaPorts.Add(port); } diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index 635c0ed1..faa97a14 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -44,12 +44,12 @@ { if (b.Protocol == "http" || b.Protocol == "https") { - var url = GetUrl(b); + var url = GetUrl(service, b); @url } else { - @GetUrl(b) + @GetUrl(service, b) } } else @@ -77,9 +77,14 @@ private List _subscriptions = new List(); - string GetUrl(ServiceBinding b) + string GetUrl(Service service, ServiceBinding b) { - return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}"; + var url = $"{(b.Protocol ?? "tcp")}://{service.Address}"; + if (b.Port != 80 && b.Port != 443) + { + url += $":{b.Port}"; + } + return url; } protected override void OnInitialized() diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs index 5c56c38c..25f8e6db 100644 --- a/src/Microsoft.Tye.Hosting/DockerRunner.cs +++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs @@ -244,6 +244,7 @@ namespace Microsoft.Tye.Hosting status.Ports = ports.Select(p => p.Port); // These are the ports that the application should use for binding + var host = service.Address!.ToString(); // 1. Tell the docker container what port to bind to portString = docker.Private ? "" : string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.ContainerPort ?? p.Port}")); @@ -262,7 +263,7 @@ namespace Microsoft.Tye.Hosting } // 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list. - environment["PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}")); + environment["PORT"] = string.Join(";", ports.Select(p => $"{host}:{p.ContainerPort ?? p.Port}")); // This the port for the container proxy (containerport:externalport) environment["PROXY_PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}:{p.ExternalPort}")); diff --git a/src/Microsoft.Tye.Hosting/Model/Service.cs b/src/Microsoft.Tye.Hosting/Model/Service.cs index 03862258..94e68003 100644 --- a/src/Microsoft.Tye.Hosting/Model/Service.cs +++ b/src/Microsoft.Tye.Hosting/Model/Service.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net; using System.Reactive.Subjects; namespace Microsoft.Tye.Hosting.Model @@ -69,5 +70,6 @@ namespace Microsoft.Tye.Hosting.Model public Subject Logs { get; } = new Subject(); public Subject ReplicaEvents { get; } = new Subject(); + public IPAddress? Address { get; set; } } } diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index 776f51da..1563cc3f 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -168,10 +169,7 @@ 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"; + var host = service.Address == IPAddress.Any ? "*" : service.Address!.ToString(); // These are the ports that the application should use for binding @@ -235,7 +233,7 @@ namespace Microsoft.Tye.Hosting { if (hasPorts) { - _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}"))); + _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://{service.Address}:{p.Port}"))); } else { diff --git a/src/Microsoft.Tye.Hosting/ProxyService.cs b/src/Microsoft.Tye.Hosting/ProxyService.cs index 2723c1b2..5b606f88 100644 --- a/src/Microsoft.Tye.Hosting/ProxyService.cs +++ b/src/Microsoft.Tye.Hosting/ProxyService.cs @@ -63,7 +63,7 @@ namespace Microsoft.Tye.Hosting // 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) ? IPAddress.Any : IPAddress.Loopback; + var host = service.Address; sockets.Listen(host, binding.Port.Value, o => { diff --git a/src/Microsoft.Tye.Hosting/TyeHost.cs b/src/Microsoft.Tye.Hosting/TyeHost.cs index b40d95d3..221ab3c1 100644 --- a/src/Microsoft.Tye.Hosting/TyeHost.cs +++ b/src/Microsoft.Tye.Hosting/TyeHost.cs @@ -263,7 +263,7 @@ namespace Microsoft.Tye.Hosting var processors = new List { new EventPipeDiagnosticsRunner(logger, diagnosticsCollector), - new PortAssigner(logger), + new AddressAssigner(logger), new ProxyService(logger), new HttpProxyService(logger), new DockerImagePuller(logger),