Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ResourceLoggerService and ResourceNotification service for DCP based resource changes #2731

Merged
merged 2 commits into from
Mar 10, 2024
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: 5 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ private void UpdateResourcesList()
var builder = ImmutableList.CreateBuilder<SelectViewModel<ResourceTypeDetails>>();
builder.Add(_noSelection);

foreach (var resourceGroupsByApplicationName in _resourceByName.Values.OrderBy(c => c.Name).GroupBy(resource => resource.DisplayName))
foreach (var resourceGroupsByApplicationName in _resourceByName
.Where(r => r.Value.State != ResourceStates.HiddenState)
.OrderBy(c => c.Value.Name)
.GroupBy(r => r.Value.DisplayName, r => r.Value))
{
if (resourceGroupsByApplicationName.Count() > 1)
{
Expand Down Expand Up @@ -203,7 +206,7 @@ SelectViewModel<ResourceTypeDetails> ToOption(ResourceViewModel resource, bool i

string GetDisplayText()
{
var resourceName = ResourceViewModel.GetResourceName(resource, _resourceByName.Values);
var resourceName = ResourceViewModel.GetResourceName(resource, _resourceByName);

return resource.State switch
{
Expand Down
13 changes: 9 additions & 4 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public Resources()
_visibleResourceTypes = new(StringComparers.ResourceType);
}

private bool Filter(ResourceViewModel resource) => _visibleResourceTypes.ContainsKey(resource.ResourceType) && (_filter.Length == 0 || resource.MatchesFilter(_filter));
private bool Filter(ResourceViewModel resource) => _visibleResourceTypes.ContainsKey(resource.ResourceType) && (_filter.Length == 0 || resource.MatchesFilter(_filter)) && resource.State != ResourceStates.HiddenState;

protected void OnResourceTypeVisibilityChanged(string resourceType, bool isVisible)
{
Expand Down Expand Up @@ -101,7 +101,7 @@ static bool UnionWithKeys(ConcurrentDictionary<string, bool> left, ConcurrentDic
}
}

private bool HasResourcesWithCommands => _resourceByName.Values.Any(r => r.Commands.Any());
private bool HasResourcesWithCommands => _resourceByName.Any(r => r.Value.Commands.Any());

private IQueryable<ResourceViewModel>? FilteredResources => _resourceByName.Values.Where(Filter).OrderBy(e => e.ResourceType).ThenBy(e => e.Name).AsQueryable();

Expand Down Expand Up @@ -207,13 +207,18 @@ private void ClearSelectedResource()
SelectedResource = null;
}

private string GetResourceName(ResourceViewModel resource) => ResourceViewModel.GetResourceName(resource, _resourceByName.Values);
private string GetResourceName(ResourceViewModel resource) => ResourceViewModel.GetResourceName(resource, _resourceByName);

private bool HasMultipleReplicas(ResourceViewModel resource)
{
var count = 0;
foreach (var item in _resourceByName.Values)
foreach (var (_, item) in _resourceByName)
{
if (item.State == ResourceStates.HiddenState)
{
continue;
}

if (item.DisplayName == resource.DisplayName)
{
count++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@
<FluentIcon Title="@string.Format(Loc[Columns.StateColumnResourceExitedUnexpectedly], Resource.ResourceType, exitCode)"
Icon="Icons.Filled.Size16.ErrorCircle"
Color="Color.Error"
Class="severity-icon"/>
Class="severity-icon" />
}
else
{
<!-- process completed, which may not have been unexpected -->
<FluentIcon Title="@string.Format(Loc[Columns.StateColumnResourceExited], Resource.ResourceType)"
Icon="Icons.Filled.Size16.Warning"
Color="Color.Warning"
Class="severity-icon"/>
Class="severity-icon" />
}
}
else if (Resource is { State: ResourceStates.StartingState })
{
<FluentIcon Icon="Icons.Filled.Size16.Circle"
Color="Color.Info"
Class="severity-icon"/>
Class="severity-icon" />
}
else if (Resource is { State: /* unknown */ null })
else if (Resource is { State: /* unknown */ null or { Length: 0 } })
{
<FluentIcon Icon="Icons.Filled.Size16.Circle"
Color="Color.Neutral"
Expand All @@ -40,10 +40,17 @@ else
{
<FluentIcon Icon="Icons.Filled.Size16.CheckmarkCircle"
Color="Color.Success"
Class="severity-icon"/>
Class="severity-icon" />
}

@(Resource.State?.Humanize() ?? "Unknown")
@if (string.IsNullOrEmpty(Resource.State))
{
<span>Unknown</span>
}
else
{
<span>@Resource.State.Humanize()</span>
}

<UnreadLogErrorsBadge UnviewedErrorCounts="UnviewedErrorCounts" Resource="@Resource" />

Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Model/ResourceEndpointHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ internal static class ResourceEndpointHelpers
/// </summary>
public static List<DisplayedEndpoint> GetEndpoints(ILogger logger, ResourceViewModel resource, bool excludeServices = false, bool includeEndpointUrl = false)
{
var isKnownResourceType = resource.IsContainer() || resource.IsExecutable(allowSubtypes: false) || resource.IsProject();

var displayedEndpoints = new List<DisplayedEndpoint>();

var isKnownResourceType = resource.IsContainer() || resource.IsExecutable(allowSubtypes: false) || resource.IsProject();

if (isKnownResourceType)
{
if (!excludeServices)
Expand Down
11 changes: 9 additions & 2 deletions src/Aspire.Dashboard/Model/ResourceViewModel.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.Collections.Frozen;
using System.Collections.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -30,11 +31,16 @@ internal bool MatchesFilter(string filter)
return Name.Contains(filter, StringComparisons.UserTextSearch);
}

public static string GetResourceName(ResourceViewModel resource, IEnumerable<ResourceViewModel> allResources)
public static string GetResourceName(ResourceViewModel resource, ConcurrentDictionary<string, ResourceViewModel> allResources)
{
var count = 0;
foreach (var item in allResources)
foreach (var (_, item) in allResources)
{
if (item.State == ResourceStates.HiddenState)
{
continue;
}

if (item.DisplayName == resource.DisplayName)
{
count++;
Expand Down Expand Up @@ -127,4 +133,5 @@ public static class ResourceStates
public const string FailedToStartState = "FailedToStart";
public const string StartingState = "Starting";
public const string RunningState = "Running";
public const string HiddenState = "Hidden";
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ public override async Task<bool> ConfigureResourceAsync(IConfiguration configura

await notificationService.PublishUpdateAsync(resource, state =>
{
ImmutableArray<(string, string)> props = [
ImmutableArray<(string, object?)> props = [
.. state.Properties,
("azure.subscription.id", configuration["Azure:SubscriptionId"] ?? ""),
("azure.subscription.id", configuration["Azure:SubscriptionId"]),
// ("azure.resource.group", configuration["Azure:ResourceGroup"]!),
("azure.tenant.domain", configuration["Azure:Tenant"] ?? ""),
("azure.location", configuration["Azure:Location"] ?? ""),
(CustomResourceKnownProperties.Source, section["Id"] ?? "")
("azure.tenant.domain", configuration["Azure:Tenant"]),
("azure.location", configuration["Azure:Location"]),
(CustomResourceKnownProperties.Source, section["Id"])
];

return state with
Expand Down Expand Up @@ -308,7 +308,7 @@ await notificationService.PublishUpdateAsync(resource, state =>

await notificationService.PublishUpdateAsync(resource, state =>
{
ImmutableArray<(string, string)> properties = [
ImmutableArray<(string, object?)> properties = [
.. state.Properties,
(CustomResourceKnownProperties.Source, deployment.Id.Name)
];
Expand Down
19 changes: 17 additions & 2 deletions src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public sealed record CustomResourceSnapshot
/// <summary>
/// The properties that should show up in the dashboard for this resource.
/// </summary>
public required ImmutableArray<(string Key, string Value)> Properties { get; init; }
public required ImmutableArray<(string Key, object? Value)> Properties { get; init; }

/// <summary>
/// The creation timestamp of the resource.
Expand All @@ -30,13 +30,28 @@ public sealed record CustomResourceSnapshot
/// </summary>
public string? State { get; init; }

/// <summary>
/// The exit code of the resource.
/// </summary>
public int? ExitCode { get; init; }

/// <summary>
/// The environment variables that should show up in the dashboard for this resource.
/// </summary>
public ImmutableArray<(string Name, string Value)> EnvironmentVariables { get; init; } = [];
public ImmutableArray<(string Name, string Value, bool IsFromSpec)> EnvironmentVariables { get; init; } = [];

/// <summary>
/// The URLs that should show up in the dashboard for this resource.
/// </summary>
public ImmutableArray<(string Name, string Url)> Urls { get; init; } = [];

/// <summary>
/// The services that should show up in the dashboard for this resource.
/// </summary>
public ImmutableArray<(string Name, string? AllocatedAddress, int? AllocatedPort)> Services { get; init; } = [];

/// <summary>
/// The endpoints that should show up in the dashboard for this resource.
/// </summary>
public ImmutableArray<(string EndpointUrl, string ProxyUrl)> Endpoints { get; init; } = [];
}
5 changes: 5 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public string Transport
/// <remarks>Defaults to <c>true</c>.</remarks>
public bool IsProxied { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether the endpoint is from a launch profile.
/// </summary>
internal bool FromLaunchProfile { get; set; }

/// <summary>
/// Gets or sets the allocated endpoint.
/// </summary>
Expand Down
42 changes: 32 additions & 10 deletions src/Aspire.Hosting/ApplicationModel/ResourceLoggerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public class ResourceLoggerService
public ILogger GetLogger(IResource resource) =>
GetResourceLoggerState(resource.Name).Logger;

/// <summary>
/// Gets the logger for the resource to write to.
/// </summary>
/// <param name="resourceName"></param>
/// <returns></returns>
public ILogger GetLogger(string resourceName) =>
GetResourceLoggerState(resourceName).Logger;

/// <summary>
/// Watch for changes to the log stream for a resource.
/// </summary>
Expand Down Expand Up @@ -50,6 +58,19 @@ public void Complete(IResource resource)
logger.Complete();
}
}

/// <summary>
/// Completes the log stream for the resource.
/// </summary>
/// <param name="name">The name of the resource.</param>
public void Complete(string name)
{
if (_loggers.TryGetValue(name, out var logger))
{
logger.Complete();
}
}

private ResourceLoggerState GetResourceLoggerState(string resourceName) =>
_loggers.GetOrAdd(resourceName, _ => new ResourceLoggerState());

Expand All @@ -76,7 +97,14 @@ public ResourceLoggerState()
/// Watch for changes to the log stream for a resource.
/// </summary>
/// <returns> The log stream for the resource. </returns>
public IAsyncEnumerable<IReadOnlyList<(string Content, bool IsErrorMessage)>> WatchAsync() => new LogAsyncEnumerable(this);
public IAsyncEnumerable<IReadOnlyList<(string Content, bool IsErrorMessage)>> WatchAsync()
{
lock (_backlog)
{
// REVIEW: Performance makes me very sad, but we can optimize this later.
return new LogAsyncEnumerable(this, _backlog.ToList());
}
}

// This provides the fan out to multiple subscribers.
private Action<(string, bool)>? OnNewLog { get; set; }
Expand Down Expand Up @@ -123,19 +151,13 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
}
}

private sealed class LogAsyncEnumerable(ResourceLoggerState annotation) : IAsyncEnumerable<IReadOnlyList<(string, bool)>>
private sealed class LogAsyncEnumerable(ResourceLoggerState annotation, List<(string, bool)> backlogSnapshot) : IAsyncEnumerable<IReadOnlyList<(string, bool)>>
{
public async IAsyncEnumerator<IReadOnlyList<(string, bool)>> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
// Yield the backlog first.

lock (annotation._backlog)
if (backlogSnapshot.Count > 0)
{
if (annotation._backlog.Count > 0)
{
// REVIEW: Performance makes me very sad, but we can optimize this later.
yield return annotation._backlog.ToList();
}
yield return backlogSnapshot;
}

var channel = Channel.CreateUnbounded<(string, bool)>();
Expand Down
Loading