using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Core.Hosting; namespace ZB.MOM.WW.OtOpcUa.Server; /// /// Task #248 — bridges the gap surfaced by the Phase 7 live smoke (#240) where /// DriverInstance rows in the central config DB had no path to materialise /// as live instances in . /// Called from OpcUaServerService.ExecuteAsync after the bootstrap loads /// the published generation, before address-space build. /// /// /// /// Per row: looks up the DriverType string in /// , calls the factory with the row's /// DriverInstanceId + DriverConfig JSON to construct an /// , then registers via /// which invokes InitializeAsync /// under the host's lifecycle semantics. /// /// /// Unknown DriverType = factory not registered = log a warning and skip. /// Per plan decision #12 (driver isolation), failure to construct or initialize /// one driver doesn't prevent the rest from coming up — the Server keeps serving /// the others' subtrees + the operator can fix the misconfigured row + republish /// to retry. /// /// public sealed class DriverInstanceBootstrapper( DriverFactoryRegistry factories, DriverHost driverHost, IServiceScopeFactory scopeFactory, ILogger logger) { public async Task RegisterDriversFromGenerationAsync(long generationId, CancellationToken ct) { using var scope = scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var rows = await db.DriverInstances.AsNoTracking() .Where(d => d.GenerationId == generationId && d.Enabled) .ToListAsync(ct).ConfigureAwait(false); var registered = 0; var skippedUnknownType = 0; var failedInit = 0; foreach (var row in rows) { var factory = factories.TryGet(row.DriverType); if (factory is null) { logger.LogWarning( "DriverInstance {Id} skipped — DriverType '{Type}' has no registered factory (known: {Known})", row.DriverInstanceId, row.DriverType, string.Join(",", factories.RegisteredTypes)); skippedUnknownType++; continue; } try { var driver = factory(row.DriverInstanceId, row.DriverConfig); await driverHost.RegisterAsync(driver, row.DriverConfig, ct).ConfigureAwait(false); registered++; logger.LogInformation( "DriverInstance {Id} ({Type}) registered + initialized", row.DriverInstanceId, row.DriverType); } catch (Exception ex) { // Plan decision #12 — driver isolation. Log + continue so one bad row // doesn't deny the OPC UA endpoint to the rest of the fleet. logger.LogError(ex, "DriverInstance {Id} ({Type}) failed to initialize — driver state will reflect Faulted; operator can republish to retry", row.DriverInstanceId, row.DriverType); failedInit++; } } logger.LogInformation( "DriverInstanceBootstrapper: gen={Gen} registered={Registered} skippedUnknownType={Skipped} failedInit={Failed}", generationId, registered, skippedUnknownType, failedInit); return registered; } }