mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
fix(e2e): stop flagging a Cluster bind to a single-homed node's own address (#789)
checkSocketScope compared a socket's bind address to node.Addr and called it the node's WAN address, but node.Addr is the cluster peer address (harness.Node's own doc). On CI's single-homed tofu nodes this is the node's only address, so every Cluster-scope service failed no matter what it bound. Compare against a positively-known SPINIFEX_WAN_IP instead, distinct from the peer address, and record via harness.Detail when no such address is available so the skipped check is visible rather than silently passing.
This commit is contained in:
@@ -5,6 +5,7 @@ package multinode
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -34,6 +35,19 @@ func runListenerInvariant(t *testing.T, fix *Fixture) {
|
||||
t.Fatalf("parse inbound listener inventory: %v", err)
|
||||
}
|
||||
|
||||
// SPINIFEX_WAN_IP is read directly (not via harness.Env.WANHost, which
|
||||
// falls back to the first NodeIPs entry — i.e. some node's own peer
|
||||
// address — when unset) so a WAN address is only ever trusted here when
|
||||
// it was positively supplied, never inferred.
|
||||
wanAddr := strings.TrimSpace(os.Getenv("SPINIFEX_WAN_IP"))
|
||||
if wanAddr == "" {
|
||||
harness.Detail(t, "wan_bind_check",
|
||||
"skipped for every node: SPINIFEX_WAN_IP is unset, so no address can be "+
|
||||
"distinguished from each node's cluster peer address")
|
||||
} else {
|
||||
harness.Detail(t, "wan_addr", wanAddr)
|
||||
}
|
||||
|
||||
ssh := harness.NewPeerSSH()
|
||||
var violations []string
|
||||
for _, node := range fix.Cluster.Nodes {
|
||||
@@ -45,12 +59,18 @@ func runListenerInvariant(t *testing.T, fix *Fixture) {
|
||||
t.Fatalf("ss -tulnp on %s: %v", node.Name, runErr)
|
||||
}
|
||||
|
||||
if wanAddr != "" && wanAddr == node.Addr {
|
||||
harness.Detail(t, "wan_bind_check", fmt.Sprintf(
|
||||
"skipped on %s: SPINIFEX_WAN_IP equals this node's peer address, so it is "+
|
||||
"single-homed for the purpose of this check", node.Name))
|
||||
}
|
||||
|
||||
for _, sock := range parseSSOutput(string(out)) {
|
||||
for _, r := range table.RowsForPort(sock.port) {
|
||||
if r.Scope != listenerinventory.ScopeCluster && r.Scope != listenerinventory.ScopeEncap {
|
||||
continue
|
||||
}
|
||||
if v := checkSocketScope(node, sock, r); v != "" {
|
||||
if v := checkSocketScope(node, sock, r, wanAddr); v != "" {
|
||||
violations = append(violations, v)
|
||||
}
|
||||
}
|
||||
@@ -67,8 +87,13 @@ func runListenerInvariant(t *testing.T, fix *Fixture) {
|
||||
|
||||
// checkSocketScope reports a violation message if sock is bound somewhere
|
||||
// row's Scope does not permit, honoring row's own doc-declared wildcard
|
||||
// exception. Returns "" if the socket is clean for this row.
|
||||
func checkSocketScope(node harness.Node, sock ssSocket, row listenerinventory.Row) string {
|
||||
// exception. node.Addr is the cluster peer address (SSH target, not
|
||||
// necessarily WAN-facing — a single-homed node has no other address).
|
||||
// wanAddr is a positively-known WAN address, or "" when none is available;
|
||||
// it is only meaningful when distinct from node.Addr, since on a
|
||||
// single-homed node they are the same address by definition. Returns "" if
|
||||
// the socket is clean for this row.
|
||||
func checkSocketScope(node harness.Node, sock ssSocket, row listenerinventory.Row, wanAddr string) string {
|
||||
wildcard := listenerinventory.IsWildcardAddr(sock.addr)
|
||||
switch {
|
||||
case wildcard && row.WildcardOK():
|
||||
@@ -77,10 +102,10 @@ func checkSocketScope(node harness.Node, sock ssSocket, row listenerinventory.Ro
|
||||
return fmt.Sprintf(
|
||||
"%s: port %d (%s, %s scope) bound to the wildcard address %s; its inventory row does not declare a wildcard exception",
|
||||
node.Name, sock.port, sock.proto, row.Scope, sock.addr)
|
||||
case sock.addr == node.Addr:
|
||||
case wanAddr != "" && wanAddr != node.Addr && sock.addr == wanAddr:
|
||||
return fmt.Sprintf(
|
||||
"%s: port %d (%s, %s scope) bound to %s, the node's WAN address",
|
||||
node.Name, sock.port, sock.proto, row.Scope, sock.addr)
|
||||
"%s: port %d (%s, %s scope) bound to %s, a WAN address distinct from the node's peer address %s",
|
||||
node.Name, sock.port, sock.proto, row.Scope, sock.addr, node.Addr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//go:build e2e
|
||||
|
||||
package multinode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/network/listenerinventory"
|
||||
"github.com/mulgadc/spinifex/tests/e2e/harness"
|
||||
)
|
||||
|
||||
// TestCheckSocketScope covers the four outcomes checkSocketScope must
|
||||
// produce: the wildcard rule (with and without a row-declared exception)
|
||||
// and the WAN-vs-peer-address rule (single-homed passes, a genuinely
|
||||
// distinct WAN bind fails).
|
||||
func TestCheckSocketScope(t *testing.T) {
|
||||
clusterRow := listenerinventory.Row{Scope: listenerinventory.ScopeCluster}
|
||||
wildcardOKRow := listenerinventory.Row{
|
||||
Scope: listenerinventory.ScopeCluster,
|
||||
Purpose: "Binds the wildcard by design.",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
node harness.Node
|
||||
sock ssSocket
|
||||
row listenerinventory.Row
|
||||
wanAddr string
|
||||
wantMessage bool
|
||||
}{
|
||||
{
|
||||
name: "wildcard bind with declared exception passes",
|
||||
node: harness.Node{Name: "node1", Addr: "10.2.0.5"},
|
||||
sock: ssSocket{proto: "tcp", addr: "0.0.0.0", port: 5300},
|
||||
row: wildcardOKRow,
|
||||
wanAddr: "",
|
||||
wantMessage: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard bind without a declared exception fails",
|
||||
node: harness.Node{Name: "node1", Addr: "10.2.0.5"},
|
||||
sock: ssSocket{proto: "tcp", addr: "0.0.0.0", port: 6641},
|
||||
row: clusterRow,
|
||||
wanAddr: "",
|
||||
wantMessage: true,
|
||||
},
|
||||
{
|
||||
name: "single-homed node bound to its only address passes",
|
||||
node: harness.Node{Name: "node1", Addr: "192.168.0.22"},
|
||||
sock: ssSocket{proto: "tcp", addr: "192.168.0.22", port: 4432},
|
||||
row: clusterRow,
|
||||
wanAddr: "",
|
||||
wantMessage: false,
|
||||
},
|
||||
{
|
||||
name: "multi-homed node bound to a distinct WAN address fails",
|
||||
node: harness.Node{Name: "node4", Addr: "10.2.0.5"},
|
||||
sock: ssSocket{proto: "tcp", addr: "72.52.77.231", port: 6641},
|
||||
row: clusterRow,
|
||||
wanAddr: "72.52.77.231",
|
||||
wantMessage: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := checkSocketScope(tc.node, tc.sock, tc.row, tc.wanAddr)
|
||||
if tc.wantMessage && got == "" {
|
||||
t.Fatalf("checkSocketScope() = %q, want a violation message", got)
|
||||
}
|
||||
if !tc.wantMessage && got != "" {
|
||||
t.Fatalf("checkSocketScope() = %q, want no violation", got)
|
||||
}
|
||||
t.Logf("checkSocketScope() = %q", got)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user