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
7 changes: 7 additions & 0 deletions src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ await _notificationService.PublishUpdateAsync(child, s => s with
StopTimeStamp = stopTimeStamp,
Properties = s.Properties.SetResourceProperty(KnownProperties.Resource.ParentName, parentName)
}).ConfigureAwait(false);

// the parent name needs to be an instance name, not the resource name.
// parent the children of the child under the first resource instance.
await SetChildResourceAsync(child, child.GetResolvedResourceNames()[0], state, startTimeStamp, stopTimeStamp)
Copy link
Member

@JamesNK JamesNK Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work well with replicas. But I don't think that's new.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you expect it to work with replicas? Which replica should the child go under?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it can work today. Lets see if it comes up before worrying about it.

.ConfigureAwait(false);
}
}

Expand All @@ -253,6 +258,8 @@ await _notificationService.PublishUpdateAsync(child, s => s with
{
Properties = s.Properties.SetResourceProperty(KnownProperties.Resource.ParentName, parentName)
}).ConfigureAwait(false);

await SetExecutableChildResourceAsync(child).ConfigureAwait(false);
}
}

Expand Down
24 changes: 5 additions & 19 deletions src/Aspire.Hosting/Orchestrator/RelationshipEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,16 @@ internal static class RelationshipEvaluator
{
public static ILookup<IResource, IResource> GetParentChildLookup(DistributedApplicationModel model)
{
static IResource? SelectParentContainerResource(IResource resource) => resource switch
{
IResourceWithParent rp => SelectParentContainerResource(rp.Parent),
IResource r when r.IsContainer() => r,
_ => null
};

// parent -> children lookup
// Built from IResourceWithParent first, then from annotations.
return model.Resources.OfType<IResourceWithParent>()
.Select(x => (Child: (IResource)x, Root: SelectParentContainerResource(x.Parent)))
.Where(x => x.Root is not null)
.Select(x => (Child: (IResource)x, Parent: x.Parent))
.Where(x => x.Parent is not null)
.Concat(GetParentChildRelationshipsFromAnnotations(model))
.ToLookup(x => x.Root!, x => x.Child);
.ToLookup(x => x.Parent!, x => x.Child);
}

private static IEnumerable<(IResource Child, IResource? Root)> GetParentChildRelationshipsFromAnnotations(DistributedApplicationModel model)
private static IEnumerable<(IResource Child, IResource Parent)> GetParentChildRelationshipsFromAnnotations(DistributedApplicationModel model)
{
static bool TryGetParent(IResource resource, [NotNullWhen(true)] out IResource? parent)
{
Expand All @@ -55,14 +48,7 @@ IResource r when TryGetParent(r, out var parent) => parent,

ValidateRelationships(result!);

static IResource? SelectRootResource(IResource? resource) => resource switch
{
IResource r when TryGetParent(r, out var parent) => SelectRootResource(parent) ?? parent,
_ => null
};

// translate the result to child -> root, which the dashboard expects
return result.Select(x => (x.Child, Root: SelectRootResource(x.Child)));
return result!;
}

private static void ValidateRelationships((IResource Child, IResource Parent)[] relationships)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public async Task WithParentRelationshipSetsParentPropertyCorrectly()
await appOrchestrator.RunApplicationAsync();

string? parentResourceId = null;
string? childResourceId = null;
string? childParentResourceId = null;
string? child2ParentResourceId = null;
string? nestedChildParentResourceId = null;
Expand All @@ -96,6 +97,7 @@ public async Task WithParentRelationshipSetsParentPropertyCorrectly()
}
else if (item.Resource == child.Resource)
{
childResourceId = item.ResourceId;
childParentResourceId = item.Snapshot.Properties.SingleOrDefault(p => p.Name == KnownProperties.Resource.ParentName)?.Value?.ToString();
}
else if (item.Resource == nestedChild.Resource)
Expand All @@ -121,8 +123,8 @@ public async Task WithParentRelationshipSetsParentPropertyCorrectly()
Assert.Equal(parentResourceId, childParentResourceId);
Assert.Equal(parentResourceId, child2ParentResourceId);

// Nested child should have parent set to the root parent, not direct parent
Assert.Equal(parentResourceId, nestedChildParentResourceId);
// Nested child should be parented on the direct parent
Assert.Equal(childResourceId, nestedChildParentResourceId);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.Orchestrator;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Aspire.Hosting.Tests.Orchestrator;

public class RelationshipEvaluatorTests
{
[Fact]
public void HandlesNestedChildren()
{
var builder = DistributedApplication.CreateBuilder();

var parentResource = builder.AddContainer("parent", "image");
var childResource = builder.AddResource(new CustomChildResource("child", parentResource.Resource));
var grandChildResource = builder.AddResource(new CustomChildResource("grandchild", childResource.Resource));
var greatGrandChildResource = builder.AddResource(new CustomChildResource("greatgrandchild", grandChildResource.Resource));

var childWithAnnotationsResource = builder.AddContainer("child-with-annotations", "image")
.WithParentRelationship(parentResource.Resource);

var grandChildWithAnnotationsResource = builder.AddContainer("grandchild-with-annotations", "image")
.WithParentRelationship(childWithAnnotationsResource.Resource);

using var app = builder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var parentChildLookup = RelationshipEvaluator.GetParentChildLookup(appModel);
Assert.Equal(4, parentChildLookup.Count);

Assert.Collection(parentChildLookup[parentResource.Resource],
x => Assert.Equal(childResource.Resource, x),
x => Assert.Equal(childWithAnnotationsResource.Resource, x));

Assert.Single(parentChildLookup[childResource.Resource], grandChildResource.Resource);
Assert.Single(parentChildLookup[grandChildResource.Resource], greatGrandChildResource.Resource);

Assert.Empty(parentChildLookup[greatGrandChildResource.Resource]);

Assert.Single(parentChildLookup[childWithAnnotationsResource.Resource], grandChildWithAnnotationsResource.Resource);

Assert.Empty(parentChildLookup[grandChildWithAnnotationsResource.Resource]);
}

private sealed class CustomChildResource(string name, IResource parent) : Resource(name), IResourceWithParent
{
public IResource Parent => parent;
}
}