Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a69a434
wip
iljarotar Aug 21, 2026
9c3ea28
merge
iljarotar Aug 27, 2026
65e8136
pin api
iljarotar Aug 27, 2026
5ec5ddb
Merge branch 'main' of github.com:metal-stack/metal-apiserver into ex…
iljarotar Aug 31, 2026
3e3d262
list external members
iljarotar Aug 31, 2026
ce46444
add external member
iljarotar Sep 1, 2026
20de5bd
remove external member
iljarotar Sep 2, 2026
8b81e9c
fix test
iljarotar Sep 2, 2026
868f2c4
fix integer pool test
iljarotar Sep 2, 2026
0f17c9b
Revert "fix test"
iljarotar Sep 2, 2026
25c7f1e
fix network test
iljarotar Sep 2, 2026
1ce01f1
fix tokens again
iljarotar Sep 2, 2026
e059711
remove debug log
iljarotar Sep 2, 2026
b17e252
pin api
iljarotar Sep 2, 2026
108fe3a
add switch nic memberships
iljarotar Sep 3, 2026
95c1963
use explicit memberships for external network members
iljarotar Sep 3, 2026
af95e36
merge
iljarotar Sep 4, 2026
87ef703
use test logger
iljarotar Sep 4, 2026
1df592f
fix register test
iljarotar Sep 4, 2026
438114b
fix update nics test
iljarotar Sep 4, 2026
6ef5367
fix connect machine with switches test
iljarotar Sep 7, 2026
29f006f
fix switch service tests
iljarotar Sep 7, 2026
4756b81
Do not test infra twice
majst01 Sep 8, 2026
0597945
fix machine connections test
iljarotar Sep 8, 2026
6425b78
fix boot service tests
iljarotar Sep 8, 2026
6c8fbbf
reverse network type validation condition
iljarotar Sep 8, 2026
0c5369b
fix switch repo tests
iljarotar Sep 8, 2026
0b2cd26
validate machine can't connect to external ports
iljarotar Sep 8, 2026
9a69401
make ports for deleted machines unmanaged
iljarotar Sep 8, 2026
bfc386b
return network
iljarotar Sep 8, 2026
8b67321
fix network service test
iljarotar Sep 8, 2026
b4e5ee6
format
iljarotar Sep 8, 2026
d8485fb
return network and members from all network member funcs
iljarotar Sep 9, 2026
06505b3
also return rack and partition of each member
iljarotar Sep 9, 2026
136e5d3
rack is not a pointer
iljarotar Sep 9, 2026
6af7174
merge
iljarotar Sep 9, 2026
1ff4ab7
update nic memberships during heartbeat
iljarotar Sep 9, 2026
bbcb414
default metal nic memberships
iljarotar Sep 9, 2026
d28ab79
quick fix vrf
iljarotar Sep 9, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
grep -v "metal-apiserver/pkg/service/admin/tenant$" |
grep -v "metal-apiserver/pkg/service/admin/switch$" |
grep -v "metal-apiserver/pkg/service/admin/vpn$" |
grep -v "metal-apiserver/pkg/service/infra$" |
grep -v "metal-apiserver/pkg/service/infra" |
tr '\n' ' ')

steps:
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/datastore-cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func newDatastoreCmd() *cli.Command {
return fmt.Errorf("unable to create logger %w", err)
}

