mirror of
https://github.com/siderolabs/talos.git
synced 2025-12-18 07:51:56 +01:00
feat: add bond slaves ordering
Before this change, we didn't preserve bonded interfaces ordering, which caused problems in some scenarios. Fix this by remembering their position in the original config. Fixes #5207. Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
This commit is contained in:
parent
03ef62ad8b
commit
867d38f28f
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ links:
|
||||
kind: ""
|
||||
type: netrom
|
||||
masterName: bond0
|
||||
slaveIndex: 1
|
||||
layer: platform
|
||||
- name: bond0
|
||||
logical: true
|
||||
|
||||
61
pkg/machinery/ordered/ordered.go
Normal file
61
pkg/machinery/ordered/ordered.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
46
pkg/machinery/ordered/ordered_test.go
Normal file
46
pkg/machinery/ordered/ordered_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
48
pkg/machinery/ordered/triple.go
Normal file
48
pkg/machinery/ordered/triple.go
Normal file
@ -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
|
||||
}
|
||||
@ -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](
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user