9cad9ed0fc
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
127 lines
6.5 KiB
C#
127 lines
6.5 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers;
|
|
|
|
/// <summary>
|
|
/// Mutable, all-nullable form model for the driver resilience override. Binds the typed
|
|
/// fields in DriverResilienceSection; null/blank = "use the driver's tier default", so a
|
|
/// blank form serializes back to null (preserving DriverInstance.ResilienceConfig = null).
|
|
/// Emits / reads the exact override JSON shape DriverResilienceOptionsParser consumes.
|
|
/// </summary>
|
|
public sealed class ResilienceFormModel
|
|
{
|
|
public static readonly string[] Capabilities =
|
|
["Read", "Write", "Discover", "Subscribe", "Probe", "AlarmSubscribe", "AlarmAcknowledge", "HistoryRead"];
|
|
|
|
/// <summary>Gets or sets the bulkhead max-concurrency override; null = use the tier default.</summary>
|
|
public int? BulkheadMaxConcurrent { get; set; }
|
|
/// <summary>Gets or sets the bulkhead max-queue-length override; null = use the tier default.</summary>
|
|
public int? BulkheadMaxQueue { get; set; }
|
|
/// <summary>Gets or sets the driver recycle-interval override, in seconds; null = use the tier default.</summary>
|
|
public int? RecycleIntervalSeconds { get; set; }
|
|
|
|
// capability name -> (timeout, retry, breaker), each nullable.
|
|
/// <summary>Gets or sets the per-capability resilience overrides, keyed by capability name.</summary>
|
|
public Dictionary<string, CapabilityRow> Policies { get; set; } =
|
|
Capabilities.ToDictionary(c => c, _ => new CapabilityRow(), StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>Per-capability timeout/retry/breaker override row; null fields fall back to the tier default.</summary>
|
|
public sealed class CapabilityRow
|
|
{
|
|
/// <summary>Gets or sets the timeout override, in seconds; null = use the tier default.</summary>
|
|
public int? TimeoutSeconds { get; set; }
|
|
/// <summary>Gets or sets the retry-count override; null = use the tier default.</summary>
|
|
public int? RetryCount { get; set; }
|
|
/// <summary>Gets or sets the circuit-breaker failure-threshold override; null = use the tier default.</summary>
|
|
public int? BreakerFailureThreshold { get; set; }
|
|
/// <summary>Gets a value indicating whether all fields in this row are unset.</summary>
|
|
public bool IsEmpty => TimeoutSeconds is null && RetryCount is null && BreakerFailureThreshold is null;
|
|
}
|
|
|
|
private static readonly JsonSerializerOptions ReadOpts = new() { PropertyNameCaseInsensitive = true };
|
|
private static readonly JsonSerializerOptions WriteOpts = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
};
|
|
|
|
/// <summary>Parses the override JSON into a form model; malformed or blank JSON yields an empty (all-default) form.</summary>
|
|
/// <param name="json">The raw resilience-override JSON, or null/blank if there is no override.</param>
|
|
/// <returns>A populated form model, or an all-default one when <paramref name="json"/> is blank or malformed.</returns>
|
|
public static ResilienceFormModel FromJson(string? json)
|
|
{
|
|
var model = new ResilienceFormModel();
|
|
if (string.IsNullOrWhiteSpace(json)) return model;
|
|
|
|
Shape? shape;
|
|
try { shape = JsonSerializer.Deserialize<Shape>(json, ReadOpts); }
|
|
catch (JsonException) { return model; } // malformed -> empty form; raw view (next task) shows the text
|
|
if (shape is null) return model;
|
|
|
|
model.BulkheadMaxConcurrent = shape.BulkheadMaxConcurrent;
|
|
model.BulkheadMaxQueue = shape.BulkheadMaxQueue;
|
|
model.RecycleIntervalSeconds = shape.RecycleIntervalSeconds;
|
|
if (shape.CapabilityPolicies is not null)
|
|
foreach (var (cap, p) in shape.CapabilityPolicies)
|
|
if (model.Policies.TryGetValue(cap, out var row))
|
|
{
|
|
row.TimeoutSeconds = p.TimeoutSeconds;
|
|
row.RetryCount = p.RetryCount;
|
|
row.BreakerFailureThreshold = p.BreakerFailureThreshold;
|
|
}
|
|
return model;
|
|
}
|
|
|
|
/// <summary>Emit only the non-null overrides; returns null when nothing is overridden.</summary>
|
|
/// <returns>The serialized override JSON, or null when no field in this form is overridden.</returns>
|
|
public string? ToJson()
|
|
{
|
|
var caps = Policies
|
|
.Where(kv => !kv.Value.IsEmpty)
|
|
.ToDictionary(kv => kv.Key, kv => new PolicyShape
|
|
{
|
|
TimeoutSeconds = kv.Value.TimeoutSeconds,
|
|
RetryCount = kv.Value.RetryCount,
|
|
BreakerFailureThreshold = kv.Value.BreakerFailureThreshold,
|
|
});
|
|
|
|
var hasAny = BulkheadMaxConcurrent is not null || BulkheadMaxQueue is not null
|
|
|| RecycleIntervalSeconds is not null || caps.Count > 0;
|
|
if (!hasAny) return null;
|
|
|
|
var shape = new Shape
|
|
{
|
|
BulkheadMaxConcurrent = BulkheadMaxConcurrent,
|
|
BulkheadMaxQueue = BulkheadMaxQueue,
|
|
RecycleIntervalSeconds = RecycleIntervalSeconds,
|
|
CapabilityPolicies = caps.Count > 0 ? caps : null,
|
|
};
|
|
return JsonSerializer.Serialize(shape, WriteOpts);
|
|
}
|
|
|
|
/// <summary>Wire shape of the resilience-override JSON, as consumed by DriverResilienceOptionsParser.</summary>
|
|
private sealed class Shape
|
|
{
|
|
/// <summary>Gets or sets the bulkhead max-concurrency override.</summary>
|
|
public int? BulkheadMaxConcurrent { get; set; }
|
|
/// <summary>Gets or sets the bulkhead max-queue-length override.</summary>
|
|
public int? BulkheadMaxQueue { get; set; }
|
|
/// <summary>Gets or sets the driver recycle-interval override, in seconds.</summary>
|
|
public int? RecycleIntervalSeconds { get; set; }
|
|
/// <summary>Gets or sets the per-capability overrides, keyed by capability name.</summary>
|
|
public Dictionary<string, PolicyShape>? CapabilityPolicies { get; set; }
|
|
}
|
|
|
|
/// <summary>Wire shape of a single capability's timeout/retry/breaker override.</summary>
|
|
private sealed class PolicyShape
|
|
{
|
|
/// <summary>Gets or sets the timeout override, in seconds.</summary>
|
|
public int? TimeoutSeconds { get; set; }
|
|
/// <summary>Gets or sets the retry-count override.</summary>
|
|
public int? RetryCount { get; set; }
|
|
/// <summary>Gets or sets the circuit-breaker failure-threshold override.</summary>
|
|
public int? BreakerFailureThreshold { get; set; }
|
|
}
|
|
}
|