kedge/internal/vlan/manager.go
Tyler King 6058e62348 Initial commit: Kedge network automation platform
Go-based network automation with YANG models, gRPC, Ansible,
Terraform, and Kubernetes integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:09:30 -05:00

99 lines
2.7 KiB
Go

package vlan
import (
"context"
"fmt"
"github.com/vishvananda/netlink"
"go.uber.org/zap"
"github.com/guildhouse-co/kedge/internal/config"
)
// Manager manages VLAN interfaces on the node for underlay mode.
type Manager struct {
cfg config.VLANConfig
log *zap.SugaredLogger
}
// NewManager creates a new VLAN interface manager.
func NewManager(cfg config.VLANConfig, log *zap.SugaredLogger) *Manager {
return &Manager{cfg: cfg, log: log}
}
// Run starts the VLAN manager. It ensures all configured VLAN interfaces
// and bridges exist on the node.
func (m *Manager) Run(ctx context.Context) error {
if !m.cfg.Enabled {
m.log.Info("underlay mode disabled, vlan manager idle")
<-ctx.Done()
return nil
}
// Ensure all configured VLANs exist.
for _, v := range m.cfg.VLANs {
if err := m.ensureVLAN(v); err != nil {
m.log.Warnw("failed to ensure VLAN", "vlan_id", v.ID, "error", err)
}
}
m.log.Infof("vlan manager started, managing %d VLANs", len(m.cfg.VLANs))
<-ctx.Done()
return nil
}
// VLANCount returns the number of managed VLAN interfaces.
func (m *Manager) VLANCount() int {
return len(m.cfg.VLANs)
}
// ensureVLAN creates a VLAN interface and bridge if they don't already exist.
func (m *Manager) ensureVLAN(v config.VLANEntry) error {
bridgeName := fmt.Sprintf("vlan%d", v.ID)
// Check if bridge already exists.
if _, err := netlink.LinkByName(bridgeName); err == nil {
m.log.Debugw("bridge already exists", "name", bridgeName)
return nil
}
// Get the parent interface.
parent, err := netlink.LinkByName(v.ParentInterface)
if err != nil {
return fmt.Errorf("parent interface %s not found: %w", v.ParentInterface, err)
}
// Create VLAN sub-interface.
vlanLink := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
Name: fmt.Sprintf("%s.%d", v.ParentInterface, v.ID),
ParentIndex: parent.Attrs().Index,
},
VlanId: v.ID,
}
if err := netlink.LinkAdd(vlanLink); err != nil {
return fmt.Errorf("failed to create VLAN %d: %w", v.ID, err)
}
if err := netlink.LinkSetUp(vlanLink); err != nil {
return fmt.Errorf("failed to bring up VLAN %d: %w", v.ID, err)
}
// Create bridge for the VLAN.
bridge := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{Name: bridgeName},
}
if err := netlink.LinkAdd(bridge); err != nil {
return fmt.Errorf("failed to create bridge %s: %w", bridgeName, err)
}
if err := netlink.LinkSetUp(bridge); err != nil {
return fmt.Errorf("failed to bring up bridge %s: %w", bridgeName, err)
}
// Add VLAN interface to bridge.
if err := netlink.LinkSetMaster(vlanLink, bridge); err != nil {
return fmt.Errorf("failed to add VLAN to bridge: %w", err)
}
m.log.Infow("VLAN created", "id", v.ID, "bridge", bridgeName, "parent", v.ParentInterface)
return nil
}