Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Commit

Permalink
Auto assign placement host port and remove sniffing
Browse files Browse the repository at this point in the history
  • Loading branch information
dasiths committed Dec 2, 2020
1 parent 4f3e2ac commit abb10b1
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 67 deletions.
18 changes: 11 additions & 7 deletions samples/dapr/pub-sub/tye.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ name: dapr
extensions:
- name: dapr

# If you want to use a different tag or container port
# placement-image: daprio/dapr
# placement-container-port: 50005

# log-level configures the log level of the dapr sidecar
log-level: debug

Expand All @@ -19,6 +23,13 @@ extensions:

# components-path configures the components path of the dapr sidecard
components-path: "./components/"

# You can instruct Tye to not create the Dapr placement container on your behalf. This is required if you have Dapr running and want to use that container.
# Doing a `docker ps` can show if its already running. If it's running then you can set 'exclude-placement-container: true' with `placement-port: xxxx` set to the host port of that container.
# (i.e. In Windows + WSL2, Dapr uses 6050 as the host port)

# exclude-placement-container: true
# placement-port: 6050
services:
- name: orders
project: orders/orders.csproj
Expand All @@ -35,10 +46,3 @@ services:
image: redis
bindings:
- port: 6379
# To ensure that your are running a dapr placement container with the right binding port.
# (50005 as HostPort)
- name: placement
image: daprio/dapr
args: ./placement
bindings:
- port: 50005
14 changes: 8 additions & 6 deletions samples/dapr/service-invocation/tye.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ name: distributedtyedemo
extensions:
- name: dapr

# If you want to use a different tag or container port
# placement-image: daprio/dapr
# placement-container-port: 50005

# log-level configures the log level of the dapr sidecar
log-level: debug

Expand All @@ -20,13 +24,11 @@ extensions:
# components-path configures the components path of the dapr sidecard
# components-path: "./components/"

# if you don't want Tye to create the placement container on your behalf. This is required if you have Dapr running and want to use that container.
# Doing a `docker ps` can show if its already running. If that's not the case then comment out out when running locally.
# exclude-placement-container: true
# You can instruct Tye to not create the Dapr placement container on your behalf. This is required if you have Dapr running and want to use that container.
# Doing a `docker ps` can show if its already running. If it's running then you can set 'exclude-placement-container: true' with `placement-port: xxxx` set to the host port of that container.
# (i.e. In Windows + WSL2, Dapr uses 6050 as the host port)

# if you want to define the Dapr placement container port.
# You can use this in combinations with 'exclude-placement-container' to use an existing container instance managed by Dapr (i.e. In Windows + WSL2, Dapr uses 6050 as the host port)
# or if the default port 50005 port is being used by something else
# exclude-placement-container: true
# placement-port: 6050
services:
# uppercase service is a node app and is run via a dockerfile
Expand Down
26 changes: 26 additions & 0 deletions src/Microsoft.Tye.Core/NextPortFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Net;
using System.Net.Sockets;

