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 the API to toggle switch ports #506

Merged
merged 26 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dd6d7f5
allow the API to toggle switch ports
ulrichSchreiner Feb 26, 2024
bae5b4b
add path parameter
ulrichSchreiner Feb 29, 2024
b559143
repair test to make linter happy
ulrichSchreiner Feb 29, 2024
ef31bab
see linter errors on local machine before pushing to gh
ulrichSchreiner Feb 29, 2024
de9aef0
Merge branch 'master' into toggle-switch-port
majst01 Mar 4, 2024
aa588f0
not needed any more
ulrichSchreiner Mar 18, 2024
20f329d
send desired state to switch if it is set
ulrichSchreiner Mar 18, 2024
e3e8d5d
add enum types for port state
ulrichSchreiner Mar 19, 2024
ea27662
merge master
ulrichSchreiner Mar 19, 2024
2d0093a
set the enum values in the endpoint
ulrichSchreiner Mar 19, 2024
2bee462
Merge branch 'master' into toggle-switch-port
ulrichSchreiner Apr 2, 2024
0a629c9
merge master
ulrichSchreiner Apr 2, 2024
c8c8c5c
merge master
ulrichSchreiner Apr 4, 2024
d5e6e9f
comment code
ulrichSchreiner Apr 4, 2024
b118ddc
fix typo
ulrichSchreiner Apr 4, 2024
5276d55
fix typo
ulrichSchreiner Apr 4, 2024
d3d32ed
add check for bugs and unused
ulrichSchreiner Apr 8, 2024
cb1fac4
set connection nic state to the REAL state not the desired state
ulrichSchreiner Apr 17, 2024
e2f3552
nic state could be nil, so check state too
ulrichSchreiner Apr 22, 2024
5b62d99
make sure we do not crash with old versions of metal-core
ulrichSchreiner Apr 22, 2024
3a3c6d6
resolve review conversation
ulrichSchreiner Apr 22, 2024
4dfae64
FIX: wrong comment
ulrichSchreiner Apr 22, 2024
2345b8b
document the different nic-states in the different fields of the resp…
ulrichSchreiner Apr 22, 2024
2af9749
resolve review comments
ulrichSchreiner Apr 23, 2024
8cd2f0d
add tests for state methods
ulrichSchreiner Apr 23, 2024
109a56a
the NIC names of sonic are case sensitive
ulrichSchreiner Apr 25, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ vendor
generate
coverage.out
__debug_bin
.mirrord
10 changes: 9 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,12 @@ run:
deadline: 10m
linters:
disable:
- musttag
- musttag
- protogetter
enable:
- testifylint
- unused
ulrichSchreiner marked this conversation as resolved.
Show resolved Hide resolved
presets:
- bugs
- unused
fast: true
4 changes: 0 additions & 4 deletions Dockerfile.dev

This file was deleted.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protoc:
.PHONY: mini-lab-push
mini-lab-push:
make
docker build -f Dockerfile.dev -t metalstack/metal-api:latest .
docker build -f Dockerfile -t metalstack/metal-api:latest .
kind --name metal-control-plane load docker-image metalstack/metal-api:latest
kubectl --kubeconfig=$(MINI_LAB_KUBECONFIG) patch deployments.apps -n metal-control-plane metal-api --patch='{"spec":{"template":{"spec":{"containers":[{"name": "metal-api","imagePullPolicy":"IfNotPresent","image":"metalstack/metal-api:latest"}]}}}}'
kubectl --kubeconfig=$(MINI_LAB_KUBECONFIG) delete pod -n metal-control-plane -l app=metal-api
Expand Down
128 changes: 128 additions & 0 deletions cmd/metal-api/internal/metal/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,37 @@ import (
"fmt"
"net"
"strings"

"github.com/samber/lo"
)

// SwitchPortStatus is a type alias for a string that represents the status of a switch port.
// Valid values are defined as constants in this package.
type SwitchPortStatus string

