Integrations -> Abstractions/Exceptions/Extensions/Models/Options/ Services (включая Storage); Persistence-конфигурации -> Configurations; корень Deal.Api (оркестратор/планировщики/DTO) -> Services/Dtos. namespace приведён к путям, using добавлены/дедуплицированы, FQN обновлены.
82 lines
4.5 KiB
C#
82 lines
4.5 KiB
C#
using Grpc.Core;
|
||
using Grpc.Health.V1;
|
||
using Grpc.Net.Client;
|
||
using Deal.Infrastructure.Integrations.Abstractions;
|
||
using Deal.Infrastructure.Integrations.Exceptions;
|
||
using Deal.Infrastructure.Integrations.Extensions;
|
||
using Deal.Infrastructure.Integrations.Models;
|
||
using Deal.Infrastructure.Integrations.Options;
|
||
|
||
namespace Deal.Infrastructure.Integrations.Services;
|
||
|
||
/// <summary>
|
||
/// Health-проба grpc.health.v1 автономных сервисов (ml/ai/telegram) для операторского health
|
||
/// (план Task 10: GET /api/operator/health, Ruling 3/6/9).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Каждый вызов строит свой короткоживущий канал к <c>endpoint</c> сервиса (dev — без TLS, Ruling 2;
|
||
/// mTLS (Ruling 6, Task 13): при включённом флаге канал подписывает запрос клиентским сертификатом и
|
||
/// проверяет CA сервера — сертификаты передаются <see cref="MtlsCertificates"/> в конструктор) и спрашивает
|
||
/// Health.Check("") с дедлайном 3 с — health не должен висеть дольше таймаута. Классификация: ответ
|
||
/// <c>SERVING</c> → Reachable+Serving; ответ с иным статусом → Reachable без
|
||
/// Serving; таймаут/нет соединения (Unavailable/DeadlineExceeded, HTTP-транспорт) и сервис без health-контракта
|
||
/// (Unimplemented) → <see cref="ServiceHealthResult.Unreachable"/>.
|
||
/// </remarks>
|
||
public sealed class ServiceHealthProbe
|
||
{
|
||
/// <summary>
|
||
/// Дедлайн health-RPC, секунд (Ruling 3/9: операторский health отвечает за ~3 с на сервис).
|
||
/// </summary>
|
||
public const int HealthTimeoutSeconds = 3;
|
||
|
||
private readonly MtlsCertificates? _mtlsCertificates;
|
||
|
||
/// <summary>
|
||
/// Создаёт пробу; mTLS-каналы — при переданных сертификатах (иначе plaintext, dev).
|
||
/// </summary>
|
||
/// <param name="mtlsCertificates">Сертификаты mTLS (Ruling 6, Task 13): null — plaintext-канал.</param>
|
||
public ServiceHealthProbe(MtlsCertificates? mtlsCertificates = null)
|
||
{
|
||
_mtlsCertificates = mtlsCertificates;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет health-контракт gRPC-сервиса по базовому адресу (grpc.health.v1, сервис "").
|
||
/// </summary>
|
||
/// <param name="endpoint">Базовый адрес сервиса (http://host:port; пустой/пробельный — ошибка аргумента).</param>
|
||
/// <param name="ct">Токен отмены вызывающего.</param>
|
||
/// <returns>Результат пробы (см. <see cref="ServiceHealthResult"/>).</returns>
|
||
public async Task<ServiceHealthResult> ProbeAsync(string endpoint, CancellationToken ct)
|
||
{
|
||
ArgumentException.ThrowIfNullOrWhiteSpace(endpoint);
|
||
using SocketsHttpHandler? handler = _mtlsCertificates?.CreateClientHttpHandler();
|
||
GrpcChannelOptions channelOptions = new() { MaxRetryAttempts = 0, HttpHandler = handler };
|
||
using var channel = GrpcChannel.ForAddress(endpoint, channelOptions);
|
||
var client = new Health.HealthClient(channel);
|
||
try
|
||
{
|
||
HealthCheckResponse response = await client.CheckAsync(
|
||
new HealthCheckRequest { Service = string.Empty },
|
||
deadline: DateTime.UtcNow.AddSeconds(HealthTimeoutSeconds),
|
||
cancellationToken: ct);
|
||
return new ServiceHealthResult(
|
||
Reachable: true,
|
||
Serving: response.Status == HealthCheckResponse.Types.ServingStatus.Serving);
|
||
}
|
||
catch (RpcException exception) when (exception.IsCommunicationFailure())
|
||
{
|
||
return ServiceHealthResult.Unreachable;
|
||
}
|
||
catch (HttpRequestException)
|
||
{
|
||
// Ошибка транспорта HTTP/2 (DNS/соединение) — до gRPC-статуса не дошло.
|
||
return ServiceHealthResult.Unreachable;
|
||
}
|
||
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
|
||
{
|
||
// Сработал дедлайн пробы (отмена вызывающего выше пробросилась бы дальше) — сервис не ответил за 3 с.
|
||
return ServiceHealthResult.Unreachable;
|
||
}
|
||
}
|
||
}
|