namespace Microsoft.Tye
{
public sealed class NextPortFinder
{
public static NextPortFinder Instance { get; } = new NextPortFinder();

private NextPortFinder()
{
}

public 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;
}
}
}
81 changes: 41 additions & 40 deletions src/Microsoft.Tye.Extensions/Dapr/DaprExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,58 +21,59 @@ public override Task ProcessAsync(ExtensionContext context, ExtensionConfigurati
if (context.Operation == ExtensionContext.OperationKind.LocalRun)
{
// default placement port number
var placementPort = 50005;
var isCustomPlacementPort = false;
var daprPlacementImage = "daprio/dapr";
var daprPlacementContainerPort = 50005;
var daprPlacementPort = NextPortFinder.Instance.GetNextPort();
var isCustomPlacementPortDefined = false;

var existingDaprPlacementDefinition = context.Application.Services.FirstOrDefault(s =>
s is ContainerServiceBuilder serviceBuilder && serviceBuilder.Image == "daprio/dapr");

// see if a placement port number has been defined at the extension level
// see if a placement port number has been defined
if (config.Data.TryGetValue("placement-port", out var obj) && obj?.ToString() is string && int.TryParse(obj.ToString(), out var customPlacementPort))
{
context.Output.WriteDebugLine($"Using Dapr placement service port {customPlacementPort} from 'placement-port'");
placementPort = customPlacementPort;
isCustomPlacementPort = true;
context.Output.WriteDebugLine($"Using Dapr placement service host port {customPlacementPort} from 'placement-port'");
daprPlacementPort = customPlacementPort;
isCustomPlacementPortDefined = true;
}

// see if a placement image has been defined
if (config.Data.TryGetValue("placement-image", out obj) && obj?.ToString() is string customPlacementImage)
{
context.Output.WriteDebugLine($"Using Dapr placement service image {customPlacementImage} from 'placement-image'");
daprPlacementImage = customPlacementImage;
}

// check to see if a custom service definition for the placement container exists.
if (existingDaprPlacementDefinition == null)
// see if a placement container port has been defined
if (config.Data.TryGetValue("placement-container-port", out obj) && obj?.ToString() is string && int.TryParse(obj.ToString(), out var customPlacementContainerPort))
{
context.Output.WriteDebugLine("Dapr placement service not defined in Tye.yaml. Trying to create one...");
context.Output.WriteDebugLine($"Using Dapr placement service container port {customPlacementContainerPort} from 'placement-container-port'");
daprPlacementContainerPort = customPlacementContainerPort;
}

if (!(config.Data.TryGetValue("exclude-placement-container", out obj) &&
obj?.ToString() is string excludePlacementContainer && excludePlacementContainer == "true"))
// We can only skip injecting a Dapr placement container if a 'placement-port' has been defined and 'exclude-placement-container=true'
if (!(isCustomPlacementPortDefined && config.Data.TryGetValue("exclude-placement-container", out obj) &&
obj?.ToString() is string excludePlacementContainer && excludePlacementContainer == "true"))
{
if (!isCustomPlacementPortDefined)
{
context.Output.WriteDebugLine("Injecting Dapr placement service...");
var daprPlacement = new ContainerServiceBuilder("placement", "daprio/dapr")
{
Args = "./placement",
Bindings = {
new BindingBuilder() {
Port = placementPort,
ContainerPort = 50005,
Protocol = "http"
}
}
};
context.Application.Services.Add(daprPlacement);
context.Output.WriteDebugLine("A 'placement-port' has not been defined. So the 'exclude-placement-container' will default to 'false'.");
}
else

context.Output.WriteDebugLine("Injecting Dapr placement service...");
var daprPlacement = new ContainerServiceBuilder("placement", daprPlacementImage)
{
context.Output.WriteDebugLine("Skipping injecting Dapr placement service because 'exclude-placement-container=true'...");
}
Args = "./placement",
Bindings = {
new BindingBuilder() {
Port = daprPlacementPort,
ContainerPort = daprPlacementContainerPort,
Protocol = "http"
}
}
};
context.Application.Services.Add(daprPlacement);
}
else
{
// use the port defined in the custom service definition is possible
var definedPlacementPort = existingDaprPlacementDefinition.Bindings.FirstOrDefault(b => b.Port.HasValue);
if (definedPlacementPort?.Port != null && !isCustomPlacementPort)
{
context.Output.WriteDebugLine($"Using Dapr placement service port {definedPlacementPort.Port.Value} from service definition...");
placementPort = definedPlacementPort.Port.Value;
}

context.Output.WriteDebugLine("Skipping injecting Dapr placement service because it's already defined in Tye.yaml...");
context.Output.WriteDebugLine("Skipping injecting Dapr placement service because 'exclude-placement-container=true'.");
}

// For local run, enumerate all projects, and add services for each dapr proxy.
Expand Down Expand Up @@ -106,7 +107,7 @@ public override Task ProcessAsync(ExtensionContext context, ExtensionConfigurati

// These environment variables are replaced with environment variables
// defined for this service.
Args = $"-app-id {project.Name} -app-port %APP_PORT% -dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT% --placement-address localhost:{placementPort}",
Args = $"-app-id {project.Name} -app-port %APP_PORT% -dapr-grpc-port %DAPR_GRPC_PORT% --dapr-http-port %DAPR_HTTP_PORT% --metrics-port %METRICS_PORT% --placement-host-address localhost:{daprPlacementPort}",
};

// When running locally `-config` specifies a filename, not a configuration name. By convention
Expand Down
16 changes: 2 additions & 14 deletions src/Microsoft.Tye.Hosting/PortAssigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,12 @@ public Task StartAsync(Application application)
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)
{
// Auto assign a port
if (binding.Port == null)
{
binding.Port = GetNextPort();
binding.Port = NextPortFinder.Instance.GetNextPort();
}

if (service.Description.Readiness == null && service.Description.Replicas == 1)
Expand All @@ -60,7 +48,7 @@ static int GetNextPort()
for (var i = 0; i < service.Description.Replicas; i++)
{
// Reserve a port for each replica
var port = GetNextPort();
var port = NextPortFinder.Instance.GetNextPort();
binding.ReplicaPorts.Add(port);
}

Expand Down

0 comments on commit abb10b1

Please sign in to comment.