// SwitchPortStatus defines the possible statuses for a switch port.
// UNKNOWN indicates the status is not known.
// UP indicates the port is up and operational.
// DOWN indicates the port is down and not operational.
const (
SwitchPortStatusUnknown SwitchPortStatus = "UNKNOWN"
SwitchPortStatusUp SwitchPortStatus = "UP"
SwitchPortStatusDown SwitchPortStatus = "DOWN"
)

// IsConcrete returns true if the SwitchPortStatus is UP or DOWN,
// which are concrete, known statuses. It returns false if the status
// is UNKNOWN, which indicates the status is not known.
func (s SwitchPortStatus) IsConcrete() bool {
return s == SwitchPortStatusUp || s == SwitchPortStatusDown
}

// IsValid returns true if the SwitchPortStatus is a known valid value
// (UP, DOWN, UNKNOWN).
func (s SwitchPortStatus) IsValid() bool {
return s == SwitchPortStatusUp || s == SwitchPortStatusDown || s == SwitchPortStatusUnknown
}

// A MacAddress is the type for mac addresses. When using a
// custom type, we cannot use strings directly.
type MacAddress string
Expand All @@ -18,6 +47,105 @@ type Nic struct {
Vrf string `rethinkdb:"vrf" json:"vrf"`
Neighbors Nics `rethinkdb:"neighbors" json:"neighbors"`
Hostname string `rethinkdb:"hostname" json:"hostname"`
State *NicState `rethinkdb:"state" json:"state"`
}

// NicState represents the desired and actual state of a network interface
// controller (NIC). The Desired field indicates the intended state of the
// NIC, while Actual indicates its current operational state. The Desired
// state will be removed when the actual state is equal to the desired state.
type NicState struct {
Desired *SwitchPortStatus `rethinkdb:"desired" json:"desired"`
Actual SwitchPortStatus `rethinkdb:"actual" json:"actual"`
}

// SetState updates the NicState with the given SwitchPortStatus. It returns
// a new NicState and a bool indicating if the state was changed.
//
// If the given status matches the current Actual state, it checks if Desired
// is set and matches too. If so, Desired is set to nil since the desired
// state has been reached.
//
// If the given status differs from the current Actual state, Desired is left
// unchanged if it differes from the new state so the desired state is still tracked.
// The Actual state is updated to the given status.
//
// This allows tracking both the desired and actual states, while clearing
// Desired once the desired state is achieved.
func (ns *NicState) SetState(s SwitchPortStatus) (NicState, bool) {
if ns == nil {
return NicState{
Actual: s,
Desired: nil,
}, true
}
if ns.Actual == s {
if ns.Desired != nil {
if *ns.Desired == s {
// we now have the desired state, so set the desired state to nil
return NicState{
Actual: s,
Desired: nil,
}, true
} else {
// we already have the reported state, but the desired one is different
// so nothing changed
return *ns, false
}
}
// nothing changed
return *ns, false
}
// we got another state as we had before
if ns.Desired != nil {
if *ns.Desired == s {
// we now have the desired state, so set the desired state to nil
return NicState{
Actual: s,
Desired: nil,
}, true
} else {
// a new state was reported, but the desired one is different
// so we have to update the state but keep the desired state
return NicState{
Actual: s,
Desired: ns.Desired,
}, true
}
}
return NicState{
Actual: s,
Desired: nil,
}, true
}

// WantState sets the desired state for the NIC. It returns a new NicState
// struct with the desired state set and a bool indicating if the state changed.
// If the current state already matches the desired state, it returns a state
// with a cleared desired field.
func (ns *NicState) WantState(s SwitchPortStatus) (NicState, bool) {
if ns == nil {
return NicState{
Actual: SwitchPortStatusUnknown,
Desired: &s,
}, true
}
if ns.Actual == s {
// we want a state we already have
if ns.Desired != nil {
return NicState{
Actual: s,
Desired: nil,
}, true
}
return *ns, false
}
// return a new state with the desired state set and a bool indicating a state change
// only if the desired state is different from the current one
return NicState{
Actual: ns.Actual,
Desired: &s,
}, lo.FromPtr(ns.Desired) != s
}

