Browse Source

Split out port assignment from the proxy (#221)

* Split out port assignment from the proxy

* Copyright
davidfowl/dependencies
David Fowler 7 years ago
committed by GitHub
parent
commit
70938f85d8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 108
      src/Microsoft.Tye.Hosting/PortAssigner.cs
  2. 52
      src/Microsoft.Tye.Hosting/ProxyService.cs
  3. 1
      src/Microsoft.Tye.Hosting/TyeHost.cs

108
src/Microsoft.Tye.Hosting/PortAssigner.cs

@ -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;
}
}
}

52
src/Microsoft.Tye.Hosting/ProxyService.cs

@ -3,10 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
@ -45,63 +43,21 @@ namespace Microsoft.Tye.Hosting
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;
}
if (binding.Port == null)
{
binding.Port = GetNextPort();
}
if (binding.Protocol == "http" || (binding.Protocol == null && service.ServiceType == Model.ServiceType.Project))
{
binding.ContainerPort = 80;
}
else if (binding.Protocol == "https")
{
binding.ContainerPort = 443;
// There's no port so nothing to proxy
continue;
}
if (service.Description.Replicas == 1)
{
// No need to proxy
service.PortMap[binding.Port.Value] = new List<int> { binding.Port.Value };
// No need to proxy for a single replica, we may want to do this later but right now we skip it
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 ports = service.PortMap[binding.Port.Value];
sockets.Listen(IPAddress.Loopback, binding.Port.Value, o =>
{

1
src/Microsoft.Tye.Hosting/TyeHost.cs

@ -257,6 +257,7 @@ namespace Microsoft.Tye.Hosting
var processors = new List<IApplicationProcessor>
{
new EventPipeDiagnosticsRunner(logger, diagnosticsCollector),
new PortAssigner(logger),
new ProxyService(logger),
new HttpProxyService(logger),
new DockerRunner(logger),

Loading…
Cancel
Save