Repository navigation
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.sh | bash -s -- 2336Or
iex "& { $(irm https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.ps1) } 2336" |
…m from floci-az Probing a free port and releasing it before the Artemis sidecar binds it leaves a window in which another process can take the port, and Docker cannot recover from an explicit binding. Ports left unset are now passed to floci-az as 0, so Docker binds them atomically, and the child's endpoints are allocated from floci-az's namespace API once the emulator is ready. Explicitly configured ports are unchanged. Closes CommunityToolkit#2335
Docker-assigned AMQP host ports are discovered through the amqpHostPort / amqpsHostPort fields that floci-az 0.16.0 added to the namespace API. The image tag is already 0.16.0 on main. The baseline drops IResourceWithParent<FlociAzureContainerResource> from FlociAzureServiceBusResource.
f7980d6 to
a6f8832
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The allocator can let containers start despite Service Bus discovery failures, fail with valid non-default namespace configurations, and leave late container-network lookups waiting indefinitely after discovery errors.
3 open issues
What changed in this PR
This PR addresses #2335 by replacing Floci Service Bus port probing with Docker-assigned ports, removing the gap between selecting a port and binding it.
Changes:
- Discovers dynamic host ports through floci-az’s namespace API while preserving explicitly configured ports.
- Adds child lifecycle and failure handling, adjusts container waits, and removes
IResourceWithParentto avoid startup deadlock. - Updates tests, documentation, and the generated API baseline.
| File | Description |
|---|---|
tests/CommunityToolkit.Aspire.Hosting.Floci.Tests/AzureServiceBusResourceTests.cs |
Tests dynamic discovery, configured ports, lifecycle states, failures, and wait redirection. |
src/CommunityToolkit.Aspire.Hosting.Floci/README.md |
Documents port discovery, container behavior, and the floci-az 0.16.0 requirement. |
src/CommunityToolkit.Aspire.Hosting.Floci/FlociServiceBusEndpointAllocator.cs |
Replaces probing with API discovery and manages child state and endpoint allocation. |
src/CommunityToolkit.Aspire.Hosting.Floci/FlociHostingExtension.Azure.cs |
Passes unset ports as zero and defers container port resolution. |
src/CommunityToolkit.Aspire.Hosting.Floci/FlociAzureServiceBusResource.cs |
Removes the parent interface while retaining the parent property and relationship. |
src/CommunityToolkit.Aspire.Hosting.Floci/api/CommunityToolkit.Aspire.Hosting.Floci.cs |
Reflects the public interface removal in the generated baseline. |
Review effort: Balanced
| container.Annotations.Remove(wait); | ||
| container.Annotations.Add(new WaitAnnotation(resource.Parent, wait.WaitType, wait.ExitCode) | ||
| { | ||
| WaitBehavior = wait.WaitBehavior | ||
| }); |
There was a problem hiding this comment.
Fixed in 465be67. Redirected WaitUntilHealthy waits now also add an internal ServiceBusWaitAnnotation. A BeforeResourceStartedEvent handler calls WaitForResourceHealthyAsync on the child with the original wait behavior. Aspire raises that event before DCP creates the container, so the child never becomes a tunnel dependency, and the container fails to start when the child is FailedToStart. Tests cover both cases: WaitFor only, and a configured AMQP port with dynamic AMQPS.
| var uri = new Uri( | ||
| $"http://{hostAndPort}/{FlociAzureContainerResource.DefaultAccountName}-servicebus/namespaces/{DefaultNamespace}"); |
There was a problem hiding this comment.
Fixed in 465be67. Discovery now calls GET /{account}-servicebus/namespaces and picks a namespace in this order: one whose host port matches a configured port (floci-az binds the configured ports to the first topology namespace), then default, then the only namespace running. If none matches, it throws an error that lists the running namespaces. Tests cover a sbemulatorns topology namespace.
| foreach (var snapshot in endpoint.AllAllocatedEndpoints.Where(snapshot => !snapshot.Snapshot.IsValueSet)) | ||
| { | ||
| snapshot.Snapshot.SetException(exception); |
There was a problem hiding this comment.
Fixed in 465be67. On failure, FailPendingEndpoints now adds a faulted snapshot for both the localhost and default container networks, or faults the existing unset snapshot. Added ContainerNetworkLookupsAfterAFailedDiscoveryFail, which starts container-network resolution only after the discovery error.
…amespace, fault late lookups - Keep a child readiness gate for containers whose waits are redirected to the parent: enforce it in BeforeResourceStartedEvent so the container fails to start when Service Bus fails, without making the child a DCP dependency. - List floci-az's Service Bus namespaces instead of assuming `default`, so a mounted Config.json topology with another namespace name still works. - On discovery failure, fault both supported network snapshots, including ones first requested after the failure.
|
|
||
| [AspireExport(ExposeProperties = true)] | ||
| public partial class FlociAzureServiceBusResource : Resource, IResourceWithParent<FlociAzureContainerResource>, IResourceWithParent, IResource, IResourceWithConnectionString, IExpressionValue, IValueProvider, IManifestExpressionProvider, IValueWithReferences, IResourceWithEndpoints | ||
| public partial class FlociAzureServiceBusResource : Resource, IResourceWithConnectionString, IResource, IExpressionValue, IValueProvider, IManifestExpressionProvider, IValueWithReferences, IResourceWithEndpoints |
There was a problem hiding this comment.
This will get picked up by an automated job so it can be revered here.
| public class FlociAzureServiceBusResource( | ||
| string name, | ||
| FlociAzureContainerResource parent) : Resource(name), | ||
| IResourceWithParent<FlociAzureContainerResource>, |
There was a problem hiding this comment.
This is technically a breaking API change, since the type signature of the Resoruce type is going to be different.
IMO the change is likely to be minimally impactful on the users - do you agree?

Closes #2335
When
WithServiceBus()gets no ports, the integration no longer chooses the Service Bus host ports itself. Docker assigns them atomically when floci-az starts the Artemis sidecar, and the integration reads them back from floci-az once the emulator is ready. #2240 / #2241 narrowed the port range but kept the probe-and-release step, so a port could still be taken by another process in the seconds before Docker bound it. Only an atomic bind closes that window.Change
FLOCI_AZ_SERVICES_SERVICE_BUS_AMQP_PORT/..._AMQP_TLS_PORTare0, which floci-az already passes to Docker as a dynamic binding. The child'samqp/amqpsendpoints stay unallocated until the parent raisesResourceReadyEvent. The allocator then callsGET /devstoreaccount1-servicebus/namespaces/defaulton the emulator and allocates the endpoints fromamqpHostPort/amqpsHostPorton both the localhost network and the container network (host gateway, same port). It then publishesResourceEndpointsAllocatedEventandConnectionStringAvailableEventand marks the childRunning. If floci-az does not report usable ports, the error is logged to the child's resource log, the child is markedFailedToStart, and consumers already resolving its endpoints get the error instead of waiting forever.BeforeStartEvent, with no probing and no API call. A mixed call (one port given, one not) allocates the given port up front and discovers the other.Waitinguntil the parent is ready, thenRunning, andExitedwhen the parent stops. Executables and projects canWaitFor(serviceBus).Container tunnel service ... should have valid address. While ports are Docker-assigned, a container'sWaitFor(serviceBus)is redirected to the emulator. Its connection string still resolves only after the ports are allocated, so the ordering holds. The container reference helper now reads the endpoint'sPort, which waits for allocation, instead ofTargetPort, which throws when read before discovery.SelectPort,GetReservedPorts, the 20000-32767 range) and its tests.Why discovery goes through floci-az and not the container runtime
The other option was reading the sidecar's bindings with
docker inspect. That works today, but it needs the Docker CLI on the AppHost's PATH, it does not cover Podman without extra runtime detection, and it depends on floci-az's internal container naming (floci-az-<FLOCI_AZ_DOCKER_RESOURCE_NAMESPACE>-servicebus-<namespace>). floci-az documents its namespace API as the way orchestrators discover the endpoint at runtime, and it knows the published ports first-hand.Dependency
When floci-az runs in a container, which is how this integration runs it,
0.15.0's namespace API reports only the sidecar's container-internal ports (5672/5671). floci-io/floci-az#592 / floci-io/floci-az#593 addamqpHostPort/amqpsHostPort, the Docker-published host ports in every mode. This PR reads those fields. They ship in floci-az0.16.0, whichmainalready uses (FlociContainerImageTags.AzureTag). The "Requires floci-az ..." line in the README and XML docs now says0.16.0.Breaking change note
FlociAzureServiceBusResourceno longer implementsIResourceWithParent<FlociAzureContainerResource>. Aspire publishesConnectionStringAvailableEventforIResourceWithParentchildren before it creates the parent container. Resolving this child's connection string waits for its endpoints, which now exist only after the parent has started, so keeping the interface deadlocks startup. TheParentproperty and the dashboard nesting (the existingWithParentRelationshipannotation) are kept. Code that treated the child asIResourceWithParent(for example casting it, or relying on its inherited health checks) is affected. Its readiness now comes from the child's ownRunningstate, which is set only after floci-az is healthy. The API baseline (api/*.cs) is regenerated. Its only change is this interface removal.Tests
WithServiceBusPassesConfiguredPortsAndLetsDockerAssignTheRest: configured ports are allocated duringBeforeStartEventand passed through. Unset ones stay unallocated and are passed as0.ParentReadyAllocatesDockerAssignedPortsFromTheNamespaceApi(unset, AMQP-only, and AMQPS-only configured): uses a stubbed namespace API to check the request URL, that the host-port fields are used, the allocated ports, the connection string,ConnectionStringAvailableEvent, and theWaiting→Running→Exitedstates.ParentReadyWithConfiguredPortsDoesNotQueryTheNamespaceApi,ParentReadyMarksTheChildFailedWhenPortsCannotBeDiscovered,ContainersWaitForTheEmulatorWhileServiceBusPortsAreUnknown,ParseNamespacePorts*,ServiceBusIsLinkedToItsParentByRelationship.MultipleEmulatorsUseDistinctChildNamesAndConnectionKeysnow discovers ports per emulator.CommunityToolkit.Aspire.Hosting.Floci.Testsrun on Windows with Docker: 104 tests, 101 passed. The 3 failures are the Docker-backed Service Bus tests listed under Dependency (2 functional tests and the TypeScript AppHost test).AzureServiceBusResourceTests: 31/31 passed.PR Checklist
Other information
A downstream AppHost has run the same approach (dynamic ports, discovery after floci-az is ready, a parent-relationship annotation instead of
IResourceWithParent) and has not hit the bind failure since.