Skip to content

Commit 5ec0504

Browse files
committed
Retain bounded node diagnostics for RF3 failures
Save the last 120 log lines per node on startup or leader-loss failure. Run RF3 verification before the longer crash suite so platform failures surface earlier while keeping every required gate. Validation: Release integration build and both local RF3 tests passed.
1 parent 75d4c5a commit 5ec0504

3 files changed

Lines changed: 36 additions & 2 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ jobs:
2525
- run: dotnet restore KeyLoad.slnx --locked-mode
2626
- run: dotnet build KeyLoad.slnx --no-restore --configuration Release
2727
- run: dotnet test --project tests/KeyLoad.UnitTests --no-build --no-restore --configuration Release
28-
- run: dotnet test --project tests/KeyLoad.RecoveryTests --no-build --no-restore --configuration Release
2928
- run: dotnet test --project tests/KeyLoad.IntegrationTests --no-build --no-restore --configuration Release
29+
- run: dotnet test --project tests/KeyLoad.RecoveryTests --no-build --no-restore --configuration Release
3030
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
3131
if: always()
3232
with:

‎tests/KeyLoad.IntegrationTests/ClusterFixture.cs‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Aspire.Hosting;
2+
using Aspire.Hosting.ApplicationModel;
23
using Aspire.Hosting.Testing;
34
using KeyLoad.Client;
45
using Microsoft.Extensions.DependencyInjection;
@@ -25,13 +26,42 @@ public async ValueTask InitializeAsync()
2526
await App.StartAsync(timeout.Token);
2627
using var profile = System.Text.Json.JsonDocument.Parse(File.ReadAllBytes(Path.Combine(Root, "local-profile.json")));
2728
AdminKey = profile.RootElement.GetProperty("AdminKey").GetString()!;
28-
await Task.WhenAll(Enumerable.Range(1, 3).Select(i => App.ResourceNotifications.WaitForResourceHealthyAsync($"node{i}", timeout.Token)));
29+
try { await Task.WhenAll(Enumerable.Range(1, 3).Select(i => App.ResourceNotifications.WaitForResourceHealthyAsync($"node{i}", timeout.Token))); }
30+
catch { await SaveFailureDiagnosticsAsync(); throw; }
2931
}
3032
public KeyLoadClient Client(string node, string? key = null)
3133
{
3234
var http = App.CreateHttpClient(node, "http"); http.Timeout = TimeSpan.FromSeconds(30);
3335
return new(http, key ?? AdminKey);
3436
}
37+
public async Task SaveFailureDiagnosticsAsync()
38+
{
39+
var repository = new DirectoryInfo(AppContext.BaseDirectory);
40+
while (repository.Parent is not null && !File.Exists(Path.Combine(repository.FullName, "KeyLoad.slnx"))) repository = repository.Parent;
41+
var output = Path.Combine(repository.FullName, "artifacts", "qualification"); Directory.CreateDirectory(output);
42+
var logs = App.Services.GetRequiredService<ResourceLoggerService>();
43+
foreach (var number in Enumerable.Range(1, 3))
44+
{
45+
var name = $"node{number}";
46+
try
47+
{
48+
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(5));
49+
var lines = new Queue<string>();
50+
await foreach (var batch in logs.GetAllAsync(name).WithCancellation(deadline.Token))
51+
{
52+
foreach (var line in batch)
53+
{
54+
lines.Enqueue(line.Content);
55+
if (lines.Count > 120) lines.Dequeue();
56+
}
57+
}
58+
var tail = lines.ToArray();
59+
File.WriteAllLines(Path.Combine(output, "rf3-failure-" + name + ".log"), tail);
60+
foreach (var line in tail) TestContext.Current.TestOutputHelper?.WriteLine($"{name}: {line}");
61+
}
62+
catch (OperationCanceledException) { TestContext.Current.TestOutputHelper?.WriteLine($"{name}: log retrieval timed out."); }
63+
}
64+
}
3565
public async ValueTask DisposeAsync()
3666
{
3767
if (App is not null) { await App.StopAsync(); await App.DisposeAsync(); }

‎tests/KeyLoad.IntegrationTests/ClusterTests.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public sealed class ClusterTests(ClusterFixture fixture)
1717
public async Task ReplicatedAtomicBatchSurvivesLeaderProcessKillAndMinorityRejectsWrites()
1818
{
1919
using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2));
20+
try
21+
{
2022
var clients = Enumerable.Range(1, 3).Select(i => fixture.Client($"node{i}")).ToArray();
2123
var partition = new PartitionRef("integration", "database", "orders", Guid.NewGuid().ToString("N"));
2224
Success(await clients[0].ConfigureResourceAsync(Guid.NewGuid(), new(partition.TenantId, partition.DatabaseId,
@@ -61,6 +63,8 @@ public async Task ReplicatedAtomicBatchSurvivesLeaderProcessKillAndMinorityRejec
6163
await EventuallyAsync(async () => (await surviving.StatusAsync(timeout.Token)).IsSuccess, timeout.Token);
6264
Assert.Null(Success(await surviving.GetAsync(new(partition, "orders", "minority"), timeout.Token)));
6365
foreach (var index in Enumerable.Range(0, 3)) await fixture.App.ResourceNotifications.WaitForResourceHealthyAsync($"node{index + 1}", timeout.Token);
66+
}
67+
catch { await fixture.SaveFailureDiagnosticsAsync(); throw; }
6468
}
6569
[Fact]
6670
public async Task UnsignedPeerRequestsAndClientSuppliedPrincipalAreRejected()

0 commit comments

Comments
 (0)