// GetIdentifier returns the identifier of a nic.
Expand Down
216 changes: 216 additions & 0 deletions cmd/metal-api/internal/metal/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,219 @@ func TestPrefix_Equals(t *testing.T) {
})
}
}

func TestNicState_WantState(t *testing.T) {
up := SwitchPortStatusUp
down := SwitchPortStatusDown
unknown := SwitchPortStatusUnknown

tests := []struct {
name string
nic *NicState
arg SwitchPortStatus
want NicState
changed bool
}{
{
name: "up to desired down",
nic: &NicState{
Desired: nil,
Actual: down,
},
arg: up,
want: NicState{
Desired: &up,
Actual: down,
},
changed: true,
},
{
name: "up to up with empty desired",
nic: &NicState{
Desired: nil,
Actual: up,
},
arg: up,
want: NicState{
Desired: nil,
Actual: up,
},
changed: false,
},
{
name: "up to up with other desired",
nic: &NicState{
Desired: &down,
Actual: up,
},
arg: up,
want: NicState{
Desired: nil,
Actual: up,
},
changed: true,
},
{
name: "nil to up",
nic: nil,
arg: up,
want: NicState{
Desired: &up,
Actual: unknown,
},
changed: true,
},
{
name: "different actual with same desired",
nic: &NicState{
Desired: &down,
Actual: up,
},
arg: down,
want: NicState{
Desired: &down,
Actual: up,
},
changed: false,
},
{
name: "different actual with other desired",
nic: &NicState{
Desired: &up,
Actual: up,
},
arg: down,
want: NicState{
Desired: &down,
Actual: up,
},
changed: true,
},
{
name: "different actual with empty desired",
nic: &NicState{
Desired: nil,
Actual: up,
},
arg: down,
want: NicState{
Desired: &down,
Actual: up,
},
changed: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

got, got1 := tt.nic.WantState(tt.arg)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NicState.WantState() got = %+v, want %+v", got, tt.want)
}
if got1 != tt.changed {
t.Errorf("NicState.WantState() got1 = %v, want %v", got1, tt.changed)
}
})
}
}

func TestNicState_SetState(t *testing.T) {
up := SwitchPortStatusUp
down := SwitchPortStatusDown
unknown := SwitchPortStatusUnknown

tests := []struct {
name string
nic *NicState
arg SwitchPortStatus
want NicState
changed bool
}{
{
name: "different actual with empty desired",
nic: &NicState{
Desired: nil,
Actual: up,
},
arg: down,
want: NicState{
Desired: nil,
Actual: down,
},
changed: true,
},
{
name: "different actual with same state in desired",
nic: &NicState{
Desired: &down,
Actual: up,
},
arg: down,
want: NicState{
Desired: nil,
Actual: down,
},
changed: true,
},
{
name: "different actual with other state in desired",
nic: &NicState{
Desired: &unknown,
Actual: up,
},
arg: down,
want: NicState{
Desired: &unknown,
Actual: down,
},
changed: true,
},
{
name: "nil nic",
nic: nil,
arg: down,
want: NicState{
Desired: nil,
Actual: down,
},
changed: true,
},
{
name: "same state with same desired",
nic: &NicState{
Desired: &down,
Actual: down,
},
arg: down,
want: NicState{
Desired: nil,
Actual: down,
},
changed: true,
},
{
name: "same state with other desired",
nic: &NicState{
Desired: &up,
Actual: down,
},
arg: down,
want: NicState{
Desired: &up,
Actual: down,
},
changed: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := tt.nic.SetState(tt.arg)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NicState.SetState() got = %+v, want %+v", got, tt.want)
}
if got1 != tt.changed {
t.Errorf("NicState.SetState() got1 = %v, want %v", got1, tt.changed)
}
})
}
}
Loading