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; /// /// Verifies connectivity to message queue (RabbitMQ/other). /// public sealed class MessageQueueCheck : IDoctorCheck { /// public string CheckId => "check.servicegraph.mq"; /// public string Name => "Message Queue Connectivity"; /// public string Description => "Verifies connectivity to the message queue service"; /// public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn; /// public IReadOnlyList Tags => ["connectivity", "messaging", "rabbitmq"]; /// public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5); /// public bool CanRun(DoctorPluginContext context) { var rabbitHost = context.Configuration.GetValue("RabbitMQ:Host") ?? context.Configuration.GetValue("Messaging:RabbitMQ:Host"); return !string.IsNullOrWhiteSpace(rabbitHost); } /// public async Task RunAsync(DoctorPluginContext context, CancellationToken ct) { var result = context.CreateResult(CheckId, "stellaops.doctor.servicegraph", DoctorCategory.ServiceGraph.ToString()); var rabbitHost = context.Configuration.GetValue("RabbitMQ:Host") ?? context.Configuration.GetValue("Messaging:RabbitMQ:Host"); var rabbitPort = context.Configuration.GetValue("RabbitMQ:Port") ?? context.Configuration.GetValue("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(); } } }