From 3546af4b5fce5f01f9ab9dec00bf87e289e06218 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 09:56:01 +0000 Subject: [PATCH 1/2] Initial plan From 2278c18739e52e8b9f11ecabb9c347a3f628f8f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 10:06:45 +0000 Subject: [PATCH 2/2] Remove precomputed flow and add deprecation startup warning Agent-Logs-Url: https://github.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/sessions/8d6a7603-760b-4656-b965-ac8f771be735 Co-authored-by: mm-spr <108080048+mm-spr@users.noreply.github.com> --- example/docker-compose.yml | 2 - .../ShellDescriptorSyncHostedTests.cs | 113 ------------------ .../ConfigurationWarningLoggerTests.cs | 54 +++++++++ .../AAS.TwinEngine.DataEngine.csproj | 1 - .../Config/AasRegistryPreComputed.cs | 10 -- .../Services/ShellDescriptorSyncHosted.cs | 76 ------------ source/AAS.TwinEngine.DataEngine/Program.cs | 2 + .../ConfigurationWarningLogger.cs | 18 +++ ...astructureDependencyInjectionExtensions.cs | 3 - .../appsettings.development.json | 4 - .../appsettings.json | 4 - .../Example/docker-compose.yml | 2 - 12 files changed, 74 insertions(+), 215 deletions(-) delete mode 100644 source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/AasRegistryProvider/ShellDescriptorSyncHostedTests.cs create mode 100644 source/AAS.TwinEngine.DataEngine.UnitTests/ServiceConfiguration/ConfigurationWarningLoggerTests.cs delete mode 100644 source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Config/AasRegistryPreComputed.cs delete mode 100644 source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Services/ShellDescriptorSyncHosted.cs create mode 100644 source/AAS.TwinEngine.DataEngine/ServiceConfiguration/ConfigurationWarningLogger.cs diff --git a/example/docker-compose.yml b/example/docker-compose.yml index 50f2797a..e66befdf 100644 --- a/example/docker-compose.yml +++ b/example/docker-compose.yml @@ -59,8 +59,6 @@ services: - Semantics__SubmodelElementIndexContextPrefix=_aastwinengineindex_ - Semantics__InternalSemanticId=InternalSemanticId - AasxOptions__RootFolder=aasx - - AasRegistryPreComputed__ShellDescriptorCron=0 */3 * * * * - - AasRegistryPreComputed__IsPreComputed=false - MultiPluginConflictOption__HandlingMode=TakeFirst - PluginConfig__Plugins__0__PluginName=Plugin1 - PluginConfig__Plugins__0__PluginUrl=http://dpp-plugin:8080 diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/AasRegistryProvider/ShellDescriptorSyncHostedTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/AasRegistryProvider/ShellDescriptorSyncHostedTests.cs deleted file mode 100644 index 6a38872b..00000000 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Infrastructure/Providers/AasRegistryProvider/ShellDescriptorSyncHostedTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; -using AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Config; -using AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Services; - -using Cronos; - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -using NSubstitute; - -namespace AAS.TwinEngine.DataEngine.UnitTests.Infrastructure.Providers.AasRegistryProvider; - -public class ShellDescriptorSyncHostedTests -{ - private readonly IShellDescriptorService _syncService = Substitute.For(); - private readonly ILogger _logger = Substitute.For>(); - private readonly IServiceScopeFactory _scopeFactory = Substitute.For(); - private const string ValidCron = "0/1 * * * * ?"; - - private ShellDescriptorSyncHosted CreateService(string? cron = null) - { - var serviceProvider = Substitute.For(); - serviceProvider.GetService(typeof(IShellDescriptorService)).Returns(_syncService); - serviceProvider.GetRequiredService().Returns(_syncService); - - var scope = Substitute.For(); - scope.ServiceProvider.Returns(serviceProvider); - _scopeFactory.CreateScope().Returns(scope); - - var config = Options.Create(new AasRegistryPreComputed { ShellDescriptorCron = cron ?? ValidCron, IsPreComputed = true }); - - return new ShellDescriptorSyncHosted(_scopeFactory, config, _logger); - } - - [Fact] - public async Task RunOnceAsync_Should_Invoke_SyncShellDescriptorsAsync() - { - // Arrange - using var service = CreateService(); - - // Act - await service.RunOnceAsync(CancellationToken.None); - - // Assert - await _syncService.Received(1).SyncShellDescriptorsAsync(Arg.Any()); - } - - [Fact] - public async Task RunOnceAsync_Should_Log_And_Throw_ResourceNotFoundException_When_Exception_Occurs() - { - // Arrange - _syncService.When(x => x.SyncShellDescriptorsAsync(Arg.Any())) - .Do(_ => throw new ResourceNotFoundException()); - - using var service = CreateService(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => service.RunOnceAsync(CancellationToken.None)); - } - - [Theory] - [InlineData("* * *", typeof(CronFormatException))] - [InlineData("* * * * * ?", null)] // Valid cron - public void Should_Throw_When_Cron_Is_Invalid(string cron, Type? expectedExceptionType) - { - // Arrange - var config = Options.Create(new AasRegistryPreComputed { ShellDescriptorCron = cron, IsPreComputed = true }); - - // Act & Assert - if (expectedExceptionType == null) - { - using var instance = new ShellDescriptorSyncHosted(_scopeFactory, config, _logger); - } - else - { - Assert.Throws(expectedExceptionType, () => - { - using var instance = new ShellDescriptorSyncHosted(_scopeFactory, config, _logger); - }); - } - } - - [Fact] - public async Task RunOnceAsync_Should_Not_Invoke_SyncShellDescriptorsAsync_When_PreComputed_Is_False() - { - // Arrange - var config = Options.Create(new AasRegistryPreComputed - { - ShellDescriptorCron = ValidCron, - IsPreComputed = false - }); - - var service = new ShellDescriptorSyncHosted(_scopeFactory, config, _logger); - - // Act - await service.RunOnceAsync(CancellationToken.None); - - // Assert - await _syncService.DidNotReceive().SyncShellDescriptorsAsync(Arg.Any()); - - _logger.Received().Log( - LogLevel.Information, - Arg.Any(), - Arg.Is(o => o.ToString()!.Contains("Skipping ShellDescriptor sync as Pre-Computed set to false.")), - Arg.Any(), - Arg.Any>() - ); - } - -} diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ServiceConfiguration/ConfigurationWarningLoggerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ServiceConfiguration/ConfigurationWarningLoggerTests.cs new file mode 100644 index 00000000..79ae5058 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ServiceConfiguration/ConfigurationWarningLoggerTests.cs @@ -0,0 +1,54 @@ +using AAS.TwinEngine.DataEngine.ServiceConfiguration; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +using NSubstitute; + +namespace AAS.TwinEngine.DataEngine.UnitTests.ServiceConfiguration; + +public class ConfigurationWarningLoggerTests +{ + [Fact] + public void LogDeprecatedSections_ShouldLogWarning_WhenPreComputedSectionExists() + { + // Arrange + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + [$"{ConfigurationWarningLogger.PreComputedSection}:ShellDescriptorCron"] = "0 */3 * * * *" + }) + .Build(); + var logger = Substitute.For(); + + // Act + ConfigurationWarningLogger.LogDeprecatedSections(configuration, logger); + + // Assert + logger.Received().Log( + LogLevel.Warning, + Arg.Any(), + Arg.Is(state => state.ToString()!.Contains(ConfigurationWarningLogger.PreComputedSection)), + Arg.Any(), + Arg.Any>()); + } + + [Fact] + public void LogDeprecatedSections_ShouldNotLogWarning_WhenPreComputedSectionIsMissing() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var logger = Substitute.For(); + + // Act + ConfigurationWarningLogger.LogDeprecatedSections(configuration, logger); + + // Assert + logger.DidNotReceive().Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } +} diff --git a/source/AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj b/source/AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj index 398019d7..5d00e2f9 100644 --- a/source/AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj +++ b/source/AAS.TwinEngine.DataEngine/AAS.TwinEngine.DataEngine.csproj @@ -22,7 +22,6 @@ - diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Config/AasRegistryPreComputed.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Config/AasRegistryPreComputed.cs deleted file mode 100644 index 0c4a666f..00000000 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Config/AasRegistryPreComputed.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Config; - -public class AasRegistryPreComputed -{ - public const string Section = "AasRegistryPreComputed"; - - public required string ShellDescriptorCron { get; set; } - - public required bool IsPreComputed { get; set; } -} diff --git a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Services/ShellDescriptorSyncHosted.cs b/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Services/ShellDescriptorSyncHosted.cs deleted file mode 100644 index edbac98c..00000000 --- a/source/AAS.TwinEngine.DataEngine/Infrastructure/Providers/AasRegistryProvider/Services/ShellDescriptorSyncHosted.cs +++ /dev/null @@ -1,76 +0,0 @@ -using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; -using AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Config; - -using Cronos; - -using Microsoft.Extensions.Options; - -namespace AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Services; - -public class ShellDescriptorSyncHosted( - IServiceScopeFactory scopeFactory, - IOptions config, - ILogger logger) : BackgroundService -{ - private readonly CronExpression _cronExpression = CronExpression.Parse(config.Value.ShellDescriptorCron, CronFormat.IncludeSeconds); - private readonly bool _isPreComputed = config.Value.IsPreComputed; - private readonly TimeZoneInfo _timeZone = TimeZoneInfo.Local; - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - logger.LogInformation("ShellDescriptor Cron sync started."); - - while (!stoppingToken.IsCancellationRequested) - { - var next = _cronExpression.GetNextOccurrence(DateTimeOffset.UtcNow, _timeZone); - if (!next.HasValue) - { - logger.LogWarning("Cron expression returned no next occurrence."); - break; - } - - var delay = next.Value - DateTimeOffset.UtcNow; - if (delay > TimeSpan.Zero) - { - try - { - await Task.Delay(delay, stoppingToken).ConfigureAwait(false); - } - catch (TaskCanceledException ex) - { - logger.LogInformation(ex, "Task delay was canceled. Stopping execution loop."); - break; - } - } - - try - { - await RunOnceAsync(stoppingToken).ConfigureAwait(false); - } - catch (HttpRequestException) - { - throw new ResourceNotFoundException(); - } - catch (TaskCanceledException) - { - throw new RequestTimeoutException(); - } - } - } - - public async Task RunOnceAsync(CancellationToken cancellationToken) - { - if (!_isPreComputed) - { - logger.LogInformation("Skipping ShellDescriptor sync as Pre-Computed set to false."); - return; - } - - using var scope = scopeFactory.CreateScope(); - var syncService = scope.ServiceProvider.GetRequiredService(); - - await syncService.SyncShellDescriptorsAsync(cancellationToken).ConfigureAwait(false); - logger.LogInformation("ShellDescriptor sync completed at {Time}", DateTimeOffset.UtcNow); - } -} diff --git a/source/AAS.TwinEngine.DataEngine/Program.cs b/source/AAS.TwinEngine.DataEngine/Program.cs index ff0b43a6..828d0fa5 100644 --- a/source/AAS.TwinEngine.DataEngine/Program.cs +++ b/source/AAS.TwinEngine.DataEngine/Program.cs @@ -47,6 +47,8 @@ public static async Task Main(string[] args) var app = builder.Build(); + ConfigurationWarningLogger.LogDeprecatedSections(app.Configuration, app.Logger); + _ = app.MapHealthChecks("/healthz"); using (var scope = app.Services.CreateScope()) diff --git a/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/ConfigurationWarningLogger.cs b/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/ConfigurationWarningLogger.cs new file mode 100644 index 00000000..268e8b7c --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/ConfigurationWarningLogger.cs @@ -0,0 +1,18 @@ +namespace AAS.TwinEngine.DataEngine.ServiceConfiguration; + +public static class ConfigurationWarningLogger +{ + public const string PreComputedSection = "AasRegistryPreComputed"; + + public static void LogDeprecatedSections(IConfiguration configuration, ILogger logger) + { + if (!configuration.GetSection(PreComputedSection).Exists()) + { + return; + } + + logger.LogWarning( + "Detected deprecated configuration section '{SectionName}'. The precomputed flow has been removed and this section is ignored. Please remove it from your configuration.", + PreComputedSection); + } +} diff --git a/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs b/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs index 18042452..aaaec19e 100644 --- a/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs +++ b/source/AAS.TwinEngine.DataEngine/ServiceConfiguration/InfrastructureDependencyInjectionExtensions.cs @@ -10,7 +10,6 @@ using AAS.TwinEngine.DataEngine.Infrastructure.Http.Config; using AAS.TwinEngine.DataEngine.Infrastructure.Http.Extensions; using AAS.TwinEngine.DataEngine.Infrastructure.Monitoring; -using AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Config; using AAS.TwinEngine.DataEngine.Infrastructure.Providers.AasRegistryProvider.Services; using AAS.TwinEngine.DataEngine.Infrastructure.Providers.PluginDataProvider.Config; using AAS.TwinEngine.DataEngine.Infrastructure.Providers.PluginDataProvider.Helper; @@ -61,9 +60,7 @@ public static void ConfigureInfrastructure(this IServiceCollection services, ICo _ = services.Configure(HttpRetryPolicyOptions.TemplateProvider, configuration.GetSection($"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.TemplateProvider}")); _ = services.Configure(HttpRetryPolicyOptions.SubmodelDescriptorProvider, configuration.GetSection($"{HttpRetryPolicyOptions.Section}:{HttpRetryPolicyOptions.SubmodelDescriptorProvider}")); _ = services.Configure(configuration.GetSection(HttpRetryPolicyOptions.Section)); - _ = services.Configure(configuration.GetSection(AasRegistryPreComputed.Section)); _ = services.Configure(configuration.GetSection(MultiPluginConflictOptions.Section)); _ = services.AddSingleton(); - _ = services.AddHostedService(); } } diff --git a/source/AAS.TwinEngine.DataEngine/appsettings.development.json b/source/AAS.TwinEngine.DataEngine/appsettings.development.json index 071ae0f2..659c3b25 100644 --- a/source/AAS.TwinEngine.DataEngine/appsettings.development.json +++ b/source/AAS.TwinEngine.DataEngine/appsettings.development.json @@ -35,10 +35,6 @@ "AasxExportOptions": { "RootFolder": "aasx" }, - "AasRegistryPreComputed": { - "ShellDescriptorCron": "0 */3 * * * *", - "IsPreComputed": true - }, "TemplateMappingRules": { "SubmodelTemplateMappings": [ { diff --git a/source/AAS.TwinEngine.DataEngine/appsettings.json b/source/AAS.TwinEngine.DataEngine/appsettings.json index 3c8b7ace..6e3d5510 100644 --- a/source/AAS.TwinEngine.DataEngine/appsettings.json +++ b/source/AAS.TwinEngine.DataEngine/appsettings.json @@ -35,10 +35,6 @@ "AasxExportOptions": { "RootFolder": "aasx" }, - "AasRegistryPreComputed": { - "ShellDescriptorCron": "0 */3 * * * *", - "IsPreComputed": false - }, "TemplateMappingRules": { "SubmodelTemplateMappings": [ { diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml index a4a0ae16..329e1398 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/docker-compose.yml @@ -59,8 +59,6 @@ services: - Semantics__SubmodelElementIndexContextPrefix=_aastwinengineindex_ - Semantics__InternalSemanticId=InternalSemanticId - AasxOptions__RootFolder=aasx - - AasRegistryPreComputed__ShellDescriptorCron=0 */3 * * * * - - AasRegistryPreComputed__IsPreComputed=false - MultiPluginConflictOption__HandlingMode=TakeFirst - PluginConfig__Plugins__0__PluginName=Plugin - PluginConfig__Plugins__0__PluginUrl=http://twinengine-plugin:8080