♻️ Remove etcd, replace with asprie. Move infra to aspire. Disable gateway for now

This commit is contained in:
2025-09-15 00:16:13 +08:00
parent 5c97733b3e
commit 091097a858
46 changed files with 360 additions and 723 deletions

View File

@@ -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
{
/// <summary>
/// Provides reverse proxy for DysonNetwork.Gateway.
/// Give the ability to the contained frontend to access other services via the gateway.
/// </summary>
/// <param name="app">The asp.net core application</param>
/// <returns>The modified application</returns>
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<ServiceRegistry>();
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);
}
}

View File

@@ -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<RegistryHostedService> 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<bool>("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.");
}
}

View File

@@ -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<RingService.RingServiceClient>(sp =>
services.AddGrpcClient<RingService.RingServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<AccountService.AccountServiceClient>(sp =>
services.AddGrpcClient<AccountService.AccountServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<AccountClientHelper>();
services.AddSingleton<BotAccountReceiverService.BotAccountReceiverServiceClient>(sp =>
services.AddGrpcClient<BotAccountReceiverService.BotAccountReceiverServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<ActionLogService.ActionLogServiceClient>(sp =>
services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<PaymentService.PaymentServiceClient>(sp =>
services.AddGrpcClient<PaymentService.PaymentServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<FileService.FileServiceClient>(sp =>
services.AddGrpcClient<FileService.FileServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<FileReferenceService.FileReferenceServiceClient>(sp =>
services.AddGrpcClient<FileReferenceService.FileReferenceServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<PublisherService.PublisherServiceClient>(sp =>
services.AddGrpcClient<PublisherService.PublisherServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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<CustomAppService.CustomAppServiceClient>(sp =>
services.AddGrpcClient<CustomAppService.CustomAppServiceClient>(o =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
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;

View File

@@ -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<ServiceRegistry> 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<string?> GetServiceUrl(string serviceName)
{
var key = $"/services/{serviceName}";
var response = await etcd.GetAsync(key);
return response.Kvs.Count == 0 ? null : response.Kvs[0].Value.ToStringUtf8();
}
}

View File

@@ -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<bool>("Etcd:Insecure");
});
services.AddSingleton<ServiceRegistry>();
services.AddHostedService<RegistryHostedService>();
if (addForwarder)
services.AddHttpForwarder();
return services;
}
}