Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing switch OS during replace #526

Draft
wants to merge 31 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5771813
implement port to line mapping for cumulus and sonic
iljarotar May 21, 2024
17d2b79
linter
iljarotar May 27, 2024
e1e5b84
fix unit tests
iljarotar May 27, 2024
71bdc33
merge main in
iljarotar May 27, 2024
314f191
merge error
iljarotar May 27, 2024
e88f0de
test switch port mapping
iljarotar May 28, 2024
67d519b
test port name translation
iljarotar May 28, 2024
485cdc4
Merge branch 'master' into switch-replace-change-os
iljarotar May 28, 2024
6a959e3
add adopt nics from cumulus to sonic test
iljarotar May 28, 2024
fa69da8
test switching os in adopt connections and adopt from twin
iljarotar May 28, 2024
567a3a7
allow switches with different os during machine register
iljarotar Jun 7, 2024
9b56716
Merge branch 'master' into switch-replace-change-os
iljarotar Jun 7, 2024
76da6b7
map port names function
iljarotar Jun 7, 2024
e399025
map port names test
iljarotar Jun 13, 2024
4c01e8f
naming
iljarotar Jun 13, 2024
078a75b
Merge branch 'master' into switch-replace-change-os
iljarotar Jun 13, 2024
4de439e
fix boot service test
iljarotar Jun 13, 2024
ab212bd
Make integration pass
majst01 Jun 13, 2024
7fca3c6
adjust machine connections and integration test
iljarotar Jun 18, 2024
b89b33c
adjust machines not depending on nic hostnames
iljarotar Jun 18, 2024
44fcd34
fix integration test
iljarotar Jun 19, 2024
049d13e
linter
iljarotar Jun 19, 2024
56280c2
refactor integration test
iljarotar Jun 20, 2024
5d35d15
unexport error check function
iljarotar Jun 21, 2024
c06ac1c
move error check to switch test
iljarotar Jun 21, 2024
dc197fe
comments
iljarotar Jun 21, 2024
28f5008
always adjust machine connections
iljarotar Jun 21, 2024
b925c54
Merge branch 'master' into switch-replace-change-os
iljarotar Jul 5, 2024
4f89744
Merge branch 'master' into switch-replace-change-os
iljarotar Aug 5, 2024
2f58618
Merge branch 'master' of github.com:metal-stack/metal-api into switch…
Aug 13, 2024
e4e4744
Merge branch 'master' of github.com:metal-stack/metal-api into switch…
Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions cmd/metal-api/internal/datastore/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,21 @@ func (rs *RethinkStore) ConnectMachineWithSwitches(m *metal.Machine) error {
if err != nil {
return err
}
// e.g. "swp0s0" -> "Ethernet0"
dictionary, err := s1.MapPortNames(s2.OS.Vendor)
if err != nil {
return fmt.Errorf("could not create port mapping %w", err)
}

for _, con := range s1.MachineConnections[m.ID] {
if con2, has := byNicName[con.Nic.Name]; has {
if con.Nic.Name != con2.Nic.Name {
// get the corresponding interface name for s2
name, ok := dictionary[con.Nic.Name]
if !ok {
return fmt.Errorf("could not translate port name %s to equivalent port name of switch os %s", con.Nic.Name, s1.OS.Vendor)
}
// check if s2 contains nic of name corresponding to con.Nic.Name
if con2, has := byNicName[name]; has {
if name != con2.Nic.Name {
return connectionMapError
}
} else {
Expand Down
11 changes: 7 additions & 4 deletions cmd/metal-api/internal/metal/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,12 @@ func (nics Nics) FilterByHostname(hostname string) (res Nics) {
return res
}

// NicMap maps nic names to the corresponding nics
type NicMap map[string]*Nic

// ByName creates a map (nic names --> nic) from a nic list.
func (nics Nics) ByName() map[string]*Nic {
res := make(map[string]*Nic)
func (nics Nics) ByName() NicMap {
res := make(NicMap)

for i, n := range nics {
res[n.Name] = &nics[i]
Expand All @@ -314,8 +317,8 @@ func (nics Nics) ByName() map[string]*Nic {
}

// ByIdentifier creates a map (nic identifier --> nic) from a nic list.
func (nics Nics) ByIdentifier() map[string]*Nic {
res := make(map[string]*Nic)
func (nics Nics) ByIdentifier() NicMap {
res := make(NicMap)

for i, n := range nics {
res[n.GetIdentifier()] = &nics[i]
Expand Down
4 changes: 2 additions & 2 deletions cmd/metal-api/internal/metal/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ func TestNics_ByIdentifier(t *testing.T) {
nicArray[i].Neighbors = append(nicArray[0:i], nicArray[i+1:countOfNics]...)
}

map1 := map[string]*Nic{}
map1 := NicMap{}
for i, n := range nicArray {
map1[string(n.MacAddress)] = &nicArray[i]
}

tests := []struct {
name string
nics Nics
want map[string]*Nic
want NicMap
}{
{
name: "TestNics_ByIdentifier Test 1",
Expand Down
210 changes: 207 additions & 3 deletions cmd/metal-api/internal/metal/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package metal

import (
"fmt"
"strconv"
"strings"
"time"
)

Expand All @@ -24,11 +26,20 @@ type Switch struct {
type Switches []Switch

type SwitchOS struct {
Vendor string `rethinkdb:"vendor" json:"vendor"`
Version string `rethinkdb:"version" json:"version"`
MetalCoreVersion string `rethinkdb:"metal_core_version" json:"metal_core_version"`
Vendor SwitchOSVendor `rethinkdb:"vendor" json:"vendor"`
Version string `rethinkdb:"version" json:"version"`
MetalCoreVersion string `rethinkdb:"metal_core_version" json:"metal_core_version"`
}

// SwitchOSVendor is an enum denoting the name of a switch OS
type SwitchOSVendor string

// The enums for switch OS vendors
const (
SwitchOSVendorSonic SwitchOSVendor = "SONiC"
SwitchOSVendorCumulus SwitchOSVendor = "Cumulus"
)

// Connection between switch port and machine.
type Connection struct {
Nic Nic `rethinkdb:"nic" json:"nic"`
Expand Down Expand Up @@ -144,3 +155,196 @@ func (s *Switch) SetVrfOfMachine(m *Machine, vrf string) {
}
s.Nics = nics
}

// TranslateNicMap creates a NicMap where the keys are translated to the naming convention of the target OS
// example mapping from cumulus to sonic for one single port:
//
// map[string]Nic {
// "swp0s1": Nic{
// Name: "Ethernet1",
// MacAddress: ""
// }
// }
func (s *Switch) TranslateNicMap(targetOS SwitchOSVendor) (NicMap, error) {
nicMap := s.Nics.ByName()
translatedNicMap := make(NicMap)

if s.OS.Vendor == targetOS {
return nicMap, nil
}

ports := make([]string, 0)
for name := range nicMap {
ports = append(ports, name)
}

lines, err := getLinesFromPortNames(ports, s.OS.Vendor)
if err != nil {
return nil, err
}

for _, p := range ports {
targetPort, err := mapPortName(p, s.OS.Vendor, targetOS, lines)
if err != nil {
return nil, err
}

nic, ok := nicMap[p]
if !ok {
return nil, fmt.Errorf("an unknown error occured during port name translation")
}
translatedNicMap[targetPort] = nic
}

return translatedNicMap, nil
}

// MapPortNames creates a dictionary that maps the naming convention of this switch's OS to that of the target OS
func (s *Switch) MapPortNames(targetOS SwitchOSVendor) (map[string]string, error) {
nics := s.Nics.ByName()
portNamesMap := make(map[string]string, len(s.Nics))

ports := make([]string, 0)
for name := range nics {
ports = append(ports, name)
}

lines, err := getLinesFromPortNames(ports, s.OS.Vendor)
if err != nil {
return nil, err
}

for _, p := range ports {
targetPort, err := mapPortName(p, s.OS.Vendor, targetOS, lines)
if err != nil {
return nil, err
}
portNamesMap[p] = targetPort
}

return portNamesMap, nil
}

func mapPortName(port string, sourceOS, targetOS SwitchOSVendor, allLines []int) (string, error) {
line, err := portNameToLine(port, sourceOS)
if err != nil {
return "", fmt.Errorf("unable to get line number from port name, %w", err)
}

if targetOS == SwitchOSVendorCumulus {
return cumulusPortByLineNumber(line, allLines), nil
}
if targetOS == SwitchOSVendorSonic {
return sonicPortByLineNumber(line), nil
}

return "", fmt.Errorf("unknown target switch os %s", targetOS)
}

func getLinesFromPortNames(ports []string, os SwitchOSVendor) ([]int, error) {
lines := make([]int, 0)
for _, p := range ports {
l, err := portNameToLine(p, os)
if err != nil {
return nil, fmt.Errorf("unable to get line number from port name, %w", err)
}

lines = append(lines, l)
}

return lines, nil
}

func portNameToLine(port string, os SwitchOSVendor) (int, error) {
if os == SwitchOSVendorSonic {
return sonicPortNameToLine(port)
}
if os == SwitchOSVendorCumulus {
return cumulusPortNameToLine(port)
}
return 0, fmt.Errorf("unknow switch os %s", os)
}

func sonicPortNameToLine(port string) (int, error) {
// to prevent accidentally parsing a substring to a negative number
if strings.Contains(port, "-") {
return 0, fmt.Errorf("invalid token '-' in port name %s", port)
}

prefix, lineString, found := strings.Cut(port, "Ethernet")
if !found {
return 0, fmt.Errorf("invalid port name %s, expected to find prefix 'Ethernet'", port)
}

if prefix != "" {
return 0, fmt.Errorf("invalid port name %s, port name is expected to start with 'Ethernet'", port)
}

line, err := strconv.Atoi(lineString)
if err != nil {
return 0, fmt.Errorf("unable to convert port name to line number: %w", err)
}

return line, nil
}

func cumulusPortNameToLine(port string) (int, error) {
// to prevent accidentally parsing a substring to a negative number
if strings.Contains(port, "-") {
return 0, fmt.Errorf("invalid token '-' in port name %s", port)
}

prefix, suffix, found := strings.Cut(port, "swp")
if !found {
return 0, fmt.Errorf("invalid port name %s, expected to find prefix 'swp'", port)
}

if prefix != "" {
return 0, fmt.Errorf("invalid port name %s, port name is expected to start with 'swp'", port)
}

var line int

countString, indexString, found := strings.Cut(suffix, "s")
if !found {
count, err := strconv.Atoi(suffix)
if err != nil {
return 0, fmt.Errorf("unable to convert port name to line number: %w", err)
}
line = count * 4
} else {
count, err := strconv.Atoi(countString)
if err != nil {
return 0, fmt.Errorf("unable to convert port name to line number: %w", err)
}

index, err := strconv.Atoi(indexString)
if err != nil {
return 0, fmt.Errorf("unable to convert port name to line number: %w", err)
}
line = count*4 + index
}

return line, nil
}

func sonicPortByLineNumber(line int) string {
return fmt.Sprintf("Ethernet%d", line)
}

func cumulusPortByLineNumber(line int, allLines []int) string {
if line%4 > 0 {
return fmt.Sprintf("swp%ds%d", line/4, line%4)
}

for _, l := range allLines {
if l == line {
continue
}
if l/4 == line/4 {
return fmt.Sprintf("swp%ds%d", line/4, line%4)
}
}

return fmt.Sprintf("swp%d", line/4)
}
Loading
Loading