diff --git a/internal/app/machined/pkg/controllers/network/cmdline.go b/internal/app/machined/pkg/controllers/network/cmdline.go index d5df91d74..990f1ec6e 100644 --- a/internal/app/machined/pkg/controllers/network/cmdline.go +++ b/internal/app/machined/pkg/controllers/network/cmdline.go @@ -16,6 +16,7 @@ import ( "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1" "github.com/talos-systems/talos/pkg/machinery/constants" + "github.com/talos-systems/talos/pkg/machinery/ordered" "github.com/talos-systems/talos/pkg/machinery/resources/network" ) @@ -244,13 +245,13 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) { linkSpecSpecs = append(linkSpecSpecs, bondLinkSpec) - for _, slave := range bondSlaves { + for idx, slave := range bondSlaves { slaveLinkSpec := network.LinkSpecSpec{ Name: slave, Up: true, ConfigLayer: network.ConfigCmdline, } - SetBondSlave(&slaveLinkSpec, bondName) + SetBondSlave(&slaveLinkSpec, ordered.MakePair(bondName, idx)) linkSpecSpecs = append(linkSpecSpecs, slaveLinkSpec) } } diff --git a/internal/app/machined/pkg/controllers/network/cmdline_test.go b/internal/app/machined/pkg/controllers/network/cmdline_test.go index 7faf35800..6b642c3ef 100644 --- a/internal/app/machined/pkg/controllers/network/cmdline_test.go +++ b/internal/app/machined/pkg/controllers/network/cmdline_test.go @@ -63,14 +63,20 @@ func (suite *CmdlineSuite) TestParse() { Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond0", + BondSlave: netconfig.BondSlave{ + MasterName: "bond0", + SlaveIndex: 0, + }, }, { Name: "eth1", Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond0", + BondSlave: netconfig.BondSlave{ + MasterName: "bond0", + SlaveIndex: 1, + }, }, }, } @@ -233,14 +239,20 @@ func (suite *CmdlineSuite) TestParse() { Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond1", + BondSlave: netconfig.BondSlave{ + MasterName: "bond1", + SlaveIndex: 0, + }, }, { Name: "eth4", Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond1", + BondSlave: netconfig.BondSlave{ + MasterName: "bond1", + SlaveIndex: 1, + }, }, }, }, @@ -275,14 +287,20 @@ func (suite *CmdlineSuite) TestParse() { Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond1", + BondSlave: netconfig.BondSlave{ + MasterName: "bond1", + SlaveIndex: 0, + }, }, { Name: "eth4", Up: true, Logical: false, ConfigLayer: netconfig.ConfigCmdline, - MasterName: "bond1", + BondSlave: netconfig.BondSlave{ + MasterName: "bond1", + SlaveIndex: 1, + }, }, }, }, diff --git a/internal/app/machined/pkg/controllers/network/link_config.go b/internal/app/machined/pkg/controllers/network/link_config.go index 0913f2ea0..36336d841 100644 --- a/internal/app/machined/pkg/controllers/network/link_config.go +++ b/internal/app/machined/pkg/controllers/network/link_config.go @@ -18,6 +18,7 @@ import ( talosconfig "github.com/talos-systems/talos/pkg/machinery/config" "github.com/talos-systems/talos/pkg/machinery/nethelpers" + "github.com/talos-systems/talos/pkg/machinery/ordered" "github.com/talos-systems/talos/pkg/machinery/resources/config" "github.com/talos-systems/talos/pkg/machinery/resources/network" ) @@ -269,7 +270,7 @@ func (ctrl *LinkConfigController) parseCmdline(logger *zap.Logger) ([]network.Li //nolint:gocyclo func (ctrl *LinkConfigController) parseMachineConfiguration(logger *zap.Logger, cfgProvider talosconfig.Provider) []network.LinkSpecSpec { // scan for the bonds - bondedLinks := map[string]string{} // mapping physical interface -> bond interface + bondedLinks := map[string]ordered.Pair[string, int]{} // mapping physical interface -> bond interface for _, device := range cfgProvider.Machine().Network().Devices() { if device.Ignore() { @@ -280,12 +281,12 @@ func (ctrl *LinkConfigController) parseMachineConfiguration(logger *zap.Logger, continue } - for _, linkName := range device.Bond().Interfaces() { - if bondName, exists := bondedLinks[linkName]; exists && bondName != device.Interface() { + for idx, linkName := range device.Bond().Interfaces() { + if bondData, exists := bondedLinks[linkName]; exists && bondData.F1 != device.Interface() { logger.Sugar().Warnf("link %q is included into more than two bonds", linkName) } - bondedLinks[linkName] = device.Interface() + bondedLinks[linkName] = ordered.MakePair(device.Interface(), idx) } } @@ -331,7 +332,7 @@ func (ctrl *LinkConfigController) parseMachineConfiguration(logger *zap.Logger, } } - for slaveName, bondName := range bondedLinks { + for slaveName, bondData := range bondedLinks { if _, exists := linkMap[slaveName]; !exists { linkMap[slaveName] = &network.LinkSpecSpec{ Name: slaveName, @@ -340,7 +341,7 @@ func (ctrl *LinkConfigController) parseMachineConfiguration(logger *zap.Logger, } } - SetBondSlave(linkMap[slaveName], bondName) + SetBondSlave(linkMap[slaveName], bondData) } links := make([]network.LinkSpecSpec, 0, len(linkMap)) diff --git a/internal/app/machined/pkg/controllers/network/link_config_test.go b/internal/app/machined/pkg/controllers/network/link_config_test.go index a7ca65d50..c107c714b 100644 --- a/internal/app/machined/pkg/controllers/network/link_config_test.go +++ b/internal/app/machined/pkg/controllers/network/link_config_test.go @@ -316,7 +316,7 @@ func (suite *LinkConfigSuite) TestMachineConfiguration() { case "eth2", "eth3": suite.Assert().True(r.TypedSpec().Up) suite.Assert().False(r.TypedSpec().Logical) - suite.Assert().Equal("bond0", r.TypedSpec().MasterName) + suite.Assert().Equal("bond0", r.TypedSpec().BondSlave.MasterName) case "bond0": suite.Assert().True(r.TypedSpec().Up) suite.Assert().True(r.TypedSpec().Logical) diff --git a/internal/app/machined/pkg/controllers/network/link_spec.go b/internal/app/machined/pkg/controllers/network/link_spec.go index d6c3708d2..143587996 100644 --- a/internal/app/machined/pkg/controllers/network/link_spec.go +++ b/internal/app/machined/pkg/controllers/network/link_spec.go @@ -7,6 +7,7 @@ package network import ( "context" "fmt" + "sort" "github.com/AlekSi/pointer" "github.com/cosi-project/runtime/pkg/controller" @@ -20,6 +21,7 @@ import ( networkadapter "github.com/talos-systems/talos/internal/app/machined/pkg/adapters/network" "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network/watch" "github.com/talos-systems/talos/pkg/machinery/nethelpers" + "github.com/talos-systems/talos/pkg/machinery/ordered" "github.com/talos-systems/talos/pkg/machinery/resources/network" ) @@ -111,6 +113,8 @@ func (ctrl *LinkSpecController) Run(ctx context.Context, r controller.Runtime, l // loop over links and make reconcile decision var multiErr *multierror.Error + SortBonds(list.Items) + for _, res := range list.Items { link := res.(*network.LinkSpec) //nolint:forcetypeassert,errcheck @@ -125,6 +129,27 @@ func (ctrl *LinkSpecController) Run(ctx context.Context, r controller.Runtime, l } } +// SortBonds sort resources in increasing order, except it places slave interfaces right after the bond +// in proper order. +func SortBonds(items []resource.Resource) { + sort.Slice(items, func(i, j int) bool { + left := items[i].Spec().(network.LinkSpecSpec) //nolint:errcheck + right := items[j].Spec().(network.LinkSpecSpec) //nolint:errcheck + + l := ordered.MakeTriple(left.Name, 0, "") + if left.BondSlave.MasterName != "" { + l = ordered.MakeTriple(left.BondSlave.MasterName, left.BondSlave.SlaveIndex, left.Name) + } + + r := ordered.MakeTriple(right.Name, 0, "") + if right.BondSlave.MasterName != "" { + r = ordered.MakeTriple(right.BondSlave.MasterName, right.BondSlave.SlaveIndex, right.Name) + } + + return l.LessThan(r) + }) +} + func findLink(links []rtnetlink.LinkMessage, name string) *rtnetlink.LinkMessage { for i, link := range links { if link.Attributes.Name == name { @@ -341,7 +366,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti Master: pointer.ToUint32(0), }, }); err != nil { - return fmt.Errorf("error unslaving link %q under %q: %w", slave.Attributes.Name, link.TypedSpec().MasterName, err) + return fmt.Errorf("error unslaving link %q under %q: %w", slave.Attributes.Name, link.TypedSpec().BondSlave.MasterName, err) } (*links)[i].Attributes.Master = nil @@ -448,8 +473,8 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti // sync master index (for links which are bond slaves) var masterIndex uint32 - if link.TypedSpec().MasterName != "" { - if master := findLink(*links, link.TypedSpec().MasterName); master != nil { + if link.TypedSpec().BondSlave.MasterName != "" { + if master := findLink(*links, link.TypedSpec().BondSlave.MasterName); master != nil { masterIndex = master.Index } } @@ -464,12 +489,12 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti Master: pointer.ToUint32(masterIndex), }, }); err != nil { - return fmt.Errorf("error enslaving/unslaving link %q under %q: %w", link.TypedSpec().Name, link.TypedSpec().MasterName, err) + return fmt.Errorf("error enslaving/unslaving link %q under %q: %w", link.TypedSpec().Name, link.TypedSpec().BondSlave.MasterName, err) } existing.Attributes.Master = pointer.ToUint32(masterIndex) - logger.Info("enslaved/unslaved link", zap.String("parent", link.TypedSpec().MasterName)) + logger.Info("enslaved/unslaved link", zap.String("parent", link.TypedSpec().BondSlave.MasterName)) } } diff --git a/internal/app/machined/pkg/controllers/network/link_spec_test.go b/internal/app/machined/pkg/controllers/network/link_spec_test.go index 43657dcf0..73314fc37 100644 --- a/internal/app/machined/pkg/controllers/network/link_spec_test.go +++ b/internal/app/machined/pkg/controllers/network/link_spec_test.go @@ -19,6 +19,7 @@ import ( "github.com/cosi-project/runtime/pkg/state" "github.com/cosi-project/runtime/pkg/state/impl/inmem" "github.com/cosi-project/runtime/pkg/state/impl/namespaced" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/talos-systems/go-retry/retry" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -371,24 +372,30 @@ func (suite *LinkSpecSuite) TestBond() { dummy0Name := suite.uniqueDummyInterface() dummy0 := network.NewLinkSpec(network.NamespaceName, dummy0Name) *dummy0.TypedSpec() = network.LinkSpecSpec{ - Name: dummy0Name, - Type: nethelpers.LinkEther, - Kind: "dummy", - Up: true, - Logical: true, - MasterName: bondName, + Name: dummy0Name, + Type: nethelpers.LinkEther, + Kind: "dummy", + Up: true, + Logical: true, + BondSlave: network.BondSlave{ + MasterName: bondName, + SlaveIndex: 0, + }, ConfigLayer: network.ConfigDefault, } dummy1Name := suite.uniqueDummyInterface() dummy1 := network.NewLinkSpec(network.NamespaceName, dummy1Name) *dummy1.TypedSpec() = network.LinkSpecSpec{ - Name: dummy1Name, - Type: nethelpers.LinkEther, - Kind: "dummy", - Up: true, - Logical: true, - MasterName: bondName, + Name: dummy1Name, + Type: nethelpers.LinkEther, + Kind: "dummy", + Up: true, + Logical: true, + BondSlave: network.BondSlave{ + MasterName: bondName, + SlaveIndex: 1, + }, ConfigLayer: network.ConfigDefault, } @@ -460,7 +467,7 @@ func (suite *LinkSpecSuite) TestBond() { // unslave one of the interfaces _, err = suite.state.UpdateWithConflicts( suite.ctx, dummy0.Metadata(), func(r resource.Resource) error { - r.(*network.LinkSpec).TypedSpec().MasterName = "" + r.(*network.LinkSpec).TypedSpec().BondSlave.MasterName = "" return nil }, @@ -533,12 +540,15 @@ func (suite *LinkSpecSuite) TestBond8023ad() { dummyName := suite.uniqueDummyInterface() dummy := network.NewLinkSpec(network.NamespaceName, dummyName) *dummy.TypedSpec() = network.LinkSpecSpec{ - Name: dummyName, - Type: nethelpers.LinkEther, - Kind: "dummy", - Up: true, - Logical: true, - MasterName: bondName, + Name: dummyName, + Type: nethelpers.LinkEther, + Kind: "dummy", + Up: true, + Logical: true, + BondSlave: network.BondSlave{ + MasterName: bondName, + SlaveIndex: 0, + }, ConfigLayer: network.ConfigDefault, } @@ -743,3 +753,72 @@ func (suite *LinkSpecSuite) TearDownTest() { func TestLinkSpecSuite(t *testing.T) { suite.Run(t, new(LinkSpecSuite)) } + +func TestSortBonds(t *testing.T) { + expectedSlice := []network.LinkSpecSpec{ + { + Name: "A", + }, { + Name: "G", + BondSlave: network.BondSlave{ + MasterName: "A", + SlaveIndex: 0, + }, + }, { + Name: "C", + }, { + Name: "E", + BondSlave: network.BondSlave{ + MasterName: "C", + SlaveIndex: 0, + }, + }, { + Name: "F", + BondSlave: network.BondSlave{ + MasterName: "C", + SlaveIndex: 1, + }, + }, { + Name: "B", + BondSlave: network.BondSlave{ + MasterName: "C", + SlaveIndex: 2, + }, + }, + } + + seed := time.Now().Unix() + rnd := rand.New(rand.NewSource(seed)) + + for i := 0; i < 100; i++ { + res := toResources(expectedSlice) + rnd.Shuffle(len(res), func(i, j int) { res[i], res[j] = res[j], res[i] }) + netctrl.SortBonds(res) + sorted := toSpecs(res) + require.Equal(t, expectedSlice, sorted, "failed with seed %d iteration %d", seed, i) + } +} + +func toResources(slice []network.LinkSpecSpec) []resource.Resource { + result := make([]resource.Resource, 0, len(slice)) + + for _, elem := range slice { + link := network.NewLinkSpec(network.NamespaceName, "bar") + *link.TypedSpec() = elem + + result = append(result, link) + } + + return result +} + +func toSpecs(slice []resource.Resource) []network.LinkSpecSpec { + result := make([]network.LinkSpecSpec, 0, len(slice)) + + for _, elem := range slice { + v := elem.Spec().(network.LinkSpecSpec) //nolint:errcheck + result = append(result, v) + } + + return result +} diff --git a/internal/app/machined/pkg/controllers/network/network.go b/internal/app/machined/pkg/controllers/network/network.go index b3b99d2ee..ce7950704 100644 --- a/internal/app/machined/pkg/controllers/network/network.go +++ b/internal/app/machined/pkg/controllers/network/network.go @@ -11,6 +11,7 @@ import ( networkadapter "github.com/talos-systems/talos/internal/app/machined/pkg/adapters/network" talosconfig "github.com/talos-systems/talos/pkg/machinery/config" "github.com/talos-systems/talos/pkg/machinery/nethelpers" + "github.com/talos-systems/talos/pkg/machinery/ordered" "github.com/talos-systems/talos/pkg/machinery/resources/network" ) @@ -18,8 +19,11 @@ import ( const DefaultRouteMetric = 1024 // SetBondSlave sets the bond slave spec. -func SetBondSlave(link *network.LinkSpecSpec, bondName string) { - link.MasterName = bondName +func SetBondSlave(link *network.LinkSpecSpec, bond ordered.Pair[string, int]) { + link.BondSlave = network.BondSlave{ + MasterName: bond.F1, + SlaveIndex: bond.F2, + } } // SetBondMaster sets the bond master spec. diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go index 76cc7edb7..4c94396bf 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/equinix.go @@ -117,6 +117,8 @@ func (p *EquinixMetal) ParseMetadata(equinixMetadata *Metadata) (*runtime.Platfo return nil, fmt.Errorf("error listing host interfaces: %w", err) } + slaveIndex := 0 + for _, iface := range equinixMetadata.Network.Interfaces { if iface.Bond == "" { continue @@ -136,11 +138,15 @@ func (p *EquinixMetal) ParseMetadata(equinixMetadata *Metadata) (*runtime.Platfo networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{ + Name: hostIf.Name, + Up: true, + BondSlave: network.BondSlave{ + MasterName: bondName, + SlaveIndex: slaveIndex, + }, ConfigLayer: network.ConfigPlatform, - Name: hostIf.Name, - Up: true, - MasterName: bondName, }) + slaveIndex++ break } @@ -154,8 +160,12 @@ func (p *EquinixMetal) ParseMetadata(equinixMetadata *Metadata) (*runtime.Platfo ConfigLayer: network.ConfigPlatform, Name: iface.Name, Up: true, - MasterName: bondName, + BondSlave: network.BondSlave{ + MasterName: bondName, + SlaveIndex: slaveIndex, + }, }) + slaveIndex++ } } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/testdata/expected.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/testdata/expected.yaml index 1ea7a966d..b53883398 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/testdata/expected.yaml +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal/testdata/expected.yaml @@ -33,6 +33,7 @@ links: kind: "" type: netrom masterName: bond0 + slaveIndex: 1 layer: platform - name: bond0 logical: true diff --git a/pkg/machinery/ordered/ordered.go b/pkg/machinery/ordered/ordered.go new file mode 100644 index 000000000..ab03f48d3 --- /dev/null +++ b/pkg/machinery/ordered/ordered.go @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package ordered + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +type Ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string +} + +// Pair is two element tuple of ordered values. +type Pair[T1, T2 Ordered] struct { + F1 T1 + F2 T2 +} + +// MakePair creates a new Pair. +func MakePair[T1, T2 Ordered](v1 T1, v2 T2) Pair[T1, T2] { + return Pair[T1, T2]{ + F1: v1, + F2: v2, + } +} + +// Compare returns an integer comparing two pairs in natural order. +// The result will be 0 if p == other, -1 if p < other, and +1 if p > other. +func (p Pair[T1, T2]) Compare(other Pair[T1, T2]) int { + if result := cmp(p.F1, other.F1); result != 0 { + return result + } + + return cmp(p.F2, other.F2) +} + +// MoreThan checks if current pair is bigger than the other. +func (p Pair[T1, T2]) MoreThan(other Pair[T1, T2]) bool { + return p.Compare(other) == 1 +} + +// LessThan checks if current pair is lesser than the other. +func (p Pair[T1, T2]) LessThan(other Pair[T1, T2]) bool { + return p.Compare(other) == -1 +} + +// Equal checks if current pair is equal to the other. +func (p Pair[T1, T2]) Equal(other Pair[T1, T2]) bool { + return p.Compare(other) == 0 +} + +func cmp[T Ordered](a, b T) int { + switch { + case a == b: + return 0 + case a < b: + return -1 + default: + return +1 + } +} diff --git a/pkg/machinery/ordered/ordered_test.go b/pkg/machinery/ordered/ordered_test.go new file mode 100644 index 000000000..963fbf338 --- /dev/null +++ b/pkg/machinery/ordered/ordered_test.go @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package ordered_test + +import ( + "math" + "math/rand" + "sort" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/talos-systems/talos/pkg/machinery/ordered" +) + +func TestTriple(t *testing.T) { + t.Parallel() + + expectedSlice := []ordered.Triple[int, string, float64]{ + ordered.MakeTriple(math.MinInt64, "Alpha", 69.0), + ordered.MakeTriple(-200, "Alpha", 69.0), + ordered.MakeTriple(-200, "Beta", -69.0), + ordered.MakeTriple(-200, "Beta", 69.0), + ordered.MakeTriple(1, "", 69.0), + ordered.MakeTriple(1, "Alpha", 67.0), + ordered.MakeTriple(1, "Alpha", 68.0), + ordered.MakeTriple(10, "Alpha", 68.0), + ordered.MakeTriple(10, "Beta", 68.0), + ordered.MakeTriple(math.MaxInt64, "", 69.0), + } + + seed := time.Now().Unix() + rnd := rand.New(rand.NewSource(seed)) + + for i := 0; i < 1000; i++ { + a := append([]ordered.Triple[int, string, float64](nil), expectedSlice...) + rnd.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) + sort.Slice(a, func(i, j int) bool { + return a[i].LessThan(a[j]) + }) + require.Equal(t, expectedSlice, a, "failed with seed %d iteration %d", seed, i) + } +} diff --git a/pkg/machinery/ordered/triple.go b/pkg/machinery/ordered/triple.go new file mode 100644 index 000000000..8b9bad5d3 --- /dev/null +++ b/pkg/machinery/ordered/triple.go @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package ordered + +// Triple is three element tuple of ordered values. +type Triple[T1, T2, T3 Ordered] struct { + V1 T1 + V2 T2 + V3 T3 +} + +// MakeTriple creates a new Triple. +func MakeTriple[T1, T2, T3 Ordered](v1 T1, v2 T2, v3 T3) Triple[T1, T2, T3] { + return Triple[T1, T2, T3]{ + V1: v1, + V2: v2, + V3: v3, + } +} + +// Compare returns an integer comparing two triples in natural order. +// The result will be 0 if t == other, -1 if t < other, and +1 if t > other. +func (t Triple[T1, T2, T3]) Compare(other Triple[T1, T2, T3]) int { + if result := cmp(t.V1, other.V1); result != 0 { + return result + } else if result := cmp(t.V2, other.V2); result != 0 { + return result + } + + return cmp(t.V3, other.V3) +} + +// MoreThan checks if current triple is bigger than the other. +func (t Triple[T1, T2, T3]) MoreThan(other Triple[T1, T2, T3]) bool { + return t.Compare(other) == 1 +} + +// LessThan checks if current triple is lesser than the other. +func (t Triple[T1, T2, T3]) LessThan(other Triple[T1, T2, T3]) bool { + return t.Compare(other) == -1 +} + +// Equal checks if current triple is equal to the other. +func (t Triple[T1, T2, T3]) Equal(other Triple[T1, T2, T3]) bool { + return t.Compare(other) == 0 +} diff --git a/pkg/machinery/resources/network/link_spec.go b/pkg/machinery/resources/network/link_spec.go index 0be3f4be5..0f1313de2 100644 --- a/pkg/machinery/resources/network/link_spec.go +++ b/pkg/machinery/resources/network/link_spec.go @@ -43,7 +43,7 @@ type LinkSpecSpec struct { ParentName string `yaml:"parentName,omitempty"` // MasterName indicates master link for enslaved bonded interfaces. - MasterName string `yaml:"masterName,omitempty"` + BondSlave BondSlave `yaml:",omitempty,inline"` // These structures are present depending on "Kind" for Logical intefaces. VLAN VLANSpec `yaml:"vlan,omitempty"` @@ -54,6 +54,15 @@ type LinkSpecSpec struct { ConfigLayer ConfigLayer `yaml:"layer"` } +// BondSlave contains a bond's master name and slave index. +type BondSlave struct { + // MasterName indicates master link for enslaved bonded interfaces. + MasterName string `yaml:"masterName,omitempty"` + + // SlaveIndex indicates a slave's position in bond. + SlaveIndex int `yaml:"slaveIndex,omitempty"` +} + // DeepCopy generates a deep copy of LinkSpecSpec. func (spec LinkSpecSpec) DeepCopy() LinkSpecSpec { cp := spec @@ -72,51 +81,20 @@ func (spec LinkSpecSpec) DeepCopy() LinkSpecSpec { return cp } -var ( - zeroVLAN VLANSpec - zeroBondMaster BondMasterSpec -) - // Merge with other, overwriting fields from other if set. // //nolint:gocyclo func (spec *LinkSpecSpec) Merge(other *LinkSpecSpec) error { // prefer Logical, as it is defined for bonds/vlans, etc. - if other.Logical { - spec.Logical = other.Logical - } - - if other.Up { - spec.Up = other.Up - } - - if other.MTU != 0 { - spec.MTU = other.MTU - } - - if other.Kind != "" { - spec.Kind = other.Kind - } - - if other.Type != 0 { - spec.Type = other.Type - } - - if other.ParentName != "" { - spec.ParentName = other.ParentName - } - - if other.MasterName != "" { - spec.MasterName = other.MasterName - } - - if other.VLAN != zeroVLAN { - spec.VLAN = other.VLAN - } - - if other.BondMaster != zeroBondMaster { - spec.BondMaster = other.BondMaster - } + updateIfNotZero(&spec.Logical, other.Logical) + updateIfNotZero(&spec.Up, other.Up) + updateIfNotZero(&spec.MTU, other.MTU) + updateIfNotZero(&spec.Kind, other.Kind) + updateIfNotZero(&spec.Type, other.Type) + updateIfNotZero(&spec.ParentName, other.ParentName) + updateIfNotZero(&spec.BondSlave, other.BondSlave) + updateIfNotZero(&spec.VLAN, other.VLAN) + updateIfNotZero(&spec.BondMaster, other.BondMaster) // Wireguard config should be able to apply non-zero values in earlier config layers which may be zero values in later layers. // Thus, we handle each Wireguard configuration value discretely. @@ -133,6 +111,13 @@ func (spec *LinkSpecSpec) Merge(other *LinkSpecSpec) error { return nil } +func updateIfNotZero[T comparable](target *T, source T) { + var zero T + if source != zero { + *target = source + } +} + // NewLinkSpec initializes a LinkSpec resource. func NewLinkSpec(namespace resource.Namespace, id resource.ID) *LinkSpec { return typed.NewResource[LinkSpecSpec, LinkSpecRD]( diff --git a/pkg/machinery/resources/network/link_spec_test.go b/pkg/machinery/resources/network/link_spec_test.go index 71a6354b5..a2189068b 100644 --- a/pkg/machinery/resources/network/link_spec_test.go +++ b/pkg/machinery/resources/network/link_spec_test.go @@ -26,7 +26,10 @@ func TestLinkSpecMarshalYAML(t *testing.T) { Kind: "eth", Type: nethelpers.LinkEther, ParentName: "eth1", - MasterName: "bond0", + BondSlave: network.BondSlave{ + MasterName: "bond0", + SlaveIndex: 0, + }, VLAN: network.VLANSpec{ VID: 25, Protocol: nethelpers.VLANProtocol8021AD,