Skip to content

Commit

Permalink
Comments and clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Sep 19, 2024
1 parent 5cc7e52 commit 677f88b
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 29 deletions.
33 changes: 22 additions & 11 deletions src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2006,31 +2006,42 @@ internal async Task StartResourceAsync(string resourceName, CancellationToken ca

async Task StartExecutableOrContainerAsync<T>(T resource) where T : CustomResource
{
var resourceNotFound = false;
try
{
// Note that DeleteAsync returns before the resource is completely deleted.
await kubernetesService.DeleteAsync<T>(resource.Metadata.Name, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// No-op if the resource wasn't found.
// This could happen in a race condition, e.g. double clicking start button.
resourceNotFound = true;
}

// Ensure resource is deleted.
// The resource must be properly deleted before it is started again because they share the same name. Poll to check.
for (var i = 0; i < 5; i++)
// Ensure resource is deleted. DeleteAsync returns before the resource is completely deleted so we must poll
// to discover when it is safe to recreate the resource. This is required because the resources share the same name.
if (!resourceNotFound)
{
// Pause to give DCP a chance to finish deleting the resource.
await Task.Delay(100 * (i + 1), cancellationToken).ConfigureAwait(false);

try
// Limit polling to 5 attempts to avoid hanging with an infinite loop.
for (var i = 0; i < 5; i++)
{
await kubernetesService.GetAsync<T>(resource.Metadata.Name, cancellationToken: cancellationToken).ConfigureAwait(false);
// Pause to give DCP a chance to finish deleting the resource.
await Task.Delay(100 * (i + 1), cancellationToken).ConfigureAwait(false);

try
{
await kubernetesService.GetAsync<T>(resource.Metadata.Name, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
resourceNotFound = true;
break;
}
}
catch (HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)

if (!resourceNotFound)
{
break;
throw new InvalidOperationException($"Failed to successfully delete '{resource.Metadata.Name}' before restart.");
}
}

Expand Down
7 changes: 1 addition & 6 deletions src/Aspire.Hosting/Dcp/Model/ExecutableReplicaSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ internal sealed class ExecutableReplicaSetSpec
[JsonPropertyName("replicas")]
public int Replicas { get; set; } = 1;

// Should the replica be soft deleted on scale down instead of deleted?
[JsonPropertyName("stopOnScaleDown")]
public bool? StopOnScaleDown { get; set; }

// Template describing the configuration of child Executable objects created by the replica set
[JsonPropertyName("template")]
public ExecutableTemplate Template { get; set; } = new ExecutableTemplate();
Expand Down Expand Up @@ -104,8 +100,7 @@ public static ExecutableReplicaSet Create(string name, int replicas, string exec

var ers = new ExecutableReplicaSet(new ExecutableReplicaSetSpec
{
Replicas = replicas,
StopOnScaleDown = true
Replicas = replicas
});
ers.Kind = Dcp.ExecutableReplicaSetKind;
ers.ApiVersion = Dcp.GroupVersion.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ public async Task PublishAsync(HealthReport report, CancellationToken cancellati
// Make sure every annotation is represented as health in the report, and if an entry is missing that means it is unhealthy.
var status = annotations.All(a => report.Entries.TryGetValue(a.Key, out var entry) && entry.Status == HealthStatus.Healthy) ? HealthStatus.Healthy : HealthStatus.Unhealthy;

_ = resourceNotificationService;
await Task.Yield();
//await resourceNotificationService.PublishUpdateAsync(resource, s => s with
//{
// HealthStatus = status
//}).ConfigureAwait(false);
await resourceNotificationService.PublishUpdateAsync(resource, s => s with
{
HealthStatus = status
}).ConfigureAwait(false);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Aspire.Hosting.DistributedApplicationExecutionContextOptions.ServiceProvider.get
Aspire.Hosting.DistributedApplicationExecutionContextOptions.ServiceProvider.set -> void
static Aspire.Hosting.ResourceBuilderExtensions.WaitFor<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T>! builder, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.IResource!>! dependency) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T>!
static Aspire.Hosting.ResourceBuilderExtensions.WaitForCompletion<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T>! builder, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.IResource!>! dependency, int exitCode = 0) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T>!
static Aspire.Hosting.ResourceBuilderExtensions.WithCommand<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T>! builder, string! type, string! displayName, System.Func<Aspire.Hosting.ApplicationModel.UpdateCommandStateContext!, Aspire.Hosting.ApplicationModel.ResourceCommandState>! updateState, System.Func<Aspire.Hosting.ApplicationModel.ExecuteCommandContext!, System.Threading.Tasks.Task<Aspire.Hosting.ApplicationModel.ExecuteCommandResult!>!>! executeCommand, string? iconName = null, bool isHighlighted = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T>!
static Aspire.Hosting.ResourceBuilderExtensions.WithHealthCheck<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T>! builder, string! key) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T>!
static readonly Aspire.Hosting.ApplicationModel.KnownResourceStates.Exited -> string!
static readonly Aspire.Hosting.ApplicationModel.KnownResourceStates.FailedToStart -> string!
Expand Down
31 changes: 25 additions & 6 deletions src/Aspire.Hosting/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -771,16 +771,37 @@ public static IResourceBuilder<T> WithHealthCheck<T>(this IResourceBuilder<T> bu
return builder;
}

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable RS0016 // Add public types and members to the declared API
/// <summary>
/// Adds a <see cref="ResourceCommandAnnotation"/> to the resource annotations to add a resource command.
/// </summary>
/// <typeparam name="T">The type of the resource.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="type">The type of command. The type uniquely identifies the command.</param>
/// <param name="displayName">The display name visible in UI.</param>
/// <param name="updateState">
/// A callback that is used to update the command state.
/// The callback is executed when the command's resource snapshot is updated.
/// </param>
/// <param name="executeCommand">
/// A callback that is executed when the command is executed. The callback is run inside the .NET Aspire host.
/// The callback result is used to indicate success or failure in the UI.
/// </param>
/// <param name="iconName">The icon name for the command. The name should be a valid FluentUI icon name. https://aka.ms/fluentui-system-icons</param>
/// <param name="isHighlighted">A flag indicating whether the command is highlighted in the UI.</param>
/// <returns>The resource builder.</returns>
/// <remarks>
/// <para>The <c>WithCommand</c> method is used to add commands to the resource. Commands are displayed in the dashboard
/// and can be executed by a user using the dashboard UI.</para>
/// <para>When a command is executed, the <paramref name="executeCommand"/> callback is called and is run inside the .NET Aspire host.</para>
/// </remarks>
public static IResourceBuilder<T> WithCommand<T>(
this IResourceBuilder<T> builder,
string type,
string displayName,
Func<UpdateCommandStateContext, ResourceCommandState> updateState,
Func<ExecuteCommandContext, Task<ExecuteCommandResult>> executeCommand,
string? iconName,
bool isHighlighted) where T : IResource
string? iconName = null,
bool isHighlighted = false) where T : IResource
{
// Replace existing annotation with the same name.
var existingAnnotation = builder.Resource.Annotations.OfType<ResourceCommandAnnotation>().SingleOrDefault(a => a.Type == type);
Expand All @@ -791,6 +812,4 @@ public static IResourceBuilder<T> WithCommand<T>(

return builder.WithAnnotation(new ResourceCommandAnnotation(type, displayName, updateState, executeCommand, iconName, isHighlighted));
}
#pragma warning restore RS0016 // Add public types and members to the declared API
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}

0 comments on commit 677f88b

Please sign in to comment.