Files
Deal/src/core/Deal.Infrastructure/Integrations/Services/ServiceHealthProbe.cs
T
Rustam Khalimov 410194b0cb Разбить Infrastructure и корень Deal.Api по назначению
Integrations -> Abstractions/Exceptions/Extensions/Models/Options/
Services (включая Storage); Persistence-конфигурации -> Configurations;
корень Deal.Api (оркестратор/планировщики/DTO) -> Services/Dtos.
namespace приведён к путям, using добавлены/дедуплицированы, FQN
обновлены.
2026-09-11 13:20:10 +03:00

82 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}