Skip to content

Commit 0076d53

Browse files
committed
feat(agent): attach VXLAN interface to bridge on EnsureVXLAN
1 parent e2ab459 commit 0076d53

4 files changed

Lines changed: 68 additions & 27 deletions

File tree

internal/agent/network/net.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ type NetManager interface {
4545
// If egressIface is empty, the default-route interface is used.
4646
RemoveNAT(ctx context.Context, subnet, egressIface string) error
4747

48-
// EnsureVXLAN creates or reconciles the VXLAN interface for the given network.
49-
// vni is the VXLAN Network Identifier. ifaceName is the interface name to use.
50-
// nodeIP is the local node's IP for VTEP termination.
51-
EnsureVXLAN(ctx context.Context, vni uint32, ifaceName, nodeIP string) error
48+
// EnsureVXLAN creates or reconciles the VXLAN interface for the given network,
49+
// attaches it to bridgeName, and brings it up. bridgeName must already exist
50+
// (call EnsureNetwork first). Idempotent.
51+
EnsureVXLAN(ctx context.Context, vni uint32, ifaceName, nodeIP, bridgeName string) error
5252

5353
// SyncFDB reconciles the local FDB (forwarding database) on the VXLAN interface
5454
// to match the provided entries. Entries not in the list are removed.
@@ -58,13 +58,14 @@ type NetManager interface {
5858
// StubNetManager is a no-op NetManager for tests.
5959
// It records calls so tests can verify interactions.
6060
type StubNetManager struct {
61-
EnsureNetworkCalls []string // bridgeName
62-
SetupVMCalls []string // tapName
63-
TeardownVMCalls []string // tapName
64-
EnsureNATCalls []string // subnet
65-
RemoveNATCalls []string // subnet
66-
EnsureVXLANCalls []string // ifaceName
67-
SyncFDBCalls []string // ifaceName
61+
EnsureNetworkCalls []string // bridgeName
62+
SetupVMCalls []string // tapName
63+
TeardownVMCalls []string // tapName
64+
EnsureNATCalls []string // subnet
65+
RemoveNATCalls []string // subnet
66+
EnsureVXLANCalls []string // ifaceName
67+
EnsureVXLANBridgeCalls []string // bridgeName
68+
SyncFDBCalls []string // ifaceName
6869

6970
EnsureNetworkErr error
7071
SetupVMErr error
@@ -100,8 +101,9 @@ func (s *StubNetManager) RemoveNAT(_ context.Context, subnet, _ string) error {
100101
return s.RemoveNATErr
101102
}
102103

103-
func (s *StubNetManager) EnsureVXLAN(_ context.Context, _ uint32, ifaceName, _ string) error {
104+
func (s *StubNetManager) EnsureVXLAN(_ context.Context, _ uint32, ifaceName, _, bridgeName string) error {
104105
s.EnsureVXLANCalls = append(s.EnsureVXLANCalls, ifaceName)
106+
s.EnsureVXLANBridgeCalls = append(s.EnsureVXLANBridgeCalls, bridgeName)
105107
return s.EnsureVXLANErr
106108
}
107109

internal/agent/network/vxlan.go

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,34 @@ import (
1111
"github.com/vishvananda/netlink"
1212
)
1313

14-
// EnsureVXLAN creates or reconciles the VXLAN interface for the given network.
15-
// vni is the VXLAN Network Identifier, ifaceName is the interface name to use,
16-
// and nodeIP is the local node's IP for VTEP termination.
17-
// Idempotent: no-ops if the interface already exists with matching configuration.
18-
func (m *LinuxNetManager) EnsureVXLAN(_ context.Context, vni uint32, ifaceName, nodeIP string) error {
14+
// EnsureVXLAN creates or reconciles the VXLAN interface for the given network,
15+
// attaches it to bridgeName, and brings it up. bridgeName must already exist
16+
// (call EnsureNetwork first). Idempotent.
17+
func (m *LinuxNetManager) EnsureVXLAN(_ context.Context, vni uint32, ifaceName, nodeIP, bridgeName string) error {
1918
localIP := net.ParseIP(nodeIP)
2019
if localIP == nil {
2120
return fmt.Errorf("invalid nodeIP %q", nodeIP)
2221
}
2322

2423
link, err := netlink.LinkByName(ifaceName)
2524
if err == nil {
26-
// Interface already exists — ensure it is up.
27-
return netlink.LinkSetUp(link)
25+
// Interface already exists — ensure it is up and attached to bridge.
26+
if err := netlink.LinkSetUp(link); err != nil {
27+
return err
28+
}
29+
return m.attachToBridge(link, bridgeName)
2830
}
2931

3032
vx := &netlink.Vxlan{
3133
LinkAttrs: netlink.LinkAttrs{
3234
Name: ifaceName,
3335
},
34-
VxlanId: int(vni),
35-
SrcAddr: localIP.To4(),
36-
Port: 8472,
37-
Learning: false,
38-
L2miss: false,
39-
L3miss: false,
36+
VxlanId: int(vni),
37+
SrcAddr: localIP.To4(),
38+
Port: 8472,
39+
Learning: false,
40+
L2miss: false,
41+
L3miss: false,
4042
}
4143
if err := netlink.LinkAdd(vx); err != nil {
4244
return fmt.Errorf("create vxlan %s (vni %d): %w", ifaceName, vni, err)
@@ -46,7 +48,27 @@ func (m *LinuxNetManager) EnsureVXLAN(_ context.Context, vni uint32, ifaceName,
4648
if err != nil {
4749
return fmt.Errorf("fetch vxlan %s after create: %w", ifaceName, err)
4850
}
49-
return netlink.LinkSetUp(link)
51+
if err := netlink.LinkSetUp(link); err != nil {
52+
return err
53+
}
54+
return m.attachToBridge(link, bridgeName)
55+
}
56+
57+
// attachToBridge attaches link to the bridge named bridgeName.
58+
// No-op if link is already a member of that bridge or bridgeName is empty.
59+
func (m *LinuxNetManager) attachToBridge(link netlink.Link, bridgeName string) error {
60+
if bridgeName == "" {
61+
return nil
62+
}
63+
bridge, err := netlink.LinkByName(bridgeName)
64+
if err != nil {
65+
return fmt.Errorf("get bridge %s: %w", bridgeName, err)
66+
}
67+
// Already a member?
68+
if link.Attrs().MasterIndex == bridge.Attrs().Index {
69+
return nil
70+
}
71+
return netlink.LinkSetMaster(link, bridge)
5072
}
5173

5274
// SyncFDB reconciles the local FDB (forwarding database) on the VXLAN interface

internal/agent/network/vxlan_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package network_test
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/syscode-labs/imp/internal/agent/network"
@@ -63,3 +64,17 @@ func TestVXLANParams_distinct(t *testing.T) {
6364
t.Errorf("different UIDs produced same ifaceName %q", iface1)
6465
}
6566
}
67+
68+
func TestStubNetManager_EnsureVXLAN_recordsBridgeName(t *testing.T) {
69+
stub := &network.StubNetManager{}
70+
_ = stub.EnsureVXLAN(context.Background(), 42, "impvx-abc", "10.0.0.1", "impbr-xyz")
71+
if len(stub.EnsureVXLANCalls) != 1 {
72+
t.Fatalf("expected 1 EnsureVXLAN call, got %d", len(stub.EnsureVXLANCalls))
73+
}
74+
if stub.EnsureVXLANCalls[0] != "impvx-abc" {
75+
t.Errorf("expected ifaceName impvx-abc, got %q", stub.EnsureVXLANCalls[0])
76+
}
77+
if stub.EnsureVXLANBridgeCalls[0] != "impbr-xyz" {
78+
t.Errorf("expected bridgeName impbr-xyz, got %q", stub.EnsureVXLANBridgeCalls[0])
79+
}
80+
}

internal/agent/reconciler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,11 @@ func (r *ImpVMReconciler) syncFDB(ctx context.Context, vm *impdevv1alpha1.ImpVM)
332332
return client.IgnoreNotFound(err)
333333
}
334334

335+
netKey := impNet.Namespace + "/" + impNet.Name
336+
bridgeName := network.BridgeName(netKey)
335337
vni, ifaceName := network.VXLANParams(string(impNet.UID))
336338

337-
if err := r.Net.EnsureVXLAN(ctx, vni, ifaceName, r.NodeIP); err != nil {
339+
if err := r.Net.EnsureVXLAN(ctx, vni, ifaceName, r.NodeIP, bridgeName); err != nil {
338340
return err
339341
}
340342

0 commit comments

Comments
 (0)