1331 lines
46 KiB
C#
1331 lines
46 KiB
C#
// Copyright 2017-2026 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// Adapted from server/reload.go in the NATS server Go source.
|
|
|
|
using System.Reflection;
|
|
using System.Net.Security;
|
|
using ZB.MOM.NatsNet.Server.Auth;
|
|
using ZB.MOM.NatsNet.Server.Internal;
|
|
|
|
namespace ZB.MOM.NatsNet.Server;
|
|
|
|
// =============================================================================
|
|
// IReloadOption — mirrors Go `option` interface in reload.go
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Represents a hot-swappable configuration setting that can be applied to a
|
|
/// running server. Mirrors Go <c>option</c> interface in server/reload.go.
|
|
/// </summary>
|
|
public interface IReloadOption
|
|
{
|
|
/// <summary>Apply this option to the running server.</summary>
|
|
void Apply(NatsServer server);
|
|
|
|
/// <summary>Returns true if this option requires reloading the logger.</summary>
|
|
bool IsLoggingChange();
|
|
|
|
/// <summary>
|
|
/// Returns true if this option requires reloading the cached trace level.
|
|
/// Clients store trace level separately.
|
|
/// </summary>
|
|
bool IsTraceLevelChange();
|
|
|
|
/// <summary>Returns true if this option requires reloading authorization.</summary>
|
|
bool IsAuthChange();
|
|
|
|
/// <summary>Returns true if this option requires reloading TLS.</summary>
|
|
bool IsTlsChange();
|
|
|
|
/// <summary>Returns true if this option requires reloading cluster permissions.</summary>
|
|
bool IsClusterPermsChange();
|
|
|
|
/// <summary>
|
|
/// Returns true if this option requires special handling for changes in
|
|
/// cluster pool size or accounts list.
|
|
/// </summary>
|
|
bool IsClusterPoolSizeOrAccountsChange();
|
|
|
|
/// <summary>
|
|
/// Returns true if this option indicates a change in the server's JetStream config.
|
|
/// Account changes are handled separately in reloadAuthorization.
|
|
/// </summary>
|
|
bool IsJetStreamChange();
|
|
|
|
/// <summary>Returns true if this change requires publishing the server's statz.</summary>
|
|
bool IsStatszChange();
|
|
}
|
|
|
|
// =============================================================================
|
|
// NoopReloadOption — mirrors Go `noopOption` struct in reload.go
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Base class providing no-op implementations for all <see cref="IReloadOption"/>
|
|
/// methods. Concrete option types override only the methods relevant to them.
|
|
/// Mirrors Go <c>noopOption</c> struct in server/reload.go.
|
|
/// </summary>
|
|
public abstract class NoopReloadOption : IReloadOption
|
|
{
|
|
/// <inheritdoc/>
|
|
public virtual void Apply(NatsServer server) => _ = server;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsLoggingChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsTraceLevelChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsAuthChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsTlsChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsClusterPermsChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsClusterPoolSizeOrAccountsChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsJetStreamChange() => false;
|
|
|
|
/// <inheritdoc/>
|
|
public virtual bool IsStatszChange() => false;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Intermediate base classes (mirrors Go loggingOption / traceLevelOption)
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Base for all logging-related reload options.
|
|
/// Mirrors Go <c>loggingOption</c> struct.
|
|
/// </summary>
|
|
internal abstract class LoggingReloadOption : NoopReloadOption
|
|
{
|
|
public override bool IsLoggingChange() => true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base for all trace-level reload options.
|
|
/// Mirrors Go <c>traceLevelOption</c> struct.
|
|
/// </summary>
|
|
internal abstract class TraceLevelReloadOption : LoggingReloadOption
|
|
{
|
|
public override bool IsTraceLevelChange() => true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base for all authorization-related reload options.
|
|
/// Mirrors Go <c>authOption</c> struct.
|
|
/// </summary>
|
|
internal abstract class AuthReloadOption : NoopReloadOption
|
|
{
|
|
public override bool IsAuthChange() => true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base for TLS reload options.
|
|
/// Mirrors Go <c>tlsOption</c> (as a base, not the concrete type).
|
|
/// </summary>
|
|
internal abstract class TlsBaseReloadOption : NoopReloadOption
|
|
{
|
|
public override bool IsTlsChange() => true;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Logging & Trace option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>trace</c> setting.
|
|
/// Mirrors Go <c>traceOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TraceReloadOption : TraceLevelReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public TraceReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: trace = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>trace_verbose</c> setting.
|
|
/// Mirrors Go <c>traceVerboseOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TraceVerboseReloadOption : TraceLevelReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public TraceVerboseReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: trace_verbose = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>trace_headers</c> setting.
|
|
/// Mirrors Go <c>traceHeadersOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TraceHeadersReloadOption : TraceLevelReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public TraceHeadersReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: trace_headers = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>debug</c> setting.
|
|
/// Mirrors Go <c>debugOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class DebugReloadOption : LoggingReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public DebugReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
server.Noticef("Reloaded: debug = {0}", _newValue);
|
|
// DEFERRED: session 13 — call server.ReloadDebugRaftNodes(_newValue)
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>logtime</c> setting.
|
|
/// Mirrors Go <c>logtimeOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class LogtimeReloadOption : LoggingReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public LogtimeReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: logtime = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>logtime_utc</c> setting.
|
|
/// Mirrors Go <c>logtimeUTCOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class LogtimeUTCOption : LoggingReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public LogtimeUTCOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: logtime_utc = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>log_file</c> setting.
|
|
/// Mirrors Go <c>logfileOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class LogfileOption : LoggingReloadOption
|
|
{
|
|
private readonly string _newValue;
|
|
public LogfileOption(string newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: log_file = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>syslog</c> setting.
|
|
/// Mirrors Go <c>syslogOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class SyslogReloadOption : LoggingReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public SyslogReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: syslog = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>remote_syslog</c> setting.
|
|
/// Mirrors Go <c>remoteSyslogOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class RemoteSyslogReloadOption : LoggingReloadOption
|
|
{
|
|
private readonly string _newValue;
|
|
public RemoteSyslogReloadOption(string newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: remote_syslog = {0}", _newValue);
|
|
}
|
|
|
|
// =============================================================================
|
|
// TLS option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>tls</c> setting.
|
|
/// Mirrors Go <c>tlsOption</c> struct in reload.go.
|
|
/// The TLS config is stored as <c>object?</c> because the full
|
|
/// <c>TlsConfig</c> type is not yet ported.
|
|
/// DEFERRED: session 13 — replace object? with the ported TlsConfig type.
|
|
/// </summary>
|
|
internal sealed class TlsReloadOption : NoopReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object? with ported TlsConfig type
|
|
private readonly object? _newValue;
|
|
public TlsReloadOption(object? newValue) => _newValue = newValue;
|
|
|
|
public override bool IsTlsChange() => true;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
var message = _newValue is null ? "disabled" : "enabled";
|
|
server.Noticef("Reloaded: tls = {0}", message);
|
|
// DEFERRED: session 13 — update server.Info.TLSRequired / TLSVerify
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the TLS <c>timeout</c> setting.
|
|
/// Mirrors Go <c>tlsTimeoutOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TlsTimeoutReloadOption : NoopReloadOption
|
|
{
|
|
private readonly double _newValue;
|
|
public TlsTimeoutReloadOption(double newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: tls timeout = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the TLS <c>pinned_certs</c> setting.
|
|
/// Mirrors Go <c>tlsPinnedCertOption</c> struct in reload.go.
|
|
/// The pinned cert set is stored as <c>object?</c> pending the port
|
|
/// of the PinnedCertSet type.
|
|
/// DEFERRED: session 13 — replace object? with ported PinnedCertSet type.
|
|
/// </summary>
|
|
internal sealed class TlsPinnedCertReloadOption : NoopReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object? with ported PinnedCertSet type
|
|
private readonly object? _newValue;
|
|
public TlsPinnedCertReloadOption(object? newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: pinned_certs");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the TLS <c>handshake_first</c> setting.
|
|
/// Mirrors Go <c>tlsHandshakeFirst</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TlsHandshakeFirstReloadOption : NoopReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public TlsHandshakeFirstReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: Client TLS handshake first: {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the TLS <c>handshake_first_fallback</c> delay setting.
|
|
/// Mirrors Go <c>tlsHandshakeFirstFallback</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TlsHandshakeFirstFallbackReloadOption : NoopReloadOption
|
|
{
|
|
private readonly TimeSpan _newValue;
|
|
public TlsHandshakeFirstFallbackReloadOption(TimeSpan newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: Client TLS handshake first fallback delay: {0}", _newValue);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Authorization option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>username</c> authorization setting.
|
|
/// Mirrors Go <c>usernameOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class UsernameReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization username");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>password</c> authorization setting.
|
|
/// Mirrors Go <c>passwordOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class PasswordReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization password");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>token</c> authorization setting.
|
|
/// Mirrors Go <c>authorizationOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class AuthorizationReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization token");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the authorization <c>timeout</c> setting.
|
|
/// Note: this is a NoopReloadOption (not auth) because authorization
|
|
/// will be reloaded with options separately.
|
|
/// Mirrors Go <c>authTimeoutOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class AuthTimeoutReloadOption : NoopReloadOption
|
|
{
|
|
private readonly double _newValue;
|
|
public AuthTimeoutReloadOption(double newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization timeout = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>tags</c> setting.
|
|
/// Mirrors Go <c>tagsOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class TagsReloadOption : NoopReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: tags");
|
|
|
|
public override bool IsStatszChange() => true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>metadata</c> setting.
|
|
/// Mirrors Go <c>metadataOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MetadataReloadOption : NoopReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: metadata");
|
|
|
|
public override bool IsStatszChange() => true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the authorization <c>users</c> setting.
|
|
/// Mirrors Go <c>usersOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class UsersReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization users");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the authorization <c>nkeys</c> setting.
|
|
/// Mirrors Go <c>nkeysOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class NkeysReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: authorization nkey users");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>accounts</c> setting.
|
|
/// Mirrors Go <c>accountsOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class AccountsReloadOption : AuthReloadOption
|
|
{
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: accounts");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Cluster option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>cluster</c> setting.
|
|
/// Stores cluster options as <c>object?</c> pending the port of <c>ClusterOpts</c>.
|
|
/// Mirrors Go <c>clusterOption</c> struct in reload.go.
|
|
/// DEFERRED: session 13 — replace object? with ported ClusterOpts type.
|
|
/// </summary>
|
|
internal sealed class ClusterReloadOption : AuthReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object? with ported ClusterOpts type
|
|
private readonly object? _newValue;
|
|
private readonly bool _permsChanged;
|
|
private bool _poolSizeChanged;
|
|
private readonly bool _compressChanged;
|
|
private string[] _accsAdded;
|
|
private string[] _accsRemoved;
|
|
|
|
public ClusterReloadOption(
|
|
object? newValue,
|
|
bool permsChanged,
|
|
bool poolSizeChanged,
|
|
bool compressChanged,
|
|
string[] accsAdded,
|
|
string[] accsRemoved)
|
|
{
|
|
_newValue = newValue;
|
|
_permsChanged = permsChanged;
|
|
_poolSizeChanged = poolSizeChanged;
|
|
_compressChanged = compressChanged;
|
|
_accsAdded = accsAdded;
|
|
_accsRemoved = accsRemoved;
|
|
}
|
|
|
|
public override bool IsClusterPermsChange()
|
|
=> _permsChanged;
|
|
|
|
public override bool IsClusterPoolSizeOrAccountsChange()
|
|
=> _poolSizeChanged || _accsAdded.Length > 0 || _accsRemoved.Length > 0;
|
|
|
|
internal ClusterOpts? ClusterValue => _newValue as ClusterOpts;
|
|
internal bool PoolSizeChanged => _poolSizeChanged;
|
|
internal bool CompressChanged => _compressChanged;
|
|
internal IReadOnlyList<string> AccountsAdded => _accsAdded;
|
|
internal IReadOnlyList<string> AccountsRemoved => _accsRemoved;
|
|
|
|
/// <summary>
|
|
/// Computes pool/account deltas used by reload orchestration.
|
|
/// Mirrors Go <c>clusterOption.diffPoolAndAccounts</c>.
|
|
/// </summary>
|
|
public void DiffPoolAndAccounts(ClusterOpts oldValue)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(oldValue);
|
|
|
|
if (_newValue is not ClusterOpts newValue)
|
|
{
|
|
_poolSizeChanged = false;
|
|
_accsAdded = [];
|
|
_accsRemoved = [];
|
|
return;
|
|
}
|
|
|
|
_poolSizeChanged = newValue.PoolSize != oldValue.PoolSize;
|
|
|
|
var oldAccounts = new HashSet<string>(oldValue.PinnedAccounts, StringComparer.Ordinal);
|
|
var newAccounts = new HashSet<string>(newValue.PinnedAccounts, StringComparer.Ordinal);
|
|
|
|
_accsAdded = newAccounts.Except(oldAccounts).ToArray();
|
|
_accsRemoved = oldAccounts.Except(newAccounts).ToArray();
|
|
}
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — full cluster apply logic (TLS, route info, compression)
|
|
server.Noticef("Reloaded: cluster");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the cluster <c>routes</c> setting.
|
|
/// Routes to add/remove are stored as <c>object[]</c> pending the port of URL handling.
|
|
/// Mirrors Go <c>routesOption</c> struct in reload.go.
|
|
/// DEFERRED: session 13 — replace object[] with Uri[] when route types are ported.
|
|
/// </summary>
|
|
internal sealed class RoutesReloadOption : NoopReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object[] with Uri[] when route URL types are ported
|
|
private readonly object[] _add;
|
|
private readonly object[] _remove;
|
|
|
|
public RoutesReloadOption(object[] add, object[] remove)
|
|
{
|
|
_add = add;
|
|
_remove = remove;
|
|
}
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — add/remove routes, update varzUpdateRouteURLs
|
|
server.Noticef("Reloaded: cluster routes");
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Connection limit & network option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>max_connections</c> setting.
|
|
/// Mirrors Go <c>maxConnOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MaxConnReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MaxConnReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — close random connections if over limit
|
|
server.Noticef("Reloaded: max_connections = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>pid_file</c> setting.
|
|
/// Mirrors Go <c>pidFileOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class PidFileReloadOption : NoopReloadOption
|
|
{
|
|
private readonly string _newValue;
|
|
public PidFileReloadOption(string newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
if (string.IsNullOrEmpty(_newValue))
|
|
return;
|
|
// DEFERRED: session 13 — call server.LogPid()
|
|
server.Noticef("Reloaded: pid_file = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>ports_file_dir</c> setting.
|
|
/// Mirrors Go <c>portsFileDirOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class PortsFileDirReloadOption : NoopReloadOption
|
|
{
|
|
private readonly string _oldValue;
|
|
private readonly string _newValue;
|
|
|
|
public PortsFileDirReloadOption(string oldValue, string newValue)
|
|
{
|
|
_oldValue = oldValue;
|
|
_newValue = newValue;
|
|
}
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — call server.DeletePortsFile(_oldValue) and server.LogPorts()
|
|
server.Noticef("Reloaded: ports_file_dir = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>max_control_line</c> setting.
|
|
/// Mirrors Go <c>maxControlLineOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MaxControlLineReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MaxControlLineReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — update mcl on each connected client
|
|
server.Noticef("Reloaded: max_control_line = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>max_payload</c> setting.
|
|
/// Mirrors Go <c>maxPayloadOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MaxPayloadReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MaxPayloadReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — update server info and mpay on each client
|
|
server.Noticef("Reloaded: max_payload = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>ping_interval</c> setting.
|
|
/// Mirrors Go <c>pingIntervalOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class PingIntervalReloadOption : NoopReloadOption
|
|
{
|
|
private readonly TimeSpan _newValue;
|
|
public PingIntervalReloadOption(TimeSpan newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: ping_interval = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>ping_max</c> setting.
|
|
/// Mirrors Go <c>maxPingsOutOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MaxPingsOutReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MaxPingsOutReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: ping_max = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>write_deadline</c> setting.
|
|
/// Mirrors Go <c>writeDeadlineOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class WriteDeadlineReloadOption : NoopReloadOption
|
|
{
|
|
private readonly TimeSpan _newValue;
|
|
public WriteDeadlineReloadOption(TimeSpan newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: write_deadline = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>client_advertise</c> setting.
|
|
/// Mirrors Go <c>clientAdvertiseOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class ClientAdvertiseReloadOption : NoopReloadOption
|
|
{
|
|
private readonly string _newValue;
|
|
public ClientAdvertiseReloadOption(string newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — call server.SetInfoHostPort()
|
|
server.Noticef("Reload: client_advertise = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// JetStream option type
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>jetstream</c> setting.
|
|
/// Mirrors Go <c>jetStreamOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class JetStreamReloadOption : NoopReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public JetStreamReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
internal bool Value => _newValue;
|
|
|
|
public override bool IsJetStreamChange() => true;
|
|
public override bool IsStatszChange() => true;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: JetStream");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Miscellaneous option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>default_sentinel</c> setting.
|
|
/// Mirrors Go <c>defaultSentinelOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class DefaultSentinelReloadOption : NoopReloadOption
|
|
{
|
|
private readonly string _newValue;
|
|
public DefaultSentinelReloadOption(string newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: default_sentinel = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the OCSP setting.
|
|
/// The new value is stored as <c>object?</c> pending the port of <c>OCSPConfig</c>.
|
|
/// Mirrors Go <c>ocspOption</c> struct in reload.go.
|
|
/// DEFERRED: session 13 — replace object? with ported OcspConfig type.
|
|
/// </summary>
|
|
internal sealed class OcspReloadOption : TlsBaseReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object? with ported OcspConfig type
|
|
private readonly object? _newValue;
|
|
public OcspReloadOption(object? newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: OCSP");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the OCSP response cache setting.
|
|
/// The new value is stored as <c>object?</c> pending the port of
|
|
/// <c>OCSPResponseCacheConfig</c>.
|
|
/// Mirrors Go <c>ocspResponseCacheOption</c> struct in reload.go.
|
|
/// DEFERRED: session 13 — replace object? with ported OcspResponseCacheConfig type.
|
|
/// </summary>
|
|
internal sealed class OcspResponseCacheReloadOption : TlsBaseReloadOption
|
|
{
|
|
// DEFERRED: session 13 — replace object? with ported OcspResponseCacheConfig type
|
|
private readonly object? _newValue;
|
|
public OcspResponseCacheReloadOption(object? newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded OCSP peer cache");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>connect_error_reports</c> setting.
|
|
/// Mirrors Go <c>connectErrorReports</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class ConnectErrorReportsReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public ConnectErrorReportsReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: connect_error_reports = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>reconnect_error_reports</c> setting.
|
|
/// Mirrors Go <c>reconnectErrorReports</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class ReconnectErrorReportsReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public ReconnectErrorReportsReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: reconnect_error_reports = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>max_traced_msg_len</c> setting.
|
|
/// Mirrors Go <c>maxTracedMsgLenOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MaxTracedMsgLenReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MaxTracedMsgLenReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — update server.Opts.MaxTracedMsgLen under lock
|
|
server.Noticef("Reloaded: max_traced_msg_len = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// MQTT option types
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>ack_wait</c> setting.
|
|
/// Mirrors Go <c>mqttAckWaitReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttAckWaitReloadOption : NoopReloadOption
|
|
{
|
|
private readonly TimeSpan _newValue;
|
|
public MqttAckWaitReloadOption(TimeSpan newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: MQTT ack_wait = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>max_ack_pending</c> setting.
|
|
/// Mirrors Go <c>mqttMaxAckPendingReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttMaxAckPendingReloadOption : NoopReloadOption
|
|
{
|
|
private readonly ushort _newValue;
|
|
public MqttMaxAckPendingReloadOption(ushort newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — call server.MqttUpdateMaxAckPending(_newValue)
|
|
server.Noticef("Reloaded: MQTT max_ack_pending = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>stream_replicas</c> setting.
|
|
/// Mirrors Go <c>mqttStreamReplicasReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttStreamReplicasReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MqttStreamReplicasReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: MQTT stream_replicas = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>consumer_replicas</c> setting.
|
|
/// Mirrors Go <c>mqttConsumerReplicasReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttConsumerReplicasReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public MqttConsumerReplicasReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: MQTT consumer_replicas = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>consumer_memory_storage</c> setting.
|
|
/// Mirrors Go <c>mqttConsumerMemoryStorageReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttConsumerMemoryStorageReloadOption : NoopReloadOption
|
|
{
|
|
private readonly bool _newValue;
|
|
public MqttConsumerMemoryStorageReloadOption(bool newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: MQTT consumer_memory_storage = {0}", _newValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload option for the MQTT <c>consumer_inactive_threshold</c> setting.
|
|
/// Mirrors Go <c>mqttInactiveThresholdReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class MqttInactiveThresholdReloadOption : NoopReloadOption
|
|
{
|
|
private readonly TimeSpan _newValue;
|
|
public MqttInactiveThresholdReloadOption(TimeSpan newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
=> server.Noticef("Reloaded: MQTT consumer_inactive_threshold = {0}", _newValue);
|
|
}
|
|
|
|
// =============================================================================
|
|
// Profiling option type
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>prof_block_rate</c> setting.
|
|
/// Mirrors Go <c>profBlockRateReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class ProfBlockRateReloadOption : NoopReloadOption
|
|
{
|
|
private readonly int _newValue;
|
|
public ProfBlockRateReloadOption(int newValue) => _newValue = newValue;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — call server.SetBlockProfileRate(_newValue)
|
|
server.Noticef("Reloaded: prof_block_rate = {0}", _newValue);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// LeafNode option type
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for leaf-node settings (TLS handshake-first, compression, disabled).
|
|
/// Mirrors Go <c>leafNodeOption</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class LeafNodeReloadOption : NoopReloadOption
|
|
{
|
|
private readonly bool _tlsFirstChanged;
|
|
private readonly bool _compressionChanged;
|
|
private readonly bool _disabledChanged;
|
|
|
|
public LeafNodeReloadOption(bool tlsFirstChanged, bool compressionChanged, bool disabledChanged)
|
|
{
|
|
_tlsFirstChanged = tlsFirstChanged;
|
|
_compressionChanged = compressionChanged;
|
|
_disabledChanged = disabledChanged;
|
|
}
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — full leaf-node apply logic from Go leafNodeOption.Apply()
|
|
if (_tlsFirstChanged)
|
|
server.Noticef("Reloaded: LeafNode TLS HandshakeFirst settings");
|
|
if (_compressionChanged)
|
|
server.Noticef("Reloaded: LeafNode compression settings");
|
|
if (_disabledChanged)
|
|
server.Noticef("Reloaded: LeafNode disabled/enabled state");
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// NoFastProducerStall option type
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>no_fast_producer_stall</c> setting.
|
|
/// Mirrors Go <c>noFastProdStallReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class NoFastProducerStallReloadOption : NoopReloadOption
|
|
{
|
|
private readonly bool _noStall;
|
|
public NoFastProducerStallReloadOption(bool noStall) => _noStall = noStall;
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
var not = _noStall ? "not " : string.Empty;
|
|
server.Noticef("Reloaded: fast producers will {0}be stalled", not);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Proxies option type
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Reload option for the <c>proxies</c> trusted keys setting.
|
|
/// Mirrors Go <c>proxiesReload</c> struct in reload.go.
|
|
/// </summary>
|
|
internal sealed class ProxiesReloadOption : NoopReloadOption
|
|
{
|
|
private readonly string[] _add;
|
|
private readonly string[] _del;
|
|
|
|
public ProxiesReloadOption(string[] add, string[] del)
|
|
{
|
|
_add = add;
|
|
_del = del;
|
|
}
|
|
|
|
public override void Apply(NatsServer server)
|
|
{
|
|
// DEFERRED: session 13 — disconnect proxied clients for removed keys,
|
|
// call server.ProcessProxiesTrustedKeys()
|
|
if (_del.Length > 0)
|
|
server.Noticef("Reloaded: proxies trusted keys {0} were removed", string.Join(", ", _del));
|
|
if (_add.Length > 0)
|
|
server.Noticef("Reloaded: proxies trusted keys {0} were added", string.Join(", ", _add));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// ConfigReloader — stub for server/reload.go Reload() / ReloadOptions()
|
|
// =============================================================================
|
|
|
|
/// <summary>
|
|
/// Stub for the configuration reloader.
|
|
/// Full reload logic (diffOptions, applyOptions, recheckPinnedCerts) will be
|
|
/// implemented in a future session.
|
|
/// Mirrors Go <c>Server.Reload()</c> and <c>Server.ReloadOptions()</c> in
|
|
/// server/reload.go.
|
|
/// </summary>
|
|
internal sealed class ConfigReloader
|
|
{
|
|
// DEFERRED: session 13 — full reload logic
|
|
// Mirrors Go server.Reload() / server.ReloadOptions() in server/reload.go
|
|
|
|
/// <summary>
|
|
/// Stub: read and apply the server config file.
|
|
/// Returns null on success; a non-null Exception describes the failure.
|
|
/// </summary>
|
|
public Exception? Reload(NatsServer server) => null;
|
|
|
|
/// <summary>
|
|
/// Applies bool-valued config precedence for reload:
|
|
/// config-file explicit values first, then explicit command-line flags.
|
|
/// Mirrors Go <c>applyBoolFlags</c>.
|
|
/// </summary>
|
|
public static void ApplyBoolFlags(ServerOptions newOptions, ServerOptions flagOptions)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(newOptions);
|
|
ArgumentNullException.ThrowIfNull(flagOptions);
|
|
|
|
foreach (var (name, value) in newOptions.InConfig)
|
|
SetBooleanMember(newOptions, name, value);
|
|
|
|
foreach (var (name, value) in flagOptions.InCmdLine)
|
|
SetBooleanMember(newOptions, name, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sorts order-insensitive values in place prior to deep comparisons.
|
|
/// Mirrors Go <c>imposeOrder</c>.
|
|
/// </summary>
|
|
public static Exception? ImposeOrder(object? value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case List<Account> accounts:
|
|
accounts.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
|
|
break;
|
|
case List<User> users:
|
|
users.Sort((a, b) => string.CompareOrdinal(a.Username, b.Username));
|
|
break;
|
|
case List<NkeyUser> nkeys:
|
|
nkeys.Sort((a, b) => string.CompareOrdinal(a.Nkey, b.Nkey));
|
|
break;
|
|
case List<Uri> urls:
|
|
urls.Sort((a, b) => string.CompareOrdinal(a.ToString(), b.ToString()));
|
|
break;
|
|
case List<string> strings:
|
|
strings.Sort(StringComparer.Ordinal);
|
|
break;
|
|
case GatewayOpts gateway:
|
|
gateway.Gateways.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
|
|
break;
|
|
case WebsocketOpts websocket:
|
|
websocket.AllowedOrigins.Sort(StringComparer.Ordinal);
|
|
break;
|
|
case null:
|
|
case string:
|
|
case bool:
|
|
case byte:
|
|
case ushort:
|
|
case uint:
|
|
case ulong:
|
|
case int:
|
|
case long:
|
|
case TimeSpan:
|
|
case float:
|
|
case double:
|
|
case LeafNodeOpts:
|
|
case ClusterOpts:
|
|
case SslServerAuthenticationOptions:
|
|
case PinnedCertSet:
|
|
case IAccountResolver:
|
|
case MqttOpts:
|
|
case Dictionary<string, string>:
|
|
case JsLimitOpts:
|
|
case StoreCipher:
|
|
case OcspResponseCacheConfig:
|
|
case ProxiesConfig:
|
|
case WriteTimeoutPolicy:
|
|
case AuthCalloutOpts:
|
|
case JsTpmOpts:
|
|
break;
|
|
default:
|
|
return new InvalidOperationException(
|
|
$"OnReload, sort or explicitly skip type: {value.GetType().FullName}");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns remote gateway configs copied for diffing, with TLS runtime fields stripped.
|
|
/// Mirrors Go <c>copyRemoteGWConfigsWithoutTLSConfig</c>.
|
|
/// </summary>
|
|
public static List<RemoteGatewayOpts>? CopyRemoteGWConfigsWithoutTLSConfig(List<RemoteGatewayOpts>? current)
|
|
{
|
|
if (current is not { Count: > 0 })
|
|
return null;
|
|
|
|
var copied = new List<RemoteGatewayOpts>(current.Count);
|
|
foreach (var config in current)
|
|
{
|
|
copied.Add(new RemoteGatewayOpts
|
|
{
|
|
Name = config.Name,
|
|
TlsConfig = null,
|
|
TlsConfigOpts = null,
|
|
TlsTimeout = config.TlsTimeout,
|
|
Urls = [.. config.Urls.Select(static u => new Uri(u.ToString(), UriKind.Absolute))],
|
|
});
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns remote leaf-node configs copied for diffing, with runtime-mutated
|
|
/// fields stripped or normalized.
|
|
/// Mirrors Go <c>copyRemoteLNConfigForReloadCompare</c>.
|
|
/// </summary>
|
|
public static List<RemoteLeafOpts>? CopyRemoteLNConfigForReloadCompare(List<RemoteLeafOpts>? current)
|
|
{
|
|
if (current is not { Count: > 0 })
|
|
return null;
|
|
|
|
var copied = new List<RemoteLeafOpts>(current.Count);
|
|
foreach (var config in current)
|
|
{
|
|
copied.Add(new RemoteLeafOpts
|
|
{
|
|
LocalAccount = config.LocalAccount,
|
|
NoRandomize = config.NoRandomize,
|
|
Urls = [.. config.Urls.Select(static u => new Uri(u.ToString(), UriKind.Absolute))],
|
|
Credentials = config.Credentials,
|
|
Nkey = config.Nkey,
|
|
SignatureCb = config.SignatureCb,
|
|
Tls = false,
|
|
TlsConfig = null,
|
|
TlsConfigOpts = null,
|
|
TlsTimeout = config.TlsTimeout,
|
|
TlsHandshakeFirst = false,
|
|
Hub = config.Hub,
|
|
DenyImports = [],
|
|
DenyExports = [],
|
|
FirstInfoTimeout = config.FirstInfoTimeout,
|
|
Compression = new CompressionOpts(),
|
|
Websocket = new RemoteLeafWebsocketOpts
|
|
{
|
|
Compression = config.Websocket.Compression,
|
|
NoMasking = config.Websocket.NoMasking,
|
|
},
|
|
Proxy = new RemoteLeafProxyOpts
|
|
{
|
|
Url = config.Proxy.Url,
|
|
Username = config.Proxy.Username,
|
|
Password = config.Proxy.Password,
|
|
Timeout = config.Proxy.Timeout,
|
|
},
|
|
JetStreamClusterMigrate = config.JetStreamClusterMigrate,
|
|
JetStreamClusterMigrateDelay = config.JetStreamClusterMigrateDelay,
|
|
LocalIsolation = config.LocalIsolation,
|
|
RequestIsolation = config.RequestIsolation,
|
|
Disabled = false,
|
|
});
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates non-reloadable cluster settings and advertise syntax.
|
|
/// Mirrors Go <c>validateClusterOpts</c>.
|
|
/// </summary>
|
|
public static Exception? ValidateClusterOpts(ClusterOpts oldValue, ClusterOpts newValue)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(oldValue);
|
|
ArgumentNullException.ThrowIfNull(newValue);
|
|
|
|
if (!string.Equals(oldValue.Host, newValue.Host, StringComparison.Ordinal))
|
|
{
|
|
return new InvalidOperationException(
|
|
$"config reload not supported for cluster host: old={oldValue.Host}, new={newValue.Host}");
|
|
}
|
|
|
|
if (oldValue.Port != newValue.Port)
|
|
{
|
|
return new InvalidOperationException(
|
|
$"config reload not supported for cluster port: old={oldValue.Port}, new={newValue.Port}");
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(newValue.Advertise))
|
|
{
|
|
var (_, _, err) = ServerUtilities.ParseHostPort(newValue.Advertise, 0);
|
|
if (err != null)
|
|
return new InvalidOperationException(
|
|
$"invalid Cluster.Advertise value of {newValue.Advertise}, err={err.Message}", err);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Diffs old/new route lists and returns routes to add/remove.
|
|
/// Mirrors Go <c>diffRoutes</c>.
|
|
/// </summary>
|
|
public static (List<Uri> Add, List<Uri> Remove) DiffRoutes(IReadOnlyList<Uri> oldRoutes, IReadOnlyList<Uri> newRoutes)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(oldRoutes);
|
|
ArgumentNullException.ThrowIfNull(newRoutes);
|
|
|
|
var add = new List<Uri>();
|
|
var remove = new List<Uri>();
|
|
|
|
foreach (var oldRoute in oldRoutes)
|
|
{
|
|
if (!newRoutes.Any(newRoute => ServerUtilities.UrlsAreEqual(oldRoute, newRoute)))
|
|
remove.Add(oldRoute);
|
|
}
|
|
|
|
foreach (var newRoute in newRoutes)
|
|
{
|
|
if (!oldRoutes.Any(oldRoute => ServerUtilities.UrlsAreEqual(oldRoute, newRoute)))
|
|
add.Add(newRoute);
|
|
}
|
|
|
|
return (add, remove);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Diffs proxy trusted keys and returns added/removed key sets.
|
|
/// Mirrors Go <c>diffProxiesTrustedKeys</c>.
|
|
/// </summary>
|
|
public static (List<string> Add, List<string> Del) DiffProxiesTrustedKeys(
|
|
IReadOnlyList<ProxyConfig>? oldTrusted,
|
|
IReadOnlyList<ProxyConfig>? newTrusted)
|
|
{
|
|
var oldList = oldTrusted ?? [];
|
|
var newList = newTrusted ?? [];
|
|
|
|
var add = new List<string>();
|
|
var del = new List<string>();
|
|
|
|
foreach (var oldProxy in oldList)
|
|
{
|
|
if (!newList.Any(np => string.Equals(np.Key, oldProxy.Key, StringComparison.Ordinal)))
|
|
del.Add(oldProxy.Key);
|
|
}
|
|
|
|
foreach (var newProxy in newList)
|
|
{
|
|
if (!oldList.Any(op => string.Equals(op.Key, newProxy.Key, StringComparison.Ordinal)))
|
|
add.Add(newProxy.Key);
|
|
}
|
|
|
|
return (add, del);
|
|
}
|
|
|
|
private static void SetBooleanMember(ServerOptions options, string path, bool value)
|
|
{
|
|
var segments = path.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
if (segments.Length == 0)
|
|
return;
|
|
|
|
object? target = options;
|
|
for (int i = 0; i < segments.Length - 1; i++)
|
|
{
|
|
if (target == null)
|
|
return;
|
|
|
|
var segment = segments[i];
|
|
var targetType = target.GetType();
|
|
var prop = targetType.GetProperty(segment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
if (prop != null)
|
|
{
|
|
target = prop.GetValue(target);
|
|
continue;
|
|
}
|
|
|
|
var field = targetType.GetField(segment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
if (field != null)
|
|
{
|
|
target = field.GetValue(target);
|
|
continue;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (target == null)
|
|
return;
|
|
|
|
var leaf = segments[^1];
|
|
var leafType = target.GetType();
|
|
var leafProperty = leafType.GetProperty(leaf, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
if (leafProperty?.PropertyType == typeof(bool) && leafProperty.CanWrite)
|
|
{
|
|
leafProperty.SetValue(target, value);
|
|
return;
|
|
}
|
|
|
|
var leafField = leafType.GetField(leaf, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
if (leafField?.FieldType == typeof(bool))
|
|
leafField.SetValue(target, value);
|
|
}
|
|
}
|