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
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -502,6 +525,12 @@ public sealed class Account : INatsAccount
|
||||
/// </summary>
|
||||
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
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -966,6 +995,12 @@ public sealed class Account : INatsAccount
|
||||
internal int NumLocalConnectionsLocked() =>
|
||||
(_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>
|
||||
/// Returns all local connections including leaf nodes (minus system clients).
|
||||
/// Mirrors Go <c>(a *Account) numLocalAndLeafConnections() int</c>.
|
||||
@@ -1049,6 +1084,13 @@ public sealed class Account : INatsAccount
|
||||
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>
|
||||
/// Returns the total leaf-node count (local + remote).
|
||||
/// 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
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -2146,6 +2436,35 @@ public sealed class Account : INatsAccount
|
||||
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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user