Files
llmgw/internal/control/fabrics/fabrics_test.go
T
Ilker ManapandClaude Opus 5.5 23648fa19a Watch each fabric's subnet managers, and keep a fabric one through a failover
A fabric's identity was its master subnet manager, which changes on a
failover: for the minute its ports took to agree, one fabric read as
two, and a name covering it as two separate fabrics. Each agent now
also lists every subnet manager on a fabric (saquery SMIR, through one
port per master, once a minute); managers listed together are one
fabric, named by the lowest GUID among them, so the identity survives
the master changing.

The same list says what a fabric can survive. The cluster page's new
subnet manager table, /api/v1/fabrics, doctor and metrics
(llmgw_fabric_master_sm, llmgw_fabric_subnet_managers,
llmgw_fabric_problems) show each fabric's master, priority and
standbys, and say when a fabric has no standby, its ports disagree on
the master, ports are up with no subnet manager (INIT), or the master
changed in the last hour. Every change of master a port reports is
kept, and a fabric's last day of them is listed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 07:47:03 +02:00

151 lines
5.5 KiB
Go

package fabrics
import (
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/inventory"
)
func ibNode(id, declared, sm string, hcas ...string) inventory.Node {
n := inventory.Node{ID: id}
if declared != "" {
n.Fabrics = []inventory.FabricAttachment{{Fabric: declared, HCAs: hcas}}
}
for _, h := range hcas {
n.IBPorts = append(n.IBPorts, inventory.IBPort{HCA: h, Port: 1, State: "ACTIVE", RateGbps: 400,
LinkLayer: "InfiniBand", SMGUID: sm, SMLID: 1, GIDPrefix: "fe80:0000:0000:0000"})
}
return n
}
func problem(t *testing.T, r Report, l Level, fragment string) {
t.Helper()
for _, p := range r.Problems {
if p.Level == l && strings.Contains(p.Message, fragment) {
return
}
}
t.Errorf("no %s problem containing %q in %+v", l, fragment, r.Problems)
}
func TestIndependentFabricsWithTheirOwnNamesAreFine(t *testing.T) {
r := analyze([]inventory.Node{
ibNode("a1", "ib-a", "0x01", "mlx5_0"), ibNode("a2", "ib-a", "0x01", "mlx5_0"),
ibNode("b1", "ib-b", "0x02", "mlx5_0"),
})
if len(r.Physical) != 2 {
t.Fatalf("physical = %+v", r.Physical)
}
if r.Physical[0].Declared[0] != "ib-a" || len(r.Physical[0].Members) != 2 || r.Physical[0].SM.GUID != "0x01" {
t.Errorf("ib-a = %+v", r.Physical[0])
}
for _, p := range r.Problems {
if p.Level != Info {
t.Errorf("unexpected problem: %+v", p)
}
}
// Both on the default prefix: said, quietly.
problem(t, r, Info, "share the subnet prefix")
}
func TestOneNameOnTwoFabricsIsAFailure(t *testing.T) {
r := analyze([]inventory.Node{
ibNode("a1", "ib-a", "0x01", "mlx5_0"), ibNode("b1", "ib-a", "0x02", "mlx5_0"),
})
problem(t, r, Fail, "ib-a is 2 separate fabrics")
}
func TestTwoNamesOnOneFabricAreSaid(t *testing.T) {
r := analyze([]inventory.Node{
ibNode("a1", "ib-a", "0x01", "mlx5_0"), ibNode("b1", "ib-b", "0x01", "mlx5_0"),
})
problem(t, r, Warn, "ib-a and ib-b are one fabric")
}
func TestAnAdapterUpButUndeclaredIsSaid(t *testing.T) {
n := ibNode("a1", "ib-a", "0x01", "mlx5_0")
n.IBPorts = append(n.IBPorts, inventory.IBPort{HCA: "mlx5_3", Port: 1, State: "ACTIVE", LinkLayer: "InfiniBand", SMGUID: "0x09"})
problem(t, analyze([]inventory.Node{n}), Warn, "mlx5_3 is up on fabric of subnet manager 0x09")
}
func TestOneNodesAdaptersOnTwoFabricsAreAFailure(t *testing.T) {
n := ibNode("x", "ib-a", "0x01", "mlx5_0", "mlx5_1")
n.IBPorts[1].SMGUID = "0x02"
problem(t, analyze([]inventory.Node{n}), Fail, "cabled to 2 different fabrics")
}
func TestANodeThatCannotSayIsReported(t *testing.T) {
n := ibNode("x", "ib-a", "", "mlx5_0")
n.IBPorts[0].SMError = "sminfo not found: install infiniband-diags"
problem(t, analyze([]inventory.Node{n}), Info, "install infiniband-diags")
}
func TestRoCEIsItsNetwork(t *testing.T) {
n := inventory.Node{ID: "r1", Fabrics: []inventory.FabricAttachment{{Fabric: "roce-a", HCAs: []string{"mlx5_0"}}},
IBPorts: []inventory.IBPort{{HCA: "mlx5_0", Port: 1, State: "ACTIVE", LinkLayer: "Ethernet", Network: "10.8.0.0/24"}}}
r := analyze([]inventory.Node{n})
if len(r.Physical) != 1 || r.Physical[0].Kind != "roce" || r.Physical[0].Label != "RoCE 10.8.0.0/24" {
t.Errorf("physical = %+v", r.Physical)
}
}
func analyze(nodes []inventory.Node) Report { return Analyze(nodes, nil, time.Now()) }
func withSMs(n inventory.Node, sms ...inventory.SubnetManager) inventory.Node {
n.IBPorts[0].SMs = sms
return n
}
func TestAFailoverKeepsAFabricOneFabric(t *testing.T) {
// Mid-failover some ports see the old master, some the new. Keyed by the
// master alone, one fabric read as two for a minute — and a declared name
// covering it as "two separate fabrics". The list of managers is the same
// before and after, which is what keeps it one.
sms := []inventory.SubnetManager{{GUID: "0x01", State: "master", Priority: 15}, {GUID: "0x02", State: "standby", Priority: 14}}
r := analyze([]inventory.Node{
withSMs(ibNode("a1", "ib-a", "0x01", "mlx5_0"), sms...),
ibNode("a2", "ib-a", "0x02", "mlx5_0"),
})
if len(r.Physical) != 1 {
t.Fatalf("one fabric read as %d: %+v", len(r.Physical), r.Physical)
}
for _, p := range r.Problems {
if p.Level == Fail {
t.Errorf("a failover was reported as a failure: %s", p.Message)
}
}
problem(t, r, Warn, "do not agree on the master subnet manager")
if r.Physical[0].Standbys != 1 || len(r.Physical[0].SMs) != 2 || r.Physical[0].SMs[0].State != "master" {
t.Errorf("managers = %+v", r.Physical[0].SMs)
}
}
func TestAFabricWithNoStandbyIsSaid(t *testing.T) {
r := analyze([]inventory.Node{withSMs(ibNode("a1", "ib-a", "0x01", "mlx5_0"),
inventory.SubnetManager{GUID: "0x01", State: "master"})})
problem(t, r, Warn, "one subnet manager and no standby")
}
func TestPortsUpWithoutASubnetManagerAreAFailure(t *testing.T) {
n := ibNode("a1", "ib-a", "", "mlx5_0")
n.IBPorts[0].State, n.IBPorts[0].PhysState = "INIT", "LinkUp"
problem(t, analyze([]inventory.Node{n}), Fail, "no subnet manager has configured them")
}
func TestARecentFailoverIsShown(t *testing.T) {
now := time.Now()
nodes := []inventory.Node{withSMs(ibNode("a1", "ib-a", "0x02", "mlx5_0"),
inventory.SubnetManager{GUID: "0x01", State: "standby"}, inventory.SubnetManager{GUID: "0x02", State: "master"})}
changes := []inventory.SMChange{
{At: now.Add(-20 * time.Minute), Node: "a1", Port: "mlx5_0/1", From: "0x01", To: "0x02"},
{At: now.Add(-48 * time.Hour), Node: "a1", Port: "mlx5_0/1", From: "0x02", To: "0x01"},
}
r := Analyze(nodes, changes, now)
if got := r.Physical[0].Changes; len(got) != 1 || got[0].To != "0x02" {
t.Errorf("changes in the last day = %+v", got)
}
problem(t, r, Warn, "the master subnet manager changed 20m0s ago")
}