feat(host): add NodeName to NodeOptions + INodeIdentityProvider
- NodeName: semantic role-within-cluster identifier (node-a/node-b on sites, central-a/central-b on central). Bound from ScadaLink:Node:NodeName. - INodeIdentityProvider exposes the trimmed name (null if unconfigured) so downstream audit writers can stamp the new SourceNode column.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
namespace ScadaLink.Commons.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Surfaces the local node's semantic role-within-cluster name so downstream
|
||||
/// audit writers can stamp it on the SourceNode column.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Conventional values follow the pattern <c>node-a</c>/<c>node-b</c> on site
|
||||
/// nodes and <c>central-a</c>/<c>central-b</c> on central nodes. The value is
|
||||
/// a free-form operator-supplied label — there is no enforced format. When the
|
||||
/// configuration value is missing, empty, or whitespace, implementations
|
||||
/// return <c>null</c> so audit writers can persist NULL rather than an empty
|
||||
/// string.
|
||||
/// </remarks>
|
||||
public interface INodeIdentityProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The configured semantic node name, trimmed of surrounding whitespace.
|
||||
/// <c>null</c> when unconfigured.
|
||||
/// </summary>
|
||||
string? NodeName { get; }
|
||||
}
|
||||
20
src/ScadaLink.Host/NodeIdentityProvider.cs
Normal file
20
src/ScadaLink.Host/NodeIdentityProvider.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
|
||||
namespace ScadaLink.Host;
|
||||
|
||||
/// <summary>
|
||||
/// Binds <see cref="INodeIdentityProvider"/> to <see cref="NodeOptions.NodeName"/>.
|
||||
/// Empty or whitespace values are normalised to <c>null</c>; otherwise the value
|
||||
/// is returned trimmed.
|
||||
/// </summary>
|
||||
internal sealed class NodeIdentityProvider : INodeIdentityProvider
|
||||
{
|
||||
public string? NodeName { get; }
|
||||
|
||||
public NodeIdentityProvider(IOptions<NodeOptions> nodeOptions)
|
||||
{
|
||||
var configured = nodeOptions.Value.NodeName;
|
||||
NodeName = string.IsNullOrWhiteSpace(configured) ? null : configured.Trim();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,14 @@ public class NodeOptions
|
||||
{
|
||||
public string Role { get; set; } = string.Empty;
|
||||
public string NodeHostname { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Operator-configured semantic node name used to stamp the SourceNode
|
||||
/// column on audit rows. Conventional values are <c>node-a</c>/<c>node-b</c>
|
||||
/// on site nodes and <c>central-a</c>/<c>central-b</c> on central nodes,
|
||||
/// but the value is a free-form label — no validation is enforced.
|
||||
/// </summary>
|
||||
public string NodeName { get; set; } = string.Empty;
|
||||
public string? SiteId { get; set; }
|
||||
public int RemotingPort { get; set; } = 8081;
|
||||
public int GrpcPort { get; set; } = 8083;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ScadaLink.Host.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Akka.Cluster.Hosting" />
|
||||
<PackageReference Include="Akka.Cluster.Tools" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ScadaLink.AuditLog;
|
||||
using ScadaLink.ClusterInfrastructure;
|
||||
using ScadaLink.Communication;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.DataConnectionLayer;
|
||||
using ScadaLink.ExternalSystemGateway;
|
||||
using ScadaLink.HealthMonitoring;
|
||||
@@ -96,5 +97,10 @@ public static class SiteServiceRegistration
|
||||
services.Configure<HealthMonitoringOptions>(config.GetSection("ScadaLink:HealthMonitoring"));
|
||||
services.Configure<NotificationOptions>(config.GetSection("ScadaLink:Notification"));
|
||||
services.Configure<LoggingOptions>(config.GetSection("ScadaLink:Logging"));
|
||||
|
||||
// Audit Log (#23) — exposes ScadaLink:Node:NodeName to downstream audit
|
||||
// writers so they can stamp the SourceNode column. Registered here in
|
||||
// shared bootstrap because every node (central + site) needs it.
|
||||
services.AddSingleton<INodeIdentityProvider, NodeIdentityProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
50
tests/ScadaLink.Host.Tests/NodeIdentityProviderTests.cs
Normal file
50
tests/ScadaLink.Host.Tests/NodeIdentityProviderTests.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
|
||||
namespace ScadaLink.Host.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for NodeIdentityProvider — surfaces the operator-configured semantic
|
||||
/// node name (e.g. node-a / node-b / central-a / central-b) used by downstream
|
||||
/// audit writers to stamp the SourceNode column.
|
||||
/// </summary>
|
||||
public class NodeIdentityProviderTests
|
||||
{
|
||||
private static INodeIdentityProvider BuildProvider(string nodeName)
|
||||
{
|
||||
var options = Options.Create(new NodeOptions { NodeName = nodeName });
|
||||
return new NodeIdentityProvider(options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeIdentityProvider_returns_configured_NodeName()
|
||||
{
|
||||
var provider = BuildProvider("central-a");
|
||||
|
||||
Assert.Equal("central-a", provider.NodeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeIdentityProvider_returns_null_when_NodeName_unset()
|
||||
{
|
||||
var provider = BuildProvider(string.Empty);
|
||||
|
||||
Assert.Null(provider.NodeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeIdentityProvider_returns_null_when_NodeName_whitespace()
|
||||
{
|
||||
var provider = BuildProvider(" ");
|
||||
|
||||
Assert.Null(provider.NodeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeIdentityProvider_trims_whitespace()
|
||||
{
|
||||
var provider = BuildProvider(" node-a ");
|
||||
|
||||
Assert.Equal("node-a", provider.NodeName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user