61c00d59a9
Readiness covered only the auth store, so a gateway whose worker exe was missing or of the wrong architecture reported ready while every OpenSession failed. The new ready-tier check worker-executable applies the launcher's own rule (path resolution, .exe, presence, PE architecture), now shared as WorkerExecutableValidator.ResolveAndValidate, and reports only the launch error code (the endpoint is anonymous). The session check stays on the active tier, so zero sessions keeps readiness green. The bounded Galaxy SQL check is not in this change: its tier, failure status and probe cadence need a decision (see the issue). Tests: WorkerExecutableHealthCheckTests (healthy, missing, wrong architecture, and the ready-tier registration, which fails without the wiring).
107 lines
4.4 KiB
C#
107 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.Health;
|
|
using ZB.MOM.WW.MxGateway.Server;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Tests.Diagnostics;
|
|
|
|
/// <summary>Readiness coverage for the worker executable (#206).</summary>
|
|
public sealed class WorkerExecutableHealthCheckTests : IDisposable
|
|
{
|
|
private readonly string _directory = Path.Combine(Path.GetTempPath(), $"mxgw-workercheck-{Guid.NewGuid():N}");
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="WorkerExecutableHealthCheckTests"/> class.</summary>
|
|
public WorkerExecutableHealthCheckTests() => Directory.CreateDirectory(_directory);
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose() => Directory.Delete(_directory, recursive: true);
|
|
|
|
/// <summary>A present x86 worker image is healthy.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Healthy_WhenWorkerExecutableIsLaunchable()
|
|
{
|
|
string path = WritePeStub(machine: 0x014c);
|
|
|
|
HealthCheckResult result = await CheckAsync(path, WorkerArchitecture.X86);
|
|
|
|
Assert.Equal(HealthStatus.Healthy, result.Status);
|
|
}
|
|
|
|
/// <summary>A missing worker executable is unhealthy and reports only the error code, never the path.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Unhealthy_WhenWorkerExecutableIsMissing()
|
|
{
|
|
string path = Path.Combine(_directory, "missing", "ZB.MOM.WW.MxGateway.Worker.exe");
|
|
|
|
HealthCheckResult result = await CheckAsync(path, WorkerArchitecture.X86);
|
|
|
|
Assert.Equal(HealthStatus.Unhealthy, result.Status);
|
|
Assert.Equal("ExecutableNotFound", result.Data["error_code"]);
|
|
Assert.DoesNotContain(_directory, result.Description, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
/// <summary>A worker image of the wrong architecture is unhealthy, as the launcher would reject it.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Unhealthy_WhenWorkerExecutableHasWrongArchitecture()
|
|
{
|
|
string path = WritePeStub(machine: 0x8664);
|
|
|
|
HealthCheckResult result = await CheckAsync(path, WorkerArchitecture.X86);
|
|
|
|
Assert.Equal(HealthStatus.Unhealthy, result.Status);
|
|
Assert.Equal("InvalidExecutable", result.Data["error_code"]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The gateway registers the check on the ready tier, and the session check stays on the active
|
|
/// tier so zero sessions keeps readiness green.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task Build_RegistersWorkerExecutableCheckOnReadyTier()
|
|
{
|
|
await using WebApplication app = GatewayApplication.Build([]);
|
|
HealthCheckServiceOptions options =
|
|
app.Services.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
|
|
|
HealthCheckRegistration worker = Assert.Single(options.Registrations, r => r.Name == "worker-executable");
|
|
Assert.Contains(ZbHealthTags.Ready, worker.Tags);
|
|
HealthCheckRegistration sessions = Assert.Single(options.Registrations, r => r.Name == "mxaccess-sessions");
|
|
Assert.DoesNotContain(ZbHealthTags.Ready, sessions.Tags);
|
|
}
|
|
|
|
private static Task<HealthCheckResult> CheckAsync(string path, WorkerArchitecture architecture)
|
|
{
|
|
WorkerExecutableHealthCheck check = new(Options.Create(new GatewayOptions
|
|
{
|
|
Worker = new WorkerOptions
|
|
{
|
|
ExecutablePath = path,
|
|
RequiredArchitecture = architecture,
|
|
},
|
|
}));
|
|
return check.CheckHealthAsync(new HealthCheckContext());
|
|
}
|
|
|
|
private string WritePeStub(ushort machine)
|
|
{
|
|
string path = Path.Combine(_directory, "ZB.MOM.WW.MxGateway.Worker.exe");
|
|
byte[] bytes = new byte[0x100];
|
|
bytes[0] = (byte)'M';
|
|
bytes[1] = (byte)'Z';
|
|
BitConverter.GetBytes(0x80).CopyTo(bytes, 0x3c);
|
|
bytes[0x80] = (byte)'P';
|
|
bytes[0x81] = (byte)'E';
|
|
BitConverter.GetBytes(machine).CopyTo(bytes, 0x84);
|
|
File.WriteAllBytes(path, bytes);
|
|
return path;
|
|
}
|
|
}
|