Merge pull request #2153 from binwiederhier/cluster-status-self

ntfy cluster status: this node as the table's first row
This commit is contained in:
Philipp C. Heckel authored and GitHub committed 2026-11-02 22:46:08 -05:00
commit c0111091fd
4 files changed
+30 -15

No files matched your search

+9 -9
View File
@@ -132,20 +132,17 @@ func clusterStatusBaseURL(listen string) (string, error) {
}
func printClusterStatus(w io.Writer, status *cluster.Status) {
leader, healthy := "not leader", "healthy"
// This node first, then its peers; the link columns are the peers' only
self, live := string(status.NodeID)+" (this node)", "yes"
if status.Leader {
leader = "leader"
self = string(status.NodeID) + " (this node, leader)"
}
if !status.Healthy {
healthy = "NOT healthy (registry heartbeat stale)"
}
fmt.Fprintf(w, "Node %s: %s, %s, %s\n\n", status.NodeID, status.Role, leader, healthy)
if len(status.Peers) == 0 {
fmt.Fprintln(w, "No peers.")
return
live = "no"
}
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "PEER\tROLE\tLIVE\tHEARTBEAT\tSENT OK\tFAILED\tQUEUED\tDROPPED\tLAST SENT\tRECEIVED\tLAST RECEIVED")
fmt.Fprintln(tw, "NODE\tROLE\tLIVE\tHEARTBEAT\tSENT OK\tFAILED\tQUEUED\tDROPPED\tLAST SENT\tRECEIVED\tLAST RECEIVED")
fmt.Fprintf(tw, "%s\t%s\t%s\t-\t-\t-\t-\t-\t-\t-\t-\n", self, status.Role, live)
for _, p := range status.Peers {
live, heartbeat := "no", "last seen "+clusterStatusAge(status.Time, p.LastSeen)
if p.Live {
@@ -156,6 +153,9 @@ func printClusterStatus(w io.Writer, status *cluster.Status) {
}
tw.Flush()
problems := clusterStatusProblems(status, "")
if !status.Healthy {
problems = append([]string{fmt.Sprintf("%s (this node): not healthy, its registry heartbeat is stale", status.NodeID)}, problems...)
}
if len(problems) == 0 {
return
}
+15
View File
@@ -84,6 +84,21 @@ func TestCLI_Cluster_Status(t *testing.T) {
require.Contains(t, out, "nyc3-app-1: no batch accepted") // The warning that matters
}
func TestCLI_Cluster_Status_ThisNodeInTheList(t *testing.T) {
// The node itself is the table's first row, not a line above it; a leader and an unhealthy node say so
status := &cluster.Status{NodeID: "nyc3-app-1", Role: cluster.RoleActive, Leader: true, Healthy: false, Time: 1792330000, Peers: []*cluster.PeerStatus{{
NodeID: "nyc3-app-2", Role: cluster.RolePassive, Live: true, HeartbeatAge: 1, Out: cluster.PeerOutbound{BatchesOK: 7, LastOK: 1792329999},
}}}
addr := newTestClusterStatusServerFor(t, func() *cluster.Status { return status })
app, _, stdout, _ := newTestApp()
require.Nil(t, app.Run([]string{"ntfy", "cluster", "--experimental-cluster-listen", addr, "--experimental-cluster-secret", "s3cret", "status"}))
lines := strings.Split(strings.TrimSpace(stdout.String()), "\n")
require.True(t, strings.HasPrefix(lines[0], "NODE "), "the table comes first:\n%s", stdout.String())
require.Regexp(t, `^nyc3-app-1 \(this node, leader\)\s+active\s+no\s`, lines[1])
require.Regexp(t, `^nyc3-app-2\s+passive\s+yes\s+1s ago\s+7\s`, lines[2])
require.Contains(t, stdout.String(), "nyc3-app-1 (this node): not healthy, its registry heartbeat is stale")
}
func TestCLI_Cluster_Status_JSON(t *testing.T) {
addr := newTestClusterStatusServer(t)
app, _, stdout, _ := newTestApp()
+5 -6
View File
@@ -2376,15 +2376,14 @@ once.
`/v1/health` only covers a node's own database heartbeat. A link between two nodes that works in one direction only,
e.g. because a firewall drops the cluster port one way, keeps both nodes healthy while messages published on one of them
reach the other only late, through the database. To see each node's view of its peers, run `ntfy cluster status` on the
node. It reads `experimental-cluster-listen` and `experimental-cluster-secret` from the server config, and shows per
peer the message batches sent there and received from there:
node. It reads `experimental-cluster-listen` and `experimental-cluster-secret` from the server config, and lists the
node itself (its role, and whether it leads), then per peer the message batches sent there and received from there:
```
$ ntfy cluster status
Node nyc3-app-2: passive, not leader, healthy
PEER ROLE LIVE HEARTBEAT SENT OK FAILED QUEUED DROPPED LAST SENT RECEIVED LAST RECEIVED
nyc3-app-1 active yes 2s ago 0 219 0 0 never 5120 1s ago
NODE ROLE LIVE HEARTBEAT SENT OK FAILED QUEUED DROPPED LAST SENT RECEIVED LAST RECEIVED
nyc3-app-2 (this node) passive yes - - - - - - - -
nyc3-app-1 active yes 2s ago 0 219 0 0 never 5120 1s ago
Problems:
nyc3-app-1: no batch accepted, 219 failed; last error 10s ago: Post "http://10.108.0.2:2587/v1/cluster/message": context deadline exceeded
+1
View File
@@ -2356,6 +2356,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Bug fixes + maintenance:**
* Do not send TCP keep-alive probes on the client listeners (`listen-http`, `listen-https`): Go sends one every 15 seconds on an idle connection by default, which, without a proxy in front of ntfy, woke phones holding a stream three times as often as ntfy's own keepalive event ([#2152](https://github.com/binwiederhier/ntfy/pull/2152))
* `ntfy cluster status` lists the node itself as the first row of the table (marked "this node", and "leader" if it leads) instead of a line above it ([#2153](https://github.com/binwiederhier/ntfy/pull/2153))
### ntfy iOS app v1.8.0 (UNRELEASED)