Files
seaweedfs/weed/admin/dash/node_versions_test.go
T
Chris Lu 8f6db360da admin: warn on dashboard when cluster nodes run mixed versions (#12196)
* admin: warn on dashboard when cluster nodes run mixed versions

A partially-finished upgrade leaves servers on different releases, and
nothing surfaces that drift today. The dashboard now compares the
binary version reported by every node:

- volume servers are asked directly via VolumeServerStatus (the
  heartbeat topology carries no version), fanned out with bounded
  concurrency and a 60s cache since versions only change on restart
- filers, S3 servers, and message brokers already report their version
  over KeepConnected; ListClusterNodes returns it, the dashboard just
  never read the field
- masters are excluded: raft membership does not carry versions

When every node matches, the Cluster panel shows the shared version.
When they differ, a warning banner calls out the mixed cluster and the
panel lists each node type with its count grouped by version, e.g.
"31x 4.39, 24x 4.44". Version columns on the volume server, filer, and
S3 tables (and the dedicated volume servers page) identify which nodes
are stale. Versions are compared without the volume-size-limit prefix
("8000GB 4.44 abc123" -> "4.44 abc123") so capacity config does not
masquerade as version drift; nodes reporting nothing count as
"unknown".

* admin: run the volume version queries concurrently, not under the map lock

The per-server VolumeServerStatus call ran while holding the versions
mutex, so the semaphore-bounded fan-out was fully serialized: every
server's RPC waited for the previous one, up to the 3s timeout each.

* admin: export the volume server table's real columns

The new Version cell shifted every later column, so the CSV read the
version as Rack and dropped Max Volumes, EC Shards and Usage a slot
early. Map the cells the table actually renders and add Version to the
export.

* admin: warn on mixed versions only when nodes report versions

An empty or master-only cluster produced a non-uniform summary with an
empty node list, which rendered the "Mixed cluster versions detected"
banner and warning icon over nothing. Mixed() requires reported nodes,
and the version card shows a dash when none answered.
2026-10-29 00:13:51 -07:00

102 lines
2.9 KiB
Go
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.
package dash
import (
"testing"
)
func TestShortVersion(t *testing.T) {
tests := []struct {
in string
want string
}{
{"8000GB 4.44-enterprise 192465e6", "4.44-enterprise 192465e6"},
{"30GB 4.51", "4.51"},
{"4.51 abc123", "4.51 abc123"},
{"4.51", "4.51"},
{"", "unknown"},
{" ", "unknown"},
}
for _, tc := range tests {
if got := ShortVersion(tc.in); got != tc.want {
t.Errorf("ShortVersion(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}
func TestBuildClusterVersionSummary(t *testing.T) {
vs := func(version string, n int) (out []VolumeServer) {
for i := 0; i < n; i++ {
out = append(out, VolumeServer{Version: version})
}
return
}
filers := func(version string, n int) (out []FilerNode) {
for i := 0; i < n; i++ {
out = append(out, FilerNode{Version: version})
}
return
}
t.Run("uniform across types", func(t *testing.T) {
s := buildClusterVersionSummary(
vs("8000GB 4.44 aaa", 3),
filers("30GB 4.44 aaa", 2), // different size-limit prefix, same binary
nil, nil,
)
if !s.Uniform || s.Version != "4.44 aaa" {
t.Fatalf("expected uniform 4.44 aaa, got %+v", s)
}
if len(s.NodeTypes) != 2 {
t.Fatalf("expected 2 node types, got %+v", s.NodeTypes)
}
})
t.Run("mixed volume server versions", func(t *testing.T) {
servers := append(vs("8000GB 4.44 aaa", 24), vs("8000GB 4.39 bbb", 31)...)
s := buildClusterVersionSummary(servers, nil, nil, nil)
if s.Uniform {
t.Fatalf("expected non-uniform summary, got %+v", s)
}
if len(s.NodeTypes) != 1 {
t.Fatalf("expected 1 node type, got %+v", s.NodeTypes)
}
nt := s.NodeTypes[0]
if nt.Type != "Volume Servers" || nt.Total != 55 {
t.Fatalf("unexpected node type summary: %+v", nt)
}
// sorted by count desc: 31x 4.39 first, then 24x 4.44
if len(nt.Versions) != 2 || nt.Versions[0].Version != "4.39 bbb" || nt.Versions[0].Count != 31 {
t.Fatalf("unexpected versions: %+v", nt.Versions)
}
if nt.Spread() != "31× 4.39 bbb, 24× 4.44 aaa" {
t.Fatalf("unexpected spread: %q", nt.Spread())
}
})
t.Run("unreported versions count as unknown", func(t *testing.T) {
s := buildClusterVersionSummary(vs("", 2), nil, nil, nil)
// all empty -> a single "unknown" group, uniform but not a real version
if !s.Uniform || s.Version != "unknown" {
t.Fatalf("expected uniform unknown, got %+v", s)
}
if s.NodeTypes[0].Versions[0].Version != "unknown" || s.NodeTypes[0].Versions[0].Count != 2 {
t.Fatalf("expected unknown group, got %+v", s.NodeTypes[0].Versions)
}
})
t.Run("unknown mixed with real versions is not uniform", func(t *testing.T) {
servers := append(vs("8000GB 4.44 aaa", 1), vs("", 1)...)
s := buildClusterVersionSummary(servers, nil, nil, nil)
if s.Uniform {
t.Fatalf("expected non-uniform summary, got %+v", s)
}
})
t.Run("no nodes is not uniform", func(t *testing.T) {
s := buildClusterVersionSummary(nil, nil, nil, nil)
if s.Uniform {
t.Fatal("empty cluster should not report a uniform version")
}
})
}