mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
feat: veth pair bridge — link Linux bridge to OVS without conversion
setup-ovn.sh now detects whether the default route is on a Linux bridge or OVS bridge. When it's a Linux bridge (e.g. br-wan from cloud-init), instead of destructively converting it, the script creates an OVS bridge (br-ext) and links them with a veth pair. The Linux bridge keeps its IP, routes, and ports untouched — no WAN interruption. Also fixes all pre-existing lint issues (gofmt, ineffassign, errchkjson, gosec, modernize, whitespace) to bring golangci-lint to 0 issues. Tested: pool mode with static IP range on single-node (ben-banksia). VM gets public IP 192.168.1.201, SSH works, outbound internet works.
This commit is contained in:
+26
-21
@@ -843,13 +843,13 @@ func runAdminInit(cmd *cobra.Command, args []string) {
|
||||
OVNNBAddr: "tcp:127.0.0.1:6641",
|
||||
OVNSBAddr: "tcp:127.0.0.1:6642",
|
||||
|
||||
ExternalMode: externalMode,
|
||||
ExternalIface: externalIface,
|
||||
WanBridge: detectedWanBridge(detectedNet),
|
||||
ExternalDHCP: useExternalDHCP,
|
||||
PoolName: "wan",
|
||||
PoolStart: poolStart,
|
||||
PoolEnd: poolEnd,
|
||||
ExternalMode: externalMode,
|
||||
ExternalIface: externalIface,
|
||||
WanBridge: detectedWanBridge(detectedNet),
|
||||
ExternalDHCP: useExternalDHCP,
|
||||
PoolName: "wan",
|
||||
PoolStart: poolStart,
|
||||
PoolEnd: poolEnd,
|
||||
PoolGateway: externalGateway,
|
||||
PoolGatewayIP: gatewayIP,
|
||||
PoolPrefixLen: externalPrefixLen,
|
||||
@@ -1704,18 +1704,23 @@ func writeBootstrapFilesWithAdmin(configDir string, masterKey []byte, accessKey,
|
||||
return handlers_iam.SaveBootstrapData(filepath.Join(configDir, "bootstrap.json"), bd)
|
||||
}
|
||||
|
||||
// detectDNSServers reads the DNS servers configured on the host for the given
|
||||
// detectedWanBridge returns the WAN bridge name from the detected network
|
||||
// topology. If the WAN interface is already a bridge, returns its name.
|
||||
// Otherwise returns "br-wan" as the default.
|
||||
// detectedWanBridge returns the OVS bridge name that OVN bridge-mappings should
|
||||
// use for WAN traffic. If the WAN interface is already an OVS bridge, returns
|
||||
// its name directly. If it's a Linux bridge, returns "br-ext" — setup-ovn.sh
|
||||
// will create this OVS bridge and link it to the Linux bridge via a veth pair.
|
||||
// If it's a physical NIC, returns "br-wan" as the default.
|
||||
func detectedWanBridge(detected *admin.DetectedNetwork) string {
|
||||
if detected == nil || detected.WAN == nil {
|
||||
return ""
|
||||
}
|
||||
// Check if the WAN interface name looks like a bridge (starts with "br-")
|
||||
// If so, it's already set up — use it directly.
|
||||
if strings.HasPrefix(detected.WAN.Name, "br-") {
|
||||
return detected.WAN.Name
|
||||
name := detected.WAN.Name
|
||||
if strings.HasPrefix(name, "br-") {
|
||||
// Check if it's an OVS bridge (use directly) or Linux bridge (need br-ext)
|
||||
if out, err := exec.Command("sudo", "ovs-vsctl", "br-exists", name).CombinedOutput(); err == nil && len(out) == 0 {
|
||||
return name // OVS bridge — use directly
|
||||
}
|
||||
// Linux bridge — setup-ovn.sh creates br-ext + veth pair
|
||||
return "br-ext"
|
||||
}
|
||||
return "br-wan"
|
||||
}
|
||||
@@ -1748,7 +1753,7 @@ func detectDNSServers(iface string) []string {
|
||||
data, err := os.ReadFile("/etc/resolv.conf")
|
||||
if err == nil {
|
||||
var servers []string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
for line := range strings.SplitSeq(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver ") {
|
||||
ip := strings.TrimSpace(strings.TrimPrefix(line, "nameserver"))
|
||||
@@ -1774,14 +1779,14 @@ func detectDNSServers(iface string) []string {
|
||||
// Format: "Link 2 (enp0s3): 192.168.1.1 8.8.8.8 1.1.1.1"
|
||||
func parseDNSFromResolvectl(output string) []string {
|
||||
var servers []string
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
for line := range strings.SplitSeq(output, "\n") {
|
||||
// Find the colon separator, IPs come after it
|
||||
idx := strings.Index(line, ":")
|
||||
if idx < 0 {
|
||||
_, after, ok := strings.Cut(line, ":")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line[idx+1:])
|
||||
for _, f := range fields {
|
||||
fields := strings.FieldsSeq(after)
|
||||
for f := range fields {
|
||||
if net.ParseIP(f) != nil && f != "127.0.0.53" && f != "127.0.0.1" {
|
||||
servers = append(servers, f)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ else
|
||||
echo " /var/lib/ovn not found, skipping OVN DB cleanup"
|
||||
fi
|
||||
|
||||
# Remove veth pair created by setup-ovn.sh (veth mode — Linux bridge ↔ OVS bridge)
|
||||
if ip link show veth-wan-br >/dev/null 2>&1; then
|
||||
echo " Deleting veth pair: veth-wan-br ↔ veth-wan-ovs"
|
||||
sudo ip link del veth-wan-br 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Remove macvlan interfaces created by setup-ovn.sh
|
||||
for iface in $(ip -o link show type macvlan 2>/dev/null | awk -F': ' '{print $2}' | grep '^spx-ext-'); do
|
||||
echo " Deleting macvlan: $iface"
|
||||
|
||||
+75
-12
@@ -26,7 +26,9 @@
|
||||
#
|
||||
# WAN Bridge Auto-Detection:
|
||||
# When no --wan-bridge is given, the script checks the default route interface:
|
||||
# - If it's already a bridge (e.g. br-wan from cloud-init) → use it directly
|
||||
# - If it's an OVS bridge → use it directly for bridge-mappings
|
||||
# - If it's a Linux bridge → create OVS br-ext + veth pair to link them
|
||||
# (non-destructive, Linux bridge keeps IP/routes, no interruption)
|
||||
# - If it's a physical NIC → stop and print guidance (cannot safely move NIC)
|
||||
#
|
||||
# Examples:
|
||||
@@ -81,7 +83,8 @@ done
|
||||
|
||||
# --- WAN bridge auto-detection ---
|
||||
# Determine the WAN bridge name and how to set it up.
|
||||
WAN_BRIDGE_MODE="" # "existing", "direct", "macvlan", or ""
|
||||
WAN_BRIDGE_MODE="" # "existing", "veth", "direct", "macvlan", or ""
|
||||
LINUX_BRIDGE="" # Set when WAN_BRIDGE_MODE="veth" — the Linux bridge behind the veth pair
|
||||
|
||||
detect_wan_bridge() {
|
||||
# If --wan-bridge was explicitly given, use it
|
||||
@@ -130,10 +133,19 @@ detect_wan_bridge() {
|
||||
fi
|
||||
|
||||
if [ "$is_bridge" = true ]; then
|
||||
# Default route goes through a bridge — use it directly
|
||||
WAN_BRIDGE="$default_dev"
|
||||
WAN_BRIDGE_MODE="existing"
|
||||
echo " Auto-detected WAN bridge: $WAN_BRIDGE (default route interface)"
|
||||
if sudo ovs-vsctl br-exists "$default_dev" 2>/dev/null; then
|
||||
# Already an OVS bridge — use it directly for bridge-mappings
|
||||
WAN_BRIDGE="$default_dev"
|
||||
WAN_BRIDGE_MODE="existing"
|
||||
echo " Auto-detected WAN bridge: $WAN_BRIDGE (OVS bridge, default route)"
|
||||
else
|
||||
# Linux bridge — link to OVS via veth pair (non-destructive)
|
||||
LINUX_BRIDGE="$default_dev"
|
||||
WAN_BRIDGE="br-ext"
|
||||
WAN_BRIDGE_MODE="veth"
|
||||
echo " Auto-detected Linux bridge: $LINUX_BRIDGE (default route)"
|
||||
echo " Will create OVS bridge br-ext + veth pair to link them"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -196,6 +208,9 @@ echo "=== Spinifex OVN Compute Node Setup ==="
|
||||
echo " Management node: $MANAGEMENT"
|
||||
if [ -n "$WAN_BRIDGE" ]; then
|
||||
echo " WAN bridge: $WAN_BRIDGE ($WAN_BRIDGE_MODE)"
|
||||
if [ -n "$LINUX_BRIDGE" ]; then
|
||||
echo " Linux bridge: $LINUX_BRIDGE (linked via veth pair)"
|
||||
fi
|
||||
if [ -n "$WAN_IFACE" ]; then
|
||||
echo " WAN interface: $WAN_IFACE"
|
||||
fi
|
||||
@@ -280,16 +295,56 @@ if [ -n "$WAN_BRIDGE" ]; then
|
||||
|
||||
case "$WAN_BRIDGE_MODE" in
|
||||
existing)
|
||||
# Bridge already exists (e.g. br-wan from cloud-init).
|
||||
# Ensure it's registered as an OVS bridge. If it's a Linux bridge
|
||||
# that OVS doesn't know about, create a new OVS bridge with the
|
||||
# same name — OVS will take over.
|
||||
# Already an OVS bridge (from a previous run or explicit --wan-bridge).
|
||||
if ! sudo ovs-vsctl br-exists "$WAN_BRIDGE" 2>/dev/null; then
|
||||
sudo ovs-vsctl --may-exist add-br "$WAN_BRIDGE"
|
||||
echo " created OVS bridge: $WAN_BRIDGE"
|
||||
fi
|
||||
sudo ip link set "$WAN_BRIDGE" up
|
||||
echo " $WAN_BRIDGE: existing bridge, up"
|
||||
echo " $WAN_BRIDGE: OVS bridge, up"
|
||||
;;
|
||||
|
||||
veth)
|
||||
# Linux bridge detected (e.g. br-wan from cloud-init/netplan).
|
||||
# OVN bridge-mappings require an OVS bridge. Rather than converting
|
||||
# the Linux bridge (destructive, causes WAN interruption), we create
|
||||
# a separate OVS bridge and link them with a veth pair:
|
||||
#
|
||||
# br-wan (Linux, keeps IP/routes) ←→ veth pair ←→ br-ext (OVS, for OVN)
|
||||
#
|
||||
# No network interruption. The Linux bridge is untouched.
|
||||
|
||||
# Create OVS bridge
|
||||
if ! sudo ovs-vsctl br-exists "$WAN_BRIDGE" 2>/dev/null; then
|
||||
sudo ovs-vsctl --may-exist add-br "$WAN_BRIDGE"
|
||||
echo " created OVS bridge: $WAN_BRIDGE"
|
||||
fi
|
||||
sudo ip link set "$WAN_BRIDGE" up
|
||||
|
||||
# Create veth pair (idempotent)
|
||||
if ! ip link show veth-wan-br >/dev/null 2>&1; then
|
||||
sudo ip link add veth-wan-br type veth peer name veth-wan-ovs
|
||||
echo " created veth pair: veth-wan-br ↔ veth-wan-ovs"
|
||||
else
|
||||
echo " veth pair already exists: veth-wan-br ↔ veth-wan-ovs"
|
||||
fi
|
||||
|
||||
# Enslave veth-wan-br to the Linux bridge
|
||||
if ! ip link show veth-wan-br 2>/dev/null | grep -q "master $LINUX_BRIDGE"; then
|
||||
sudo ip link set veth-wan-br master "$LINUX_BRIDGE"
|
||||
echo " veth-wan-br → $LINUX_BRIDGE (Linux bridge)"
|
||||
fi
|
||||
sudo ip link set veth-wan-br up
|
||||
|
||||
# Add veth-wan-ovs to the OVS bridge
|
||||
if ! sudo ovs-vsctl port-to-br veth-wan-ovs >/dev/null 2>&1; then
|
||||
sudo ovs-vsctl --may-exist add-port "$WAN_BRIDGE" veth-wan-ovs
|
||||
echo " veth-wan-ovs → $WAN_BRIDGE (OVS bridge)"
|
||||
fi
|
||||
sudo ip link set veth-wan-ovs up
|
||||
|
||||
echo " $LINUX_BRIDGE (Linux) ↔ veth pair ↔ $WAN_BRIDGE (OVS)"
|
||||
echo " $LINUX_BRIDGE keeps its IP and routes — no interruption"
|
||||
;;
|
||||
|
||||
direct)
|
||||
@@ -587,7 +642,15 @@ fi
|
||||
if [ -n "$WAN_BRIDGE" ]; then
|
||||
if sudo ovs-vsctl br-exists "$WAN_BRIDGE"; then
|
||||
echo " $WAN_BRIDGE:$(printf '%*s' $((15 - ${#WAN_BRIDGE})) '') OK"
|
||||
if [ "$WAN_BRIDGE_MODE" = "direct" ]; then
|
||||
if [ "$WAN_BRIDGE_MODE" = "veth" ]; then
|
||||
if ip link show veth-wan-br >/dev/null 2>&1 && ip link show veth-wan-ovs >/dev/null 2>&1; then
|
||||
echo " veth pair: OK (veth-wan-br ↔ veth-wan-ovs)"
|
||||
echo " linux bridge: $LINUX_BRIDGE (untouched)"
|
||||
else
|
||||
echo " veth pair: FAILED (veth-wan-br/veth-wan-ovs not found)"
|
||||
OK=false
|
||||
fi
|
||||
elif [ "$WAN_BRIDGE_MODE" = "direct" ]; then
|
||||
if sudo ovs-vsctl port-to-br "$WAN_IFACE" >/dev/null 2>&1; then
|
||||
echo " direct bridge: OK ($WAN_IFACE on $WAN_BRIDGE)"
|
||||
else
|
||||
|
||||
+16
-5
@@ -150,9 +150,10 @@ has_service "predastore" && stop_service "predastore" "$PID_DIR"
|
||||
has_service "nats" && stop_service "nats" "$PID_DIR"
|
||||
|
||||
# Stop OVN networking (prevents idle CPU burn when spinifex isn't running)
|
||||
# CRITICAL: Do NOT stop openvswitch-switch if br-external has an IP.
|
||||
# When setup-ovn.sh migrates the host's WAN IP to br-external, stopping OVS
|
||||
# destroys the bridge and the host loses all WAN connectivity (unreachable via SSH).
|
||||
# CRITICAL: Do NOT stop openvswitch-switch if OVS carries WAN traffic.
|
||||
# This can happen when an OVS bridge has a WAN IP (direct bridge mode) or
|
||||
# when a veth pair links an OVS bridge to a Linux WAN bridge (veth mode).
|
||||
# Stopping OVS would destroy the bridge and break WAN connectivity.
|
||||
# OVS is infrastructure — only stop ovn-controller and ovn-central.
|
||||
if pidof systemd >/dev/null 2>&1; then
|
||||
echo "🛑 Stopping OVN networking..."
|
||||
@@ -161,9 +162,13 @@ if pidof systemd >/dev/null 2>&1; then
|
||||
sudo systemctl stop ovn-central 2>/dev/null && echo "✅ ovn-central stopped" || true
|
||||
fi
|
||||
|
||||
# Only stop OVS if the WAN bridge does NOT have an IP (safe to tear down).
|
||||
# Check all OVS bridges — any bridge with a WAN IP means OVS is infrastructure.
|
||||
# Only stop OVS if tearing it down won't kill WAN connectivity.
|
||||
# Two cases where OVS is load-bearing for WAN:
|
||||
# 1. An OVS bridge has a WAN IP directly (direct bridge mode)
|
||||
# 2. A veth pair links a Linux bridge to an OVS bridge (veth mode) —
|
||||
# stopping OVS destroys the OVS bridge end, breaking the link
|
||||
WAN_BRIDGE_HAS_IP=false
|
||||
VETH_LINKS_WAN=false
|
||||
for br in $(sudo ovs-vsctl list-br 2>/dev/null); do
|
||||
if [ "$br" = "br-int" ]; then continue; fi
|
||||
if ip -4 addr show "$br" 2>/dev/null | grep -q "inet "; then
|
||||
@@ -171,8 +176,14 @@ if pidof systemd >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Check if a veth pair links an OVS bridge to a Linux bridge with a WAN IP
|
||||
if ip link show veth-wan-br >/dev/null 2>&1; then
|
||||
VETH_LINKS_WAN=true
|
||||
fi
|
||||
if [ "$WAN_BRIDGE_HAS_IP" = true ]; then
|
||||
echo "⚠️ Skipping openvswitch-switch stop ($br has WAN IP — stopping would kill connectivity)"
|
||||
elif [ "$VETH_LINKS_WAN" = true ]; then
|
||||
echo "⚠️ Skipping openvswitch-switch stop (veth pair links OVS to WAN bridge)"
|
||||
else
|
||||
sudo systemctl stop openvswitch-switch 2>/dev/null && echo "✅ openvswitch-switch stopped" || true
|
||||
fi
|
||||
|
||||
+11
-11
@@ -65,17 +65,17 @@ type ConfigSettings struct {
|
||||
OVNSBAddr string
|
||||
|
||||
// External networking for public subnets
|
||||
ExternalMode string // "pool", "nat", or "" (disabled)
|
||||
ExternalIface string // WAN NIC name (e.g., "eth0", "eth1")
|
||||
WanBridge string // OVS bridge for WAN traffic (default "br-wan")
|
||||
ExternalDHCP bool // Obtain gateway IP via DHCP on macvlan/bridge
|
||||
PoolName string // External pool name (e.g., "wan")
|
||||
PoolStart string // First IP in external pool range
|
||||
PoolEnd string // Last IP in external pool range
|
||||
PoolGateway string // WAN gateway IP
|
||||
PoolGatewayIP string // Explicit SNAT IP (for nat mode without DHCP)
|
||||
PoolPrefixLen int // Subnet prefix length (default 24)
|
||||
PoolDNSServers []string // DNS servers for VM DHCP (auto-detected from host)
|
||||
ExternalMode string // "pool", "nat", or "" (disabled)
|
||||
ExternalIface string // WAN NIC name (e.g., "eth0", "eth1")
|
||||
WanBridge string // OVS bridge for WAN traffic (default "br-wan")
|
||||
ExternalDHCP bool // Obtain gateway IP via DHCP on macvlan/bridge
|
||||
PoolName string // External pool name (e.g., "wan")
|
||||
PoolStart string // First IP in external pool range
|
||||
PoolEnd string // Last IP in external pool range
|
||||
PoolGateway string // WAN gateway IP
|
||||
PoolGatewayIP string // Explicit SNAT IP (for nat mode without DHCP)
|
||||
PoolPrefixLen int // Subnet prefix length (default 24)
|
||||
PoolDNSServers []string // DNS servers for VM DHCP (auto-detected from host)
|
||||
|
||||
// Other nodes in the cluster (for config source of truth)
|
||||
RemoteNodes []RemoteNode
|
||||
|
||||
@@ -62,7 +62,7 @@ func DetectNetwork() (*DetectedNetwork, error) {
|
||||
seen := make(map[string]bool)
|
||||
var interfaces []DetectedInterface
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(allRoutes)), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(string(allRoutes)), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
@@ -169,10 +169,10 @@ func SuggestPoolRange(wan *DetectedInterface) (start, end string) {
|
||||
for i := 3; i >= 0 && carry > 0; i-- {
|
||||
val := int(startIP[i]) - carry
|
||||
if val < 0 {
|
||||
startIP[i] = byte(256 + val)
|
||||
startIP[i] = byte(256 + val) //nolint:gosec // val is in [-256,0), so 256+val is in [0,256)
|
||||
carry = 1
|
||||
} else {
|
||||
startIP[i] = byte(val)
|
||||
startIP[i] = byte(val) //nolint:gosec // val is non-negative and <= 255
|
||||
carry = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ type VPCDConfig struct {
|
||||
OVNNBAddr string `mapstructure:"ovn_nb_addr"` // OVN Northbound DB address (e.g., "tcp:127.0.0.1:6641")
|
||||
OVNSBAddr string `mapstructure:"ovn_sb_addr"` // OVN Southbound DB address (e.g., "tcp:127.0.0.1:6642")
|
||||
ExternalInterface string `mapstructure:"external_interface"` // WAN NIC name (e.g., "eth1", "enp0s3") — the physical NIC on the WAN bridge
|
||||
WanBridge string `mapstructure:"wan_bridge"` // OVS bridge for WAN traffic (default "br-wan", maps to OVN "external" network)
|
||||
WanBridge string `mapstructure:"wan_bridge"` // OVS bridge for WAN traffic (default "br-wan", maps to OVN "external" network)
|
||||
BridgeMode string `mapstructure:"bridge_mode"` // "direct" or "macvlan" (auto-detected if empty)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ import (
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
"github.com/mulgadc/spinifex/spinifex/config"
|
||||
handlers_ec2_account "github.com/mulgadc/spinifex/spinifex/handlers/ec2/account"
|
||||
handlers_ec2_eip "github.com/mulgadc/spinifex/spinifex/handlers/ec2/eip"
|
||||
handlers_ec2_eigw "github.com/mulgadc/spinifex/spinifex/handlers/ec2/eigw"
|
||||
handlers_ec2_eip "github.com/mulgadc/spinifex/spinifex/handlers/ec2/eip"
|
||||
handlers_ec2_igw "github.com/mulgadc/spinifex/spinifex/handlers/ec2/igw"
|
||||
handlers_ec2_image "github.com/mulgadc/spinifex/spinifex/handlers/ec2/image"
|
||||
handlers_ec2_instance "github.com/mulgadc/spinifex/spinifex/handlers/ec2/instance"
|
||||
|
||||
@@ -188,4 +188,3 @@ func (d *Daemon) ensureDefaultVPCInfrastructure() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
gateway_ec2_account "github.com/mulgadc/spinifex/spinifex/gateway/ec2/account"
|
||||
gateway_ec2_eigw "github.com/mulgadc/spinifex/spinifex/gateway/ec2/eigw"
|
||||
gateway_ec2_eip "github.com/mulgadc/spinifex/spinifex/gateway/ec2/eip"
|
||||
gateway_ec2_igw "github.com/mulgadc/spinifex/spinifex/gateway/ec2/igw"
|
||||
gateway_ec2_image "github.com/mulgadc/spinifex/spinifex/gateway/ec2/image"
|
||||
gateway_ec2_instance "github.com/mulgadc/spinifex/spinifex/gateway/ec2/instance"
|
||||
@@ -22,7 +23,6 @@ import (
|
||||
gateway_ec2_snapshot "github.com/mulgadc/spinifex/spinifex/gateway/ec2/snapshot"
|
||||
gateway_ec2_tags "github.com/mulgadc/spinifex/spinifex/gateway/ec2/tags"
|
||||
gateway_ec2_volume "github.com/mulgadc/spinifex/spinifex/gateway/ec2/volume"
|
||||
gateway_ec2_eip "github.com/mulgadc/spinifex/spinifex/gateway/ec2/eip"
|
||||
gateway_ec2_vpc "github.com/mulgadc/spinifex/spinifex/gateway/ec2/vpc"
|
||||
gateway_ec2_zone "github.com/mulgadc/spinifex/spinifex/gateway/ec2/zone"
|
||||
"github.com/mulgadc/spinifex/spinifex/utils"
|
||||
|
||||
@@ -87,7 +87,6 @@ func (s *IGWServiceImpl) CreateInternetGateway(input *ec2.CreateInternetGatewayI
|
||||
}
|
||||
|
||||
func (s *IGWServiceImpl) createIGW(input *ec2.CreateInternetGatewayInput, accountID, igwID string) (*ec2.CreateInternetGatewayOutput, error) {
|
||||
|
||||
record := IGWRecord{
|
||||
InternetGatewayId: igwID,
|
||||
State: "available",
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
type ExternalIPAllocation struct {
|
||||
Type string `json:"type"` // "gateway", "auto_assign", "elastic_ip"
|
||||
AllocationID string `json:"allocation_id,omitempty"` // For elastic IPs
|
||||
Association string `json:"association,omitempty"` // ENI ID for elastic IPs
|
||||
Association string `json:"association,omitempty"` // ENI ID for elastic IPs
|
||||
ENIId string `json:"eni_id,omitempty"` // ENI owning this IP
|
||||
InstanceId string `json:"instance_id,omitempty"` // Instance owning this IP
|
||||
Note string `json:"note,omitempty"` // Human-readable note
|
||||
|
||||
@@ -450,12 +450,12 @@ func (s *VPCServiceImpl) RevokeSecurityGroupEgress(input *ec2.RevokeSecurityGrou
|
||||
// sgRecordToEC2 converts a SecurityGroupRecord to an EC2 SecurityGroup.
|
||||
func (s *VPCServiceImpl) sgRecordToEC2(record *SecurityGroupRecord, accountID string) *ec2.SecurityGroup {
|
||||
sg := &ec2.SecurityGroup{
|
||||
GroupId: aws.String(record.GroupId),
|
||||
GroupName: aws.String(record.GroupName),
|
||||
Description: aws.String(record.Description),
|
||||
VpcId: aws.String(record.VpcId),
|
||||
OwnerId: aws.String(accountID),
|
||||
IpPermissions: sgRulesToIpPermissions(record.IngressRules),
|
||||
GroupId: aws.String(record.GroupId),
|
||||
GroupName: aws.String(record.GroupName),
|
||||
Description: aws.String(record.Description),
|
||||
VpcId: aws.String(record.VpcId),
|
||||
OwnerId: aws.String(accountID),
|
||||
IpPermissions: sgRulesToIpPermissions(record.IngressRules),
|
||||
IpPermissionsEgress: sgRulesToIpPermissions(record.EgressRules),
|
||||
}
|
||||
|
||||
|
||||
@@ -731,11 +731,13 @@ func (s *IAMServiceImpl) seedAdminAccount(admin *AdminBootstrapData) error {
|
||||
// this account. Without this, the admin account has no default VPC/subnet
|
||||
// and the user must create one manually.
|
||||
if s.natsConn != nil {
|
||||
evt, _ := json.Marshal(struct {
|
||||
evt, err := json.Marshal(struct {
|
||||
AccountID string `json:"account_id"`
|
||||
AccountName string `json:"account_name"`
|
||||
}{AccountID: admin.AccountID, AccountName: admin.AccountName})
|
||||
if err := s.natsConn.Publish("iam.account.created", evt); err != nil {
|
||||
if err != nil {
|
||||
slog.Warn("Failed to marshal account creation event", "accountID", admin.AccountID, "error", err)
|
||||
} else if err := s.natsConn.Publish("iam.account.created", evt); err != nil {
|
||||
slog.Warn("Failed to publish account creation event for admin account", "accountID", admin.AccountID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@ type LogicalRouterStaticRoute struct {
|
||||
type PortGroup struct {
|
||||
UUID string `ovsdb:"_uuid"`
|
||||
Name string `ovsdb:"name"`
|
||||
Ports []string `ovsdb:"ports"` // UUIDs of logical switch ports
|
||||
ACLs []string `ovsdb:"acls"` // UUIDs of ACLs
|
||||
Ports []string `ovsdb:"ports"` // UUIDs of logical switch ports
|
||||
ACLs []string `ovsdb:"acls"` // UUIDs of ACLs
|
||||
ExternalIDs map[string]string `ovsdb:"external_ids"`
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestReconcile_CreatesBootstrapTopology(t *testing.T) {
|
||||
_ = ovn.Connect(context.Background())
|
||||
topo := NewTopologyHandler(ovn,
|
||||
WithExternalNetwork("pool", []ExternalPoolConfig{{
|
||||
Name: "wan",
|
||||
Name: "wan",
|
||||
RangeStart: "192.168.1.200",
|
||||
RangeEnd: "192.168.1.250",
|
||||
Gateway: "192.168.1.1",
|
||||
|
||||
@@ -655,7 +655,6 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
|
||||
gatewayIP := "169.254.0.1"
|
||||
gatewayNetwork := "169.254.0.1/30"
|
||||
wanGateway := "169.254.0.2"
|
||||
prefixLen := 30
|
||||
|
||||
if pool != nil {
|
||||
gip := pool.GatewayIP
|
||||
@@ -663,7 +662,7 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
|
||||
gip = pool.RangeStart // Default: first IP in range
|
||||
}
|
||||
gatewayIP = gip
|
||||
prefixLen = pool.PrefixLen
|
||||
prefixLen := pool.PrefixLen
|
||||
if prefixLen == 0 {
|
||||
prefixLen = 24
|
||||
}
|
||||
@@ -759,10 +758,9 @@ func (h *TopologyHandler) handleIGWAttach(msg *nats.Msg) {
|
||||
// 7. Schedule gateway chassis for HA — tells OVN which hosts can handle external traffic
|
||||
if len(h.chassisNames) > 0 {
|
||||
for i, chassis := range h.chassisNames {
|
||||
priority := 20 - (i * 5) // First chassis gets highest priority
|
||||
if priority < 1 {
|
||||
priority = 1
|
||||
}
|
||||
priority := max(
|
||||
// First chassis gets highest priority
|
||||
20-(i*5), 1)
|
||||
if err := h.ovn.SetGatewayChassis(ctx, gwPortName, chassis, priority); err != nil {
|
||||
slog.Warn("vpcd: failed to set gateway chassis", "port", gwPortName, "chassis", chassis, "priority", priority, "err", err)
|
||||
} else {
|
||||
@@ -1060,7 +1058,6 @@ func (h *TopologyHandler) reconcileIGW(ctx context.Context, vpcId, igwId string)
|
||||
gatewayIP := "169.254.0.1"
|
||||
gatewayNetwork := "169.254.0.1/30"
|
||||
wanGateway := "169.254.0.2"
|
||||
prefixLen := 30
|
||||
|
||||
if pool != nil {
|
||||
gip := pool.GatewayIP
|
||||
@@ -1068,7 +1065,7 @@ func (h *TopologyHandler) reconcileIGW(ctx context.Context, vpcId, igwId string)
|
||||
gip = pool.RangeStart
|
||||
}
|
||||
gatewayIP = gip
|
||||
prefixLen = pool.PrefixLen
|
||||
prefixLen := pool.PrefixLen
|
||||
if prefixLen == 0 {
|
||||
prefixLen = 24
|
||||
}
|
||||
@@ -1106,10 +1103,10 @@ func (h *TopologyHandler) reconcileIGW(ctx context.Context, vpcId, igwId string)
|
||||
reconcileLocalnetOpts["nat-addresses"] = "router"
|
||||
}
|
||||
localnetPort := &nbdb.LogicalSwitchPort{
|
||||
Name: extPortName,
|
||||
Type: "localnet",
|
||||
Addresses: []string{"unknown"},
|
||||
Options: reconcileLocalnetOpts,
|
||||
Name: extPortName,
|
||||
Type: "localnet",
|
||||
Addresses: []string{"unknown"},
|
||||
Options: reconcileLocalnetOpts,
|
||||
ExternalIDs: portExtIDs,
|
||||
}
|
||||
if err := h.ovn.CreateLogicalSwitchPort(ctx, extSwitchName, localnetPort); err != nil {
|
||||
@@ -1183,10 +1180,7 @@ func (h *TopologyHandler) reconcileIGW(ctx context.Context, vpcId, igwId string)
|
||||
|
||||
// 7. Schedule gateway chassis
|
||||
for i, chassis := range h.chassisNames {
|
||||
priority := 20 - (i * 5)
|
||||
if priority < 1 {
|
||||
priority = 1
|
||||
}
|
||||
priority := max(20-(i*5), 1)
|
||||
if err := h.ovn.SetGatewayChassis(ctx, gwPortName, chassis, priority); err != nil {
|
||||
slog.Warn("vpcd reconcile: failed to set gateway chassis", "chassis", chassis, "err", err)
|
||||
}
|
||||
|
||||
@@ -64,8 +64,10 @@ type Config struct {
|
||||
// ExternalInterface is the WAN NIC name (e.g., "enp0s3"). Used to align
|
||||
// the macvlan MAC with the OVN gateway MAC for inbound traffic.
|
||||
ExternalInterface string
|
||||
// WanBridge is the OVS bridge name for WAN traffic (default "br-wan").
|
||||
// WanBridge is the OVS bridge name for WAN traffic.
|
||||
// Maps to OVN logical network "external" via ovn-bridge-mappings.
|
||||
// Typically "br-ext" (veth mode linking Linux bridge to OVS) or the
|
||||
// bridge name itself when the default route is already on an OVS bridge.
|
||||
WanBridge string
|
||||
// BridgeMode is "direct" or "macvlan". Direct bridge adds the WAN NIC
|
||||
// directly to the WAN bridge; macvlan creates a sub-interface. When empty,
|
||||
|
||||
Reference in New Issue
Block a user