fix(galaxy): seed platforms with one template filter per request (#864)
The gateway's shared GalaxyHierarchyProjector ANDs template_chain_contains entries, so the single request carrying both $WinPlatform and $AppEngine matched no object and the probe watcher always seeded 0 platforms. GatewayGalaxyPlatformSource now runs one paged sweep per template and unions/dedupes by tag name. The tests use a fake gateway that applies the projector's AND semantics (they fail on the old single request). Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -5,11 +5,19 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="IGalaxyPlatformSource"/>: a platform-only <c>DiscoverHierarchy</c> sweep
|
||||
/// over the gateway's <see cref="GalaxyRepositoryClient"/> — the request filters on
|
||||
/// <c>template_chain_contains</c> (<c>$WinPlatform</c>, <c>$AppEngine</c>) and asks for no
|
||||
/// attributes, so it is a few rows per galaxy rather than the full object + attribute dump the
|
||||
/// address-space discovery pulls. Paged at the configured <c>Repository.DiscoverPageSize</c>.
|
||||
/// over the gateway's <see cref="GalaxyRepositoryClient"/> — one sweep per platform template
|
||||
/// (<c>$WinPlatform</c>, then <c>$AppEngine</c>), each filtered on that single
|
||||
/// <c>template_chain_contains</c> token with no attributes, so it is a few rows per galaxy rather
|
||||
/// than the full object + attribute dump the address-space discovery pulls. Paged at the
|
||||
/// configured <c>Repository.DiscoverPageSize</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⚠️ <b>One template per request (#864).</b> The gateway's shared
|
||||
/// <c>GalaxyHierarchyProjector.MatchesFilters</c> ANDs the <c>template_chain_contains</c> entries —
|
||||
/// an object must match <i>every</i> token — so a single request carrying both templates matches no
|
||||
/// platform (a <c>$WinPlatform</c>'s chain has no <c>$AppEngine</c>, and vice versa) and the probe
|
||||
/// watcher seeded 0 platforms in production. The sweeps are unioned and de-duplicated by tag name.
|
||||
/// </remarks>
|
||||
public sealed class GatewayGalaxyPlatformSource : IGalaxyPlatformSource
|
||||
{
|
||||
/// <summary>Template-chain tokens that identify a platform / engine object.</summary>
|
||||
@@ -32,15 +40,16 @@ public sealed class GatewayGalaxyPlatformSource : IGalaxyPlatformSource
|
||||
=> QueryAsync(_client.DiscoverHierarchyRawAsync, _pageSize, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// The platform-only sweep behind a delegate so it is unit-testable without a channel. Every
|
||||
/// request carries the template filter and <c>include_attributes = false</c>; the reply is
|
||||
/// The platform-only sweeps behind a delegate so it is unit-testable without a channel. One
|
||||
/// paged sweep per entry of <see cref="PlatformTemplates"/>; every request carries exactly that
|
||||
/// one template token and <c>include_attributes = false</c>; the reply is
|
||||
/// re-filtered on the returned template chain (a gateway that ignores the filter still
|
||||
/// yields only platforms) and reduced to non-empty tag names.
|
||||
/// </summary>
|
||||
/// <param name="discover">The raw <c>DiscoverHierarchy</c> RPC.</param>
|
||||
/// <param name="pageSize">Objects per page.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the walk.</param>
|
||||
/// <returns>Distinct platform tag names in gateway order.</returns>
|
||||
/// <returns>Distinct platform tag names: <c>$WinPlatform</c> sweep first, then <c>$AppEngine</c>, each in gateway order.</returns>
|
||||
internal static async Task<IReadOnlyList<string>> QueryAsync(
|
||||
Func<DiscoverHierarchyRequest, CancellationToken, Task<DiscoverHierarchyReply>> discover,
|
||||
int pageSize,
|
||||
@@ -51,33 +60,37 @@ public sealed class GatewayGalaxyPlatformSource : IGalaxyPlatformSource
|
||||
|
||||
var names = new List<string>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var seenPageTokens = new HashSet<string>(StringComparer.Ordinal);
|
||||
var pageToken = string.Empty;
|
||||
do
|
||||
foreach (var template in PlatformTemplates)
|
||||
{
|
||||
var request = new DiscoverHierarchyRequest
|
||||
var seenPageTokens = new HashSet<string>(StringComparer.Ordinal);
|
||||
var pageToken = string.Empty;
|
||||
do
|
||||
{
|
||||
PageSize = effective,
|
||||
PageToken = pageToken,
|
||||
IncludeAttributes = false,
|
||||
};
|
||||
request.TemplateChainContains.Add(PlatformTemplates);
|
||||
var request = new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = effective,
|
||||
PageToken = pageToken,
|
||||
IncludeAttributes = false,
|
||||
};
|
||||
// Exactly ONE token per request: the gateway ANDs template filters (#864).
|
||||
request.TemplateChainContains.Add(template);
|
||||
|
||||
var reply = await discover(request, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var obj in reply.Objects)
|
||||
{
|
||||
if (string.IsNullOrEmpty(obj.TagName) || !IsPlatform(obj)) continue;
|
||||
if (seen.Add(obj.TagName)) names.Add(obj.TagName);
|
||||
}
|
||||
var reply = await discover(request, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var obj in reply.Objects)
|
||||
{
|
||||
if (string.IsNullOrEmpty(obj.TagName) || !IsPlatform(obj)) continue;
|
||||
if (seen.Add(obj.TagName)) names.Add(obj.TagName);
|
||||
}
|
||||
|
||||
pageToken = reply.NextPageToken ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(pageToken) && !seenPageTokens.Add(pageToken))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Galaxy DiscoverHierarchy (platform query) returned a repeated page token '{pageToken}' — refusing to loop.");
|
||||
pageToken = reply.NextPageToken ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(pageToken) && !seenPageTokens.Add(pageToken))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Galaxy DiscoverHierarchy (platform query, {template}) returned a repeated page token '{pageToken}' — refusing to loop.");
|
||||
}
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(pageToken));
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(pageToken));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user