diff --git a/DysonNetwork.Control/AppHost.cs b/DysonNetwork.Control/AppHost.cs new file mode 100644 index 0000000..a431e7c --- /dev/null +++ b/DysonNetwork.Control/AppHost.cs @@ -0,0 +1,30 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var database = builder.AddPostgres("database"); +var cache = builder.AddConnectionString("cache"); +var queue = builder.AddNats("queue").WithJetStream(); + +var ring = builder.AddProject("ring") + .WithReference(database) + .WithReference(queue); +var pass = builder.AddProject("pass") + .WithReference(database) + .WithReference(cache) + .WithReference(queue) + .WithReference(ring); +builder.AddProject("drive") + .WithReference(database) + .WithReference(cache) + .WithReference(queue) + .WithReference(pass); +builder.AddProject("sphere") + .WithReference(database) + .WithReference(cache) + .WithReference(queue) + .WithReference(pass); +builder.AddProject("develop") + .WithReference(database) + .WithReference(cache) + .WithReference(pass); + +builder.Build().Run(); \ No newline at end of file diff --git a/DysonNetwork.Control/DysonNetwork.Control.csproj b/DysonNetwork.Control/DysonNetwork.Control.csproj new file mode 100644 index 0000000..ed47623 --- /dev/null +++ b/DysonNetwork.Control/DysonNetwork.Control.csproj @@ -0,0 +1,30 @@ + + + + + + Exe + net9.0 + enable + enable + a68b3195-a00d-40c2-b5ed-d675356b7cde + DysonNetwork.Control + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.Control/Properties/launchSettings.json b/DysonNetwork.Control/Properties/launchSettings.json new file mode 100644 index 0000000..ee4fd34 --- /dev/null +++ b/DysonNetwork.Control/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17025;http://localhost:15057", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21175", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22189" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15057", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19163", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20185" + } + } + } +} diff --git a/DysonNetwork.Control/appsettings.json b/DysonNetwork.Control/appsettings.json new file mode 100644 index 0000000..1bdb86e --- /dev/null +++ b/DysonNetwork.Control/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "cache": "localhost:6379" + } +} diff --git a/DysonNetwork.Develop/DysonNetwork.Develop.csproj b/DysonNetwork.Develop/DysonNetwork.Develop.csproj index 1577bf3..49cbd38 100644 --- a/DysonNetwork.Develop/DysonNetwork.Develop.csproj +++ b/DysonNetwork.Develop/DysonNetwork.Develop.csproj @@ -31,6 +31,7 @@ + diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs index df62582..3dae1e0 100644 --- a/DysonNetwork.Develop/Program.cs +++ b/DysonNetwork.Develop/Program.cs @@ -1,17 +1,16 @@ using DysonNetwork.Develop; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; -using DysonNetwork.Shared.Registry; using DysonNetwork.Develop.Startup; -using DysonNetwork.Shared.Stream; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + builder.ConfigureAppKestrel(builder.Configuration); -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppAuthentication(); builder.Services.AddAppSwagger(); @@ -22,6 +21,8 @@ builder.Services.AddDriveService(); var app = builder.Build(); +app.MapDefaultEndpoints(); + using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); diff --git a/DysonNetwork.Develop/appsettings.json b/DysonNetwork.Develop/appsettings.json index 1d407a2..ba758b3 100644 --- a/DysonNetwork.Develop/appsettings.json +++ b/DysonNetwork.Develop/appsettings.json @@ -24,8 +24,6 @@ }, "Service": { "Name": "DysonNetwork.Develop", - "Url": "https://localhost:7192", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7192" } } diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj index 6e54599..d77a199 100644 --- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj +++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj @@ -66,6 +66,7 @@ + diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index eef5c20..91fe0c7 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -5,18 +5,18 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; using tusdotnet.Stores; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue); // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); + builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -39,6 +39,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -51,8 +53,6 @@ var tusDiskStore = app.Services.GetRequiredService(); // Configure application middleware pipeline app.ConfigureAppMiddleware(tusDiskStore, builder.Environment.ContentRootPath); -app.MapGatewayProxy(); - app.MapPages(Path.Combine(app.Environment.WebRootPath, "dist", "index.html")); // Configure gRPC diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json index 872ee07..d1f5c30 100644 --- a/DysonNetwork.Drive/appsettings.json +++ b/DysonNetwork.Drive/appsettings.json @@ -131,8 +131,6 @@ ], "Service": { "Name": "DysonNetwork.Drive", - "Url": "https://localhost:7092", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7092" } } diff --git a/DysonNetwork.Gateway/Controllers/WellKnownController.cs b/DysonNetwork.Gateway/Controllers/WellKnownController.cs index 9fddcf3..5523c8f 100644 --- a/DysonNetwork.Gateway/Controllers/WellKnownController.cs +++ b/DysonNetwork.Gateway/Controllers/WellKnownController.cs @@ -71,7 +71,7 @@ public class WellKnownController( { if (!domainMappings.TryGetValue(key, out var domain)) continue; if (domain is not null) - serviceMap[key] = "https://" + domain; + serviceMap[key] = "http://" + domain; } return Ok(serviceMap); diff --git a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj index 7403f3c..47c67dc 100644 --- a/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj +++ b/DysonNetwork.Gateway/DysonNetwork.Gateway.csproj @@ -17,6 +17,7 @@ + diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index 7c981ea..0bf2a37 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.HttpOverrides; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); builder.WebHost.ConfigureKestrel(options => { @@ -18,6 +20,8 @@ builder.Services.AddControllers(); var app = builder.Build(); +app.MapDefaultEndpoints(); + app.ConfigureForwardedHeaders(app.Configuration); app.UseRequestTimeouts(); diff --git a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs index 147983f..1ac05d4 100644 --- a/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Gateway/Startup/ServiceCollectionExtensions.cs @@ -1,6 +1,4 @@ using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using DysonNetwork.Shared.Registry; using Yarp.ReverseProxy.Configuration; using Yarp.ReverseProxy.Transforms; @@ -30,7 +28,6 @@ public static class ServiceCollectionExtensions context.AddXForwarded(action: ForwardedTransformActions.Set); }); - services.AddRegistryService(configuration, addForwarder: false); services.AddSingleton(); return services; diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index c5e9687..88f6e6b 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -49,6 +49,7 @@ + diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs index 77b68e0..71714c3 100644 --- a/DysonNetwork.Pass/Email/EmailService.cs +++ b/DysonNetwork.Pass/Email/EmailService.cs @@ -1,5 +1,3 @@ -using dotnet_etcd; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.AspNetCore.Components; diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 6a344fa..fc96c59 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -4,20 +4,16 @@ using DysonNetwork.Pass.Startup; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); -// Add metrics and telemetry -builder.Services.AddAppMetrics(); - // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -41,6 +37,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -51,8 +49,6 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration, builder.Environment.ContentRootPath); -app.MapGatewayProxy(); - app.MapPages(Path.Combine(builder.Environment.WebRootPath, "dist", "index.html")); // Configure gRPC diff --git a/DysonNetwork.Pass/Startup/MetricsConfiguration.cs b/DysonNetwork.Pass/Startup/MetricsConfiguration.cs deleted file mode 100644 index 78e33cd..0000000 --- a/DysonNetwork.Pass/Startup/MetricsConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Prometheus; -using Prometheus.SystemMetrics; - -namespace DysonNetwork.Pass.Startup; - -public static class MetricsConfiguration -{ - public static IServiceCollection AddAppMetrics(this IServiceCollection services) - { - // Prometheus - services.UseHttpClientMetrics(); - services.AddHealthChecks(); - services.AddSystemMetrics(); - services.AddPrometheusEntityFrameworkMetrics(); - services.AddPrometheusAspNetCoreMetrics(); - services.AddPrometheusHttpClientMetrics(); - - // OpenTelemetry - services.AddOpenTelemetry() - .WithTracing(tracing => - { - tracing - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddOtlpExporter(); - }) - .WithMetrics(metrics => - { - metrics - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation() - .AddOtlpExporter(); - }); - - return services; - } -} diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index 8b9275e..cf67169 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -83,9 +83,7 @@ ], "Service": { "Name": "DysonNetwork.Pass", - "Url": "https://localhost:7058", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7058" }, "Etcd": { "Insecure": true diff --git a/DysonNetwork.Ring/Connection/WebSocketService.cs b/DysonNetwork.Ring/Connection/WebSocketService.cs index 7454798..8c0c78d 100644 --- a/DysonNetwork.Ring/Connection/WebSocketService.cs +++ b/DysonNetwork.Ring/Connection/WebSocketService.cs @@ -1,6 +1,5 @@ using System.Collections.Concurrent; using System.Net.WebSockets; -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Proto; using Grpc.Core; @@ -11,17 +10,14 @@ public class WebSocketService { private readonly IConfiguration _configuration; private readonly ILogger _logger; - private readonly IEtcdClient _etcdClient; private readonly IDictionary _handlerMap; public WebSocketService( IEnumerable handlers, - IEtcdClient etcdClient, ILogger logger, IConfiguration configuration ) { - _etcdClient = etcdClient; _logger = logger; _configuration = configuration; _handlerMap = handlers.ToDictionary(h => h.PacketType); @@ -59,8 +55,10 @@ public class WebSocketService } catch (Exception ex) { - _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, key.DeviceId); + _logger.LogWarning(ex, "Error while closing WebSocket for {AccountId}:{DeviceId}", key.AccountId, + key.DeviceId); } + data.Cts.Cancel(); ActiveConnections.TryRemove(key, out _); } @@ -140,45 +138,24 @@ public class WebSocketService { try { - // Get the service URL from etcd for the specified endpoint - var serviceKey = $"/services/{packet.Endpoint}"; - var response = await _etcdClient.GetAsync(serviceKey); + var serviceUrl = "https://" + packet.Endpoint; - if (response.Kvs.Count > 0) + var callInvoker = GrpcClientHelper.CreateCallInvoker(serviceUrl); + var client = new RingHandlerService.RingHandlerServiceClient(callInvoker); + + try { - var serviceUrl = response.Kvs[0].Value.ToStringUtf8(); - - var clientCertPath = _configuration["Service:ClientCert"]!; - var clientKeyPath = _configuration["Service:ClientKey"]!; - var clientCertPassword = _configuration["Service:CertPassword"]; - - var callInvoker = - GrpcClientHelper.CreateCallInvoker( - serviceUrl, - clientCertPath, - clientKeyPath, - clientCertPassword - ); - var client = new RingHandlerService.RingHandlerServiceClient(callInvoker); - - try + await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest { - await client.ReceiveWebSocketPacketAsync(new ReceiveWebSocketPacketRequest - { - Account = currentUser, - DeviceId = deviceId, - Packet = packet.ToProtoValue() - }); - } - catch (RpcException ex) - { - _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); - } - - return; + Account = currentUser, + DeviceId = deviceId, + Packet = packet.ToProtoValue() + }); + } + catch (RpcException ex) + { + _logger.LogError(ex, $"Error forwarding packet to endpoint: {packet.Endpoint}"); } - - _logger.LogWarning($"No service registered for endpoint: {packet.Endpoint}"); } catch (Exception ex) { @@ -197,4 +174,4 @@ public class WebSocketService CancellationToken.None ); } -} +} \ No newline at end of file diff --git a/DysonNetwork.Ring/DysonNetwork.Ring.csproj b/DysonNetwork.Ring/DysonNetwork.Ring.csproj index ae8a231..dc52c8e 100644 --- a/DysonNetwork.Ring/DysonNetwork.Ring.csproj +++ b/DysonNetwork.Ring/DysonNetwork.Ring.csproj @@ -42,6 +42,7 @@ + diff --git a/DysonNetwork.Ring/Program.cs b/DysonNetwork.Ring/Program.cs index 2425a43..eda4ae7 100644 --- a/DysonNetwork.Ring/Program.cs +++ b/DysonNetwork.Ring/Program.cs @@ -3,17 +3,16 @@ using DysonNetwork.Ring.Startup; using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -32,6 +31,8 @@ builder.Services.AddAppScheduledJobs(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { diff --git a/DysonNetwork.Ring/Services/PusherServiceGrpc.cs b/DysonNetwork.Ring/Services/PusherServiceGrpc.cs index bb21a56..80a3e08 100644 --- a/DysonNetwork.Ring/Services/PusherServiceGrpc.cs +++ b/DysonNetwork.Ring/Services/PusherServiceGrpc.cs @@ -2,7 +2,6 @@ using DysonNetwork.Ring.Connection; using DysonNetwork.Ring.Email; using DysonNetwork.Ring.Notification; using DysonNetwork.Shared.Proto; -using DysonNetwork.Shared.Registry; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using System.Text.Json; diff --git a/DysonNetwork.Ring/Services/QueueBackgroundService.cs b/DysonNetwork.Ring/Services/QueueBackgroundService.cs index bb3b357..a2f0f1b 100644 --- a/DysonNetwork.Ring/Services/QueueBackgroundService.cs +++ b/DysonNetwork.Ring/Services/QueueBackgroundService.cs @@ -2,7 +2,6 @@ using System.Text.Json; using DysonNetwork.Ring.Email; using DysonNetwork.Ring.Notification; using DysonNetwork.Shared.Proto; -using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Stream; using Google.Protobuf; using NATS.Client.Core; diff --git a/DysonNetwork.Ring/appsettings.json b/DysonNetwork.Ring/appsettings.json index 228bc9f..d5280bb 100644 --- a/DysonNetwork.Ring/appsettings.json +++ b/DysonNetwork.Ring/appsettings.json @@ -45,9 +45,7 @@ ], "Service": { "Name": "DysonNetwork.Ring", - "Url": "https://localhost:7259", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7259" }, "Etcd": { "Insecure": true diff --git a/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj b/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj new file mode 100644 index 0000000..9f8259b --- /dev/null +++ b/DysonNetwork.ServiceDefaults/DysonNetwork.ServiceDefaults.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + diff --git a/DysonNetwork.ServiceDefaults/Extensions.cs b/DysonNetwork.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..2a1ded0 --- /dev/null +++ b/DysonNetwork.ServiceDefaults/Extensions.cs @@ -0,0 +1,132 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + builder.Services.Configure(options => + { + options.AllowedSchemes = ["https"]; + }); + + builder.AddNatsClient("queue"); + builder.AddRedisClient("cache"); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) + where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} \ No newline at end of file diff --git a/DysonNetwork.Shared/Auth/Startup.cs b/DysonNetwork.Shared/Auth/Startup.cs index 3cd02ed..9197c34 100644 --- a/DysonNetwork.Shared/Auth/Startup.cs +++ b/DysonNetwork.Shared/Auth/Startup.cs @@ -1,4 +1,3 @@ -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -11,32 +10,14 @@ public static class DysonAuthStartup this IServiceCollection services ) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://pass"); }); - - services.AddSingleton(sp => - { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - return GrpcClientHelper - .CreatePermissionServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + services.AddGrpcClient(o => + { + o.Address = new Uri("https://pass"); }); services.AddAuthentication(options => diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index 341d5bf..a79c88a 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -7,11 +7,11 @@ - + all @@ -27,7 +27,6 @@ - @@ -40,4 +39,8 @@ + + + + diff --git a/DysonNetwork.Shared/Http/KestrelConfiguration.cs b/DysonNetwork.Shared/Http/KestrelConfiguration.cs index bd6bf1b..b64021c 100644 --- a/DysonNetwork.Shared/Http/KestrelConfiguration.cs +++ b/DysonNetwork.Shared/Http/KestrelConfiguration.cs @@ -20,20 +20,6 @@ public static class KestrelConfiguration builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = maxRequestBodySize; - - var configuredUrl = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); - if (!string.IsNullOrEmpty(configuredUrl)) return; - - var certPath = configuration["Service:ClientCert"]!; - var keyPath = configuration["Service:ClientKey"]!; - - // Load PEM cert and key manually - var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath); - // Now pass the full cert - options.ListenAnyIP(5001, listenOptions => { listenOptions.UseHttps(certificate); }); - - // Optional: HTTP fallback - options.ListenAnyIP(8080); }); return builder; diff --git a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs index 65ca1d6..55ff54e 100644 --- a/DysonNetwork.Shared/Proto/GrpcClientHelper.cs +++ b/DysonNetwork.Shared/Proto/GrpcClientHelper.cs @@ -2,171 +2,13 @@ using System.Net; using Grpc.Net.Client; using System.Security.Cryptography.X509Certificates; using Grpc.Core; -using dotnet_etcd.interfaces; namespace DysonNetwork.Shared.Proto; public static class GrpcClientHelper { - public static CallInvoker CreateCallInvoker( - string url, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) + public static CallInvoker CreateCallInvoker(string url) { - var handler = new HttpClientHandler(); - handler.ClientCertificates.Add( - clientCertPassword is null - ? X509Certificate2.CreateFromPemFile(clientCertPath, clientKeyPath) - : X509Certificate2.CreateFromEncryptedPemFile(clientCertPath, clientCertPassword, clientKeyPath) - ); - handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; - var httpClient = new HttpClient(handler); - httpClient.DefaultRequestVersion = HttpVersion.Version20; - httpClient.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; - return GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpClient = httpClient }).CreateCallInvoker(); - } - - private static async Task GetServiceUrlFromEtcd(IEtcdClient etcdClient, string serviceName) - { - var response = await etcdClient.GetAsync($"/services/{serviceName}"); - return response.Kvs.Count == 0 - ? throw new InvalidOperationException($"Service '{serviceName}' not found in Etcd.") - : response.Kvs[0].Value.ToStringUtf8(); - } - - public static async Task CreateAccountServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task - CreateBotAccountReceiverServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new BotAccountReceiverService.BotAccountReceiverServiceClient(CreateCallInvoker(url, clientCertPath, - clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateActionLogServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new ActionLogService.ActionLogServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateAuthServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePermissionServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new PermissionService.PermissionServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePaymentServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass"); - return new PaymentService.PaymentServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateRingServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Ring"); - return new RingService.RingServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateFileServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); - return new FileService.FileServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateFileReferenceServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Drive"); - return new FileReferenceService.FileReferenceServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreatePublisherServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Sphere"); - return new PublisherService.PublisherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); - } - - public static async Task CreateCustomAppServiceClient( - IEtcdClient etcdClient, - string clientCertPath, - string clientKeyPath, - string? clientCertPassword = null - ) - { - var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Develop"); - return new CustomAppService.CustomAppServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath, - clientCertPassword)); + return GrpcChannel.ForAddress(url).CreateCallInvoker(); } } \ No newline at end of file diff --git a/DysonNetwork.Shared/Proto/pusher.proto b/DysonNetwork.Shared/Proto/ring.proto similarity index 100% rename from DysonNetwork.Shared/Proto/pusher.proto rename to DysonNetwork.Shared/Proto/ring.proto diff --git a/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs b/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs deleted file mode 100644 index 5e7a1c0..0000000 --- a/DysonNetwork.Shared/Registry/GatewayReverseProxy.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Diagnostics; -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Yarp.ReverseProxy.Forwarder; - -namespace DysonNetwork.Shared.Registry; - -public static class GatewayReverseProxy -{ - /// - /// Provides reverse proxy for DysonNetwork.Gateway. - /// Give the ability to the contained frontend to access other services via the gateway. - /// - /// The asp.net core application - /// The modified application - public static WebApplication MapGatewayProxy(this WebApplication app) - { - var httpClient = new HttpMessageInvoker(new SocketsHttpHandler - { - UseProxy = false, - AllowAutoRedirect = true, - AutomaticDecompression = DecompressionMethods.All, - UseCookies = true, - EnableMultipleHttp2Connections = true, - ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current), - ConnectTimeout = TimeSpan.FromSeconds(15), - }); - - var transformer = new GatewayReverseProxyTransformer(); - var requestConfig = new ForwarderRequestConfig(); - - app.Map("/cgi/{**catch-all}", async (HttpContext context, IHttpForwarder forwarder) => - { - var registry = context.RequestServices.GetRequiredService(); - var gatewayUrl = await registry.GetServiceUrl("DysonNetwork.Gateway"); - if (gatewayUrl is null) - { - context.Response.StatusCode = 404; - await context.Response.WriteAsync("Gateway not found"); - return; - } - - var error = await forwarder.SendAsync( - context, - gatewayUrl, - httpClient, - requestConfig, - transformer - ); - if (error != ForwarderError.None) - { - var errorFeature = context.GetForwarderErrorFeature(); - var exception = errorFeature?.Exception; - context.Response.StatusCode = 502; - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync($"Gateway remote error: {exception?.Message}"); - } - }); - - return app; - } -} - -public class GatewayReverseProxyTransformer : HttpTransformer -{ - private const string Value = "/cgi"; - - public override ValueTask TransformRequestAsync( - HttpContext httpContext, - HttpRequestMessage proxyRequest, - string destinationPrefix, - CancellationToken cancellationToken - ) - { - httpContext.Request.Path = httpContext.Request.Path.StartsWithSegments(Value, out var remaining) - ? remaining - : httpContext.Request.Path; - - return Default.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/RegistryHostedService.cs b/DysonNetwork.Shared/Registry/RegistryHostedService.cs deleted file mode 100644 index a1d8383..0000000 --- a/DysonNetwork.Shared/Registry/RegistryHostedService.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace DysonNetwork.Shared.Registry; - -public class RegistryHostedService( - ServiceRegistry serviceRegistry, - IConfiguration configuration, - ILogger logger -) - : IHostedService -{ - private CancellationTokenSource? _cts; - - public async Task StartAsync(CancellationToken cancellationToken) - { - var serviceName = configuration["Service:Name"]; - var serviceUrl = configuration["Service:Url"]; - var insecure = configuration.GetValue("Etcd:Insecure"); - var remote = configuration.GetConnectionString("Etcd"); - - if (insecure) - logger.LogWarning("Etcd is configured to use insecure channel."); - - if (string.IsNullOrEmpty(serviceUrl) || string.IsNullOrEmpty(serviceName)) - { - logger.LogWarning("Service URL or Service Name was not configured. Skipping Etcd registration."); - return; - } - - logger.LogInformation( - "Registering service {ServiceName} at {ServiceUrl} with Etcd ({Remote}).", - serviceName, - serviceUrl, - remote - ); - try - { - _cts = new CancellationTokenSource(); - await serviceRegistry.RegisterService(serviceName, serviceUrl, cancellationToken: _cts.Token); - logger.LogInformation("Service {ServiceName} registered successfully.", serviceName); - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to register service {ServiceName} with Etcd.", serviceName); - } - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - _cts?.Cancel(); - - // The lease will expire automatically if the service stops ungracefully. - var serviceName = configuration["Service:Name"]; - if (serviceName is not null) - await serviceRegistry.UnregisterService(serviceName); - logger.LogInformation("Service registration hosted service is stopping."); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index bd1d997..739d434 100644 --- a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -1,4 +1,3 @@ -using dotnet_etcd.interfaces; using DysonNetwork.Shared.Proto; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -9,80 +8,35 @@ public static class ServiceInjectionHelper { public static IServiceCollection AddRingService(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateRingServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); - }); + o.Address = new Uri("https://ring"); + }); return services; } public static IServiceCollection AddAccountService(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://pass"); }); services.AddSingleton(); - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateBotAccountReceiverServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://pass"); }); - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://pass"); }); - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreatePaymentServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://pass"); }); return services; @@ -90,32 +44,14 @@ public static class ServiceInjectionHelper public static IServiceCollection AddDriveService(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateFileServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://drive"); }); - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateFileReferenceServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://drive"); }); return services; @@ -123,18 +59,9 @@ public static class ServiceInjectionHelper public static IServiceCollection AddPublisherService(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreatePublisherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://sphere"); }); return services; @@ -142,18 +69,9 @@ public static class ServiceInjectionHelper public static IServiceCollection AddDevelopService(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddGrpcClient(o => { - var etcdClient = sp.GetRequiredService(); - var config = sp.GetRequiredService(); - var clientCertPath = config["Service:ClientCert"]!; - var clientKeyPath = config["Service:ClientKey"]!; - var clientCertPassword = config["Service:CertPassword"]; - - return GrpcClientHelper - .CreateCustomAppServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword) - .GetAwaiter() - .GetResult(); + o.Address = new Uri("https://develop"); }); return services; diff --git a/DysonNetwork.Shared/Registry/ServiceRegistry.cs b/DysonNetwork.Shared/Registry/ServiceRegistry.cs deleted file mode 100644 index 8c2e0e4..0000000 --- a/DysonNetwork.Shared/Registry/ServiceRegistry.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Text; -using dotnet_etcd.interfaces; -using Etcdserverpb; -using Google.Protobuf; -using Microsoft.Extensions.Logging; - -namespace DysonNetwork.Shared.Registry; - -public class ServiceRegistry(IEtcdClient etcd, ILogger logger) -{ - public async Task RegisterService( - string serviceName, - string serviceUrl, - long leaseTtlSeconds = 60, - CancellationToken cancellationToken = default - ) - { - var key = $"/services/{serviceName}"; - var leaseResponse = await etcd.LeaseGrantAsync( - new LeaseGrantRequest { TTL = leaseTtlSeconds }, - cancellationToken: cancellationToken - ); - await etcd.PutAsync(new PutRequest - { - Key = ByteString.CopyFrom(key, Encoding.UTF8), - Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8), - Lease = leaseResponse.ID - }, cancellationToken: cancellationToken); - - _ = Task.Run(async () => - { - try - { - await etcd.LeaseKeepAlive(leaseResponse.ID, cancellationToken); - } - catch (Exception ex) - { - logger.LogError($"Lease keep-alive failed: {ex.Message}"); - } - }, cancellationToken); - } - - public async Task UnregisterService(string serviceName) - { - var key = $"/services/{serviceName}"; - await etcd.DeleteAsync(key); - } - - public async Task GetServiceUrl(string serviceName) - { - var key = $"/services/{serviceName}"; - var response = await etcd.GetAsync(key); - return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Registry/Startup.cs b/DysonNetwork.Shared/Registry/Startup.cs deleted file mode 100644 index e5cdeac..0000000 --- a/DysonNetwork.Shared/Registry/Startup.cs +++ /dev/null @@ -1,28 +0,0 @@ -using dotnet_etcd.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace DysonNetwork.Shared.Registry; - -public static class RegistryStartup -{ - public static IServiceCollection AddRegistryService( - this IServiceCollection services, - IConfiguration configuration, - bool addForwarder = true - ) - { - services.AddEtcdClient(options => - { - options.ConnectionString = configuration.GetConnectionString("Etcd"); - options.UseInsecureChannel = configuration.GetValue("Etcd:Insecure"); - }); - services.AddSingleton(); - services.AddHostedService(); - - if (addForwarder) - services.AddHttpForwarder(); - - return services; - } -} \ No newline at end of file diff --git a/DysonNetwork.Shared/Stream/Connector.cs b/DysonNetwork.Shared/Stream/Connector.cs deleted file mode 100644 index 66fed40..0000000 --- a/DysonNetwork.Shared/Stream/Connector.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using NATS.Client.Core; - -namespace DysonNetwork.Shared.Stream; - -public static class Connector -{ - public static IServiceCollection AddStreamConnection(this IServiceCollection services, IConfiguration configuration) - { - var connectionString = configuration.GetConnectionString("Stream"); - if (connectionString is null) - throw new ArgumentNullException(nameof(connectionString)); - services.AddSingleton(_ => new NatsConnection(new NatsOpts() - { - Url = connectionString - })); - - return services; - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index cc01ed1..7775f38 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -55,7 +55,6 @@ - @@ -160,6 +159,7 @@ + diff --git a/DysonNetwork.Sphere/Poll/PollController.cs b/DysonNetwork.Sphere/Poll/PollController.cs index d54ea7b..46a492d 100644 --- a/DysonNetwork.Sphere/Poll/PollController.cs +++ b/DysonNetwork.Sphere/Poll/PollController.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 8700d83..bdec808 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -2,7 +2,6 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; using DysonNetwork.Shared.PageData; using DysonNetwork.Shared.Registry; -using DysonNetwork.Shared.Stream; using DysonNetwork.Sphere; using DysonNetwork.Sphere.PageData; using DysonNetwork.Sphere.Startup; @@ -11,15 +10,13 @@ using Microsoft.Extensions.FileProviders; var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); -// Add metrics and telemetry -builder.Services.AddAppMetrics(); - // Add application services -builder.Services.AddRegistryService(builder.Configuration); -builder.Services.AddStreamConnection(builder.Configuration); + builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); @@ -42,6 +39,8 @@ builder.Services.AddTransient(); var app = builder.Build(); +app.MapDefaultEndpoints(); + // Run database migrations using (var scope = app.Services.CreateScope()) { @@ -52,8 +51,6 @@ using (var scope = app.Services.CreateScope()) // Configure application middleware pipeline app.ConfigureAppMiddleware(builder.Configuration); -app.MapGatewayProxy(); - app.UseDefaultFiles(); app.UseStaticFiles(new StaticFileOptions { diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index e3bf31c..09efa48 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; using Google.Protobuf.WellKnownTypes; -using Microsoft.AspNetCore.Http.HttpResults; namespace DysonNetwork.Sphere.Realm; diff --git a/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs b/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs deleted file mode 100644 index ed427da..0000000 --- a/DysonNetwork.Sphere/Startup/MetricsConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Prometheus; -using Prometheus.SystemMetrics; - -namespace DysonNetwork.Sphere.Startup; - -public static class MetricsConfiguration -{ - public static IServiceCollection AddAppMetrics(this IServiceCollection services) - { - // Prometheus - services.UseHttpClientMetrics(); - services.AddHealthChecks(); - services.AddSystemMetrics(); - services.AddPrometheusEntityFrameworkMetrics(); - services.AddPrometheusAspNetCoreMetrics(); - services.AddPrometheusHttpClientMetrics(); - - // OpenTelemetry - services.AddOpenTelemetry() - .WithTracing(tracing => - { - tracing - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddOtlpExporter(); - }) - .WithMetrics(metrics => - { - metrics - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation() - .AddOtlpExporter(); - }); - - return services; - } -} diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index 44f42c2..e3f0399 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -39,8 +39,6 @@ }, "Service": { "Name": "DysonNetwork.Sphere", - "Url": "https://localhost:7099", - "ClientCert": "../Certificates/client.crt", - "ClientKey": "../Certificates/client.key" + "Url": "https://localhost:7099" } } diff --git a/DysonNetwork.sln b/DysonNetwork.sln index 4a515f3..6977d7a 100644 --- a/DysonNetwork.sln +++ b/DysonNetwork.sln @@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Gateway", "Dys EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Develop", "DysonNetwork.Develop\DysonNetwork.Develop.csproj", "{C577AA78-B11D-4076-89A6-1C7F0ECC04E2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.Control", "DysonNetwork.Control\DysonNetwork.Control.csproj", "{7FFED190-51C7-4302-A8B5-96C839463458}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DysonNetwork.ServiceDefaults", "DysonNetwork.ServiceDefaults\DysonNetwork.ServiceDefaults.csproj", "{877AAD96-C257-4305-9F1C-C9D9C9BEE615}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,5 +58,13 @@ Global {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C577AA78-B11D-4076-89A6-1C7F0ECC04E2}.Release|Any CPU.Build.0 = Release|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FFED190-51C7-4302-A8B5-96C839463458}.Release|Any CPU.Build.0 = Release|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {877AAD96-C257-4305-9F1C-C9D9C9BEE615}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 6c84793..572c9cc 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -6,6 +6,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -132,6 +133,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded