committed by
GitHub
3 changed files with 113 additions and 48 deletions
@ -0,0 +1,108 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Tye.Hosting.Model; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace Microsoft.Tye.Hosting |
|||
{ |
|||
public class PortAssigner : IApplicationProcessor |
|||
{ |
|||
private readonly ILogger _logger; |
|||
|
|||
public PortAssigner(ILogger logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
public Task StartAsync(Application application) |
|||
{ |
|||
foreach (var service in application.Services.Values) |
|||
{ |
|||
if (service.Description.RunInfo == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
static int GetNextPort() |
|||
{ |
|||
// 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.
|
|||
// This prevents races in parallel test runs where a test is already bound to
|
|||
// 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)); |
|||
return ((IPEndPoint)socket.LocalEndPoint).Port; |
|||
} |
|||
|
|||
foreach (var binding in service.Description.Bindings) |
|||
{ |
|||
if (binding.Port == null && !binding.AutoAssignPort) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Auto assign a port
|
|||
if (binding.Port == null) |
|||
{ |
|||
binding.Port = GetNextPort(); |
|||
} |
|||
|
|||
if (service.Description.Replicas == 1) |
|||
{ |
|||
// No need to proxy, the port maps to itself
|
|||
service.PortMap[binding.Port.Value] = new List<int> { binding.Port.Value }; |
|||
continue; |
|||
} |
|||
|
|||
var ports = new List<int>(); |
|||
|
|||
for (var i = 0; i < service.Description.Replicas; i++) |
|||
{ |
|||
// Reserve a port for each replica
|
|||
var port = GetNextPort(); |
|||
ports.Add(port); |
|||
} |
|||
|
|||
_logger.LogInformation( |
|||
"Mapping external port {ExternalPort} to internal port(s) {InternalPorts} for {ServiceName} binding {BindingName}", |
|||
binding.Port, |
|||
string.Join(", ", ports.Select(p => p.ToString())), |
|||
service.Description.Name, |
|||
binding.Name ?? binding.Protocol); |
|||
|
|||
service.PortMap[binding.Port.Value] = ports; |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
httpBinding.ContainerPort = 80; |
|||
} |
|||
|
|||
if (httpsBinding != null) |
|||
{ |
|||
httpsBinding.ContainerPort = 443; |
|||
} |
|||
|
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task StopAsync(Application application) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue