[13.0] `EndpointReference` environment variable evaluating to `http://:` (i.e. missing host & port)
Is there an existing issue for this?
- [x] I have searched the existing issues
Describe the bug
I have a test in 9.5, in which I needed to run an in memory server on a port, and make sure a dependency connects to it. (Specifically the otel connector)
After upgrading to 13.0, the endpoint is not being resolved correctly, and rather than failing, is ending up with an empty host and port http://:.
Whilst this could be a problem with my test code, it does feel like WithEnvironment() should never be able to resolve with an empty port/host, so maybe something else is wrong.
Expected Behavior
The reference should contain the port & host from the allocated endpoint.
But even more so, I wouldn't expect the final environment value to end up with missing host or port - I'd expect some kind of error to be thrown if either of these evaluated to null/empty.
Steps To Reproduce
[Test]
public async Task test()
{
var builder = DistributedApplicationTestingBuilder.Create();
var dependency = builder
.AddResource(new FakeResource())
.WithHttpEndpoint();
var consumer = builder
.AddContainer("test", "redis")
.WithImageRegistry("docker.io")
.WithReference(dependency.GetEndpoint("http"));
var endpointAnnotation = dependency.Resource.Annotations.OfType<EndpointAnnotation>().Single();
endpointAnnotation.AllocatedEndpoint = new AllocatedEndpoint(endpointAnnotation, "localhost", 1234);
using var app = builder.Build();
await app.StartAsync();
var envVars = await consumer.Resource.GetEnvironmentVariableValuesAsync();
Assert.That(envVars["services__fake__http__0"], Is.EqualTo("http://localhost:1234"));
// Actual: `http://:`
}
public class FakeResource() : Resource("fake"), IResourceWithEndpoints { }
Exceptions (if any)
Assert.That(envVars["services__fake__http__0"], Is.EqualTo("http://localhost:1234"))
Expected string length 21 but was 8. Strings differ at index 7.
Expected: "http://localhost:1234"
But was: "http://:"
.NET Version info
No response
Anything else?
No response
For context, here is my "real" test, as opposed to the strip down minimal reproduction above. This is for testing a component that scrapes prometheus metric endpoints, to forward them to the aspire dashboard's otel collector.
[Test]
public async Task PullsMetrics(CancellationToken ct)
{
var builder = Common.CreateIntegrationAppHost();
builder.TryAddPrometheusScraper();
var metricsCalled = new TaskCompletionSource();
var fakeResoruce = AddResourceWithMetricsEndpoint(builder, () => metricsCalled.TrySetResult());
var app = builder.Build();
await app.StartAsync(ct);
var envVars = (IResourceWithEnvironment)builder.Resources[0];
await app.ResourceNotifications.WaitForResourceHealthyAsync(PrometheusExtensions.CollectorResourceName, ct);
await metricsCalled.Task.WaitAsync(ct);
static IResourceBuilder<MetricsResource> AddResourceWithMetricsEndpoint(IDistributedApplicationBuilder builder, Action callback)
{
var port = TestContext.CurrentContext.Random.Next(10000, ushort.MaxValue);
var resource = new MetricsResource();
var metrics = builder.AddResource(resource)
.WithHttpEndpoint(name: "metrics", port: port, isProxied: false);
var endpoint = metrics.GetEndpoint("metrics");
metrics.WithPrometheusMetrics(endpoint);
// As this is a custom resource, aspire won't handle port allcoation for this - fake it.
var endpointAnnotation = resource.Annotations.OfType<EndpointAnnotation>().Single();
endpointAnnotation.AllocatedEndpoint = new AllocatedEndpoint(endpointAnnotation, "localhost", port);
return metrics.OnInitializeResource((resource, _, ct) =>
{
var server = new KestrelMetricServer(port);
server.Start();
Metrics.DefaultRegistry.AddBeforeCollectCallback(() => Console.WriteLine("Metrics Requested"));
Metrics.DefaultRegistry.AddBeforeCollectCallback(callback);
resource.Server = server;
ct.Register(() => server.Stop());
return Task.CompletedTask;
});
}
}
Looks related to @karolz-ms 's changes to endpoints.
I've been able to get my local test passing by moving from using the endpointAnnotation.AllocatedEndpoint setter to the following:
var endpointAnnotation = resource.Annotations.OfType<EndpointAnnotation>().Single();
var allocatedEndpoint = new AllocatedEndpoint(endpointAnnotation, KnownHostNames.DockerDesktopHostBridge, port, EndpointBindingMode.SingleAddress, targetPortExpression: port.ToString(), networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork);
var snapshot = new ValueSnapshot<AllocatedEndpoint>();
snapshot.SetValue(allocatedEndpoint);
endpointAnnotation.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot);
I would however expect using the "old" method to either work, or fail with a clear error.
Yes, in Aspire 13 AllocatedEndpoints are tied to a network (via NetworkIdentifier) and so if they are used in tests, care needs to be taken to create one with proper network association.
That said @afscrome has a point--on a cursory glance I would expect the call to GetEnvironmentVariableValuesAsync() to "hang" and not resolve to empty values. I will take a look