feat: add system request-reply monitoring services ($SYS.REQ.SERVER.*)
Register VARZ, HEALTHZ, SUBSZ, STATSZ, and IDZ request-reply handlers
on $SYS.REQ.SERVER.{id}.* subjects and $SYS.REQ.SERVER.PING.* wildcard
subjects via InitEventTracking. Also excludes the $SYS system account
from the /subz monitoring endpoint by default since its subscriptions
are internal infrastructure.
This commit is contained in:
170
tests/NATS.Server.Tests/SystemRequestReplyTests.cs
Normal file
170
tests/NATS.Server.Tests/SystemRequestReplyTests.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using NATS.Server;
|
||||
using NATS.Server.Events;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
|
||||
public class SystemRequestReplyTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Varz_request_reply_returns_server_info()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
var received = new TaskCompletionSource<byte[]>();
|
||||
var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
|
||||
server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
|
||||
{
|
||||
received.TrySetResult(msg.ToArray());
|
||||
});
|
||||
|
||||
var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "VARZ");
|
||||
server.SendInternalMsg(reqSubject, replySubject, null);
|
||||
|
||||
var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var json = Encoding.UTF8.GetString(result);
|
||||
json.ShouldContain("\"server_id\"");
|
||||
json.ShouldContain("\"version\"");
|
||||
json.ShouldContain("\"host\"");
|
||||
json.ShouldContain("\"port\"");
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Healthz_request_reply_returns_ok()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
var received = new TaskCompletionSource<byte[]>();
|
||||
var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
|
||||
server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
|
||||
{
|
||||
received.TrySetResult(msg.ToArray());
|
||||
});
|
||||
|
||||
var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "HEALTHZ");
|
||||
server.SendInternalMsg(reqSubject, replySubject, null);
|
||||
|
||||
var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var json = Encoding.UTF8.GetString(result);
|
||||
json.ShouldContain("ok");
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Subsz_request_reply_returns_subscription_count()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
var received = new TaskCompletionSource<byte[]>();
|
||||
var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
|
||||
server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
|
||||
{
|
||||
received.TrySetResult(msg.ToArray());
|
||||
});
|
||||
|
||||
var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "SUBSZ");
|
||||
server.SendInternalMsg(reqSubject, replySubject, null);
|
||||
|
||||
var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var json = Encoding.UTF8.GetString(result);
|
||||
json.ShouldContain("\"num_subscriptions\"");
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Idz_request_reply_returns_server_identity()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
var received = new TaskCompletionSource<byte[]>();
|
||||
var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
|
||||
server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
|
||||
{
|
||||
received.TrySetResult(msg.ToArray());
|
||||
});
|
||||
|
||||
var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "IDZ");
|
||||
server.SendInternalMsg(reqSubject, replySubject, null);
|
||||
|
||||
var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var json = Encoding.UTF8.GetString(result);
|
||||
json.ShouldContain("\"server_id\"");
|
||||
json.ShouldContain("\"server_name\"");
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ping_varz_responds_via_wildcard_subject()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
var received = new TaskCompletionSource<byte[]>();
|
||||
var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
|
||||
server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
|
||||
{
|
||||
received.TrySetResult(msg.ToArray());
|
||||
});
|
||||
|
||||
var pingSubject = string.Format(EventSubjects.ServerPing, "VARZ");
|
||||
server.SendInternalMsg(pingSubject, replySubject, null);
|
||||
|
||||
var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var json = Encoding.UTF8.GetString(result);
|
||||
json.ShouldContain("\"server_id\"");
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Request_without_reply_is_ignored()
|
||||
{
|
||||
using var server = CreateTestServer();
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
|
||||
// Send a request with no reply subject -- should not crash
|
||||
var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "VARZ");
|
||||
server.SendInternalMsg(reqSubject, null, null);
|
||||
|
||||
// Give it a moment to process without error
|
||||
await Task.Delay(200);
|
||||
|
||||
// Server should still be running
|
||||
server.IsShuttingDown.ShouldBeFalse();
|
||||
|
||||
await server.ShutdownAsync();
|
||||
}
|
||||
|
||||
private static NatsServer CreateTestServer()
|
||||
{
|
||||
var port = GetFreePort();
|
||||
return new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance);
|
||||
}
|
||||
|
||||
private static int GetFreePort()
|
||||
{
|
||||
using var sock = new System.Net.Sockets.Socket(
|
||||
System.Net.Sockets.AddressFamily.InterNetwork,
|
||||
System.Net.Sockets.SocketType.Stream,
|
||||
System.Net.Sockets.ProtocolType.Tcp);
|
||||
sock.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 0));
|
||||
return ((System.Net.IPEndPoint)sock.LocalEndPoint!).Port;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user