diff --git a/FunderMaps.sln b/FunderMaps.sln index 527d62389..4bc26ef17 100644 --- a/FunderMaps.sln +++ b/FunderMaps.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunderMaps.Portal", "src\Fu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.BatchNode", "src\FunderMaps.BatchNode\FunderMaps.BatchNode.csproj", "{532227C4-BAD4-46AB-B45D-65F15317AFFA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.Cli", "src\FunderMaps.Cli\FunderMaps.Cli.csproj", "{ACDD237D-7C7E-425B-88CD-EF90A0037505}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {532227C4-BAD4-46AB-B45D-65F15317AFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU {532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.Build.0 = Release|Any CPU + {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -106,6 +112,7 @@ Global {2A345D40-2842-4AAA-A552-630FAD97B9D5} = {0219D434-DD4F-4187-ADED-8CD4A62E90F1} {CF0E7A1A-20A8-4A15-8B57-D7218B9F33D1} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9} {532227C4-BAD4-46AB-B45D-65F15317AFFA} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9} + {ACDD237D-7C7E-425B-88CD-EF90A0037505} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CDA50611-E011-4C1E-8823-7A6943652DCE} diff --git a/contrib/FunderMapsCli/.gitignore b/contrib/FunderMapsCli/.gitignore deleted file mode 100644 index 4b2410fb2..000000000 --- a/contrib/FunderMapsCli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Protos diff --git a/contrib/FunderMapsCli/Makefile b/contrib/FunderMapsCli/Makefile deleted file mode 100644 index 88b9e10ce..000000000 --- a/contrib/FunderMapsCli/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -all: protos - -protos: - python3 -m grpc_tools.protoc -I ../../src/FunderMaps.Infrastructure/ --python_out=. --grpc_python_out=. ../../src/FunderMaps.Infrastructure/Protos/*.proto diff --git a/contrib/FunderMapsCli/batch_client.py b/contrib/FunderMapsCli/batch_client.py deleted file mode 100755 index fd5f59c3c..000000000 --- a/contrib/FunderMapsCli/batch_client.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2020 FunderMaps B.V. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" FunderMaps Commandline Interface """ - -import logging -import json -import sys - -import grpc - -from Protos import protocol_pb2 -from Protos import batch_pb2 -from Protos import batch_pb2_grpc -from google.protobuf import empty_pb2 - -GRPC_HOST = 'localhost:5000' - - -def _make_protocol(): - proto_version = 0xa1 - return protocol_pb2.FunderMapsProtocol( - version=proto_version, - user_agent='batch_client.py') - - -def _print_response(response): - if not response.task_id: - print("Server returned an error") - else: - print(f"Task ID : {response.task_id}") - - -def bundle_create(stub, bundle): - print("-------------- Bundle --------------") - print(f"Bundle : {bundle}") - - request = batch_pb2.EnqueueRequest( - protocol=_make_protocol(), - name="bundle_building", - payload=json.dumps({ - "BundleId": bundle, - "Formats": [0, 1]}) - ) - - response = stub.Enqueue(request) - _print_response(response) - - -def bundle_batch(stub): - print("-------------- Bundle --------------") - print(f"Bundle : all") - - request = batch_pb2.EnqueueRequest( - protocol=_make_protocol(), - name="bundle_batch", - payload=json.dumps({ - "Formats": [0, 1]}) - ) - - response = stub.Enqueue(request) - _print_response(response) - - -def foobar(stub): - print("-------------- Foobar --------------") - - request = batch_pb2.EnqueueRequest( - protocol=_make_protocol(), - name="foobar") - - response = stub.Enqueue(request) - _print_response(response) - - -def email_send(stub, recp): - print("-------------- Email --------------") - - request = batch_pb2.EnqueueRequest( - protocol=_make_protocol(), - name="incident_notify", - payload=json.dumps({ - 'recipients': [recp], - })) - - response = stub.Enqueue(request) - _print_response(response) - - -def get_status(stub): - print("-------------- Status --------------") - - response = stub.Status(empty_pb2.Empty()) - print(f"Jobs succeeded: {response.jobs_succeeded}") - print(f"Jobs failed: {response.jobs_failed}") - - -def run(): - # NOTE: .close() is possible on a channel and should be used in circumstances - # in which the with statement does not fit the needs of the code. - creds = grpc.ssl_channel_credentials() - with grpc.secure_channel(GRPC_HOST, creds) as channel: - # with grpc.insecure_channel(GRPC_HOST) as channel: - stub = batch_pb2_grpc.BatchStub(channel) - if len(sys.argv) == 1: - print('Command required') - return - - if sys.argv[1] == 'foobar': - foobar(stub) - elif sys.argv[1] == 'email': - email_send(stub, sys.argv[2]) - elif sys.argv[1] == 'bundle': - if sys.argv[2] == 'all': - bundle_batch(stub) - else: - bundle_create(stub, sys.argv[2]) - elif sys.argv[1] == 'status': - get_status(stub) - else: - print('Command required') - - -if __name__ == '__main__': - logging.basicConfig() - run() diff --git a/src/FunderMaps.BatchNode/Command/CommandInfo.cs b/src/FunderMaps.BatchNode/Command/CommandInfo.cs index a92fe57b4..e2df6683d 100644 --- a/src/FunderMaps.BatchNode/Command/CommandInfo.cs +++ b/src/FunderMaps.BatchNode/Command/CommandInfo.cs @@ -6,7 +6,7 @@ namespace FunderMaps.BatchNode.Command /// /// Command descriptor. /// - public class CommandInfo + public class CommandInfo // TODO: Move to core { /// /// Command filename. diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs index f6084a1b5..c8c0fd94f 100644 --- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs +++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs @@ -14,7 +14,7 @@ namespace FunderMaps.BatchNode.Jobs.BundleBuilder /// /// Bundle batch job entry. /// - internal class BundleBatch : CommandTask + internal class BundleBatch : CommandTask // TODO: Rename to BundleAllJob { private const string TaskName = "BUNDLE_BATCH"; diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs index 12a5c78bf..d79ceb5f0 100644 --- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs +++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs @@ -7,7 +7,7 @@ namespace FunderMaps.BatchNode.Jobs.BundleBuilder /// /// Context for executing a bundle build task. /// - public class BundleBuildingContext + public class BundleBuildingContext // TODO: Remove, this is moved to core { /// /// The id of the bundle to process. diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs index 4931af815..296ea271f 100644 --- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs +++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs @@ -32,9 +32,9 @@ public BundleLayerSource(Bundle bundle, Layer layer, string workspace) layerOutputName = layer.Slug; // Select layer field as follows: - // * If no column is specified then select everything from the layer - // * If wildcard is found, then only use wildcard. - // * If no geometry column was found, then add one. + // - If no column is specified then select everything from the layer + // - If wildcard is found, then only use wildcard. + // - If no geometry column was found, then add one. List columns = new(configuration.ColumnNames); if (columns.Count == 0) { @@ -65,4 +65,4 @@ public override void Imbue(CommandInfo commandInfo) commandInfo.ArgumentList.Add(layerOutputName); } } -} \ No newline at end of file +} diff --git a/src/FunderMaps.Cli/CommandDriver.cs b/src/FunderMaps.Cli/CommandDriver.cs new file mode 100644 index 000000000..f29dfb5ad --- /dev/null +++ b/src/FunderMaps.Cli/CommandDriver.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace FunderMaps.Cli.Drivers +{ + internal abstract class CommandDriver + { + protected static Task ResolveSelfScope(IServiceProvider services, Func shadowFunc) + { + var scopeFactory = services.GetRequiredService(); + using var serviceScope = scopeFactory.CreateScope(); + var serviceProvider = serviceScope.ServiceProvider; + TDriver driver = serviceProvider.GetService(); + return shadowFunc(driver); + } + } +} diff --git a/src/FunderMaps.Cli/DriverHandler.cs b/src/FunderMaps.Cli/DriverHandler.cs new file mode 100644 index 000000000..27a71fc3d --- /dev/null +++ b/src/FunderMaps.Cli/DriverHandler.cs @@ -0,0 +1,38 @@ +using System; +using System.CommandLine.Invocation; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace FunderMaps.Cli +{ + /// + /// Driver command handler. + /// + public static class DriverHandler + { + /// + /// Create delegate with default parametes. + /// + /// Handler action. + /// See . + public static ICommandHandler InstantiateDriver(Func action) + => CommandHandler.Create(action); + + /// + /// Create delegate with default parametes. + /// + /// Handler action. + /// See . + public static ICommandHandler InstantiateDriver(Func action) + => CommandHandler.Create(action); + + /// + /// Create delegate with default parametes. + /// + /// Handler action. + /// See . + public static ICommandHandler InstantiateDriver(Func action) + => CommandHandler.Create(action); + } +} diff --git a/src/FunderMaps.Cli/Drivers/BatchDriver.cs b/src/FunderMaps.Cli/Drivers/BatchDriver.cs new file mode 100644 index 000000000..e32ac88e0 --- /dev/null +++ b/src/FunderMaps.Cli/Drivers/BatchDriver.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using FunderMaps.Core.Interfaces; +using FunderMaps.Core.MapBundle; +using FunderMaps.Core.Threading; +using FunderMaps.Core.Types; +using Microsoft.Extensions.Hosting; + +namespace FunderMaps.Cli.Drivers +{ + /// + /// gRPC channel factory. + /// + internal class BatchDriver : CommandDriver + { + private readonly IBatchService _batchService; + private readonly IBundleService _bundleService; + + /// + /// Create new instance. + /// + public BatchDriver(IBatchService batchService, IBundleService bundleService) + => (_batchService, _bundleService) = (batchService, bundleService); + + private async Task StatusAsync(CancellationToken token = default) + { + DispatchManagerStatus status = await _batchService.StatusAsync(token); + Console.WriteLine($"Jobs failed: {status.JobsFailed}"); + Console.WriteLine($"Jobs succeeded: {status.JobsSucceeded}"); + } + + private async Task BuildBundleAsync(IEnumerable bundleIdList, CancellationToken token = default) + { + foreach (var bundleId in bundleIdList) + { + await _bundleService.BuildAsync(new BundleBuildingContext + { + BundleId = bundleId, + Formats = new List { GeometryFormat.GeoPackage }, + }); + } + } + + private async Task BuildAllAsync(CancellationToken token = default) + { + await _bundleService.BuildAllAsync(new BundleBuildingContext + { + Formats = new List { GeometryFormat.GeoPackage }, + }); + } + + #region Factory Methods + + public static Task StatusAsync(FileInfo config, IHost host) + => ResolveSelfScope(host.Services, self => self.StatusAsync()); + + public static Task BuildBundleAsync(FileInfo config, IHost host, IEnumerable bundleId) + => ResolveSelfScope(host.Services, self => self.BuildBundleAsync(bundleId)); + + public static Task BuildAllAsync(FileInfo config, IHost host) + => ResolveSelfScope(host.Services, self => self.BuildAllAsync()); + + #endregion Factory Methods + } +} diff --git a/src/FunderMaps.Cli/FunderMaps.Cli.csproj b/src/FunderMaps.Cli/FunderMaps.Cli.csproj new file mode 100644 index 000000000..0d4f3a5ec --- /dev/null +++ b/src/FunderMaps.Cli/FunderMaps.Cli.csproj @@ -0,0 +1,23 @@ + + + + + Exe + net5.0 + + + + $(SolutionDir)DocumentationFunderMapsCli.xml + + + + + + + + + + + + + diff --git a/src/FunderMaps.Cli/Program.cs b/src/FunderMaps.Cli/Program.cs new file mode 100644 index 000000000..e5a5527f9 --- /dev/null +++ b/src/FunderMaps.Cli/Program.cs @@ -0,0 +1,110 @@ +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Hosting; +using Microsoft.Extensions.Hosting; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using System; +using System.IO; +using FunderMaps.Cli.Drivers; +using Microsoft.Extensions.Logging; + +namespace FunderMaps.Cli +{ + /// + /// Application entry. + /// + public static class Program + { + /// + /// Application entry point. + /// + /// Commandline arguments. + public static Task Main(string[] args) + => CreateHostBuilder(args).Build().InvokeAsync(args); + + /// + /// Build a host and run the application. + /// + /// + /// The signature of this method should not be changed. + /// External tooling expects this function be present. + /// + /// Commandline arguments. + /// See . + public static CommandLineBuilder CreateHostBuilder(string[] args) + => BuildCommandLine() + .AddLocalCommands() + .UseHost(_ => Host.CreateDefaultBuilder(), + host => + { + host.ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddConsole(); + logging.SetMinimumLevel(LogLevel.Error); + }); + host.ConfigureServices(services => + { + // Configure FunderMaps services. + services.AddFunderMapsCoreServices(); + services.AddFunderMapsInfrastructureServices(); + + services.AddTransient(); + }); + }) + .UseDefaults(); + + /// + /// Build commandline builder. + /// + /// See . + private static CommandLineBuilder BuildCommandLine() + { + RootCommand root = new(); + root.AddGlobalOption(new Option(new[] { "-c", "--config" }, "An option whose argument is parsed as a FileInfo")); + root.Description = "FunderMaps CommandLine Interface"; + + return new(root); + } + + /// + /// Register local commands. + /// + /// See . + private static CommandLineBuilder AddLocalCommands(this CommandLineBuilder rootBuilder) + { + Command command = new("batch", "Run batch operations"); + command.AddAlias("b"); + command.AddOption(new Option(new[] { "-H", "--host" }, "The batch host to execute the command on")); + command.AddOption(new Option("--ignore-cert", "Ignore TLS certificate checks")); + + { + Command subcommand = new("build-bundle", "The maplayer bundle(s)"); + subcommand.AddArgument(new Argument("bundleId", "(Optional) Bundle identifiers to build") + { + Arity = ArgumentArity.OneOrMore, + }); + subcommand.Handler = DriverHandler.InstantiateDriver>(BatchDriver.BuildBundleAsync); + command.AddCommand(subcommand); + } + + { + Command subcommand = new("build-bundle-all", "The all maplayer bundles"); + subcommand.Handler = DriverHandler.InstantiateDriver(BatchDriver.BuildAllAsync); + command.AddCommand(subcommand); + } + + { + Command subcommand = new("status", "Print the batch status reports"); + subcommand.Handler = DriverHandler.InstantiateDriver(BatchDriver.StatusAsync); + command.AddCommand(subcommand); + } + + rootBuilder.AddCommand(command); + return rootBuilder; + } + } +} diff --git a/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs b/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs index 5f8434ef3..5286ede8c 100644 --- a/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs +++ b/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs @@ -7,7 +7,7 @@ namespace FunderMaps.Core.DataAnnotations /// Guid validation attribute. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] - public class GuidAttribute : ValidationAttribute + public class GuidAttribute : ValidationAttribute // TODO: Remove { /// /// Returns true if the value is an non empty guid. diff --git a/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs b/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs index d416d4257..183b6821d 100644 --- a/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs +++ b/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using FunderMaps.Core.Components; using FunderMaps.Core.Interfaces; +using FunderMaps.Core.MapBundle; using FunderMaps.Core.Notification; using FunderMaps.Core.Services; using FunderMaps.Core.Threading; @@ -88,6 +89,7 @@ public static IServiceCollection AddFunderMapsCoreServices(this IServiceCollecti // Register core services in DI container. services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Register core services in DI container. // NOTE: These services take time to initialize are used more often. Registering diff --git a/src/FunderMaps.Core/Interfaces/IBatchService.cs b/src/FunderMaps.Core/Interfaces/IBatchService.cs index c02db69a4..d90786d6a 100644 --- a/src/FunderMaps.Core/Interfaces/IBatchService.cs +++ b/src/FunderMaps.Core/Interfaces/IBatchService.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using FunderMaps.Core.Threading; namespace FunderMaps.Core.Interfaces { @@ -14,10 +15,16 @@ public interface IBatchService /// /// Name of task to run. /// Task payload. - /// Canellation token. + /// Cancellation token. /// Task identifier if task was enqueued. Task EnqueueAsync(string name, object value, CancellationToken token = default); + /// + /// Batch service processing status. + /// + /// Cancellation token. + Task StatusAsync(CancellationToken token = default); + /// /// Test the batch service backend. /// diff --git a/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs b/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs new file mode 100644 index 000000000..dda453cb7 --- /dev/null +++ b/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs @@ -0,0 +1,22 @@ +using FunderMaps.Core.Types; +using System; +using System.Collections.Generic; + +namespace FunderMaps.Core.MapBundle +{ + /// + /// Context for executing a bundle build task. + /// + public record BundleBuildingContext + { + /// + /// The id of the bundle to process. + /// + public Guid BundleId { get; init; } + + /// + /// Contains all formats we wish to export. + /// + public IEnumerable Formats { get; set; } + } +} diff --git a/src/FunderMaps.Core/MapBundle/BundleHub.cs b/src/FunderMaps.Core/MapBundle/BundleHub.cs new file mode 100644 index 000000000..67b588702 --- /dev/null +++ b/src/FunderMaps.Core/MapBundle/BundleHub.cs @@ -0,0 +1,40 @@ +using FunderMaps.Core.Abstractions; +using FunderMaps.Core.Interfaces; +using System; +using System.Threading.Tasks; + +#pragma warning disable CA1812 // Internal class is never instantiated +namespace FunderMaps.Core.MapBundle +{ + /// + /// Process bundles. + /// + internal class BundleHub : AppServiceBase, IBundleService + { + private const string TaskBuildName = "BUNDLE_BUILDING"; + private const string TaskBuildAllName = "BUNDLE_BATCH"; + + private readonly IBatchService _batchService; + + /// + /// Create new instance. + /// + public BundleHub(AppContext appContext, IBatchService batchService) + => (AppContext, _batchService) = (appContext, batchService); + + /// + /// Build a bundle. + /// + /// Bundle building context. + public Task BuildAsync(BundleBuildingContext context) + => _batchService.EnqueueAsync(TaskBuildName, context, AppContext.CancellationToken); + + /// + /// Build all bundles. + /// + /// Bundle building context. + public Task BuildAllAsync(BundleBuildingContext context) + => _batchService.EnqueueAsync(TaskBuildAllName, context, AppContext.CancellationToken); + } +} +#pragma warning restore CA1812 // Internal class is never instantiated diff --git a/src/FunderMaps.Core/MapBundle/IBundleService.cs b/src/FunderMaps.Core/MapBundle/IBundleService.cs new file mode 100644 index 000000000..7ffb9be3e --- /dev/null +++ b/src/FunderMaps.Core/MapBundle/IBundleService.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; + +namespace FunderMaps.Core.MapBundle +{ + /// + /// Bundle service. + /// + public interface IBundleService + { + /// + /// Build a bundle. + /// + /// Bundle building context. + Task BuildAsync(BundleBuildingContext context); + + /// + /// Build all bundles. + /// + /// Bundle building context. + Task BuildAllAsync(BundleBuildingContext context); + } +} diff --git a/src/FunderMaps.Core/Services/NullBatchService.cs b/src/FunderMaps.Core/Services/NullBatchService.cs index 6c6b8e0f2..e67e75eda 100644 --- a/src/FunderMaps.Core/Services/NullBatchService.cs +++ b/src/FunderMaps.Core/Services/NullBatchService.cs @@ -1,4 +1,5 @@ using FunderMaps.Core.Interfaces; +using FunderMaps.Core.Threading; using System; using System.Threading; using System.Threading.Tasks; @@ -12,14 +13,12 @@ namespace FunderMaps.Core.Services internal class NullBatchService : IBatchService { public Task EnqueueAsync(string name, object value, CancellationToken token = default) - { - return Task.FromResult(Guid.NewGuid()); - } + => Task.FromResult(Guid.NewGuid()); - public Task TestService() - { - return Task.CompletedTask; - } + public Task StatusAsync(CancellationToken token = default) + => Task.FromResult(new DispatchManagerStatus()); + + public Task TestService() => Task.CompletedTask; } } #pragma warning restore CA1812 // Internal class is never instantiated diff --git a/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs b/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs index 68f191623..7316cd654 100644 --- a/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs +++ b/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs @@ -12,7 +12,7 @@ namespace FunderMaps.Infrastructure.BatchClient /// /// Client connector to the batch node. /// - internal class BatchClient : IBatchService // TODO: Inherit from AppServiceBase + internal class BatchClient : IBatchService // TODO: Inherit from AppServiceBase // TODO: Rename to BatchProxy { private const string UserAgent = "FunderMaps.Infrastructure"; @@ -29,7 +29,7 @@ public BatchClient(ChannelFactory channelFactory) /// /// Name of task to run. /// Task payload. - /// Canellation token. + /// Cancellation token. public async Task EnqueueAsync(string name, object value = null, CancellationToken token = default) { EnqueueRequest request = new() @@ -39,19 +39,19 @@ public async Task EnqueueAsync(string name, object value = null, Cancellat Payload = JsonSerializer.Serialize(value), }; - var client = new Batch.BatchClient(_channelFactory.RemoteChannel); + Batch.BatchClient client = new(_channelFactory.RemoteChannel); EnqueueResponse response = await client.EnqueueAsync(request, null, null, token); return Guid.Parse(response.TaskId); } /// - /// Job processing status. + /// Batch service processing status. /// - /// Canellation token. + /// Cancellation token. public async Task StatusAsync(CancellationToken token = default) { - var client = new Batch.BatchClient(_channelFactory.RemoteChannel); + Batch.BatchClient client = new(_channelFactory.RemoteChannel); StatusResponse response = await client.StatusAsync(new(), null, null, token); return new() @@ -64,10 +64,7 @@ public async Task StatusAsync(CancellationToken token = d /// /// Test the batch service backend. /// - public async Task TestService() - { - await StatusAsync(); - } + public async Task TestService() => await StatusAsync(); } } #pragma warning restore CA1812 // Internal class is never instantiated diff --git a/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs b/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs index 0e2666632..62028c3b4 100644 --- a/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs +++ b/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs @@ -5,16 +5,16 @@ namespace FunderMaps.Infrastructure.BatchClient /// /// Options for the batch service. /// - public sealed class BatchOptions + public sealed record BatchOptions { /// /// Base service uri for batch service. /// - public Uri ServiceUri { get; set; } + public Uri ServiceUri { get; init; } = new("http://localhost"); /// /// Name of the blob storage. /// - public bool TlsValidate { get; set; } + public bool TlsValidate { get; set; } = true; } } diff --git a/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs b/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs index 596db08c9..9a200ee5c 100644 --- a/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs +++ b/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs @@ -25,9 +25,15 @@ public ChannelFactory(IOptions options, ILoggerFactory loggerFacto { _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); - // NOTE: The httpHandler is disposed when the channel is disposed. + // NOTE: The httpHandler is disposed when the GrpcChannel is disposed. HttpClientHandler httpHandler = new(); + // There is no TLS when connecting over HTTP, disable TLS verification. + if (_options.ServiceUri.Scheme == "http") + { + _options.TlsValidate = false; + } + if (!_options.TlsValidate) { httpHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;