err = generic.Initialize(
_, err = generic.Initialize(
ctx,
log.WithGroup("datastore"),
rethinkdb.ConnectOpts{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/lestrrat-go/jwx/v4 v4.4.0
github.com/looplab/fsm v1.0.4
github.com/markbates/goth v1.82.0
github.com/metal-stack/api v0.5.3
github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba
github.com/metal-stack/go-ipam v1.15.2
github.com/metal-stack/metal-lib v0.26.3
github.com/metal-stack/tenant-api v0.2.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A=
github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg=
github.com/metal-stack/api v0.5.3 h1:H6dh71kGvvL9z6X03A31LTIaiJHFe8L6y+xgtLcqVDg=
github.com/metal-stack/api v0.5.3/go.mod h1:U8c+awSMxXaRJjzWpo1IMcvlNxOi1ewu/fk1BzDM7hI=
github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba h1:4Dyhunb5seTQSY3hgGqZ5C0QT638xkEx5yS58YVGjG8=
github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ=
github.com/metal-stack/go-ipam v1.15.2 h1:5okodNdZzGhSM8dGnxto36L1jacES8EcGNNJC9qXYWw=
github.com/metal-stack/go-ipam v1.15.2/go.mod h1:MKxv0M2h0T853h+MycmXLT2RmHVvfkjHrvQX/bX7Rl4=
github.com/metal-stack/goth v0.1.0 h1:sdadAH9QG+xAjLNKAJq8+esfXct6icTs58juoR4BKrQ=
Expand Down
28 changes: 14 additions & 14 deletions pkg/db/generic/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ func AsnPoolRange(min, max uint) dataStoreOption {
}
}

func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpts ...dataStoreOption) error {
func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpts ...dataStoreOption) (*datastore, error) {
db := r.DB(opts.Database)

session, err := r.Connect(opts)
if err != nil {
return fmt.Errorf("unable to connect to database: %w", err)
return nil, fmt.Errorf("unable to connect to database: %w", err)
}

log.Info("creating / updating runtime user metal")
Expand All @@ -46,7 +46,7 @@ func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpt
Conflict: "update",
}).RunWrite(session, r.RunOpts{Context: ctx})
if err != nil {
return fmt.Errorf("unable to ensure runtime user metal: %w", err)
return nil, fmt.Errorf("unable to ensure runtime user metal: %w", err)
}

log.Info("initializing database", "database", opts.Database)
Expand All @@ -55,42 +55,42 @@ func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpt
return r.Branch(row, nil, r.DBCreate(opts.Database))
}).Exec(session, r.ExecOpts{Context: ctx})
if err != nil {
return fmt.Errorf("cannot create database: %w", err)
return nil, fmt.Errorf("cannot create database: %w", err)
}

log.Info("ensuring demoted user can read and write")

_, err = db.Grant(demotedUser, map[string]any{"read": true, "write": true}).RunWrite(session, r.RunOpts{Context: ctx})
if err != nil {
return fmt.Errorf("unable to grant read / write permissions to metal user on database %s: %w", opts.Database, err)
return nil, fmt.Errorf("unable to grant read / write permissions to metal user on database %s: %w", opts.Database, err)
}
_, err = r.DB("rethinkdb").Grant(demotedUser, map[string]any{"read": true}).RunWrite(session, r.RunOpts{Context: ctx})
if err != nil {
return fmt.Errorf("unable to grant read / write permissions to metal user on rethinkdb database: %w", err)
return nil, fmt.Errorf("unable to grant read / write permissions to metal user on rethinkdb database: %w", err)
}

log.Info("initializing tables")

ds, err := New(log, opts, dsOpts...)
if err != nil {
return fmt.Errorf("unable to create datastore: %w", err)
return nil, fmt.Errorf("unable to create datastore: %w", err)
}

ds.queryExecutor = session // the metal user cannot create tables

err = ds.createTable(ctx, migrationTableName)
if err != nil {
return fmt.Errorf("cannot create migration table: %w", err)
return nil, fmt.Errorf("cannot create migration table: %w", err)
}

err = ds.createTable(ctx, sharedMutexTableName)
if err != nil {
return fmt.Errorf("cannot create shared mutex table: %w", err)
return nil, fmt.Errorf("cannot create shared mutex table: %w", err)
}

for _, tableName := range ds.tableNames {
if err := ds.createTable(ctx, tableName); err != nil {
return fmt.Errorf("cannot create %s table: %w", tableName, err)
return nil, fmt.Errorf("cannot create %s table: %w", tableName, err)
}
}

