mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
fix: chassis names mismatch
This commit is contained in:
@@ -711,7 +711,9 @@ var vpcdStartCmd = &cobra.Command{
|
||||
})
|
||||
}
|
||||
|
||||
// Derive OVN chassis names from cluster node names (format: "chassis-{hostname}")
|
||||
// Fallback chassis names derived from config node names. vpcd prefers
|
||||
// discovering chassis from the OVN Southbound DB at startup; these are
|
||||
// only used if that discovery fails.
|
||||
var chassisNames []string
|
||||
for nodeName := range clusterConfig.Nodes {
|
||||
chassisNames = append(chassisNames, "chassis-"+nodeName)
|
||||
|
||||
@@ -160,10 +160,9 @@ elif [ -n "$WAN_IFACE" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Chassis ID must match what vpcd derives from the node name in spinifex.toml.
|
||||
# vpcd generates "chassis-{nodeName}" (e.g., chassis-node1) and uses it to schedule
|
||||
# gateway chassis in the NB DB. ovn-controller registers in the SB DB using the OVS
|
||||
# system-id. If they don't match, gateway traffic has no host to schedule to.
|
||||
# Chassis ID — vpcd discovers registered chassis from the OVN Southbound DB
|
||||
# at startup, so the name here doesn't need to match spinifex.toml node names.
|
||||
# Using "chassis-node1" for consistency with the default node name.
|
||||
NODE_NAME="node1"
|
||||
CHASSIS_ID="chassis-${NODE_NAME}"
|
||||
|
||||
|
||||
@@ -56,8 +56,9 @@ type Config struct {
|
||||
ExternalMode string
|
||||
// ExternalPools holds the cluster-wide external IP pool configs.
|
||||
ExternalPools []ExternalPoolConfig
|
||||
// ChassisNames are the OVN chassis identifiers for gateway HA scheduling.
|
||||
// Format: "chassis-{hostname}" — one per node in the cluster.
|
||||
// ChassisNames are fallback OVN chassis identifiers for gateway HA scheduling.
|
||||
// Normally discovered from the OVN Southbound DB at startup. Only used if
|
||||
// SBDB discovery fails.
|
||||
ChassisNames []string
|
||||
// Bootstrap holds the default VPC config from spinifex.toml for first-boot reconciliation.
|
||||
Bootstrap *BootstrapVPC
|
||||
@@ -181,6 +182,30 @@ var checkOVNController = func() error {
|
||||
return fmt.Errorf("ovn-controller is not running: run ./scripts/setup-ovn.sh --management")
|
||||
}
|
||||
|
||||
// discoverChassis queries the OVN Southbound DB for registered chassis names.
|
||||
// This is the source of truth — whatever names setup-ovn.sh registered are what
|
||||
// we use for gateway scheduling. Eliminates the brittle coupling between
|
||||
// spinifex.toml node names and the OVS system-id set during OVN setup.
|
||||
var discoverChassis = func(sbAddr string) ([]string, error) {
|
||||
args := []string{"--no-leader-only"}
|
||||
if sbAddr != "" {
|
||||
args = append(args, "--db="+sbAddr)
|
||||
}
|
||||
args = append(args, "list-chassis")
|
||||
out, err := sudoCommand("ovn-sbctl", args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ovn-sbctl list-chassis: %s: %w", strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
var names []string
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(string(out)), "\n") {
|
||||
name := strings.TrimSpace(line)
|
||||
if name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// preflightOVN runs all OVN preflight checks and returns the first failure.
|
||||
func preflightOVN() error {
|
||||
if err := checkBrInt(); err != nil {
|
||||
@@ -250,9 +275,16 @@ func launchService(cfg *Config) error {
|
||||
topoOpts = append(topoOpts, WithExternalNetwork(cfg.ExternalMode, cfg.ExternalPools))
|
||||
slog.Info("External network enabled", "mode", cfg.ExternalMode, "pools", len(cfg.ExternalPools))
|
||||
}
|
||||
if len(cfg.ChassisNames) > 0 {
|
||||
topoOpts = append(topoOpts, WithChassisNames(cfg.ChassisNames))
|
||||
slog.Info("Gateway chassis configured", "chassis", cfg.ChassisNames)
|
||||
// Discover chassis from OVN SBDB (source of truth). Fall back to config
|
||||
// if SBDB query fails (e.g., SBDB address not configured).
|
||||
chassisNames, err := discoverChassis(cfg.OVNSBAddr)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to discover chassis from OVN SBDB, falling back to config", "err", err)
|
||||
chassisNames = cfg.ChassisNames
|
||||
}
|
||||
if len(chassisNames) > 0 {
|
||||
topoOpts = append(topoOpts, WithChassisNames(chassisNames))
|
||||
slog.Info("Gateway chassis configured", "source", "sbdb", "chassis", chassisNames)
|
||||
}
|
||||
topoOpts = append(topoOpts, WithBridgeMode(bridgeMode))
|
||||
topo := NewTopologyHandler(liveClient, topoOpts...)
|
||||
|
||||
@@ -66,6 +66,101 @@ func TestPreflightOVN_ControllerNotRunning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverChassis_ParsesOutput(t *testing.T) {
|
||||
orig := discoverChassis
|
||||
defer func() { discoverChassis = orig }()
|
||||
|
||||
discoverChassis = func(sbAddr string) ([]string, error) {
|
||||
// Simulate ovn-sbctl list-chassis output
|
||||
output := "chassis-node1\nchassis-node2\nchassis-node3\n"
|
||||
var names []string
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(output), "\n") {
|
||||
name := strings.TrimSpace(line)
|
||||
if name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
names, err := discoverChassis("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("expected 3 chassis, got %d: %v", len(names), names)
|
||||
}
|
||||
expected := map[string]bool{"chassis-node1": true, "chassis-node2": true, "chassis-node3": true}
|
||||
for _, n := range names {
|
||||
if !expected[n] {
|
||||
t.Errorf("unexpected chassis name: %s", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverChassis_SingleNode(t *testing.T) {
|
||||
orig := discoverChassis
|
||||
defer func() { discoverChassis = orig }()
|
||||
|
||||
discoverChassis = func(sbAddr string) ([]string, error) {
|
||||
output := "chassis-spinifex-image-builder\n"
|
||||
var names []string
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(output), "\n") {
|
||||
name := strings.TrimSpace(line)
|
||||
if name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
names, err := discoverChassis("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(names) != 1 || names[0] != "chassis-spinifex-image-builder" {
|
||||
t.Errorf("expected [chassis-spinifex-image-builder], got %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverChassis_EmptyOutput(t *testing.T) {
|
||||
orig := discoverChassis
|
||||
defer func() { discoverChassis = orig }()
|
||||
|
||||
discoverChassis = func(sbAddr string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
names, err := discoverChassis("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(names) != 0 {
|
||||
t.Errorf("expected empty chassis list, got %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverChassis_Error_FallsBackToConfig(t *testing.T) {
|
||||
orig := discoverChassis
|
||||
defer func() { discoverChassis = orig }()
|
||||
|
||||
discoverChassis = func(sbAddr string) ([]string, error) {
|
||||
return nil, fmt.Errorf("connection refused")
|
||||
}
|
||||
|
||||
// Simulate the fallback logic from launchService
|
||||
_, err := discoverChassis("")
|
||||
if err == nil {
|
||||
t.Fatal("expected error from discoverChassis")
|
||||
}
|
||||
|
||||
// Fallback to config (as launchService does)
|
||||
chassisNames := []string{"chassis-node1"}
|
||||
if len(chassisNames) != 1 || chassisNames[0] != "chassis-node1" {
|
||||
t.Errorf("expected fallback to config names, got %v", chassisNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreflightOVN_BothFail_ReportsFirst(t *testing.T) {
|
||||
origBrInt := checkBrInt
|
||||
origCtrl := checkOVNController
|
||||
|
||||
Reference in New Issue
Block a user