153 lines
6.1 KiB
C#
153 lines
6.1 KiB
C#
using System.Globalization;
|
|
using System.Net.Sockets;
|
|
using Microsoft.Extensions.Configuration;
|
|
using StellaOps.Doctor.Models;
|
|
using StellaOps.Doctor.Plugins;
|
|
using StellaOps.Doctor.Plugins.Builders;
|
|
|
|
namespace StellaOps.Doctor.Plugins.ServiceGraph.Checks;
|
|
|
|
/// <summary>
|
|
/// Verifies connectivity to message queue (RabbitMQ/other).
|
|
/// </summary>
|
|
public sealed class MessageQueueCheck : IDoctorCheck
|
|
{
|
|
/// <inheritdoc />
|
|
public string CheckId => "check.servicegraph.mq";
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "Message Queue Connectivity";
|
|
|
|
/// <inheritdoc />
|
|
public string Description => "Verifies connectivity to the message queue service";
|
|
|
|
/// <inheritdoc />
|
|
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<string> Tags => ["connectivity", "messaging", "rabbitmq"];
|
|
|
|
/// <inheritdoc />
|
|
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
|
|
|
|
/// <inheritdoc />
|
|
public bool CanRun(DoctorPluginContext context)
|
|
{
|
|
var rabbitHost = context.Configuration.GetValue<string>("RabbitMQ:Host")
|
|
?? context.Configuration.GetValue<string>("Messaging:RabbitMQ:Host");
|
|
return !string.IsNullOrWhiteSpace(rabbitHost);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
|
|
{
|
|
var result = context.CreateResult(CheckId, "stellaops.doctor.servicegraph", DoctorCategory.ServiceGraph.ToString());
|
|
|
|
var rabbitHost = context.Configuration.GetValue<string>("RabbitMQ:Host")
|
|
?? context.Configuration.GetValue<string>("Messaging:RabbitMQ:Host");
|
|
var rabbitPort = context.Configuration.GetValue<int?>("RabbitMQ:Port")
|
|
?? context.Configuration.GetValue<int?>("Messaging:RabbitMQ:Port")
|
|
?? 5672;
|
|
|
|
if (string.IsNullOrWhiteSpace(rabbitHost))
|
|
{
|
|
return result
|
|
.Skip("Message queue not configured")
|
|
.WithEvidence("Configuration", e => e.Add("RabbitMQ:Host", "(not set)"))
|
|
.Build();
|
|
}
|
|
|
|
try
|
|
{
|
|
using var client = new TcpClient();
|
|
var connectTask = client.ConnectAsync(rabbitHost, rabbitPort, ct);
|
|
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5), ct);
|
|
|
|
var completedTask = await Task.WhenAny(connectTask.AsTask(), timeoutTask);
|
|
|
|
if (completedTask == timeoutTask)
|
|
{
|
|
return result
|
|
.Fail($"Connection to RabbitMQ at {rabbitHost}:{rabbitPort} timed out")
|
|
.WithEvidence("Message queue connectivity", e =>
|
|
{
|
|
e.Add("Host", rabbitHost);
|
|
e.Add("Port", rabbitPort.ToString(CultureInfo.InvariantCulture));
|
|
e.Add("Status", "timeout");
|
|
})
|
|
.WithCauses(
|
|
"RabbitMQ server is not running",
|
|
"Network connectivity issues",
|
|
"Firewall blocking AMQP port")
|
|
.WithRemediation(r => r
|
|
.AddManualStep(1, "Check RabbitMQ status", "docker ps | grep rabbitmq")
|
|
.AddManualStep(2, "Check RabbitMQ logs", "docker logs rabbitmq")
|
|
.AddManualStep(3, "Start RabbitMQ", "docker-compose up -d rabbitmq"))
|
|
.WithVerification("stella doctor --check check.servicegraph.mq")
|
|
.Build();
|
|
}
|
|
|
|
await connectTask;
|
|
|
|
if (client.Connected)
|
|
{
|
|
return result
|
|
.Pass($"Message queue reachable at {rabbitHost}:{rabbitPort}")
|
|
.WithEvidence("Message queue connectivity", e =>
|
|
{
|
|
e.Add("Host", rabbitHost);
|
|
e.Add("Port", rabbitPort.ToString(CultureInfo.InvariantCulture));
|
|
e.Add("Status", "connected");
|
|
})
|
|
.Build();
|
|
}
|
|
else
|
|
{
|
|
return result
|
|
.Fail($"Failed to connect to message queue at {rabbitHost}:{rabbitPort}")
|
|
.WithEvidence("Message queue connectivity", e =>
|
|
{
|
|
e.Add("Host", rabbitHost);
|
|
e.Add("Port", rabbitPort.ToString(CultureInfo.InvariantCulture));
|
|
e.Add("Status", "connection_failed");
|
|
})
|
|
.Build();
|
|
}
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
return result
|
|
.Fail($"Socket error connecting to message queue: {ex.Message}")
|
|
.WithEvidence("Message queue connectivity", e =>
|
|
{
|
|
e.Add("Host", rabbitHost);
|
|
e.Add("Port", rabbitPort.ToString(CultureInfo.InvariantCulture));
|
|
e.Add("SocketErrorCode", ex.SocketErrorCode.ToString());
|
|
e.Add("Error", ex.Message);
|
|
})
|
|
.WithCauses(
|
|
"RabbitMQ server is not running",
|
|
"DNS resolution failed",
|
|
"Network unreachable")
|
|
.WithRemediation(r => r
|
|
.AddManualStep(1, "Start RabbitMQ", "docker-compose up -d rabbitmq")
|
|
.AddManualStep(2, "Verify DNS", $"nslookup {rabbitHost}"))
|
|
.WithVerification("stella doctor --check check.servicegraph.mq")
|
|
.Build();
|
|
}
|
|
catch (Exception ex) when (ex is not OperationCanceledException)
|
|
{
|
|
return result
|
|
.Fail($"Error connecting to message queue: {ex.Message}")
|
|
.WithEvidence("Message queue connectivity", e =>
|
|
{
|
|
e.Add("Host", rabbitHost);
|
|
e.Add("Port", rabbitPort.ToString(CultureInfo.InvariantCulture));
|
|
e.Add("ErrorType", ex.GetType().Name);
|
|
e.Add("Error", ex.Message);
|
|
})
|
|
.Build();
|
|
}
|
|
}
|
|
}
|