baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
10
src/Dapr/Common/AssemblyInfo.cs
Normal file
10
src/Dapr/Common/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// <copyright file="AssemblyInfo.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MUnique.OpenMU.Dapr.Common")]
|
||||
10
src/Dapr/Common/ConfigurationChangeArguments.cs
Normal file
10
src/Dapr/Common/ConfigurationChangeArguments.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// <copyright file="ConfigurationChangeArguments.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the change notifications of <see cref="ConfigurationChangePublisher"/>.
|
||||
/// </summary>
|
||||
public record class ConfigurationChangeArguments(Type Type, Guid Id, object? Configuration);
|
||||
67
src/Dapr/Common/ConfigurationChangePublisher.cs
Normal file
67
src/Dapr/Common/ConfigurationChangePublisher.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// <copyright file="ConfigurationChangePublisher.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using global::Dapr.Client;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Publisher for configuration changes.
|
||||
/// Changes are published to the configured pub/sub Dapr component.
|
||||
/// </summary>
|
||||
public class ConfigurationChangePublisher : IConfigurationChangePublisher
|
||||
{
|
||||
private readonly DaprClient _daprClient;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigurationChangePublisher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="daprClient">The dapr client.</param>
|
||||
public ConfigurationChangePublisher(DaprClient daprClient)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a changed, previously existing configuration.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the configuration.</param>
|
||||
/// <param name="id">The identifier of the changed configuration.</param>
|
||||
/// <param name="configuration">The changed configuration.</param>
|
||||
public Task ConfigurationChangedAsync(Type type, Guid id, object configuration)
|
||||
{
|
||||
return this._daprClient.PublishEventAsync(
|
||||
"pubsub",
|
||||
nameof(this.ConfigurationChangedAsync),
|
||||
new ConfigurationChangeArguments(type, id, configuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes an added configuration.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the configuration.</param>
|
||||
/// <param name="id">The identifier of the added configuration.</param>
|
||||
/// <param name="configuration">The added configuration.</param>
|
||||
public Task ConfigurationAddedAsync(Type type, Guid id, object configuration)
|
||||
{
|
||||
return this._daprClient.PublishEventAsync(
|
||||
"pubsub",
|
||||
nameof(this.ConfigurationAddedAsync),
|
||||
new ConfigurationChangeArguments(type, id, configuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a removed, previously existing configuration.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the configuration.</param>
|
||||
/// <param name="id">The identifier of the removed configuration.</param>
|
||||
public Task ConfigurationRemovedAsync(Type type, Guid id)
|
||||
{
|
||||
return this._daprClient.PublishEventAsync(
|
||||
"pubsub",
|
||||
nameof(this.ConfigurationRemovedAsync),
|
||||
new ConfigurationChangeArguments(type, id, null));
|
||||
}
|
||||
}
|
||||
70
src/Dapr/Common/DaprService.cs
Normal file
70
src/Dapr/Common/DaprService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
// <copyright file="DaprService.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||
using MUnique.OpenMU.Persistence.EntityFramework.Json;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to create an <see cref="WebApplicationBuilder"/> which predefined common
|
||||
/// services for all of our service applications.
|
||||
/// </summary>
|
||||
public static class DaprService
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebApplicationBuilder" /> class with preconfigured defaults.
|
||||
/// </summary>
|
||||
/// <param name="serviceName">Name of the service, used for the OpenAPI and OpenTelemetry tracing.</param>
|
||||
/// <param name="args">Command line arguments.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="WebApplicationBuilder" />.
|
||||
/// </returns>
|
||||
public static WebApplicationBuilder CreateBuilder(string serviceName, string[] args)
|
||||
{
|
||||
JsonConverterRegistry.RegisterConverter(new LocalizedStringJsonConverter());
|
||||
JsonConverterRegistry.RegisterConverter(new BinaryAsHexJsonConverter());
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.WebHost.UseUrls($"http://*:8080");
|
||||
|
||||
var services = builder.Services;
|
||||
services.AddControllers();
|
||||
services.AddDaprClient();
|
||||
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(t =>
|
||||
{
|
||||
t.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddZipkinExporter(o => o.Endpoint = new Uri("http://zipkin:9411/api/v2/spans"));
|
||||
});
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = serviceName, Version = "v1" });
|
||||
});
|
||||
|
||||
services.AddSingleton<IDatabaseConnectionSettingProvider, SecretStoreDatabaseConnectionSettingsProvider>();
|
||||
|
||||
// Logging:
|
||||
builder.UseLoki(serviceName);
|
||||
builder.Logging.AddOpenTelemetry(options => options.AddOtlpExporter());
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
352
src/Dapr/Common/Extensions.cs
Normal file
352
src/Dapr/Common/Extensions.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
// <copyright file="Extensions.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
using Nito.AsyncEx.Synchronous;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Metrics;
|
||||
using Prometheus;
|
||||
using Serilog;
|
||||
using Serilog.Debugging;
|
||||
using Serilog.Events;
|
||||
using Serilog.Filters;
|
||||
using Serilog.Sinks.Grafana.Loki;
|
||||
|
||||
/// <summary>
|
||||
/// Common extensions for the building of daprized services.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="PersistenceContextProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="publishConfigChanges">If set to <c>true</c>, configuration changes are published to other Dapr services.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection AddPeristenceProvider(this IServiceCollection services, bool publishConfigChanges = false)
|
||||
{
|
||||
services.AddSingleton<IConfigurationChangeListener, ConfigurationChangeListener>();
|
||||
if (publishConfigChanges)
|
||||
{
|
||||
services.AddSingleton<IConfigurationChangePublisher, ConfigurationChangePublisher>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton(e => IConfigurationChangePublisher.None);
|
||||
}
|
||||
|
||||
return services
|
||||
.AddSingleton<IMigratableDatabaseContextProvider, PersistenceContextProvider>()
|
||||
.AddSingleton(s => (PersistenceContextProvider)s.GetService<IMigratableDatabaseContextProvider>()!)
|
||||
.AddSingleton(s => (IPersistenceContextProvider)s.GetService<IMigratableDatabaseContextProvider>()!)
|
||||
.AddSingleton(s => new Lazy<IPersistenceContextProvider>(s.GetRequiredService<IPersistenceContextProvider>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the plug in manager.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="plugInConfigurations">The plug in configurations.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection AddPlugInManager(this IServiceCollection services, ICollection<PlugInConfiguration> plugInConfigurations)
|
||||
{
|
||||
return services
|
||||
.AddSingleton(plugInConfigurations)
|
||||
.AddSingleton<PlugInManager>()
|
||||
.AddTransient<ReferenceHandler, ByDataSourceReferenceHandler>(provider =>
|
||||
{
|
||||
var persistenceContextProvider = provider.GetService<IPersistenceContextProvider>();
|
||||
var dataSource = new GameConfigurationDataSource(
|
||||
provider.GetService<ILogger<GameConfigurationDataSource>>()!,
|
||||
persistenceContextProvider!);
|
||||
var configId = persistenceContextProvider!.CreateNewConfigurationContext().GetDefaultGameConfigurationIdAsync(default).AsTask().WaitAndUnwrapException();
|
||||
dataSource.GetOwnerAsync(configId!.Value).AsTask().WaitAndUnwrapException();
|
||||
var referenceHandler = new ByDataSourceReferenceHandler(dataSource);
|
||||
return referenceHandler;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load the plug in configurations.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
/// <param name="plugInConfigurations">The list of plug in configurations, where the loaded configurations will be added.</param>
|
||||
public static async ValueTask TryLoadPlugInConfigurationsAsync(this IServiceProvider serviceProvider, List<PlugInConfiguration> plugInConfigurations)
|
||||
{
|
||||
if (serviceProvider.GetService<IMigratableDatabaseContextProvider>() is not { } persistenceContextProvider)
|
||||
{
|
||||
throw new Exception($"{nameof(IPersistenceContextProvider)} not registered.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
if (!await persistenceContextProvider.CanConnectToDatabaseAsync(cts.Token).ConfigureAwait(false)
|
||||
|| !await persistenceContextProvider.DatabaseExistsAsync(cts.Token).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var configs = await persistenceContextProvider.CreateNewTypedContext(typeof(PlugInConfiguration), false).GetAsync<PlugInConfiguration>().ConfigureAwait(false);
|
||||
plugInConfigurations.AddRange(configs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't load it yet, because the database is not initialized, we just return
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a persistent object as singleton to the services.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The base type of the persistent object.</typeparam>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="predicate">The predicate to select actual object.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection AddPersistentSingleton<T>(this IServiceCollection services, Func<T, bool>? predicate = null)
|
||||
where T : class
|
||||
{
|
||||
return services.AddPersistentSingleton<T, T>(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the persistent object as singleton to the services.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTarget">The target, exposed type of the persistent object, usually an interface.</typeparam>
|
||||
/// <typeparam name="TActual">The actual base type of the persistent object.</typeparam>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="predicate">The predicate to select the actual object.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection AddPersistentSingleton<TTarget, TActual>(this IServiceCollection services, Func<TActual, bool>? predicate = null)
|
||||
where TActual : class, TTarget
|
||||
where TTarget : class
|
||||
{
|
||||
return services.AddSingleton(s =>
|
||||
{
|
||||
if (s.GetService<IPersistenceContextProvider>() is not { } persistenceContextProvider)
|
||||
{
|
||||
throw new Exception($"{nameof(IPersistenceContextProvider)} not registered.");
|
||||
}
|
||||
|
||||
var objects = persistenceContextProvider.CreateNewConfigurationContext().GetAsync<TActual>().AsTask().WaitAndUnwrapException();
|
||||
return (TTarget)objects.First(predicate ?? (_ => true))!;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="ManagableServerRegistry"/> to the services.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection AddManageableServerRegistry(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ManagableServerRegistry>()
|
||||
.AddSingleton<IServerProvider>(s => s.GetService<ManagableServerRegistry>()!);
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the server to other daprized services by registering a <see cref="ManagableServerStatePublisher"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TServer">The type of the server.</typeparam>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <returns>The modified service collection.</returns>
|
||||
public static IServiceCollection PublishManageableServer<TServer>(this IServiceCollection services)
|
||||
where TServer : IManageableServer
|
||||
{
|
||||
services.AddSingleton<IManageableServer>(s => s.GetService<TServer>()!)
|
||||
.AddHostedService<ManagableServerStatePublisher>()
|
||||
.AddControllers().AddApplicationPart(typeof(ManageableServerController).Assembly);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the usage of logging to loki.
|
||||
/// </summary>
|
||||
/// <param name="builder">The web application builder.</param>
|
||||
/// <param name="serviceName">Name of the service.</param>
|
||||
/// <returns>The configured web application builder.</returns>
|
||||
public static WebApplicationBuilder UseLoki(this WebApplicationBuilder builder, string serviceName)
|
||||
{
|
||||
// We just want to transmit some static labels, as suggested in the best practice in the Loki documentation
|
||||
var includeLabels = new[] { "Account", "Character", "Connection", "ServiceName", "SourceContext" };
|
||||
|
||||
var logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.Enrich.WithProperty("ServiceName", serviceName)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo
|
||||
.GrafanaLoki(
|
||||
uri: "http://loki:3100",
|
||||
propertiesAsLabels: includeLabels)
|
||||
.WriteTo
|
||||
.Console(LogEventLevel.Information)
|
||||
.Filter.ByExcluding(Matching.FromSource("Microsoft")) // We don't want all of the ASP.NET logging, because that really keeps loki and the console pretty busy
|
||||
.CreateLogger();
|
||||
|
||||
SelfLog.Enable(Console.Error);
|
||||
|
||||
builder.Host.ConfigureLogging((_, loggingBuilder) => loggingBuilder.ClearProviders());
|
||||
builder.Host.UseSerilog(logger);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the open telemetry metrics.
|
||||
/// </summary>
|
||||
/// <param name="builder">The web application builder.</param>
|
||||
/// <param name="registry">The registry.</param>
|
||||
/// <returns>The configured web application builder.</returns>
|
||||
public static WebApplicationBuilder AddOpenTelemetryMetrics(this WebApplicationBuilder builder, MetricsRegistry registry)
|
||||
{
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.WithMetrics(x =>
|
||||
{
|
||||
x.AddMeter(registry.Meters.ToArray());
|
||||
x.AddPrometheusExporter();
|
||||
x.AddOtlpExporter();
|
||||
});
|
||||
builder.Services.AddHealthChecks().ForwardToPrometheus();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and configures the web application.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
/// <param name="addBlazor">If set to <c>true</c>, it configures the application to provide a blazor server app.</param>
|
||||
/// <returns>The built and configured web application.</returns>
|
||||
public static WebApplication BuildAndConfigure(this WebApplicationBuilder builder, bool addBlazor = false)
|
||||
{
|
||||
var pathBase = Environment.GetEnvironmentVariable("PATH_BASE");
|
||||
var useReverseProxy = !string.IsNullOrWhiteSpace(pathBase);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (useReverseProxy)
|
||||
{
|
||||
app.UsePathBase(pathBase!.TrimEnd('/'));
|
||||
app.UseForwardedHeaders();
|
||||
}
|
||||
|
||||
app.ConfigureDaprService(addBlazor);
|
||||
app.MapPrometheusScrapingEndpoint();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the web application as dapr service.
|
||||
/// </summary>
|
||||
/// <param name="app">The application.</param>
|
||||
/// <param name="addBlazor">If set to <c>true</c>, it configures the application to provide a blazor server app.</param>
|
||||
/// <returns>The configured web application.</returns>
|
||||
public static WebApplication ConfigureDaprService(this WebApplication app, bool addBlazor = false)
|
||||
{
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseCloudEvents();
|
||||
app.MapControllers();
|
||||
app.MapSubscribeHandler();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the completion of outstanding database updates.
|
||||
/// </summary>
|
||||
/// <param name="app">The application.</param>
|
||||
public static async Task WaitForUpdatedDatabaseAsync(this WebApplication app)
|
||||
{
|
||||
await app.WaitForDatabaseConnectionInitializationAsync().ConfigureAwait(false);
|
||||
await app.Services.GetService<PersistenceContextProvider>()!
|
||||
.WaitForUpdatedDatabaseAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for database connection (settings) initialization.
|
||||
/// </summary>
|
||||
/// <param name="app">The application.</param>
|
||||
public static async Task WaitForDatabaseConnectionInitializationAsync(this WebApplication app)
|
||||
{
|
||||
await app.Services.GetService<IDatabaseConnectionSettingProvider>()!
|
||||
.InitializeAsync(default)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the database secrets to be loaded and then for the database to be up-to-date.
|
||||
/// This is intended to be called from <see cref="Microsoft.Extensions.Hosting.IHostedLifecycleService.StartedAsync"/>
|
||||
/// which is called after the web server has already started, breaking the circular dependency where:
|
||||
/// the Dapr sidecar needs the app HTTP API to be up before it initializes its secret store,
|
||||
/// but the app needs Dapr secrets to connect to the database.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The service provider.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static async Task WaitForDatabaseInitializationAsync(this IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var dbConnectionProvider = serviceProvider.GetRequiredService<IDatabaseConnectionSettingProvider>();
|
||||
if (dbConnectionProvider.Initialization is { } initTask)
|
||||
{
|
||||
await initTask.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await serviceProvider.GetRequiredService<PersistenceContextProvider>()
|
||||
.WaitForUpdatedDatabaseAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the ip resolver to the collection, depending on the command line arguments
|
||||
/// and the <see cref="SystemConfiguration"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <param name="args">The arguments.</param>
|
||||
/// <returns>The <paramref name="serviceCollection"/>.</returns>
|
||||
public static IServiceCollection AddIpResolver(this IServiceCollection serviceCollection, string[] args)
|
||||
{
|
||||
return serviceCollection.AddSingleton(serviceProvider =>
|
||||
{
|
||||
(IpResolverType IpResolver, string? IpResolverParameter)? settings = default;
|
||||
try
|
||||
{
|
||||
var persistenceContextProvider = serviceProvider.GetService<IPersistenceContextProvider>() ?? throw new Exception($"{nameof(IPersistenceContextProvider)} not registered.");
|
||||
using var context = persistenceContextProvider.CreateNewTypedContext(typeof(SystemConfiguration), false);
|
||||
|
||||
// TODO: this may lead to a deadlock?
|
||||
var configuration = context.GetAsync<SystemConfiguration>().AsTask().WaitAndUnwrapException().FirstOrDefault();
|
||||
if (configuration is not null)
|
||||
{
|
||||
settings = (configuration.IpResolver, configuration.IpResolverParameter);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
serviceProvider.GetService<ILogger<IIpAddressResolver>>()?.LogError(ex, "Unexpected error when trying to load the system configuration during ip resolver creation.");
|
||||
}
|
||||
|
||||
return IpAddressResolverFactory.CreateIpResolver(args, settings, serviceProvider.GetService<ILoggerFactory>()!);
|
||||
});
|
||||
}
|
||||
}
|
||||
42
src/Dapr/Common/MUnique.OpenMU.Dapr.Common.csproj
Normal file
42
src/Dapr/Common/MUnique.OpenMU.Dapr.Common.csproj
Normal file
@@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<DocumentationFile></DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Properties\**" />
|
||||
<EmbeddedResource Remove="Properties\**" />
|
||||
<None Remove="Properties\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapr.AspNetCore" />
|
||||
<PackageReference Include="Dapr.Client" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.OpenApi" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.Grafana.Loki" />
|
||||
<PackageReference Include="SharpAbp.Abp.OpenTelemetry.Exporter.Prometheus.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Interfaces\MUnique.OpenMU.Interfaces.csproj" />
|
||||
<ProjectReference Include="..\..\Persistence\EntityFramework\MUnique.OpenMU.Persistence.EntityFramework.csproj" />
|
||||
<ProjectReference Include="..\ServerClients\MUnique.OpenMU.ServerClients.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
116
src/Dapr/Common/ManagableServerRegistry.cs
Normal file
116
src/Dapr/Common/ManagableServerRegistry.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
// <copyright file="ManagableServerRegistry.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using global::Dapr.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// A registry for all <see cref="IManageableServer"/>s in the system.
|
||||
/// </summary>
|
||||
public class ManagableServerRegistry : IServerProvider, IDisposable
|
||||
{
|
||||
private readonly TimeSpan _timeout = TimeSpan.FromSeconds(20);
|
||||
private readonly CancellationTokenSource _disposeCts = new();
|
||||
private readonly ILogger<ManagableServerRegistry> _logger;
|
||||
private readonly DaprClient _daprClient;
|
||||
private readonly ConcurrentDictionary<int, ManageableServerClient> _serverClients = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManagableServerRegistry" /> class.
|
||||
/// </summary>
|
||||
/// <param name="daprClient">The dapr client.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public ManagableServerRegistry(DaprClient daprClient, ILogger<ManagableServerRegistry> logger)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
this._logger = logger;
|
||||
|
||||
async Task RunTimeoutLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.TimeoutLoopAsync(this._disposeCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error in cleanup loop");
|
||||
}
|
||||
}
|
||||
|
||||
_ = RunTimeoutLoop();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<IManageableServer> Servers => this._serverClients.Values.OfType<IManageableServer>().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Handles an update of a server state.
|
||||
/// </summary>
|
||||
/// <param name="serverData">The server data.</param>
|
||||
public void HandleUpdate(ServerStateData serverData)
|
||||
{
|
||||
var isNew = false;
|
||||
this._serverClients.AddOrUpdate(
|
||||
serverData.Id,
|
||||
_ =>
|
||||
{
|
||||
isNew = true;
|
||||
return new ManageableServerClient(this._daprClient, serverData);
|
||||
},
|
||||
(_, client) =>
|
||||
{
|
||||
client.Update(serverData);
|
||||
return client;
|
||||
});
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(this.Servers));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
this._disposeCts.Cancel();
|
||||
this._disposeCts.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="PropertyChanged"/> event with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
protected virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private async Task TimeoutLoopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!this._disposeCts.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(2000, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var server in this._serverClients.Values)
|
||||
{
|
||||
var diff = DateTime.UtcNow - server.LastUpdate;
|
||||
if (diff > this._timeout)
|
||||
{
|
||||
this._logger.LogInformation("Difference of {0} higher than timeout for server {1}", diff, server.Id);
|
||||
server.ServerState = ServerState.Timeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
178
src/Dapr/Common/ManagableServerStatePublisher.cs
Normal file
178
src/Dapr/Common/ManagableServerStatePublisher.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
// <copyright file="ManagableServerStatePublisher.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using global::Dapr.Client;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
using Nito.AsyncEx;
|
||||
using Nito.AsyncEx.Synchronous;
|
||||
|
||||
/// <summary>
|
||||
/// A state publisher for a <see cref="IManageableServer"/>,
|
||||
/// which can be handled with a corresponding <see cref="ManagableServerRegistry"/>.
|
||||
/// The server registration is deferred to <see cref="StartedAsync"/> which is called after the web application
|
||||
/// has started (i.e. the HTTP API is already available), breaking the circular startup dependency with the Dapr sidecar.
|
||||
/// </summary>
|
||||
public sealed class ManagableServerStatePublisher : IHostedLifecycleService, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The topic name for the state updates.
|
||||
/// </summary>
|
||||
public const string TopicName = "ServerState";
|
||||
|
||||
private readonly ILogger<ManagableServerStatePublisher> _logger;
|
||||
private readonly DaprClient _daprClient;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly AsyncLock _lock = new();
|
||||
|
||||
private IManageableServer? _server;
|
||||
private ServerStateData? _data;
|
||||
|
||||
private Task? _heartbeatTask;
|
||||
private CancellationTokenSource? _heartbeatCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManagableServerStatePublisher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="daprClient">The dapr client.</param>
|
||||
/// <param name="serviceProvider">The service provider used to lazily resolve <see cref="IManageableServer"/>.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public ManagableServerStatePublisher(DaprClient daprClient, IServiceProvider serviceProvider, ILogger<ManagableServerStatePublisher> logger)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
this._serviceProvider = serviceProvider;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
this.StopAsync(default).WaitAndUnwrapException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this._heartbeatCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
async Task RunHeartbeatTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.HeartbeatLoopAsync(this._heartbeatCancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error in heartbeat loop.");
|
||||
}
|
||||
}
|
||||
|
||||
this._heartbeatTask = RunHeartbeatTask();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await (this._heartbeatCancellationTokenSource?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||
this._heartbeatCancellationTokenSource?.Dispose();
|
||||
if (this._heartbeatTask is { } heartbeatTask)
|
||||
{
|
||||
this._heartbeatTask = null;
|
||||
await heartbeatTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
private async Task HeartbeatLoopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.InitializeServerAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await this.PublishCurrentStateAsync().ConfigureAwait(false);
|
||||
await Task.Delay(5000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeServerAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && this._server is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = this._serviceProvider.GetRequiredService<IManageableServer>();
|
||||
server.PropertyChanged -= this.OnPropertyChanged; // Ensure single subscription in case of retry
|
||||
server.PropertyChanged += this.OnPropertyChanged;
|
||||
this._data = new ServerStateData(server);
|
||||
this._server = server;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogWarning(ex, "Could not resolve IManageableServer yet, retrying...");
|
||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishCurrentStateAsync()
|
||||
{
|
||||
if (this._server is null || this._data is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var asyncLock = await this._lock.LockAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||||
if (asyncLock is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this._data.UpdateState(this._server);
|
||||
await this._daprClient.PublishEventAsync("pubsub", TopicName, this._data).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error sending server status update");
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Exceptions are catched.")]
|
||||
private async void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.PropertyName == nameof(IManageableServer.ServerState))
|
||||
{
|
||||
await this.PublishCurrentStateAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error when publishing current state after property change");
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/Dapr/Common/ManageableServerClient.cs
Normal file
138
src/Dapr/Common/ManageableServerClient.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
// <copyright file="ManageableServerClient.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using global::Dapr.Client;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// A client to control a <seealso cref="IManageableServer"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="MUnique.OpenMU.Interfaces.IManageableServer" />
|
||||
internal class ManageableServerClient : IManageableServer
|
||||
{
|
||||
private readonly DaprClient _daprClient;
|
||||
|
||||
private readonly string _targetAppId;
|
||||
private ServerState _serverState;
|
||||
private int _currentConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManageableServerClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="daprClient">The dapr client.</param>
|
||||
/// <param name="serverData">The server data.</param>
|
||||
public ManageableServerClient(DaprClient daprClient, ServerStateData serverData)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
this._targetAppId = serverData.AppId;
|
||||
this.Id = serverData.Id;
|
||||
this.Description = serverData.Description;
|
||||
this.ConfigurationId = serverData.ConfigurationId;
|
||||
this.CurrentConnections = serverData.CurrentConnections;
|
||||
this.MaximumConnections = serverData.MaximumConnections;
|
||||
this.ServerState = serverData.State;
|
||||
this.Type = serverData.Type;
|
||||
this.LastUpdate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Id { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Guid ConfigurationId { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ServerType Type { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int MaximumConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timestamp of the last update.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ServerState ServerState
|
||||
{
|
||||
get => this._serverState;
|
||||
set
|
||||
{
|
||||
if (this._serverState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._serverState = value;
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int CurrentConnections
|
||||
{
|
||||
get => this._currentConnections;
|
||||
set
|
||||
{
|
||||
if (this._currentConnections == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentConnections = value;
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return this._daprClient.InvokeMethodAsync(this._targetAppId, nameof(IManageableServer.StartAsync), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask StartAsync()
|
||||
{
|
||||
await this.StartAsync(default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return this._daprClient.InvokeMethodAsync(this._targetAppId, nameof(IManageableServer.ShutdownAsync), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShutdownAsync()
|
||||
{
|
||||
await this.StopAsync(default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified server data.
|
||||
/// </summary>
|
||||
/// <param name="serverData">The server data.</param>
|
||||
public void Update(ServerStateData serverData)
|
||||
{
|
||||
this.ServerState = serverData.State;
|
||||
this.CurrentConnections = serverData.CurrentConnections;
|
||||
this.LastUpdate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
45
src/Dapr/Common/ManageableServerController.cs
Normal file
45
src/Dapr/Common/ManageableServerController.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// <copyright file="ManageableServerController.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// An api controller to control a <see cref="IManageableServer"/>.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("")]
|
||||
public class ManageableServerController
|
||||
{
|
||||
private readonly IManageableServer _manageableServer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManageableServerController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manageableServer">The manageable server.</param>
|
||||
public ManageableServerController(IManageableServer manageableServer)
|
||||
{
|
||||
this._manageableServer = manageableServer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts the manageable server down.
|
||||
/// </summary>
|
||||
[HttpPost(nameof(IManageableServer.ShutdownAsync))]
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
await this._manageableServer.ShutdownAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the manageable server.
|
||||
/// </summary>
|
||||
[HttpPost(nameof(IManageableServer.StartAsync))]
|
||||
public async Task StartAsync()
|
||||
{
|
||||
await this._manageableServer.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
47
src/Dapr/Common/MetricsRegistry.cs
Normal file
47
src/Dapr/Common/MetricsRegistry.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// <copyright file="MetricsRegistry.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Registry for metrics which should be exposed by the application.
|
||||
/// </summary>
|
||||
public class MetricsRegistry
|
||||
{
|
||||
private readonly HashSet<string> _meters = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered meters.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Meters => this._meters;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the meter with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="meterName">Name of the meter.</param>
|
||||
public void AddMeter(string meterName)
|
||||
{
|
||||
this._meters.Add(meterName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the meters with the specified names.
|
||||
/// </summary>
|
||||
/// <param name="meterNames">The names of the meters.</param>
|
||||
public void AddMeters(IEnumerable<string> meterNames)
|
||||
{
|
||||
foreach (var meter in meterNames)
|
||||
{
|
||||
this.AddMeter(meter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the network meters.
|
||||
/// </summary>
|
||||
public void AddNetworkMeters()
|
||||
{
|
||||
this.AddMeters(MUnique.OpenMU.Network.Metrics.Meters);
|
||||
}
|
||||
}
|
||||
107
src/Dapr/Common/SecretStoreDatabaseConnectionSettingsProvider.cs
Normal file
107
src/Dapr/Common/SecretStoreDatabaseConnectionSettingsProvider.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// <copyright file="SecretStoreDatabaseConnectionSettingsProvider.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System.Threading;
|
||||
using global::Dapr;
|
||||
using global::Dapr.Client;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Persistence.EntityFramework;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IDatabaseConnectionSettingProvider"/> which retrieves the settings from the
|
||||
/// configured Dapr secret storage.
|
||||
/// </summary>
|
||||
public class SecretStoreDatabaseConnectionSettingsProvider : IDatabaseConnectionSettingProvider
|
||||
{
|
||||
private const string SecretStoreName = "secrets";
|
||||
private readonly DaprClient _daprClient;
|
||||
private readonly ILogger<SecretStoreDatabaseConnectionSettingsProvider> _logger;
|
||||
private readonly Dictionary<string, ConnectionSetting> _connectionSettings = new(StringComparer.InvariantCultureIgnoreCase);
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SecretStoreDatabaseConnectionSettingsProvider" /> class.
|
||||
/// </summary>
|
||||
/// <param name="daprClient">The dapr client.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SecretStoreDatabaseConnectionSettingsProvider(DaprClient daprClient, ILogger<SecretStoreDatabaseConnectionSettingsProvider> logger)
|
||||
{
|
||||
this._daprClient = daprClient;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task? Initialization { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task InitializeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._isInitialized)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
this.Initialization = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
this._isInitialized = false;
|
||||
while (!this._isInitialized && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("trying to get secrets ...");
|
||||
var secrets = await this._daprClient.GetBulkSecretAsync(SecretStoreName, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
foreach (var secret in secrets.Where(kvp => string.Equals(kvp.Key.Split(':')[0], "connectionStrings", StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
var contextTypeName = secret.Value.Keys.First().Split(':').Last();
|
||||
var setting = new ConnectionSetting
|
||||
{
|
||||
ContextTypeName = contextTypeName,
|
||||
ConnectionString = secret.Value.Values.First()!,
|
||||
DatabaseEngine = DatabaseEngine.Npgsql,
|
||||
};
|
||||
|
||||
this._connectionSettings.Add(contextTypeName, setting);
|
||||
}
|
||||
|
||||
Console.WriteLine("secrets retrieved :)");
|
||||
|
||||
this._isInitialized = true;
|
||||
}
|
||||
catch (DaprException ex)
|
||||
{
|
||||
// This should never happen - however, it may happen when we are using a Dapr secret store.
|
||||
// It may not be started yet, and the implementation to get it does retrieve it in the constructor already.
|
||||
this._logger.LogWarning(ex, "Error occurred when retrieving the connection strings from the secrets store. Trying again in 3 seconds...");
|
||||
Console.WriteLine("Error occurred when retrieving the connection strings from the secrets store. Trying again in 3 seconds...");
|
||||
await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
ConnectionConfigurator.Initialize(this);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConnectionSetting GetConnectionSetting<TContextType>()
|
||||
where TContextType : DbContext
|
||||
{
|
||||
return this.GetConnectionSetting(typeof(TContextType));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConnectionSetting GetConnectionSetting(Type contextType)
|
||||
{
|
||||
if (this._connectionSettings.TryGetValue(contextType.FullName ?? contextType.Name, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"No connection string for type '{contextType.FullName}' stored in the secret store.");
|
||||
}
|
||||
}
|
||||
96
src/Dapr/Common/ServerStateData.cs
Normal file
96
src/Dapr/Common/ServerStateData.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// <copyright file="ServerStateData.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.Dapr.Common;
|
||||
|
||||
using System.Diagnostics;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Data about the state of a server.
|
||||
/// </summary>
|
||||
public class ServerStateData
|
||||
{
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerStateData"/> class.
|
||||
/// </summary>
|
||||
public ServerStateData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerStateData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="server">The server.</param>
|
||||
/// <exception cref="System.InvalidOperationException">Add the environment variable 'APPID' with the app-id of this dapr app.</exception>
|
||||
public ServerStateData(IManageableServer server)
|
||||
{
|
||||
this.AppId = Environment.GetEnvironmentVariable("APPID") ?? throw new InvalidOperationException("Add the environment variable 'APPID' with the app-id of this dapr app.");
|
||||
this.Id = server.Id;
|
||||
this.Description = server.Description;
|
||||
this.ConfigurationId = server.ConfigurationId;
|
||||
this.Type = server.Type;
|
||||
this.MaximumConnections = server.MaximumConnections;
|
||||
this._stopwatch.Start();
|
||||
this.UpdateState(server);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the (dapr) application identifier.
|
||||
/// </summary>
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the server.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of the server.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the configuration of the server.
|
||||
/// </summary>
|
||||
public Guid ConfigurationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the server.
|
||||
/// </summary>
|
||||
public ServerType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state of the server.
|
||||
/// </summary>
|
||||
public ServerState State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current connection count of the server.
|
||||
/// </summary>
|
||||
public int CurrentConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum connection count which the server supports.
|
||||
/// </summary>
|
||||
public int MaximumConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the up time of the server.
|
||||
/// </summary>
|
||||
public TimeSpan UpTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the server.
|
||||
/// </summary>
|
||||
/// <param name="server">The server.</param>
|
||||
public void UpdateState(IManageableServer server)
|
||||
{
|
||||
this.State = server.ServerState;
|
||||
this.CurrentConnections = server.CurrentConnections;
|
||||
this.UpTime = this._stopwatch.Elapsed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user