feat: complete final jetstream parity transport and runtime baselines

This commit is contained in:
Joseph Doherty
2026-02-23 11:04:43 -05:00
parent 53585012f3
commit 8bce096f55
61 changed files with 2655 additions and 129 deletions
@@ -0,0 +1,34 @@
namespace NATS.Server.JetStream.Api.Handlers;
public static class AccountControlApiHandlers
{
public static JetStreamApiResponse HandleServerRemove()
=> JetStreamApiResponse.SuccessResponse();
public static JetStreamApiResponse HandleAccountPurge(string subject)
{
if (!subject.StartsWith(JetStreamApiSubjects.AccountPurge, StringComparison.Ordinal))
return JetStreamApiResponse.NotFound(subject);
var account = subject[JetStreamApiSubjects.AccountPurge.Length..].Trim();
return account.Length == 0 ? JetStreamApiResponse.NotFound(subject) : JetStreamApiResponse.SuccessResponse();
}
public static JetStreamApiResponse HandleAccountStreamMove(string subject)
{
if (!subject.StartsWith(JetStreamApiSubjects.AccountStreamMove, StringComparison.Ordinal))
return JetStreamApiResponse.NotFound(subject);
var account = subject[JetStreamApiSubjects.AccountStreamMove.Length..].Trim();
return account.Length == 0 ? JetStreamApiResponse.NotFound(subject) : JetStreamApiResponse.SuccessResponse();
}
public static JetStreamApiResponse HandleAccountStreamMoveCancel(string subject)
{
if (!subject.StartsWith(JetStreamApiSubjects.AccountStreamMoveCancel, StringComparison.Ordinal))
return JetStreamApiResponse.NotFound(subject);
var account = subject[JetStreamApiSubjects.AccountStreamMoveCancel.Length..].Trim();
return account.Length == 0 ? JetStreamApiResponse.NotFound(subject) : JetStreamApiResponse.SuccessResponse();
}
}
@@ -20,4 +20,23 @@ public static class ClusterControlApiHandlers
streams.StepDownStreamLeaderAsync(stream, default).GetAwaiter().GetResult();
return JetStreamApiResponse.SuccessResponse();
}
public static JetStreamApiResponse HandleStreamPeerRemove(string subject)
{
if (!subject.StartsWith(JetStreamApiSubjects.StreamPeerRemove, StringComparison.Ordinal))
return JetStreamApiResponse.NotFound(subject);
var stream = subject[JetStreamApiSubjects.StreamPeerRemove.Length..].Trim();
return stream.Length == 0 ? JetStreamApiResponse.NotFound(subject) : JetStreamApiResponse.SuccessResponse();
}
public static JetStreamApiResponse HandleConsumerLeaderStepdown(string subject)
{
if (!subject.StartsWith(JetStreamApiSubjects.ConsumerLeaderStepdown, StringComparison.Ordinal))
return JetStreamApiResponse.NotFound(subject);
var remainder = subject[JetStreamApiSubjects.ConsumerLeaderStepdown.Length..].Trim();
var tokens = remainder.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return tokens.Length == 2 ? JetStreamApiResponse.SuccessResponse() : JetStreamApiResponse.NotFound(subject);
}
}
@@ -168,6 +168,19 @@ public static class ConsumerApiHandlers
if (root.TryGetProperty("filter_subject", out var filterEl))
config.FilterSubject = filterEl.GetString();
if (root.TryGetProperty("filter_subjects", out var filterSubjectsEl) && filterSubjectsEl.ValueKind == JsonValueKind.Array)
{
foreach (var item in filterSubjectsEl.EnumerateArray())
{
var filter = item.GetString();
if (!string.IsNullOrWhiteSpace(filter))
config.FilterSubjects.Add(filter);
}
}
if (root.TryGetProperty("ephemeral", out var ephemeralEl) && ephemeralEl.ValueKind == JsonValueKind.True)
config.Ephemeral = true;
if (root.TryGetProperty("push", out var pushEl) && pushEl.ValueKind == JsonValueKind.True)
config.Push = true;
@@ -177,6 +190,9 @@ public static class ConsumerApiHandlers
if (root.TryGetProperty("ack_wait_ms", out var ackWaitEl) && ackWaitEl.TryGetInt32(out var ackWait))
config.AckWaitMs = ackWait;
if (root.TryGetProperty("max_ack_pending", out var maxAckPendingEl) && maxAckPendingEl.TryGetInt32(out var maxAckPending))
config.MaxAckPending = Math.Max(maxAckPending, 0);
if (root.TryGetProperty("ack_policy", out var ackPolicyEl))
{
var ackPolicy = ackPolicyEl.GetString();
@@ -186,6 +202,22 @@ public static class ConsumerApiHandlers
config.AckPolicy = AckPolicy.All;
}
if (root.TryGetProperty("deliver_policy", out var deliverPolicyEl))
{
var deliver = deliverPolicyEl.GetString();
if (string.Equals(deliver, "last", StringComparison.OrdinalIgnoreCase))
config.DeliverPolicy = DeliverPolicy.Last;
else if (string.Equals(deliver, "new", StringComparison.OrdinalIgnoreCase))
config.DeliverPolicy = DeliverPolicy.New;
}
if (root.TryGetProperty("replay_policy", out var replayPolicyEl))
{
var replay = replayPolicyEl.GetString();
if (string.Equals(replay, "original", StringComparison.OrdinalIgnoreCase))
config.ReplayPolicy = ReplayPolicy.Original;
}
return config;
}
catch (JsonException)
@@ -211,6 +211,56 @@ public static class StreamApiHandlers
if (root.TryGetProperty("max_msgs", out var maxMsgsEl) && maxMsgsEl.TryGetInt32(out var maxMsgs))
config.MaxMsgs = maxMsgs;
if (root.TryGetProperty("max_bytes", out var maxBytesEl) && maxBytesEl.TryGetInt64(out var maxBytes))
config.MaxBytes = maxBytes;
if (root.TryGetProperty("max_msgs_per", out var maxMsgsPerEl) && maxMsgsPerEl.TryGetInt32(out var maxMsgsPer))
config.MaxMsgsPer = maxMsgsPer;
if (root.TryGetProperty("max_age_ms", out var maxAgeMsEl) && maxAgeMsEl.TryGetInt32(out var maxAgeMs))
config.MaxAgeMs = maxAgeMs;
if (root.TryGetProperty("discard", out var discardEl))
{
var discard = discardEl.GetString();
if (string.Equals(discard, "new", StringComparison.OrdinalIgnoreCase))
config.Discard = DiscardPolicy.New;
else if (string.Equals(discard, "old", StringComparison.OrdinalIgnoreCase))
config.Discard = DiscardPolicy.Old;
}
if (root.TryGetProperty("storage", out var storageEl))
{
var storage = storageEl.GetString();
if (string.Equals(storage, "file", StringComparison.OrdinalIgnoreCase))
config.Storage = StorageType.File;
else
config.Storage = StorageType.Memory;
}
if (root.TryGetProperty("source", out var sourceEl))
config.Source = sourceEl.GetString();
if (root.TryGetProperty("sources", out var sourcesEl) && sourcesEl.ValueKind == JsonValueKind.Array)
{
foreach (var source in sourcesEl.EnumerateArray())
{
if (source.ValueKind == JsonValueKind.String)
{
var name = source.GetString();
if (!string.IsNullOrWhiteSpace(name))
config.Sources.Add(new StreamSourceConfig { Name = name });
}
else if (source.ValueKind == JsonValueKind.Object &&
source.TryGetProperty("name", out var sourceNameEl))
{
var name = sourceNameEl.GetString();
if (!string.IsNullOrWhiteSpace(name))
config.Sources.Add(new StreamSourceConfig { Name = name });
}
}
}
if (root.TryGetProperty("replicas", out var replicasEl) && replicasEl.TryGetInt32(out var replicas))
config.Replicas = replicas;
@@ -25,6 +25,18 @@ public sealed class JetStreamApiRouter
if (subject.Equals(JetStreamApiSubjects.Info, StringComparison.Ordinal))
return AccountApiHandlers.HandleInfo(_streamManager, _consumerManager);
if (subject.Equals(JetStreamApiSubjects.ServerRemove, StringComparison.Ordinal))
return AccountControlApiHandlers.HandleServerRemove();
if (subject.StartsWith(JetStreamApiSubjects.AccountPurge, StringComparison.Ordinal))
return AccountControlApiHandlers.HandleAccountPurge(subject);
if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMoveCancel, StringComparison.Ordinal))
return AccountControlApiHandlers.HandleAccountStreamMoveCancel(subject);
if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMove, StringComparison.Ordinal))
return AccountControlApiHandlers.HandleAccountStreamMove(subject);
if (subject.StartsWith(JetStreamApiSubjects.StreamCreate, StringComparison.Ordinal))
return StreamApiHandlers.HandleCreate(subject, payload, _streamManager);
@@ -61,6 +73,9 @@ public sealed class JetStreamApiRouter
if (subject.StartsWith(JetStreamApiSubjects.StreamLeaderStepdown, StringComparison.Ordinal))
return ClusterControlApiHandlers.HandleStreamLeaderStepdown(subject, _streamManager);
if (subject.StartsWith(JetStreamApiSubjects.StreamPeerRemove, StringComparison.Ordinal))
return ClusterControlApiHandlers.HandleStreamPeerRemove(subject);
if (subject.StartsWith(JetStreamApiSubjects.ConsumerCreate, StringComparison.Ordinal))
return ConsumerApiHandlers.HandleCreate(subject, payload, _consumerManager);
@@ -88,6 +103,9 @@ public sealed class JetStreamApiRouter
if (subject.StartsWith(JetStreamApiSubjects.ConsumerNext, StringComparison.Ordinal))
return ConsumerApiHandlers.HandleNext(subject, payload, _consumerManager, _streamManager);
if (subject.StartsWith(JetStreamApiSubjects.ConsumerLeaderStepdown, StringComparison.Ordinal))
return ClusterControlApiHandlers.HandleConsumerLeaderStepdown(subject);
if (subject.StartsWith(JetStreamApiSubjects.DirectGet, StringComparison.Ordinal))
return DirectApiHandlers.HandleGet(subject, payload, _streamManager);
@@ -3,6 +3,10 @@ namespace NATS.Server.JetStream.Api;
public static class JetStreamApiSubjects
{
public const string Info = "$JS.API.INFO";
public const string ServerRemove = "$JS.API.SERVER.REMOVE";
public const string AccountPurge = "$JS.API.ACCOUNT.PURGE.";
public const string AccountStreamMove = "$JS.API.ACCOUNT.STREAM.MOVE.";
public const string AccountStreamMoveCancel = "$JS.API.ACCOUNT.STREAM.MOVE.CANCEL.";
public const string StreamCreate = "$JS.API.STREAM.CREATE.";
public const string StreamInfo = "$JS.API.STREAM.INFO.";
public const string StreamNames = "$JS.API.STREAM.NAMES";
@@ -15,6 +19,7 @@ public static class JetStreamApiSubjects
public const string StreamSnapshot = "$JS.API.STREAM.SNAPSHOT.";
public const string StreamRestore = "$JS.API.STREAM.RESTORE.";
public const string StreamLeaderStepdown = "$JS.API.STREAM.LEADER.STEPDOWN.";
public const string StreamPeerRemove = "$JS.API.STREAM.PEER.REMOVE.";
public const string ConsumerCreate = "$JS.API.CONSUMER.CREATE.";
public const string ConsumerInfo = "$JS.API.CONSUMER.INFO.";
public const string ConsumerNames = "$JS.API.CONSUMER.NAMES.";
@@ -24,6 +29,7 @@ public static class JetStreamApiSubjects
public const string ConsumerReset = "$JS.API.CONSUMER.RESET.";
public const string ConsumerUnpin = "$JS.API.CONSUMER.UNPIN.";
public const string ConsumerNext = "$JS.API.CONSUMER.MSG.NEXT.";
public const string ConsumerLeaderStepdown = "$JS.API.CONSUMER.LEADER.STEPDOWN.";
public const string DirectGet = "$JS.API.DIRECT.GET.";
public const string MetaLeaderStepdown = "$JS.API.META.LEADER.STEPDOWN";
}