mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 19:56:33 +00:00
fix(e2e): resolve WANHost from spinifex.toml advertise on single-node (#527)
discoverSingleNodeIPs seeds 127.0.0.1 then appends net.InterfaceAddrs() in undefined order, so WANHost=nodeIPs[0] was loopback and the guest gateway URL helpers fell through to the first non-loopback interface addr — an arbitrary co-tenant bridge on multi-bridge shared hosts (wattle: br-lan-env1..23, br-wan), leaving guest VMs unable to reach the advertised gateway to register. Resolve WANHost from spinifex.toml nodes.<node>.advertise (falling back to that node's host), mirroring awsgwBindIP's toml scan; fall back to the first non-loopback discovered addr only when config is unreadable. This removes the need for the SPINIFEX_WAN_IP override on shared hosts.
This commit is contained in:
@@ -81,6 +81,20 @@ func LoadEnv(t *testing.T) *Env {
|
||||
}
|
||||
|
||||
wanHost := getenv("SPINIFEX_WAN_IP", "")
|
||||
if wanHost == "" && mode == ModeSingle {
|
||||
// Prefer the node's advertised address so multi-bridge shared hosts
|
||||
// resolve the advertised WAN bridge, not an arbitrary co-tenant bridge
|
||||
// picked by interface-enumeration order.
|
||||
wanHost = advertiseIP(configDir)
|
||||
}
|
||||
if wanHost == "" {
|
||||
for _, ip := range nodeIPs {
|
||||
if !strings.HasPrefix(ip, "127.") {
|
||||
wanHost = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if wanHost == "" && len(nodeIPs) > 0 {
|
||||
wanHost = nodeIPs[0]
|
||||
}
|
||||
@@ -196,3 +210,54 @@ func awsgwBindIP(configDir string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
tomlNodeLine = regexp.MustCompile(`(?i)^\s*node\s*=\s*"([^"]+)"`)
|
||||
tomlAdvertiseLine = regexp.MustCompile(`(?i)^\s*advertise\s*=\s*"([^":]+)`)
|
||||
tomlHostLine = regexp.MustCompile(`(?i)^\s*host\s*=\s*"([^":]+)`)
|
||||
)
|
||||
|
||||
// advertiseIP returns the externally-reachable address of the local node from
|
||||
// spinifex.toml (nodes.<node>.advertise, or that node's host when advertise is
|
||||
// unset). The local node is named by the top-level node key. Returns "" when
|
||||
// the file or node cannot be resolved so callers fall back to interface scan.
|
||||
func advertiseIP(configDir string) string {
|
||||
if configDir == "" {
|
||||
return ""
|
||||
}
|
||||
f, err := os.Open(filepath.Join(configDir, "spinifex.toml"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var node, advertise, host string
|
||||
inNode := false
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "[") {
|
||||
// Match only the [nodes.<node>] top-level table, not sub-tables
|
||||
// like [nodes.<node>.awsgw].
|
||||
inNode = node != "" && line == "[nodes."+node+"]"
|
||||
continue
|
||||
}
|
||||
if !inNode {
|
||||
if node == "" {
|
||||
if m := tomlNodeLine.FindStringSubmatch(line); m != nil {
|
||||
node = m[1]
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if m := tomlAdvertiseLine.FindStringSubmatch(line); m != nil {
|
||||
advertise = m[1]
|
||||
} else if m := tomlHostLine.FindStringSubmatch(line); m != nil {
|
||||
host = m[1]
|
||||
}
|
||||
}
|
||||
if advertise != "" {
|
||||
return advertise
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//go:build e2e
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdvertiseIP(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
toml string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "advertise preferred over host and ignores sub-tables",
|
||||
toml: `node = "wattle"
|
||||
[nodes.wattle]
|
||||
host = "10.105.1.3"
|
||||
advertise = "192.168.1.13"
|
||||
[nodes.wattle.awsgw]
|
||||
host = "10.105.1.3:9999"
|
||||
`,
|
||||
want: "192.168.1.13",
|
||||
},
|
||||
{
|
||||
name: "falls back to host when advertise unset",
|
||||
toml: `node = "dev1"
|
||||
[nodes.dev1]
|
||||
host = "192.168.1.20"
|
||||
`,
|
||||
want: "192.168.1.20",
|
||||
},
|
||||
{
|
||||
name: "resolves only the local node, not co-tenant nodes",
|
||||
toml: `node = "n2"
|
||||
[nodes.n1]
|
||||
host = "10.0.0.1"
|
||||
advertise = "1.1.1.1"
|
||||
[nodes.n2]
|
||||
host = "10.0.0.2"
|
||||
advertise = "2.2.2.2"
|
||||
`,
|
||||
want: "2.2.2.2",
|
||||
},
|
||||
{
|
||||
name: "empty when top-level node key is absent",
|
||||
toml: `[nodes.x]
|
||||
advertise = "9.9.9.9"
|
||||
`,
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "spinifex.toml"), []byte(tc.toml), 0o600); err != nil {
|
||||
t.Fatalf("write toml: %v", err)
|
||||
}
|
||||
if got := advertiseIP(dir); got != tc.want {
|
||||
t.Fatalf("advertiseIP = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvertiseIPMissing(t *testing.T) {
|
||||
if got := advertiseIP(""); got != "" {
|
||||
t.Fatalf("advertiseIP(\"\") = %q, want empty", got)
|
||||
}
|
||||
if got := advertiseIP(t.TempDir()); got != "" {
|
||||
t.Fatalf("advertiseIP(missing file) = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user