Expand All @@ -99,7 +99,7 @@ func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpt
// be graceful after table creation and wait until ready
res, err := db.Wait().Run(session, r.RunOpts{Context: ctx})
if err != nil {
return fmt.Errorf("unable to wait for database creation")
return nil, fmt.Errorf("unable to wait for database creation")
}
defer func() {
if err := res.Close(); err != nil {
Expand All @@ -110,15 +110,15 @@ func Initialize(ctx context.Context, log *slog.Logger, opts r.ConnectOpts, dsOpt
ds.log.Info("initializing pools")

if err := ds.asnPool.initialize(); err != nil {
return fmt.Errorf("unable to initialize asn pool: %w", err)
return nil, fmt.Errorf("unable to initialize asn pool: %w", err)
}
if err := ds.vrfPool.initialize(); err != nil {
return fmt.Errorf("unable to initialize vrf pool: %w", err)
return nil, fmt.Errorf("unable to initialize vrf pool: %w", err)
}

ds.log.Info("database init complete")

return nil
return ds, nil
}

func (ds *datastore) createTable(ctx context.Context, tableName string) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/db/generic/integer_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func Test_AcquireAndReleaseUniqueInteger(t *testing.T) {
{
name: "verify validation of input fails",
acquire: 524288,
acquireErr: errors.New("value '524288' is outside of the allowed range '1 - 131072'"),
acquireErr: errors.New("value '524288' is outside of the allowed range '1 - 100'"),
},
}

Expand Down
34 changes: 21 additions & 13 deletions pkg/db/metal/nic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import "github.com/samber/lo"

type (
Nic struct {
MacAddress string `rethinkdb:"macAddress"`
Name string `rethinkdb:"name"`
Identifier string `rethinkdb:"identifier"`
Vrf string `rethinkdb:"vrf"`
Neighbors Nics `rethinkdb:"neighbors"`
Hostname string `rethinkdb:"hostname"`
State *NicState `rethinkdb:"state"`
BGPPortState *SwitchBGPPortState `rethinkdb:"bgpPortState"`
MacAddress string `rethinkdb:"macAddress"`
Name string `rethinkdb:"name"`
Identifier string `rethinkdb:"identifier"`
Vrf string `rethinkdb:"vrf"`
Neighbors Nics `rethinkdb:"neighbors"`
Hostname string `rethinkdb:"hostname"`
State *NicState `rethinkdb:"state"`
BGPPortState *SwitchBGPPortState `rethinkdb:"bgpPortState"`
Membership SwitchPortMembership `rethinkdb:"membership"`
}

Nics []Nic
Expand All @@ -23,8 +24,9 @@ type (
Actual SwitchPortStatus `rethinkdb:"actual"`
}

BGPState string
SwitchPortStatus string
BGPState string
SwitchPortStatus string
SwitchPortMembership string
)

const (
Expand All @@ -37,9 +39,15 @@ const (
)

const (
SwitchPortStatusUnknown SwitchPortStatus = "UNKNOWN"
SwitchPortStatusUp SwitchPortStatus = "UP"
SwitchPortStatusDown SwitchPortStatus = "DOWN"
SwitchPortStatusUnknown = SwitchPortStatus("UNKNOWN")
SwitchPortStatusUp = SwitchPortStatus("UP")
SwitchPortStatusDown = SwitchPortStatus("DOWN")
)

const (
SwitchPortMembershipUnmanaged = SwitchPortMembership("unmanaged")
SwitchPortMembershipInternal = SwitchPortMembership("internal")
SwitchPortMembershipExternal = SwitchPortMembership("external")
)

func (nics Nics) MapByIdentifier() NicMap {
Expand Down
67 changes: 59 additions & 8 deletions pkg/db/metal/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,22 @@ func FromBGPState(state BGPState) (apiv2.BGPState, error) {
return apiv2State, nil
}

func ToMembership(membership apiv2.SwitchPortMembership) (SwitchPortMembership, error) {
strVal, err := enum.GetStringValue(membership)
if err != nil {
return SwitchPortMembership(""), err
}
return SwitchPortMembership(*strVal), nil
}

func FromMembership(membership SwitchPortMembership) (apiv2.SwitchPortMembership, error) {
apiv2Membership, err := enum.GetEnum[apiv2.SwitchPortMembership](string(membership))
if err != nil {
return apiv2.SwitchPortMembership_SWITCH_PORT_MEMBERSHIP_UNSPECIFIED, err
}
return apiv2Membership, nil
}

func (c ConnectionMap) ByNicName() (map[string]Connection, error) {
res := make(map[string]Connection)
for _, cons := range c {
Expand All @@ -159,18 +175,49 @@ func (c ConnectionMap) ByNicName() (map[string]Connection, error) {
}

func (s *Switch) ConnectMachine(machineID string, machineNics Nics) (int, error) {
physicalConnections := s.getPhysicalMachineConnections(machineID, machineNics)
newCons := s.getConnectionsFromMachineNics(machineID, machineNics)

if len(physicalConnections) < 1 {
if len(newCons) < 1 {
if _, exists := s.MachineConnections[machineID]; exists {
return 0, fmt.Errorf("machine connection between machine %s and switch %s exists in the database but not physically; if you are attempting migrate the machine from one rack to another delete it first", machineID, s.ID)
}
return 0, nil
}

delete(s.MachineConnections, machineID)
s.MachineConnections[machineID] = append(s.MachineConnections[machineID], physicalConnections...)
return len(physicalConnections), nil
oldCons := s.MachineConnections[machineID]

for _, con := range oldCons {
nic, idx, found := lo.FindIndexOf(s.Nics, func(n Nic) bool {
return n.Name == con.Nic.Name
})
if !found {
return 0, fmt.Errorf("nic %s found in machine connections but not in switch nics", con.Nic.Name)
}
nic.Membership = SwitchPortMembershipUnmanaged
s.Nics[idx] = nic
}

for i, con := range newCons {
nic, idx, found := lo.FindIndexOf(s.Nics, func(n Nic) bool {
return n.Name == con.Nic.Name
})
if !found {
return 0, fmt.Errorf("nic %s found in machine connections but not in switch nics", con.Nic.Name)
}

if nic.Membership == SwitchPortMembershipExternal {
return 0, fmt.Errorf("nic %s is an external member of a network", nic.Name)
}

nic.Membership = SwitchPortMembershipInternal
s.Nics[idx] = nic

con.Nic.Membership = SwitchPortMembershipInternal
newCons[i] = con
}

s.MachineConnections[machineID] = newCons
return len(newCons), nil
}

func (s *Switch) SetVrfOfMachine(m *Machine, vrf string) {
Expand Down Expand Up @@ -392,9 +439,10 @@ func cumulusPortByLineNumber(line int, allLines []int) string {
return fmt.Sprintf("swp%d", line/4+1)
}

// getPhysicalMachineConnections correlates machine nic information with the nics on the switch to figure out physical connections
func (s *Switch) getPhysicalMachineConnections(machineID string, machineNics Nics) Connections {
connections := make(Connections, 0)
func (s *Switch) getConnectionsFromMachineNics(machineID string, machineNics Nics) Connections {
var (
connections Connections
)
for _, machineNic := range machineNics {
neighMap := machineNic.Neighbors.FilterByHostname(s.ID).MapByIdentifier()

Expand All @@ -407,5 +455,8 @@ func (s *Switch) getPhysicalMachineConnections(machineID string, machineNics Nic
}
}
}

// FIXME: shouldn't we return an error if a machine sees a neighbor but we can't find the nic on the switch?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a good issue to work on at a later point in time


return connections
}
Loading
Loading