Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/DashboardWebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,11 @@ public DashboardWebApplication(
// This isn't used by dotnet watch but still useful to have for debugging
_logger.LogInformation("OTLP/HTTP listening on: {OtlpEndpointUri}", _otlpServiceHttpEndPointAccessor().GetResolvedAddress());
}
if (_mcpEndPointAccessor != null)
{
// This isn't used by dotnet watch but still useful to have for debugging
_logger.LogInformation("MCP listening on: {McpEndpointUri}", _mcpEndPointAccessor().GetResolvedAddress());
}

if (_dashboardOptionsMonitor.CurrentValue.Otlp.AuthMode == OtlpAuthMode.Unsecured)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ public async Task LogOutput_NoToken_GeneratedTokenLogged()
Assert.NotEqual(0, uri.Port);
},
w =>
{
Assert.Equal("MCP listening on: {McpEndpointUri}", GetValue(w.State, "{OriginalFormat}"));

var uri = new Uri((string)GetValue(w.State, "McpEndpointUri")!);
Assert.NotEqual(0, uri.Port);
},
w =>
{
Assert.Equal("OTLP server is unsecured. Untrusted apps can send telemetry to the dashboard. For more information, visit https://go.microsoft.com/fwlink/?linkid=2267030", GetValue(w.State, "{OriginalFormat}"));
Assert.Equal(LogLevel.Warning, w.LogLevel);
Expand Down
10 changes: 9 additions & 1 deletion tests/Aspire.Dashboard.Tests/Integration/ServerRetryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ public static async Task BindPortsWithRetry(Func<List<int>, Task> retryFunc, ILo
var port = GetAvailablePort(nextPortAttempt, logger);
ports.Add(port);

nextPortAttempt = port + Random.Shared.Next(100);
// Use a minimum gap of 10 between port allocations to reduce the risk of port collisions.
// Allocating consecutive ports (gap of 0) can lead to conflicts if the OS or other processes
// allocate ports in the same range. The random gap further reduces the chance of collision.
nextPortAttempt = port + Random.Shared.Next(10, 100);
}

if (ports.Count != ports.Distinct().Count())
{
throw new InvalidOperationException($"Generated ports list contains duplicate numbers: {string.Join(", ", ports)}");
}

try
Expand Down
47 changes: 36 additions & 11 deletions tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json.Nodes;
Expand Down Expand Up @@ -708,6 +709,13 @@ public async Task LogOutput_DynamicPort_PortResolvedInLogs()
Assert.NotEqual(0, uri.Port);
},
w =>
{
Assert.Equal("MCP listening on: {McpEndpointUri}", GetValue(w.State, "{OriginalFormat}"));

var uri = new Uri((string)GetValue(w.State, "McpEndpointUri")!);
Assert.NotEqual(0, uri.Port);
},
w =>
{
Assert.Equal("OTLP server is unsecured. Untrusted apps can send telemetry to the dashboard. For more information, visit https://go.microsoft.com/fwlink/?linkid=2267030", GetValue(w.State, "{OriginalFormat}"));
Assert.Equal(LogLevel.Warning, w.LogLevel);
Expand All @@ -729,31 +737,42 @@ public async Task LogOutput_DynamicPort_PortResolvedInLogs()
public async Task LogOutput_LocalhostAddress_LocalhostInLogOutput()
{
// Arrange
TestSink? testSink = null;
var testSink = new TestSink();
var loggerFactory = IntegrationTestHelpers.CreateLoggerFactory(testOutputHelper, testSink);

DashboardWebApplication? app = null;

int? frontendPort1 = null;
int? frontendPort2 = null;
int? otlpPort = null;
int? otlpGrpcPort = null;
int? otlpHttpPort = null;
try
{
await ServerRetryHelper.BindPortsWithRetry(async ports =>
{
frontendPort1 = ports[0];
frontendPort2 = ports[1];
otlpPort = ports[2];
otlpGrpcPort = ports[2];
otlpHttpPort = ports[3];

testSink = new TestSink();
app = IntegrationTestHelpers.CreateDashboardWebApplication(testOutputHelper,
// Reset sink writes. Required to clear out data from a previous failed retry.
// The following cast relies on the internal implementation detail that TestSink.Writes is a ConcurrentQueue<WriteContext>.
// If the implementation of TestSink changes, this may break. There is no public API to clear the writes.
var writes = (ConcurrentQueue<Microsoft.Extensions.Logging.Testing.WriteContext>)testSink.Writes;
writes.Clear();

app = IntegrationTestHelpers.CreateDashboardWebApplication(loggerFactory,
additionalConfiguration: data =>
{
data[DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = $"https://localhost:{frontendPort1};http://localhost:{frontendPort2}";
data[DashboardConfigNames.DashboardOtlpGrpcUrlName.ConfigKey] = $"http://localhost:{otlpPort}";
}, testSink: testSink);
data[DashboardConfigNames.DashboardOtlpGrpcUrlName.ConfigKey] = $"http://localhost:{otlpGrpcPort}";
data[DashboardConfigNames.DashboardOtlpHttpUrlName.ConfigKey] = $"http://localhost:{otlpHttpPort}";
data[DashboardConfigNames.DashboardMcpUrlName.ConfigKey] = "http://127.0.0.1:0"; // Test that a dynamic port has a set value in logs.
});

// Act
await app.StartAsync().DefaultTimeout();
}, NullLogger.Instance, portCount: 3);
}, loggerFactory.CreateLogger(GetType()), portCount: 4);
}
finally
{
Expand All @@ -764,7 +783,6 @@ await ServerRetryHelper.BindPortsWithRetry(async ports =>
}

// Assert
Assert.NotNull(testSink);
var l = testSink.Writes.Where(w => w.LoggerName == typeof(DashboardWebApplication).FullName && w.LogLevel >= LogLevel.Information).ToList();
Assert.Collection(l,
w =>
Expand All @@ -785,14 +803,21 @@ await ServerRetryHelper.BindPortsWithRetry(async ports =>
Assert.Equal("OTLP/gRPC listening on: {OtlpEndpointUri}", GetValue(w.State, "{OriginalFormat}"));

var uri = new Uri((string)GetValue(w.State, "OtlpEndpointUri")!);
Assert.NotEqual(0, uri.Port);
Assert.Equal(otlpGrpcPort, uri.Port);
},
w =>
{
Assert.Equal("OTLP/HTTP listening on: {OtlpEndpointUri}", GetValue(w.State, "{OriginalFormat}"));

var uri = new Uri((string)GetValue(w.State, "OtlpEndpointUri")!);
Assert.NotEqual(0, uri.Port);
Assert.Equal(otlpHttpPort, uri.Port);
},
w =>
{
Assert.Equal("MCP listening on: {McpEndpointUri}", GetValue(w.State, "{OriginalFormat}"));

var uri = new Uri((string)GetValue(w.State, "McpEndpointUri")!);
Assert.NotEqual(0, uri.Port); // Check that allocated port is in log message
},
w =>
{
Expand Down