Add cluster-aware APRS-IS status checking
Both /status and /status.json endpoints now show cluster-wide APRS-IS connection status instead of just local node status. Features: - get_cluster_aprs_status() checks all nodes in cluster via RPC - Returns status from any connected node (usually the leader) - Shows cluster info: total nodes, connected nodes, leader node - Graceful fallback to local status in non-clustered mode - Enhanced status page UI with cluster information display This ensures status pages accurately reflect the true APRS-IS connection state across the entire cluster. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
37c9ac79ed
commit
0f26cd7e22
3 changed files with 122 additions and 3 deletions
|
|
@ -22,6 +22,21 @@ defmodule Aprsme.Cluster.LeaderElection do
|
|||
GenServer.call(__MODULE__, :current_leader)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets APRS-IS status from across the entire cluster.
|
||||
Returns the status from whichever node has an active connection.
|
||||
"""
|
||||
def get_cluster_aprs_status do
|
||||
cluster_enabled = Application.get_env(:aprsme, :cluster_enabled, false)
|
||||
|
||||
if cluster_enabled do
|
||||
get_cluster_wide_status()
|
||||
else
|
||||
# Non-clustered mode - just return local status
|
||||
Aprsme.Is.get_status()
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
Logger.info("Starting leader election process")
|
||||
|
|
@ -179,6 +194,67 @@ defmodule Aprsme.Cluster.LeaderElection do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_cluster_wide_status do
|
||||
all_nodes = [node() | Node.list()]
|
||||
|
||||
# Check each node for APRS-IS connection status
|
||||
connected_statuses =
|
||||
all_nodes
|
||||
|> Enum.map(&get_node_status/1)
|
||||
|> Enum.filter(fn status -> status.connected end)
|
||||
|
||||
case connected_statuses do
|
||||
[status | _] ->
|
||||
# At least one node is connected - return its status
|
||||
# Add cluster info to indicate this is cluster-wide status
|
||||
Map.put(status, :cluster_info, %{
|
||||
total_nodes: length(all_nodes),
|
||||
connected_nodes: length(connected_statuses),
|
||||
leader_node: get_leader_node_name()
|
||||
})
|
||||
|
||||
[] ->
|
||||
# No nodes are connected - return local status but mark as cluster-wide
|
||||
local_status = Aprsme.Is.get_status()
|
||||
|
||||
Map.put(local_status, :cluster_info, %{
|
||||
total_nodes: length(all_nodes),
|
||||
connected_nodes: 0,
|
||||
leader_node: get_leader_node_name()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
defp get_node_status(node_name) do
|
||||
if node_name == node() do
|
||||
# Local node - call directly
|
||||
Aprsme.Is.get_status()
|
||||
else
|
||||
# Remote node - use RPC
|
||||
case :rpc.call(node_name, Aprsme.Is, :get_status, [], 5000) do
|
||||
{:badrpc, _reason} ->
|
||||
# Node unreachable - return disconnected status
|
||||
%{connected: false, server: "unreachable", port: 0}
|
||||
|
||||
status when is_map(status) ->
|
||||
status
|
||||
|
||||
_ ->
|
||||
%{connected: false, server: "error", port: 0}
|
||||
end
|
||||
end
|
||||
rescue
|
||||
_error ->
|
||||
%{connected: false, server: "error", port: 0}
|
||||
end
|
||||
|
||||
defp get_leader_node_name do
|
||||
case :global.whereis_name(@election_key) do
|
||||
:undefined -> "none"
|
||||
pid when is_pid(pid) -> pid |> node() |> to_string()
|
||||
end
|
||||
end
|
||||
|
||||
defp notify_leadership_change(became_leader) do
|
||||
Phoenix.PubSub.broadcast(
|
||||
Aprsme.PubSub,
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ defmodule AprsmeWeb.PageController do
|
|||
end
|
||||
|
||||
def status_json(conn, _params) do
|
||||
# Get APRS-IS connection status
|
||||
aprs_status = Aprsme.Is.get_status()
|
||||
# Get cluster-wide APRS-IS connection status
|
||||
aprs_status = Aprsme.Cluster.LeaderElection.get_cluster_aprs_status()
|
||||
|
||||
# Get application version
|
||||
version = :aprsme |> Application.spec(:vsn) |> List.to_string()
|
||||
|
|
|
|||
|
|
@ -283,6 +283,49 @@ defmodule AprsmeWeb.StatusLive.Index do
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cluster Information (if available) -->
|
||||
<%= if Map.has_key?(@aprs_status, :cluster_info) do %>
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">{gettext("Cluster Status")}</h2>
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Total Nodes:")}</span>
|
||||
<span class="text-sm font-mono">{@aprs_status.cluster_info.total_nodes}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Connected Nodes:")}</span>
|
||||
<span class="text-sm font-mono">{@aprs_status.cluster_info.connected_nodes}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Leader Node:")}</span>
|
||||
<span class="text-sm font-mono">{@aprs_status.cluster_info.leader_node}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">{gettext("Cluster Mode:")}</span>
|
||||
<div class="badge badge-success gap-1">
|
||||
<div class="w-2 h-2 bg-current rounded-full"></div>
|
||||
{gettext("Enabled")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs opacity-70 mt-2">
|
||||
{gettext(
|
||||
"Status is aggregated from all nodes in the cluster. Only the leader node maintains the APRS-IS connection."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -292,7 +335,7 @@ defmodule AprsmeWeb.StatusLive.Index do
|
|||
# Private functions
|
||||
|
||||
defp get_aprs_status do
|
||||
Aprsme.Is.get_status()
|
||||
Aprsme.Cluster.LeaderElection.get_cluster_aprs_status()
|
||||
rescue
|
||||
error ->
|
||||
require Logger
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue