feat(batch19): implement account trace/counter/export core methods
This commit is contained in:
@@ -475,6 +475,29 @@ public sealed class Account : INatsAccount
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets account-level message trace destination subject.
|
||||||
|
/// Mirrors Go <c>(a *Account) setTraceDest(dest string)</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal void SetTraceDest(string dest) => SetMessageTraceDestination(dest);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns trace destination and sampling.
|
||||||
|
/// Mirrors Go <c>(a *Account) getTraceDestAndSampling() (string, int)</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal (string Destination, int Sampling) GetTraceDestAndSampling()
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (_traceDest, _traceDestSampling);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Factory
|
// Factory
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -502,6 +525,12 @@ public sealed class Account : INatsAccount
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the account name.
|
||||||
|
/// Mirrors Go <c>(a *Account) String() string</c>.
|
||||||
|
/// </summary>
|
||||||
|
public string String() => Name;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Shallow copy for config reload
|
// Shallow copy for config reload
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -966,6 +995,12 @@ public sealed class Account : INatsAccount
|
|||||||
internal int NumLocalConnectionsLocked() =>
|
internal int NumLocalConnectionsLocked() =>
|
||||||
(_clients?.Count ?? 0) - _sysclients - _nleafs;
|
(_clients?.Count ?? 0) - _sysclients - _nleafs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns local non-system, non-leaf client count. Lock must be held.
|
||||||
|
/// Mirrors Go <c>(a *Account) numLocalConnections() int</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal int NumLocalConnectionsInternal() => NumLocalConnectionsLocked();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all local connections including leaf nodes (minus system clients).
|
/// Returns all local connections including leaf nodes (minus system clients).
|
||||||
/// Mirrors Go <c>(a *Account) numLocalAndLeafConnections() int</c>.
|
/// Mirrors Go <c>(a *Account) numLocalAndLeafConnections() int</c>.
|
||||||
@@ -1049,6 +1084,13 @@ public sealed class Account : INatsAccount
|
|||||||
return _nleafs + _nrleafs >= MaxLeafNodes;
|
return _nleafs + _nrleafs >= MaxLeafNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if total leaf-node count reached the configured maximum.
|
||||||
|
/// Lock must be held by the caller.
|
||||||
|
/// Mirrors Go <c>(a *Account) maxTotalLeafNodesReached() bool</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal bool MaxTotalLeafNodesReachedInternal() => MaxTotalLeafNodesReachedLocked();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the total leaf-node count (local + remote).
|
/// Returns the total leaf-node count (local + remote).
|
||||||
/// Mirrors Go <c>(a *Account) NumLeafNodes() int</c>.
|
/// Mirrors Go <c>(a *Account) NumLeafNodes() int</c>.
|
||||||
@@ -1115,6 +1157,93 @@ public sealed class Account : INatsAccount
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when there is at least one matching subscription for <paramref name="subject"/>.
|
||||||
|
/// Mirrors Go <c>(a *Account) SubscriptionInterest(subject string) bool</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool SubscriptionInterest(string subject) => Interest(subject) > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns total number of plain and queue subscriptions matching <paramref name="subject"/>.
|
||||||
|
/// Mirrors Go <c>(a *Account) Interest(subject string) int</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int Interest(string subject)
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Sublist == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var (np, nq) = Sublist.NumInterest(subject);
|
||||||
|
return np + nq;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increments the leaf-node count for a remote cluster.
|
||||||
|
/// Mirrors Go <c>(a *Account) registerLeafNodeCluster(cluster string)</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal void RegisterLeafNodeCluster(string cluster)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_leafClusters ??= new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||||
|
_leafClusters.TryGetValue(cluster, out var current);
|
||||||
|
_leafClusters[cluster] = current + 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when this account already tracks one or more leaf nodes from <paramref name="cluster"/>.
|
||||||
|
/// Mirrors Go <c>(a *Account) hasLeafNodeCluster(cluster string) bool</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal bool HasLeafNodeCluster(string cluster)
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _leafClusters != null &&
|
||||||
|
_leafClusters.TryGetValue(cluster, out var count) &&
|
||||||
|
count > 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when the account is leaf-cluster isolated to <paramref name="cluster"/>.
|
||||||
|
/// Mirrors Go <c>(a *Account) isLeafNodeClusterIsolated(cluster string) bool</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsLeafNodeClusterIsolated(string cluster)
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(cluster))
|
||||||
|
return false;
|
||||||
|
if (_leafClusters == null || _leafClusters.Count > 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _leafClusters.TryGetValue(cluster, out var count) && count == (ulong)_nleafs;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Subscription limit error throttle
|
// Subscription limit error throttle
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -1460,6 +1589,167 @@ public sealed class Account : INatsAccount
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Service export configuration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures an exported service with singleton response semantics.
|
||||||
|
/// Mirrors Go <c>(a *Account) AddServiceExport(subject string, accounts []*Account) error</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? AddServiceExport(string subject, IReadOnlyList<Account>? accounts = null) =>
|
||||||
|
AddServiceExportWithResponseAndAccountPos(subject, ServiceRespType.Singleton, accounts, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures an exported service with singleton response semantics and account-position auth.
|
||||||
|
/// Mirrors Go <c>(a *Account) addServiceExportWithAccountPos(...)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? AddServiceExportWithAccountPos(string subject, IReadOnlyList<Account>? accounts, uint accountPos) =>
|
||||||
|
AddServiceExportWithResponseAndAccountPos(subject, ServiceRespType.Singleton, accounts, accountPos);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures an exported service with explicit response type.
|
||||||
|
/// Mirrors Go <c>(a *Account) AddServiceExportWithResponse(...)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? AddServiceExportWithResponse(string subject, ServiceRespType respType, IReadOnlyList<Account>? accounts = null) =>
|
||||||
|
AddServiceExportWithResponseAndAccountPos(subject, respType, accounts, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures an exported service with explicit response type and account-position auth.
|
||||||
|
/// Mirrors Go <c>(a *Account) addServiceExportWithResponseAndAccountPos(...)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? AddServiceExportWithResponseAndAccountPos(string subject, ServiceRespType respType, IReadOnlyList<Account>? accounts, uint accountPos)
|
||||||
|
{
|
||||||
|
if (!SubscriptionIndex.IsValidSubject(subject))
|
||||||
|
return ServerErrors.ErrBadSubject;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Exports.Services ??= new Dictionary<string, ServiceExportEntry>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
if (!Exports.Services.TryGetValue(subject, out var serviceExport) || serviceExport == null)
|
||||||
|
serviceExport = new ServiceExportEntry();
|
||||||
|
|
||||||
|
if (respType != ServiceRespType.Singleton)
|
||||||
|
serviceExport.ResponseType = respType;
|
||||||
|
|
||||||
|
if (accounts != null || accountPos > 0)
|
||||||
|
{
|
||||||
|
var authErr = SetExportAuth(serviceExport, subject, accounts, accountPos);
|
||||||
|
if (authErr != null)
|
||||||
|
return authErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceExport.Account = this;
|
||||||
|
serviceExport.ResponseThreshold = ServerConstants.DefaultServiceExportResponseThreshold;
|
||||||
|
Exports.Services[subject] = serviceExport;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables latency tracking for <paramref name="service"/> with default sampling.
|
||||||
|
/// Mirrors Go <c>(a *Account) TrackServiceExport(service, results string) error</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? TrackServiceExport(string service, string results) =>
|
||||||
|
TrackServiceExportWithSampling(service, results, ServerConstants.DefaultServiceLatencySampling);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables latency tracking for <paramref name="service"/> with explicit sampling.
|
||||||
|
/// Mirrors Go <c>(a *Account) TrackServiceExportWithSampling(...)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? TrackServiceExportWithSampling(string service, string results, int sampling)
|
||||||
|
{
|
||||||
|
if (sampling != 0 && (sampling < 1 || sampling > 100))
|
||||||
|
return ServerErrors.ErrBadSampling;
|
||||||
|
if (!SubscriptionIndex.IsValidPublishSubject(results))
|
||||||
|
return ServerErrors.ErrBadPublishSubject;
|
||||||
|
if (IsExportService(results))
|
||||||
|
return ServerErrors.ErrBadPublishSubject;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Exports.Services == null)
|
||||||
|
return ServerErrors.ErrMissingService;
|
||||||
|
if (!Exports.Services.TryGetValue(service, out var serviceExport))
|
||||||
|
return ServerErrors.ErrMissingService;
|
||||||
|
|
||||||
|
serviceExport ??= new ServiceExportEntry();
|
||||||
|
if (serviceExport.ResponseType != ServiceRespType.Singleton)
|
||||||
|
return ServerErrors.ErrBadServiceType;
|
||||||
|
|
||||||
|
serviceExport.Latency = new InternalServiceLatency
|
||||||
|
{
|
||||||
|
Sampling = sampling,
|
||||||
|
Subject = results,
|
||||||
|
};
|
||||||
|
Exports.Services[service] = serviceExport;
|
||||||
|
|
||||||
|
if (Imports.Services != null)
|
||||||
|
{
|
||||||
|
foreach (var imports in Imports.Services.Values)
|
||||||
|
{
|
||||||
|
foreach (var import in imports)
|
||||||
|
{
|
||||||
|
if (import?.Account?.Name != Name)
|
||||||
|
continue;
|
||||||
|
if (SubjectTransform.IsSubsetMatch(SubjectTransform.TokenizeSubject(import.To), service))
|
||||||
|
import.Latency = serviceExport.Latency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables latency tracking for the exported service.
|
||||||
|
/// Mirrors Go <c>(a *Account) UnTrackServiceExport(service string)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public void UnTrackServiceExport(string service)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Exports.Services == null || !Exports.Services.TryGetValue(service, out var serviceExport) || serviceExport?.Latency == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
serviceExport.Latency = null;
|
||||||
|
|
||||||
|
if (Imports.Services == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var imports in Imports.Services.Values)
|
||||||
|
{
|
||||||
|
foreach (var import in imports)
|
||||||
|
{
|
||||||
|
if (import?.Account?.Name != Name)
|
||||||
|
continue;
|
||||||
|
if (SubjectTransform.IsSubsetMatch(SubjectTransform.TokenizeSubject(import.To), service))
|
||||||
|
{
|
||||||
|
import.Latency = null;
|
||||||
|
import.M1 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Export checks
|
// Export checks
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -2146,6 +2436,35 @@ public sealed class Account : INatsAccount
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies account-based authorization rules to an export descriptor.
|
||||||
|
/// Mirrors Go <c>setExportAuth(&se.exportAuth, ...)</c>.
|
||||||
|
/// </summary>
|
||||||
|
private static Exception? SetExportAuth(ExportAuth auth, string subject, IReadOnlyList<Account>? accounts, uint accountPos)
|
||||||
|
{
|
||||||
|
if (!SubscriptionIndex.IsValidSubject(subject))
|
||||||
|
return ServerErrors.ErrBadSubject;
|
||||||
|
|
||||||
|
auth.AccountPosition = accountPos;
|
||||||
|
|
||||||
|
if (accounts == null || accounts.Count == 0)
|
||||||
|
{
|
||||||
|
auth.Approved = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var approved = new Dictionary<string, Account>(accounts.Count, StringComparer.Ordinal);
|
||||||
|
foreach (var account in accounts)
|
||||||
|
{
|
||||||
|
if (account == null)
|
||||||
|
continue;
|
||||||
|
approved[account.Name] = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.Approved = approved;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Export equality helpers
|
// Export equality helpers
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user