Skip to content

Commit

Permalink
support for switch port toggle on/off
Browse files Browse the repository at this point in the history
  • Loading branch information
ulrichSchreiner committed Mar 7, 2024
1 parent 1d22706 commit 109834c
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 300 deletions.
55 changes: 47 additions & 8 deletions cmd/internal/core/reconfigure-switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"fmt"
"net"
"os"
"slices"
"strconv"
Expand All @@ -23,7 +24,7 @@ func (c *Core) ReconfigureSwitch() {
for range t.C {
c.log.Info("trigger reconfiguration")
start := time.Now()
err := c.reconfigureSwitch(host)
s, err := c.reconfigureSwitch(host)
elapsed := time.Since(start)
c.log.Info("reconfiguration took", "elapsed", elapsed)

Expand All @@ -32,6 +33,7 @@ func (c *Core) ReconfigureSwitch() {
ns := elapsed.Nanoseconds()
nr := &models.V1SwitchNotifyRequest{
SyncDuration: &ns,
PortStates: make(map[string]string),
}
if err != nil {
errStr := err.Error()
Expand All @@ -42,6 +44,24 @@ func (c *Core) ReconfigureSwitch() {
c.log.Info("reconfiguration succeeded")
}

// fill the port states of the switch
for _, n := range s.Nics {
isup, err := isLinkUp(n.Name)
if err != nil {
c.log.Error("could not check if link is up", "error", err)
if n.Name != nil {
nr.PortStates[*n.Name] = string(types.PortStatusUnknown)
}
c.metrics.CountError("switch-reconfiguration")
} else {
if isup {
nr.PortStates[*n.Name] = string(types.PortStatusUp)
} else {
nr.PortStates[*n.Name] = string(types.PortStatusDown)
}
}
}

params.Body = nr
_, err = c.driver.SwitchOperations().NotifySwitch(params, nil)
if err != nil {
Expand All @@ -51,37 +71,37 @@ func (c *Core) ReconfigureSwitch() {
}
}

func (c *Core) reconfigureSwitch(switchName string) error {
func (c *Core) reconfigureSwitch(switchName string) (*models.V1SwitchResponse, error) {
params := sw.NewFindSwitchParams()
params.ID = switchName
fsr, err := c.driver.SwitchOperations().FindSwitch(params, nil)
if err != nil {
return fmt.Errorf("could not fetch switch from metal-api: %w", err)
return nil, fmt.Errorf("could not fetch switch from metal-api: %w", err)
}

s := fsr.Payload
switchConfig, err := c.buildSwitcherConfig(s)
if err != nil {
return fmt.Errorf("could not build switcher config: %w", err)
return nil, fmt.Errorf("could not build switcher config: %w", err)
}

err = fillEth0Info(switchConfig, c.managementGateway)
if err != nil {
return fmt.Errorf("could not gather information about eth0 nic: %w", err)
return nil, fmt.Errorf("could not gather information about eth0 nic: %w", err)
}

c.log.Info("assembled new config for switch", "config", switchConfig)
if !c.enableReconfigureSwitch {
c.log.Debug("skip config application because of environment setting")
return nil
return s, nil
}

err = c.nos.Apply(switchConfig)
if err != nil {
return fmt.Errorf("could not apply switch config: %w", err)
return nil, fmt.Errorf("could not apply switch config: %w", err)
}

return nil
return s, nil
}

func (c *Core) buildSwitcherConfig(s *models.V1SwitchResponse) (*types.Conf, error) {
Expand Down Expand Up @@ -121,6 +141,12 @@ func (c *Core) buildSwitcherConfig(s *models.V1SwitchResponse) (*types.Conf, err
continue
}

if types.PortStatusDown.IsEqual(nic.Actual) {
if has := p.DownPorts[port]; !has {
p.DownPorts[port] = true
}
}

// Firewall-Port
if nic.Vrf == "default" {
fw := &types.Firewall{
Expand Down Expand Up @@ -202,3 +228,16 @@ func fillEth0Info(c *types.Conf, gw string) error {
c.Ports.Eth0.Gateway = gw
return nil
}

// isLinkUp checks if the interface with the given name is up.
// It returns a boolean indicating if the interface is up, and an error if there was a problem checking the interface.
func isLinkUp(nicname *string) (bool, error) {
if nicname == nil {
return false, fmt.Errorf("cannot determine state of empty nicname")
}
nic, err := net.InterfaceByName(*nicname)
if err != nil {
return false, fmt.Errorf("cannot query interface %q : %w", *nicname, err)
}
return nic.Flags&net.FlagUp != 0, nil
}
5 changes: 3 additions & 2 deletions cmd/internal/switcher/cumulus/cumulus.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ func New(log *slog.Logger, frrTplFile, interfacesTplFile string) *Cumulus {
}

func (c *Cumulus) Apply(cfg *types.Conf) error {
err := c.interfacesApplier.Apply(cfg)
withoutDownPorts := cfg.NewWithoutDownPorts()
err := c.interfacesApplier.Apply(withoutDownPorts)
if err != nil {
return err
}

return c.frrApplier.Apply(cfg)
return c.frrApplier.Apply(withoutDownPorts)
}

func (c *Cumulus) IsInitialized() (initialized bool, err error) {
Expand Down
9 changes: 7 additions & 2 deletions cmd/internal/switcher/sonic/db/configdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
portTable = "PORT"
adminStatus = "admin_status"
adminStatusUp = "up"
adminStatusDown = "down"
mtu = "mtu"
fec = "fec"
fecRS = "rs"
Expand Down Expand Up @@ -204,8 +205,12 @@ func (d *ConfigDB) SetPortMtu(ctx context.Context, interfaceName string, val str
return d.c.HSet(ctx, key, Val{mtu: val})
}

func (d *ConfigDB) SetAdminStatusUp(ctx context.Context, interfaceName string) error {
func (d *ConfigDB) SetAdminStatusUp(ctx context.Context, interfaceName string, up bool) error {
key := Key{portTable, interfaceName}

return d.c.HSet(ctx, key, Val{adminStatus: adminStatusUp})
status := adminStatusUp
if !up {
status = adminStatusDown
}
return d.c.HSet(ctx, key, Val{adminStatus: status})
}
15 changes: 9 additions & 6 deletions cmd/internal/switcher/sonic/redis/applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (a *Applier) Apply(cfg *types.Conf) error {
errs = append(errs, err)
}
for _, interfaceName := range vrf.Neighbors {
if err := a.configureVrfNeighbor(interfaceName, vrfName); err != nil {
if err := a.configureVrfNeighbor(interfaceName, vrfName, !cfg.Ports.DownPorts[interfaceName]); err != nil {
errs = append(errs, err)
}
}
Expand Down Expand Up @@ -124,7 +124,8 @@ func (a *Applier) configureUnprovisionedPort(interfaceName string) error {
return err
}

if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true); err != nil {
// unprovisioned ports should be up
if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true, true); err != nil {
return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err)
}

Expand All @@ -140,7 +141,8 @@ func (a *Applier) configureFirewallPort(interfaceName string) error {
return err
}

if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", true); err != nil {
// a firewall port should always be up
if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", true, true); err != nil {
return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err)
}

Expand All @@ -151,13 +153,14 @@ func (a *Applier) configureUnderlayPort(interfaceName string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", false); err != nil {
// underlay ports should be up
if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", false, true); err != nil {
return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err)
}
return a.ensureLinkLocalOnlyIsEnabled(ctx, interfaceName)
}

func (a *Applier) configureVrfNeighbor(interfaceName, vrfName string) error {
func (a *Applier) configureVrfNeighbor(interfaceName, vrfName string, isUp bool) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

Expand All @@ -171,7 +174,7 @@ func (a *Applier) configureVrfNeighbor(interfaceName, vrfName string) error {
return err
}

if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true); err != nil {
if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true, isUp); err != nil {
return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err)
}

Expand Down
8 changes: 4 additions & 4 deletions cmd/internal/switcher/sonic/redis/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/avast/retry-go/v4"
)

func (a *Applier) ensurePortConfiguration(ctx context.Context, portName, mtu string, isFecRs bool) error {
func (a *Applier) ensurePortConfiguration(ctx context.Context, portName, mtu string, isFecRs, isUp bool) error {
p, err := a.db.Config.GetPort(ctx, portName)
if err != nil {
return fmt.Errorf("could not retrieve port info for %s from redis: %w", portName, err)
Expand All @@ -29,9 +29,9 @@ func (a *Applier) ensurePortConfiguration(ctx context.Context, portName, mtu str
}
}

if !p.AdminStatus {
a.log.Debug("set admin status to", "port", portName, "admin_status", "up")
return a.db.Config.SetAdminStatusUp(ctx, portName)
if p.AdminStatus != isUp {
a.log.Debug("set admin status to", "port", portName, "admin_status_up", isUp)
return a.db.Config.SetAdminStatusUp(ctx, portName, isUp)
}

return nil
Expand Down
9 changes: 8 additions & 1 deletion cmd/internal/switcher/templates/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ func TestInterfacesTemplate(t *testing.T) {
}
}

func TestInterfacesTemplateWithDownPorts(t *testing.T) {
c := readConf(t, path.Join("test_down_interfaces", "conf_with_downports.yaml"))
c = *c.NewWithoutDownPorts()
tpl := InterfacesTemplate("")
verifyTemplate(t, tpl, &c, path.Join("test_down_interfaces", "interfaces_with_downports"))
}

func TestCumulusFrrTemplate(t *testing.T) {
tests := listTestCases()
for i := range tests {
Expand Down Expand Up @@ -76,7 +83,7 @@ func TestCustomSonicFrrTemplate(t *testing.T) {
func verifyTemplate(t *testing.T, tpl *template.Template, c *types.Conf, expectedFilename string) {
actual := renderToString(t, tpl, c)
expected := readExpected(t, expectedFilename)
require.Equal(t, expected, actual, "Wanted: %s, Got: %s", expected, actual)
require.Equal(t, expected, actual, "Wanted: %s\nGot: %s", expected, actual)
}

func renderToString(t *testing.T, tpl *template.Template, c *types.Conf) string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: leaf01
loglevel: warnings
loopback: 10.0.0.10
asn: 4200000010
metalcorecidr: 10.255.255.2/24
ports:
vrfs:
vrf1:
vni: 1
vlanid: 2
neighbors:
- swp3
cidrs:
- 10.255.255.1/28
eth0:
addresscidr: 192.168.0.11
gateway: 192.168.0.254
underlay:
- swp31
- swp32
unprovisioned:
- swp1
- swp2
downports:
swp3: True
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*.intf

# The loopback network interface
auto lo
iface lo inet loopback
address 10.0.0.10/32

# The primary network interface
auto eth0
iface eth0
address 192.168.0.11
gateway 192.168.0.254
vrf mgmt

auto mgmt
iface mgmt
address 127.0.0.1/8
vrf-table auto

auto swp31
iface swp31
mtu 9216

auto swp32
iface swp32
mtu 9216

auto bridge
iface bridge
bridge-ports vni104000 swp1 swp2 vni1
bridge-vids 4000 2
bridge-vlan-aware yes

# Tenants

auto vrf1
iface vrf1
vrf-table auto

auto vlan2
iface vlan2
mtu 9000
vlan-id 2
vlan-raw-device bridge
vrf vrf1

auto vni1
iface vni1
mtu 9000
bridge-access 2
bridge-arp-nd-suppress on
bridge-learning off
mstpctl-bpduguard yes
mstpctl-portbpdufilter yes
vxlan-id 1
vxlan-local-tunnelip 10.0.0.10

# PXE-Config
auto vlan4000
iface vlan4000
mtu 9000
address 10.255.255.2/24
vlan-id 4000
vlan-raw-device bridge

auto vni104000
iface vni104000
mtu 9000
bridge-access 4000
bridge-learning off
mstpctl-bpduguard yes
mstpctl-portbpdufilter yes
vxlan-id 104000
vxlan-local-tunnelip 10.0.0.10

auto swp1
iface swp1
mtu 9000
bridge-access 4000

auto swp2
iface swp2
mtu 9000
bridge-access 4000
Loading

0 comments on commit 109834c

Please sign in to comment.