mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
fix: correctly map link names/aliases when using VIP operator
Also fix/clean up tests, and add more tests specific to link aliases. Fixes #10402 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
7c4e47c0c0
commit
d45eaeb74c
@ -205,7 +205,7 @@ func (ctrl *AddressConfigController) parseCmdline(logger *zap.Logger) (addresses
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, network.NewEmptyLinkResolver())
|
||||
if err != nil {
|
||||
logger.Info("ignoring cmdline parse failure", zap.Error(err))
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/siderolabs/gen/pair/ordered"
|
||||
"github.com/siderolabs/gen/xslices"
|
||||
"github.com/siderolabs/go-pointer"
|
||||
"github.com/siderolabs/go-procfs/procfs"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
@ -41,30 +43,6 @@ type CmdlineLinkConfig struct {
|
||||
DHCP bool
|
||||
}
|
||||
|
||||
func (linkConfig *CmdlineLinkConfig) resolveLinkName() error {
|
||||
if !strings.HasPrefix(linkConfig.LinkName, "enx") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ifaces, _ := net.Interfaces() //nolint:errcheck // ignoring error here as ifaces will be empty
|
||||
mac := strings.ToLower(strings.TrimPrefix(linkConfig.LinkName, "enx"))
|
||||
|
||||
for _, iface := range ifaces {
|
||||
ifaceMAC := strings.ReplaceAll(iface.HardwareAddr.String(), ":", "")
|
||||
if ifaceMAC == mac {
|
||||
linkConfig.LinkName = iface.Name
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(linkConfig.LinkName, "enx") {
|
||||
return fmt.Errorf("cmdline device parse failure: interface by MAC not found %s", linkConfig.LinkName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitIPArgument splits the `ip=` kernel argument honoring the IPv6 addresses in square brackets.
|
||||
func splitIPArgument(val string) []string {
|
||||
var (
|
||||
@ -98,7 +76,7 @@ const autoconfDHCP = "dhcp"
|
||||
// ParseCmdlineNetwork parses `ip=` and Talos specific kernel cmdline argument producing all the available configuration options.
|
||||
//
|
||||
//nolint:gocyclo,cyclop
|
||||
func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
func ParseCmdlineNetwork(cmdline *procfs.Cmdline, linkNameResolver *network.LinkResolver) (CmdlineNetworking, error) {
|
||||
var (
|
||||
settings CmdlineNetworking
|
||||
err error
|
||||
@ -113,7 +91,7 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
|
||||
ignoreInterfaces := cmdline.Get(constants.KernelParamNetworkInterfaceIgnore)
|
||||
for i := 0; ignoreInterfaces.Get(i) != nil; i++ {
|
||||
settings.IgnoreInterfaces = append(settings.IgnoreInterfaces, *ignoreInterfaces.Get(i))
|
||||
settings.IgnoreInterfaces = append(settings.IgnoreInterfaces, linkNameResolver.Resolve(*ignoreInterfaces.Get(i)))
|
||||
}
|
||||
|
||||
// standard ip=
|
||||
@ -135,14 +113,10 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
case len(fields) == 2 && fields[1] == autoconfDHCP:
|
||||
// ip=<device>:dhcp
|
||||
linkConfig := CmdlineLinkConfig{
|
||||
LinkName: fields[0],
|
||||
LinkName: linkNameResolver.Resolve(fields[0]),
|
||||
DHCP: true,
|
||||
}
|
||||
|
||||
if err = linkConfig.resolveLinkName(); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
linkSpecSpecs = append(linkSpecSpecs, network.LinkSpecSpec{
|
||||
Name: linkConfig.LinkName,
|
||||
Up: true,
|
||||
@ -196,7 +170,7 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
settings.Hostname = fields[4]
|
||||
}
|
||||
case 5:
|
||||
linkConfig.LinkName = fields[5]
|
||||
linkConfig.LinkName = linkNameResolver.Resolve(fields[5])
|
||||
case 6:
|
||||
if fields[6] == autoconfDHCP {
|
||||
linkConfig.DHCP = true
|
||||
@ -222,11 +196,6 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// resolve enx* (with MAC address) to the actual interface name
|
||||
if err = linkConfig.resolveLinkName(); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
// if interface name is not set, pick the first non-loopback interface
|
||||
if linkConfig.LinkName == "" {
|
||||
ifaces, _ := net.Interfaces() //nolint:errcheck // ignoring error here as ifaces will be empty
|
||||
@ -297,6 +266,9 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// resolve bond slave names via aliases as needed
|
||||
bondSlaves = xslices.Map(bondSlaves, linkNameResolver.Resolve)
|
||||
|
||||
bondLinkSpec := network.LinkSpecSpec{
|
||||
Name: bondName,
|
||||
Up: true,
|
||||
@ -328,6 +300,7 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
linkSpecSpecs = append(linkSpecSpecs, slaveLinkSpec)
|
||||
}
|
||||
}
|
||||
|
||||
// dracut vlan=<vlanname>:<phydevice>
|
||||
vlanSettings := cmdline.Get(constants.KernelParamVlan).First()
|
||||
if vlanSettings != nil {
|
||||
@ -361,11 +334,13 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
|
||||
vlanName = nethelpers.VLANLinkName(phyDevice, uint16(vlanID))
|
||||
|
||||
phyDevice = linkNameResolver.Resolve(phyDevice)
|
||||
|
||||
linkSpecUpdated := false
|
||||
|
||||
for i, linkSpec := range linkSpecSpecs {
|
||||
if linkSpec.Name == vlanName {
|
||||
vlanLink(&linkSpecSpecs[i], phyDevice, vlanSpec)
|
||||
vlanLink(&linkSpecSpecs[i], vlanName, phyDevice, vlanSpec)
|
||||
|
||||
linkSpecUpdated = true
|
||||
|
||||
@ -380,7 +355,7 @@ func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
||||
ConfigLayer: network.ConfigCmdline,
|
||||
}
|
||||
|
||||
vlanLink(&linkSpec, phyDevice, vlanSpec)
|
||||
vlanLink(&linkSpec, vlanName, phyDevice, vlanSpec)
|
||||
|
||||
linkSpecSpecs = append(linkSpecSpecs, linkSpec)
|
||||
}
|
||||
@ -509,8 +484,7 @@ func parseBondOptions(options string) (v1alpha1.Bond, error) {
|
||||
}
|
||||
|
||||
if useCarrier == 1 {
|
||||
val := []bool{true}
|
||||
bond.BondUseCarrier = &val[0]
|
||||
bond.BondUseCarrier = pointer.To(true)
|
||||
}
|
||||
default:
|
||||
return bond, fmt.Errorf("unknown bond option: %s", optionPair[0])
|
||||
|
||||
@ -7,24 +7,24 @@ package network_test
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/siderolabs/go-procfs/procfs"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
|
||||
netconfig "github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
type CmdlineSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
func TestCmdlineParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
func (suite *CmdlineSuite) TestParse() {
|
||||
ifaces, _ := net.Interfaces() //nolint:errcheck // ignoring error here as ifaces will be empty
|
||||
|
||||
slices.SortFunc(ifaces, func(a, b net.Interface) int { return cmp.Compare(a.Name, b.Name) })
|
||||
@ -44,7 +44,7 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
defaultBondSettings := network.CmdlineNetworking{
|
||||
NetworkLinkSpecs: []netconfig.LinkSpecSpec{
|
||||
{
|
||||
Name: "bond0",
|
||||
Name: "bond1",
|
||||
Kind: "bond",
|
||||
Type: nethelpers.LinkEther,
|
||||
Logical: true,
|
||||
@ -66,7 +66,7 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
Logical: false,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
BondSlave: netconfig.BondSlave{
|
||||
MasterName: "bond0",
|
||||
MasterName: "bond1",
|
||||
SlaveIndex: 0,
|
||||
},
|
||||
},
|
||||
@ -76,7 +76,7 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
Logical: false,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
BondSlave: netconfig.BondSlave{
|
||||
MasterName: "bond0",
|
||||
MasterName: "bond1",
|
||||
SlaveIndex: 1,
|
||||
},
|
||||
},
|
||||
@ -140,7 +140,22 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
name: "no iface by mac address",
|
||||
cmdline: "ip=172.20.0.2::172.20.0.1:255.255.255.0::enx001122aabbcc",
|
||||
|
||||
expectedError: "cmdline device parse failure: interface by MAC not found enx001122aabbcc",
|
||||
expectedSettings: network.CmdlineNetworking{
|
||||
LinkConfigs: []network.CmdlineLinkConfig{
|
||||
{
|
||||
Address: netip.MustParsePrefix("172.20.0.2/24"),
|
||||
Gateway: netip.MustParseAddr("172.20.0.1"),
|
||||
LinkName: "enx001122aabbcc",
|
||||
},
|
||||
},
|
||||
NetworkLinkSpecs: []netconfig.LinkSpecSpec{
|
||||
{
|
||||
Name: "enx001122aabbcc",
|
||||
Up: true,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complete",
|
||||
@ -293,20 +308,20 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
},
|
||||
{
|
||||
name: "ignore interfaces",
|
||||
cmdline: "talos.network.interface.ignore=eth2 talos.network.interface.ignore=eth3",
|
||||
cmdline: "talos.network.interface.ignore=eth2 talos.network.interface.ignore=eth3 talos.network.interface.ignore=enxa",
|
||||
|
||||
expectedSettings: network.CmdlineNetworking{
|
||||
IgnoreInterfaces: []string{"eth2", "eth3"},
|
||||
IgnoreInterfaces: []string{"eth2", "eth3", "eth31"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bond with no interfaces and no options set",
|
||||
cmdline: "bond=bond0",
|
||||
cmdline: "bond=bond1",
|
||||
expectedSettings: defaultBondSettings,
|
||||
},
|
||||
{
|
||||
name: "bond with no interfaces and empty options set",
|
||||
cmdline: "bond=bond0:::",
|
||||
cmdline: "bond=bond1:::",
|
||||
expectedSettings: defaultBondSettings,
|
||||
},
|
||||
{
|
||||
@ -314,22 +329,7 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
cmdline: "bond=bond1:eth3,eth4",
|
||||
expectedSettings: network.CmdlineNetworking{
|
||||
NetworkLinkSpecs: []netconfig.LinkSpecSpec{
|
||||
{
|
||||
Name: "bond1",
|
||||
Kind: "bond",
|
||||
Type: nethelpers.LinkEther,
|
||||
Logical: true,
|
||||
Up: true,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
BondMaster: netconfig.BondMasterSpec{
|
||||
ResendIGMP: 1,
|
||||
LPInterval: 1,
|
||||
PacketsPerSlave: 1,
|
||||
NumPeerNotif: 1,
|
||||
TLBDynamicLB: 1,
|
||||
UseCarrier: true,
|
||||
},
|
||||
},
|
||||
defaultBondSettings.NetworkLinkSpecs[0],
|
||||
{
|
||||
Name: "eth3",
|
||||
Up: true,
|
||||
@ -353,6 +353,35 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bond with aliased interfaces",
|
||||
cmdline: "bond=bond1:enxa,enxb",
|
||||
expectedSettings: network.CmdlineNetworking{
|
||||
NetworkLinkSpecs: []netconfig.LinkSpecSpec{
|
||||
defaultBondSettings.NetworkLinkSpecs[0],
|
||||
{
|
||||
Name: "eth31",
|
||||
Up: true,
|
||||
Logical: false,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
BondSlave: netconfig.BondSlave{
|
||||
MasterName: "bond1",
|
||||
SlaveIndex: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "eth32",
|
||||
Up: true,
|
||||
Logical: false,
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
BondSlave: netconfig.BondSlave{
|
||||
MasterName: "bond1",
|
||||
SlaveIndex: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bond with interfaces, options and mtu set",
|
||||
cmdline: "bond=bond1:eth3,eth4:mode=802.3ad,xmit_hash_policy=layer2+3:1450",
|
||||
@ -498,6 +527,27 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vlan configuration with alias link name",
|
||||
cmdline: "vlan=vlan4095:enxa",
|
||||
expectedSettings: network.CmdlineNetworking{
|
||||
NetworkLinkSpecs: []netconfig.LinkSpecSpec{
|
||||
{
|
||||
Name: "enxa.4095",
|
||||
Logical: true,
|
||||
Up: true,
|
||||
Kind: netconfig.LinkKindVLAN,
|
||||
Type: nethelpers.LinkEther,
|
||||
ParentName: "eth31",
|
||||
ConfigLayer: netconfig.ConfigCmdline,
|
||||
VLAN: netconfig.VLANSpec{
|
||||
VID: 4095,
|
||||
Protocol: nethelpers.VLANProtocol8021Q,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vlan configuration with invalid vlan ID 4096",
|
||||
cmdline: "vlan=eth1.4096:eth1",
|
||||
@ -548,21 +598,31 @@ func (suite *CmdlineSuite) TestParse() {
|
||||
},
|
||||
},
|
||||
} {
|
||||
suite.Run(test.name, func() {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmdline := procfs.NewCmdline(test.cmdline)
|
||||
|
||||
settings, err := network.ParseCmdlineNetwork(cmdline)
|
||||
link1 := netconfig.NewLinkStatus(netconfig.NamespaceName, "eth31")
|
||||
link1.TypedSpec().Alias = "enxa"
|
||||
link2 := netconfig.NewLinkStatus(netconfig.NamespaceName, "eth32")
|
||||
link2.TypedSpec().Alias = "enxb"
|
||||
|
||||
settings, err := network.ParseCmdlineNetwork(
|
||||
cmdline,
|
||||
netconfig.NewLinkResolver(
|
||||
func() iter.Seq[*netconfig.LinkStatus] {
|
||||
return slices.Values([]*netconfig.LinkStatus{link1, link2})
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if test.expectedError != "" {
|
||||
suite.Assert().EqualError(err, test.expectedError)
|
||||
assert.EqualError(t, err, test.expectedError)
|
||||
} else {
|
||||
suite.Assert().NoError(err)
|
||||
suite.Assert().Equal(test.expectedSettings, settings)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedSettings, settings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdlineSuite(t *testing.T) {
|
||||
suite.Run(t, new(CmdlineSuite))
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -19,9 +18,9 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/siderolabs/gen/xslices"
|
||||
"github.com/siderolabs/gen/xtesting/must"
|
||||
"github.com/siderolabs/go-retry/retry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
@ -46,7 +45,7 @@ func expectedDNSRunners(port string) []resource.ID {
|
||||
|
||||
func (suite *DNSServer) TestResolving() {
|
||||
dnsSlice := []string{"8.8.8.8", "1.1.1.1"}
|
||||
port := must.Value(getDynamicPort())(suite.T())
|
||||
port := getDynamicPort(suite.T())
|
||||
|
||||
cfg := network.NewHostDNSConfig(network.HostDNSConfigID)
|
||||
cfg.TypedSpec().Enabled = true
|
||||
@ -104,7 +103,7 @@ func (suite *DNSServer) TestResolving() {
|
||||
|
||||
func (suite *DNSServer) TestSetupStartStop() {
|
||||
dnsSlice := []string{"8.8.8.8", "1.1.1.1"}
|
||||
port := must.Value(getDynamicPort())(suite.T())
|
||||
port := getDynamicPort(suite.T())
|
||||
|
||||
resolverSpec := network.NewResolverStatus(network.NamespaceName, network.ResolverID)
|
||||
resolverSpec.TypedSpec().DNSServers = xslices.Map(dnsSlice, netip.MustParseAddr)
|
||||
@ -148,7 +147,7 @@ func (suite *DNSServer) TestSetupStartStop() {
|
||||
}
|
||||
|
||||
func (suite *DNSServer) TestResolveMembers() {
|
||||
port := must.Value(getDynamicPort())(suite.T())
|
||||
port := getDynamicPort(suite.T())
|
||||
|
||||
const (
|
||||
id = "talos-default-controlplane-1"
|
||||
@ -264,22 +263,20 @@ func TestDNSServer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func getDynamicPort() (string, error) {
|
||||
func getDynamicPort(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
closeOnce := sync.OnceValue(l.Close)
|
||||
addr := l.Addr().String()
|
||||
|
||||
defer closeOnce() //nolint:errcheck
|
||||
require.NoError(t, l.Close())
|
||||
|
||||
_, port, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return port, closeOnce()
|
||||
return port
|
||||
}
|
||||
|
||||
func makeAddrs(port string) []netip.AddrPort {
|
||||
@ -293,7 +290,7 @@ type DNSUpstreams struct {
|
||||
}
|
||||
|
||||
func (suite *DNSUpstreams) TestOrder() {
|
||||
port := must.Value(getDynamicPort())(suite.T())
|
||||
port := getDynamicPort(suite.T())
|
||||
|
||||
cfg := network.NewHostDNSConfig(network.HostDNSConfigID)
|
||||
cfg.TypedSpec().Enabled = true
|
||||
|
||||
@ -230,7 +230,7 @@ func (ctrl *HostnameConfigController) parseCmdline(logger *zap.Logger) (spec net
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, network.NewEmptyLinkResolver())
|
||||
if err != nil {
|
||||
logger.Warn("ignoring error", zap.Error(err))
|
||||
|
||||
|
||||
@ -92,6 +92,13 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
linkStatuses, err := safe.ReaderListAll[*network.LinkStatus](ctx, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link statuses: %w", err)
|
||||
}
|
||||
|
||||
linkNameResolver := network.NewLinkResolver(linkStatuses.All)
|
||||
|
||||
// bring up loopback interface
|
||||
{
|
||||
var ids []string
|
||||
@ -113,7 +120,7 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
|
||||
}
|
||||
|
||||
// parse kernel cmdline for the interface name
|
||||
cmdlineLinks, cmdlineIgnored := ctrl.parseCmdline(logger)
|
||||
cmdlineLinks, cmdlineIgnored := ctrl.parseCmdline(logger, linkNameResolver)
|
||||
for _, cmdlineLink := range cmdlineLinks {
|
||||
if cmdlineLink.Name != "" {
|
||||
if _, ignored := ignoredInterfaces[cmdlineLink.Name]; !ignored {
|
||||
@ -133,7 +140,7 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
|
||||
|
||||
// parse machine configuration for link specs
|
||||
if len(devices) > 0 {
|
||||
links := ctrl.processDevicesConfiguration(logger, devices)
|
||||
links := ctrl.processDevicesConfiguration(logger, devices, linkNameResolver)
|
||||
|
||||
var ids []string
|
||||
|
||||
@ -178,27 +185,10 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.NamespaceName, network.LinkStatusType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link statuses: %w", err)
|
||||
}
|
||||
|
||||
outer:
|
||||
for _, item := range list.Items {
|
||||
linkStatus := item.(*network.LinkStatus) //nolint:forcetypeassert
|
||||
|
||||
if _, configured := configuredLinks[linkStatus.Metadata().ID()]; configured {
|
||||
continue
|
||||
}
|
||||
|
||||
if linkStatus.TypedSpec().Alias != "" {
|
||||
if _, configured := configuredLinks[linkStatus.TypedSpec().Alias]; configured {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, altName := range linkStatus.TypedSpec().AltNames {
|
||||
if _, configured := configuredLinks[altName]; configured {
|
||||
for linkStatus := range linkStatuses.All() {
|
||||
for linkAlias := range network.AllLinkNames(linkStatus) {
|
||||
if _, configured := configuredLinks[linkAlias]; configured {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
@ -223,13 +213,13 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
// list links for cleanup
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined))
|
||||
// list link specs for cleanup
|
||||
linkSpecs, err := safe.ReaderList[*network.LinkSpec](ctx, r, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range list.Items {
|
||||
for res := range linkSpecs.All() {
|
||||
if res.Metadata().Owner() != ctrl.Name() {
|
||||
// skip specs created by other controllers
|
||||
continue
|
||||
@ -271,12 +261,12 @@ func (ctrl *LinkConfigController) apply(ctx context.Context, r controller.Runtim
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (ctrl *LinkConfigController) parseCmdline(logger *zap.Logger) ([]network.LinkSpecSpec, []string) {
|
||||
func (ctrl *LinkConfigController) parseCmdline(logger *zap.Logger, linkNameResolver *network.LinkResolver) ([]network.LinkSpecSpec, []string) {
|
||||
if ctrl.Cmdline == nil {
|
||||
return []network.LinkSpecSpec{}, nil
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, linkNameResolver)
|
||||
if err != nil {
|
||||
logger.Info("ignoring error", zap.Error(err))
|
||||
|
||||
@ -287,7 +277,7 @@ func (ctrl *LinkConfigController) parseCmdline(logger *zap.Logger) ([]network.Li
|
||||
}
|
||||
|
||||
//nolint:gocyclo,cyclop
|
||||
func (ctrl *LinkConfigController) processDevicesConfiguration(logger *zap.Logger, devices []talosconfig.Device) []network.LinkSpecSpec {
|
||||
func (ctrl *LinkConfigController) processDevicesConfiguration(logger *zap.Logger, devices []talosconfig.Device, linkNameResolver *network.LinkResolver) []network.LinkSpecSpec {
|
||||
// scan for the bonds or bridges
|
||||
bondedLinks := map[string]ordered.Pair[string, int]{} // mapping physical interface -> bond interface
|
||||
bridgedLinks := map[string]string{} // mapping physical interface -> bridge interface
|
||||
@ -297,50 +287,58 @@ func (ctrl *LinkConfigController) processDevicesConfiguration(logger *zap.Logger
|
||||
continue
|
||||
}
|
||||
|
||||
deviceInterface := linkNameResolver.Resolve(device.Interface())
|
||||
|
||||
if device.Bond() != nil {
|
||||
for idx, linkName := range device.Bond().Interfaces() {
|
||||
if bondData, exists := bondedLinks[linkName]; exists && bondData.F1 != device.Interface() {
|
||||
linkName = linkNameResolver.Resolve(linkName)
|
||||
|
||||
if bondData, exists := bondedLinks[linkName]; exists && bondData.F1 != deviceInterface {
|
||||
logger.Sugar().Warnf("link %q is included in both bonds %q and %q", linkName,
|
||||
bondData.F1, device.Interface())
|
||||
bondData.F1, deviceInterface)
|
||||
}
|
||||
|
||||
if bridgeName, exists := bridgedLinks[linkName]; exists {
|
||||
logger.Sugar().Warnf("link %q is included in both bond %q and bridge %q", linkName,
|
||||
bridgeName, device.Interface())
|
||||
bridgeName, deviceInterface)
|
||||
}
|
||||
|
||||
bondedLinks[linkName] = ordered.MakePair(device.Interface(), idx)
|
||||
bondedLinks[linkName] = ordered.MakePair(deviceInterface, idx)
|
||||
}
|
||||
}
|
||||
|
||||
if device.Bridge() != nil {
|
||||
for _, linkName := range device.Bridge().Interfaces() {
|
||||
if bridgeName, exists := bridgedLinks[linkName]; exists && bridgeName != device.Interface() {
|
||||
linkName = linkNameResolver.Resolve(linkName)
|
||||
|
||||
if bridgeName, exists := bridgedLinks[linkName]; exists && bridgeName != deviceInterface {
|
||||
logger.Sugar().Warnf("link %q is included in both bridges %q and %q", linkName,
|
||||
bridgeName, device.Interface())
|
||||
bridgeName, deviceInterface)
|
||||
}
|
||||
|
||||
if bondData, exists := bondedLinks[linkName]; exists {
|
||||
logger.Sugar().Warnf("link %q is included in both bond %q and bridge %q", linkName,
|
||||
bondData.F1, device.Interface())
|
||||
bondData.F1, deviceInterface)
|
||||
}
|
||||
|
||||
bridgedLinks[linkName] = device.Interface()
|
||||
bridgedLinks[linkName] = deviceInterface
|
||||
}
|
||||
}
|
||||
|
||||
if device.BridgePort() != nil {
|
||||
if bridgeName, exists := bridgedLinks[device.Interface()]; exists && bridgeName != device.BridgePort().Master() {
|
||||
logger.Sugar().Warnf("link %q is included in both bridges %q and %q", device.Interface(),
|
||||
bridgeName, device.BridgePort().Master())
|
||||
bridgePortMaster := linkNameResolver.Resolve(device.BridgePort().Master())
|
||||
|
||||
if bridgeName, exists := bridgedLinks[deviceInterface]; exists && bridgeName != bridgePortMaster {
|
||||
logger.Sugar().Warnf("link %q is included in both bridges %q and %q", deviceInterface,
|
||||
bridgeName, bridgePortMaster)
|
||||
}
|
||||
|
||||
if bondData, exists := bondedLinks[device.Interface()]; exists {
|
||||
logger.Sugar().Warnf("link %q is included into both bond %q and bridge %q", device.Interface(),
|
||||
bondData.F1, device.BridgePort().Master())
|
||||
if bondData, exists := bondedLinks[deviceInterface]; exists {
|
||||
logger.Sugar().Warnf("link %q is included into both bond %q and bridge %q", deviceInterface,
|
||||
bondData.F1, bridgePortMaster)
|
||||
}
|
||||
|
||||
bridgedLinks[device.Interface()] = device.BridgePort().Master()
|
||||
bridgedLinks[deviceInterface] = bridgePortMaster
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,50 +349,51 @@ func (ctrl *LinkConfigController) processDevicesConfiguration(logger *zap.Logger
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := linkMap[device.Interface()]; !exists {
|
||||
linkMap[device.Interface()] = &network.LinkSpecSpec{
|
||||
Name: device.Interface(),
|
||||
deviceInterface := linkNameResolver.Resolve(device.Interface())
|
||||
|
||||
if _, exists := linkMap[deviceInterface]; !exists {
|
||||
linkMap[deviceInterface] = &network.LinkSpecSpec{
|
||||
Name: deviceInterface,
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
if device.MTU() != 0 {
|
||||
linkMap[device.Interface()].MTU = uint32(device.MTU())
|
||||
linkMap[deviceInterface].MTU = uint32(device.MTU())
|
||||
}
|
||||
|
||||
if device.Bond() != nil {
|
||||
if err := SetBondMaster(linkMap[device.Interface()], device.Bond()); err != nil {
|
||||
if err := SetBondMaster(linkMap[deviceInterface], device.Bond()); err != nil {
|
||||
logger.Error("error parsing bond config", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if device.Bridge() != nil {
|
||||
if err := SetBridgeMaster(linkMap[device.Interface()], device.Bridge()); err != nil {
|
||||
if err := SetBridgeMaster(linkMap[deviceInterface], device.Bridge()); err != nil {
|
||||
logger.Error("error parsing bridge config", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if device.WireguardConfig() != nil {
|
||||
if err := wireguardLink(linkMap[device.Interface()], device.WireguardConfig()); err != nil {
|
||||
if err := wireguardLink(linkMap[deviceInterface], device.WireguardConfig()); err != nil {
|
||||
logger.Error("error parsing wireguard config", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if device.Dummy() {
|
||||
dummyLink(linkMap[device.Interface()])
|
||||
dummyLink(linkMap[deviceInterface])
|
||||
}
|
||||
|
||||
for _, vlan := range device.Vlans() {
|
||||
vlanName := nethelpers.VLANLinkName(device.Interface(), vlan.ID())
|
||||
vlanName := nethelpers.VLANLinkName(device.Interface(), vlan.ID()) // [NOTE]: VLAN uses the original interface name (before resolving aliases)
|
||||
|
||||
linkMap[vlanName] = &network.LinkSpecSpec{
|
||||
Name: device.Interface(),
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
vlanLink(linkMap[vlanName], device.Interface(), vlan)
|
||||
vlanLink(linkMap[vlanName], vlanName, deviceInterface, vlan)
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,8 +429,8 @@ type vlaner interface {
|
||||
MTU() uint32
|
||||
}
|
||||
|
||||
func vlanLink(link *network.LinkSpecSpec, linkName string, vlan vlaner) {
|
||||
link.Name = nethelpers.VLANLinkName(linkName, vlan.ID())
|
||||
func vlanLink(link *network.LinkSpecSpec, vlanName, linkName string, vlan vlaner) {
|
||||
link.Name = vlanName
|
||||
link.Logical = true
|
||||
link.Up = true
|
||||
link.MTU = vlan.MTU()
|
||||
|
||||
@ -2,30 +2,22 @@
|
||||
// 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/.
|
||||
|
||||
//nolint:dupl,goconst
|
||||
//nolint:goconst
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller/runtime"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
|
||||
"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/siderolabs/go-pointer"
|
||||
"github.com/siderolabs/go-procfs/procfs"
|
||||
"github.com/siderolabs/go-retry/retry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
|
||||
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/container"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
@ -35,73 +27,21 @@ import (
|
||||
)
|
||||
|
||||
type LinkConfigSuite struct {
|
||||
suite.Suite
|
||||
|
||||
state state.State
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
|
||||
suite.state = state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
var err error
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T()))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.DeviceConfigController{}))
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer suite.wg.Done()
|
||||
|
||||
suite.Assert().NoError(suite.runtime.Run(suite.ctx))
|
||||
}()
|
||||
ctest.DefaultSuite
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) assertLinks(requiredIDs []string, check func(*network.LinkSpec, *assert.Assertions)) {
|
||||
assertResources(suite.ctx, suite.T(), suite.state, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
ctest.AssertResources(suite, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) assertNoLinks(unexpectedIDs []string) error {
|
||||
unexpIDs := make(map[string]struct{}, len(unexpectedIDs))
|
||||
|
||||
func (suite *LinkConfigSuite) assertNoLinks(unexpectedIDs []string) {
|
||||
for _, id := range unexpectedIDs {
|
||||
unexpIDs[id] = struct{}{}
|
||||
ctest.AssertNoResource[*network.LinkSpec](suite, id, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(
|
||||
suite.ctx,
|
||||
resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, unexpected := unexpIDs[res.Metadata().ID()]
|
||||
if unexpected {
|
||||
return retry.ExpectedErrorf("unexpected ID %q", res.Metadata().ID())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) TestLoopback() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.LinkConfigController{}))
|
||||
|
||||
suite.startRuntime()
|
||||
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.LinkConfigController{}))
|
||||
|
||||
suite.assertLinks(
|
||||
[]string{
|
||||
@ -117,15 +57,13 @@ func (suite *LinkConfigSuite) TestLoopback() {
|
||||
|
||||
func (suite *LinkConfigSuite) TestCmdline() {
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.LinkConfigController{
|
||||
Cmdline: procfs.NewCmdline("ip=172.20.0.2::172.20.0.1:255.255.255.0::eth1:::::"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.assertLinks(
|
||||
[]string{
|
||||
"cmdline/eth1",
|
||||
@ -142,9 +80,7 @@ func (suite *LinkConfigSuite) TestCmdline() {
|
||||
func (suite *LinkConfigSuite) TestMachineConfiguration() {
|
||||
const kernelDriver = "somekerneldriver"
|
||||
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.LinkConfigController{}))
|
||||
|
||||
suite.startRuntime()
|
||||
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.LinkConfigController{}))
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
suite.Require().NoError(err)
|
||||
@ -278,13 +214,13 @@ func (suite *LinkConfigSuite) TestMachineConfiguration() {
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
|
||||
suite.Create(cfg)
|
||||
|
||||
for _, name := range []string{"eth6", "eth7"} {
|
||||
status := network.NewLinkStatus(network.NamespaceName, name)
|
||||
status.TypedSpec().Driver = kernelDriver
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, status))
|
||||
suite.Create(status)
|
||||
}
|
||||
|
||||
suite.assertLinks(
|
||||
@ -401,9 +337,130 @@ func (suite *LinkConfigSuite) TestMachineConfiguration() {
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) TestMachineConfigurationWithAliases() {
|
||||
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.LinkConfigController{}))
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cfg := config.NewMachineConfig(
|
||||
container.NewV1Alpha1(
|
||||
&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "enx0123",
|
||||
DeviceVlans: []*v1alpha1.Vlan{
|
||||
{
|
||||
VlanID: 24,
|
||||
VlanMTU: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enx0123",
|
||||
DeviceMTU: 9001,
|
||||
},
|
||||
{
|
||||
DeviceIgnore: pointer.To(true),
|
||||
DeviceInterface: "enx0456",
|
||||
},
|
||||
{
|
||||
DeviceInterface: "bond0",
|
||||
DeviceBond: &v1alpha1.Bond{
|
||||
BondInterfaces: []string{"enxa", "enxb"},
|
||||
BondMode: "balance-xor",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
URL: u,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.Create(cfg)
|
||||
|
||||
for _, link := range []struct {
|
||||
name string
|
||||
aliases []string
|
||||
}{
|
||||
{
|
||||
name: "eth0",
|
||||
aliases: []string{"enx0123"},
|
||||
},
|
||||
{
|
||||
name: "eth1",
|
||||
aliases: []string{"enx0456"},
|
||||
},
|
||||
{
|
||||
name: "eth2",
|
||||
aliases: []string{"enxa"},
|
||||
},
|
||||
{
|
||||
name: "eth3",
|
||||
aliases: []string{"enxb"},
|
||||
},
|
||||
} {
|
||||
status := network.NewLinkStatus(network.NamespaceName, link.name)
|
||||
status.TypedSpec().AltNames = link.aliases
|
||||
|
||||
suite.Create(status)
|
||||
}
|
||||
|
||||
suite.assertLinks(
|
||||
[]string{
|
||||
"configuration/eth0",
|
||||
"configuration/enx0123.24",
|
||||
"configuration/eth2",
|
||||
"configuration/eth3",
|
||||
"configuration/bond0",
|
||||
}, func(r *network.LinkSpec, asrt *assert.Assertions) {
|
||||
asrt.Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
|
||||
|
||||
switch r.TypedSpec().Name {
|
||||
case "eth0":
|
||||
asrt.True(r.TypedSpec().Up)
|
||||
asrt.False(r.TypedSpec().Logical)
|
||||
asrt.EqualValues(9001, r.TypedSpec().MTU)
|
||||
case "eth2", "eth3":
|
||||
asrt.True(r.TypedSpec().Up)
|
||||
asrt.False(r.TypedSpec().Logical)
|
||||
asrt.Equal("bond0", r.TypedSpec().BondSlave.MasterName)
|
||||
case "eth0.24":
|
||||
asrt.True(r.TypedSpec().Up)
|
||||
asrt.True(r.TypedSpec().Logical)
|
||||
asrt.Equal(nethelpers.LinkEther, r.TypedSpec().Type)
|
||||
asrt.Equal(network.LinkKindVLAN, r.TypedSpec().Kind)
|
||||
asrt.Equal("eth0", r.TypedSpec().ParentName)
|
||||
asrt.Equal(nethelpers.VLANProtocol8021Q, r.TypedSpec().VLAN.Protocol)
|
||||
|
||||
asrt.EqualValues(24, r.TypedSpec().VLAN.VID)
|
||||
asrt.EqualValues(1000, r.TypedSpec().MTU)
|
||||
case "bond0":
|
||||
asrt.True(r.TypedSpec().Up)
|
||||
asrt.True(r.TypedSpec().Logical)
|
||||
asrt.Equal(nethelpers.LinkEther, r.TypedSpec().Type)
|
||||
asrt.Equal(network.LinkKindBond, r.TypedSpec().Kind)
|
||||
asrt.Equal(nethelpers.BondModeXOR, r.TypedSpec().BondMaster.Mode)
|
||||
asrt.True(r.TypedSpec().BondMaster.UseCarrier)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) TestDefaultUp() {
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.LinkConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth2"),
|
||||
},
|
||||
@ -419,7 +476,7 @@ func (suite *LinkConfigSuite) TestDefaultUp() {
|
||||
linkStatus.TypedSpec().AltNames = []string{"eth0"}
|
||||
}
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, linkStatus))
|
||||
suite.Create(linkStatus)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
@ -472,9 +529,7 @@ func (suite *LinkConfigSuite) TestDefaultUp() {
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
|
||||
|
||||
suite.startRuntime()
|
||||
suite.Create(cfg)
|
||||
|
||||
suite.assertLinks(
|
||||
[]string{
|
||||
@ -485,30 +540,25 @@ func (suite *LinkConfigSuite) TestDefaultUp() {
|
||||
},
|
||||
)
|
||||
|
||||
suite.Assert().NoError(
|
||||
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoLinks(
|
||||
[]string{
|
||||
"default/eth0",
|
||||
"default/eth2",
|
||||
"default/eth3",
|
||||
"default/eth4",
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
suite.assertNoLinks(
|
||||
[]string{
|
||||
"default/eth0",
|
||||
"default/eth2",
|
||||
"default/eth3",
|
||||
"default/eth4",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
}
|
||||
|
||||
func TestLinkConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(LinkConfigSuite))
|
||||
t.Parallel()
|
||||
|
||||
suite.Run(t, &LinkConfigSuite{
|
||||
DefaultSuite: ctest.DefaultSuite{
|
||||
Timeout: 5 * time.Second,
|
||||
AfterSetup: func(s *ctest.DefaultSuite) {
|
||||
s.Require().NoError(s.Runtime().RegisterController(&netctrl.DeviceConfigController{}))
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -81,38 +81,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
return fmt.Errorf("error listing link statuses: %w", err)
|
||||
}
|
||||
|
||||
// build an alias/altname map and a list of all interfaces
|
||||
linkAliasMap := map[string]string{}
|
||||
|
||||
for linkStatus := range linkStatuses.All() {
|
||||
if linkStatus.TypedSpec().Alias != "" {
|
||||
linkAliasMap[linkStatus.TypedSpec().Alias] = linkStatus.Metadata().ID()
|
||||
}
|
||||
|
||||
for _, altName := range linkStatus.TypedSpec().AltNames {
|
||||
linkAliasMap[altName] = linkStatus.Metadata().ID()
|
||||
}
|
||||
}
|
||||
|
||||
// direct names override aliases
|
||||
for linkStatus := range linkStatuses.All() {
|
||||
linkAliasMap[linkStatus.Metadata().ID()] = linkStatus.Metadata().ID()
|
||||
}
|
||||
|
||||
lookupLinkName := func(linkName string) string {
|
||||
if alias, ok := linkAliasMap[linkName]; ok {
|
||||
return alias
|
||||
}
|
||||
|
||||
return linkName
|
||||
}
|
||||
|
||||
items, err := r.List(ctx, resource.NewMetadata(network.NamespaceName, network.DeviceConfigSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error getting config: %w", err)
|
||||
}
|
||||
}
|
||||
linkNameResolver := network.NewLinkResolver(linkStatuses.All)
|
||||
|
||||
var (
|
||||
specs []network.OperatorSpecSpec
|
||||
@ -124,7 +93,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
if ctrl.Cmdline != nil {
|
||||
var settings CmdlineNetworking
|
||||
|
||||
settings, err = ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err = ParseCmdlineNetwork(ctrl.Cmdline, linkNameResolver)
|
||||
if err != nil {
|
||||
logger.Warn("ignored cmdline parse failure", zap.Error(err))
|
||||
}
|
||||
@ -140,7 +109,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
|
||||
specs = append(specs, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: lookupLinkName(linkConfig.LinkName),
|
||||
LinkName: linkNameResolver.Resolve(linkConfig.LinkName),
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: network.DefaultRouteMetric,
|
||||
@ -150,6 +119,13 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
}
|
||||
}
|
||||
|
||||
items, err := r.List(ctx, resource.NewMetadata(network.NamespaceName, network.DeviceConfigSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error getting config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
devices := xslices.Map(items.Items, func(item resource.Resource) talosconfig.Device {
|
||||
return item.(*network.DeviceConfigSpec).TypedSpec().Device
|
||||
})
|
||||
@ -158,10 +134,10 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
if len(devices) > 0 {
|
||||
for _, device := range devices {
|
||||
if device.Ignore() {
|
||||
ignoredInterfaces[device.Interface()] = struct{}{}
|
||||
ignoredInterfaces[linkNameResolver.Resolve(device.Interface())] = struct{}{}
|
||||
}
|
||||
|
||||
if _, ignore := ignoredInterfaces[device.Interface()]; ignore {
|
||||
if _, ignore := ignoredInterfaces[linkNameResolver.Resolve(device.Interface())]; ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -173,7 +149,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
|
||||
specs = append(specs, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: lookupLinkName(device.Interface()),
|
||||
LinkName: linkNameResolver.Resolve(device.Interface()),
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: routeMetric,
|
||||
@ -190,7 +166,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
|
||||
specs = append(specs, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: lookupLinkName(device.Interface()),
|
||||
LinkName: linkNameResolver.Resolve(device.Interface()),
|
||||
RequireUp: true,
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: routeMetric,
|
||||
@ -243,13 +219,13 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
// any link which has any configuration derived from the machine configuration or platform configuration should be ignored
|
||||
configuredInterfaces := map[string]struct{}{}
|
||||
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined))
|
||||
linkSpecs, err := safe.ReaderList[*network.LinkSpec](ctx, r, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link specs: %w", err)
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
linkSpec := item.(*network.LinkSpec).TypedSpec()
|
||||
for link := range linkSpecs.All() {
|
||||
linkSpec := link.TypedSpec()
|
||||
|
||||
switch linkSpec.ConfigLayer {
|
||||
case network.ConfigDefault:
|
||||
@ -258,7 +234,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
// specs produced by operators, ignore
|
||||
case network.ConfigCmdline, network.ConfigMachineConfiguration, network.ConfigPlatform:
|
||||
// interface is configured explicitly, don't run default dhcp4
|
||||
configuredInterfaces[lookupLinkName(linkSpec.Name)] = struct{}{}
|
||||
configuredInterfaces[linkNameResolver.Resolve(linkSpec.Name)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,12 +270,12 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
}
|
||||
|
||||
// list specs for cleanup
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
operators, err := safe.ReaderList[*network.OperatorSpec](ctx, r, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range list.Items {
|
||||
for res := range operators.All() {
|
||||
if res.Metadata().Owner() != ctrl.Name() {
|
||||
// skip specs created by other controllers
|
||||
continue
|
||||
|
||||
@ -6,25 +6,18 @@
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller/runtime"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
|
||||
"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/siderolabs/go-pointer"
|
||||
"github.com/siderolabs/go-procfs/procfs"
|
||||
"github.com/siderolabs/go-retry/retry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
|
||||
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/container"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
@ -34,86 +27,34 @@ import (
|
||||
)
|
||||
|
||||
type OperatorConfigSuite struct {
|
||||
suite.Suite
|
||||
|
||||
state state.State
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
|
||||
suite.state = state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
var err error
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T()))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.DeviceConfigController{}))
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer suite.wg.Done()
|
||||
|
||||
suite.Assert().NoError(suite.runtime.Run(suite.ctx))
|
||||
}()
|
||||
ctest.DefaultSuite
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) assertOperators(requiredIDs []string, check func(*network.OperatorSpec, *assert.Assertions)) {
|
||||
assertResources(suite.ctx, suite.T(), suite.state, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
ctest.AssertResources(suite, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) assertNoOperators(unexpectedIDs []string) error {
|
||||
unexpIDs := make(map[string]struct{}, len(unexpectedIDs))
|
||||
|
||||
func (suite *OperatorConfigSuite) assertNoOperators(unexpectedIDs []string) {
|
||||
for _, id := range unexpectedIDs {
|
||||
unexpIDs[id] = struct{}{}
|
||||
ctest.AssertNoResource[*network.OperatorSpec](suite, id, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(
|
||||
suite.ctx,
|
||||
resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, unexpected := unexpIDs[res.Metadata().ID()]
|
||||
if unexpected {
|
||||
return retry.ExpectedErrorf("unexpected ID %q", res.Metadata().ID())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) TestDefaultDHCP() {
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.OperatorConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth2"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
for _, link := range []string{"eth0", "eth1", "eth2"} {
|
||||
linkStatus := network.NewLinkStatus(network.NamespaceName, link)
|
||||
linkStatus.TypedSpec().Type = nethelpers.LinkEther
|
||||
linkStatus.TypedSpec().LinkState = true
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, linkStatus))
|
||||
suite.Create(linkStatus)
|
||||
}
|
||||
|
||||
suite.assertOperators(
|
||||
@ -137,21 +78,19 @@ func (suite *OperatorConfigSuite) TestDefaultDHCP() {
|
||||
|
||||
func (suite *OperatorConfigSuite) TestDefaultDHCPCmdline() {
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.OperatorConfigController{
|
||||
Cmdline: procfs.NewCmdline("ip=172.20.0.2::172.20.0.1:255.255.255.0::eth1::::: ip=eth3:dhcp"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
for _, link := range []string{"eth0", "eth1", "eth2"} {
|
||||
linkStatus := network.NewLinkStatus(network.NamespaceName, link)
|
||||
linkStatus.TypedSpec().Type = nethelpers.LinkEther
|
||||
linkStatus.TypedSpec().LinkState = true
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, linkStatus))
|
||||
suite.Create(linkStatus)
|
||||
}
|
||||
|
||||
suite.assertOperators(
|
||||
@ -177,28 +116,22 @@ func (suite *OperatorConfigSuite) TestDefaultDHCPCmdline() {
|
||||
|
||||
// remove link
|
||||
suite.Require().NoError(
|
||||
suite.state.Destroy(
|
||||
suite.ctx,
|
||||
suite.State().Destroy(
|
||||
suite.Ctx(),
|
||||
resource.NewMetadata(network.NamespaceName, network.LinkStatusType, "eth2", resource.VersionUndefined),
|
||||
),
|
||||
)
|
||||
|
||||
suite.Assert().NoError(
|
||||
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators(
|
||||
[]string{
|
||||
"default/dhcp4/eth2",
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
suite.assertNoOperators(
|
||||
[]string{
|
||||
"default/dhcp4/eth2",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.OperatorConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth5"),
|
||||
},
|
||||
@ -206,21 +139,19 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
)
|
||||
// add LinkConfig controller to produce link specs based on machine configuration
|
||||
suite.Require().NoError(
|
||||
suite.runtime.RegisterController(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.LinkConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth5"),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
for _, link := range []string{"eth0", "eth1", "eth2"} {
|
||||
linkStatus := network.NewLinkStatus(network.NamespaceName, link)
|
||||
linkStatus.TypedSpec().Type = nethelpers.LinkEther
|
||||
linkStatus.TypedSpec().LinkState = true
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, linkStatus))
|
||||
suite.Create(linkStatus)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
@ -289,7 +220,7 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
|
||||
suite.Create(cfg)
|
||||
|
||||
suite.assertOperators(
|
||||
[]string{
|
||||
@ -320,27 +251,19 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
},
|
||||
)
|
||||
|
||||
suite.Assert().NoError(
|
||||
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators(
|
||||
[]string{
|
||||
"configuration/dhcp4/eth0",
|
||||
"default/dhcp4/eth0",
|
||||
"configuration/dhcp4/eth2",
|
||||
"default/dhcp4/eth2",
|
||||
"configuration/dhcp4/eth4.26",
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
suite.assertNoOperators(
|
||||
[]string{
|
||||
"configuration/dhcp4/eth0",
|
||||
"default/dhcp4/eth0",
|
||||
"configuration/dhcp4/eth2",
|
||||
"default/dhcp4/eth2",
|
||||
"configuration/dhcp4/eth4.26",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP6() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.OperatorConfigController{}))
|
||||
|
||||
suite.startRuntime()
|
||||
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.OperatorConfigController{}))
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
suite.Require().NoError(err)
|
||||
@ -388,7 +311,7 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP6() {
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
|
||||
suite.Create(cfg)
|
||||
|
||||
suite.assertOperators(
|
||||
[]string{
|
||||
@ -409,27 +332,170 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP6() {
|
||||
},
|
||||
)
|
||||
|
||||
suite.Assert().NoError(
|
||||
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators(
|
||||
[]string{
|
||||
"configuration/dhcp6/eth1",
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
suite.assertNoOperators(
|
||||
[]string{
|
||||
"configuration/dhcp6/eth1",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
func (suite *OperatorConfigSuite) TestMachineConfigurationWithAliases() {
|
||||
suite.Require().NoError(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.OperatorConfigController{},
|
||||
),
|
||||
)
|
||||
// add LinkConfig controller to produce link specs based on machine configuration
|
||||
suite.Require().NoError(
|
||||
suite.Runtime().RegisterController(
|
||||
&netctrl.LinkConfigController{},
|
||||
),
|
||||
)
|
||||
|
||||
suite.ctxCancel()
|
||||
for _, link := range []struct {
|
||||
name string
|
||||
aliases []string
|
||||
}{
|
||||
{
|
||||
name: "eth0",
|
||||
aliases: []string{"enx0123"},
|
||||
},
|
||||
{
|
||||
name: "eth1",
|
||||
aliases: []string{"enx0456"},
|
||||
},
|
||||
{
|
||||
name: "eth2",
|
||||
aliases: []string{"enxa"},
|
||||
},
|
||||
{
|
||||
name: "eth3",
|
||||
aliases: []string{"enxb"},
|
||||
},
|
||||
{
|
||||
name: "eth4",
|
||||
aliases: []string{"enxc"},
|
||||
},
|
||||
} {
|
||||
status := network.NewLinkStatus(network.NamespaceName, link.name)
|
||||
status.TypedSpec().AltNames = link.aliases
|
||||
status.TypedSpec().Type = nethelpers.LinkEther
|
||||
status.TypedSpec().LinkState = true
|
||||
|
||||
suite.wg.Wait()
|
||||
suite.Create(status)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cfg := config.NewMachineConfig(
|
||||
container.NewV1Alpha1(
|
||||
&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "enx0123",
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enx0456",
|
||||
DeviceDHCP: pointer.To(true),
|
||||
},
|
||||
{
|
||||
DeviceIgnore: pointer.To(true),
|
||||
DeviceInterface: "enxa",
|
||||
DeviceDHCP: pointer.To(true),
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enxb",
|
||||
DeviceDHCP: pointer.To(true),
|
||||
DeviceDHCPOptions: &v1alpha1.DHCPOptions{
|
||||
DHCPIPv4: pointer.To(true),
|
||||
DHCPRouteMetric: 256,
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enxc",
|
||||
DeviceVlans: []*v1alpha1.Vlan{
|
||||
{
|
||||
VlanID: 25,
|
||||
VlanDHCP: pointer.To(true),
|
||||
},
|
||||
{
|
||||
VlanID: 26,
|
||||
},
|
||||
{
|
||||
VlanID: 27,
|
||||
VlanDHCPOptions: &v1alpha1.DHCPOptions{
|
||||
DHCPRouteMetric: 256,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enxd",
|
||||
DeviceDHCP: pointer.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
URL: u,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.Create(cfg)
|
||||
|
||||
suite.assertOperators(
|
||||
[]string{
|
||||
"configuration/dhcp4/eth1",
|
||||
"configuration/dhcp4/eth3",
|
||||
"configuration/dhcp4/enxc.25",
|
||||
}, func(r *network.OperatorSpec, asrt *assert.Assertions) {
|
||||
asrt.Equal(network.OperatorDHCP4, r.TypedSpec().Operator)
|
||||
asrt.True(r.TypedSpec().RequireUp)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "configuration/dhcp4/eth1":
|
||||
asrt.Equal("eth1", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(network.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
case "configuration/dhcp4/eth3":
|
||||
asrt.Equal("eth3", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(256, r.TypedSpec().DHCP4.RouteMetric)
|
||||
case "configuration/dhcp4/enxc.25":
|
||||
asrt.Equal("enxc.25", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(network.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
suite.assertNoOperators(
|
||||
[]string{
|
||||
"configuration/dhcp4/eth0",
|
||||
"default/dhcp4/eth0",
|
||||
"configuration/dhcp4/eth2",
|
||||
"default/dhcp4/eth2",
|
||||
"configuration/dhcp4/eth4.26",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestOperatorConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(OperatorConfigSuite))
|
||||
t.Parallel()
|
||||
|
||||
suite.Run(t, &OperatorConfigSuite{
|
||||
DefaultSuite: ctest.DefaultSuite{
|
||||
Timeout: 5 * time.Second,
|
||||
AfterSetup: func(s *ctest.DefaultSuite) {
|
||||
s.Require().NoError(s.Runtime().RegisterController(&netctrl.DeviceConfigController{}))
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,6 +42,11 @@ func (ctrl *OperatorVIPConfigController) Inputs() []controller.Input {
|
||||
Type: network.DeviceConfigSpecType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: network.NamespaceName,
|
||||
Type: network.LinkStatusType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,12 +84,19 @@ func (ctrl *OperatorVIPConfigController) Run(ctx context.Context, r controller.R
|
||||
return item.(*network.DeviceConfigSpec).TypedSpec().Device
|
||||
})
|
||||
|
||||
linkStatuses, err := safe.ReaderListAll[*network.LinkStatus](ctx, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link statuses: %w", err)
|
||||
}
|
||||
|
||||
linkNameResolver := network.NewLinkResolver(linkStatuses.All)
|
||||
|
||||
ignoredInterfaces := map[string]struct{}{}
|
||||
|
||||
if ctrl.Cmdline != nil {
|
||||
var settings CmdlineNetworking
|
||||
|
||||
settings, err = ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err = ParseCmdlineNetwork(ctrl.Cmdline, linkNameResolver)
|
||||
if err != nil {
|
||||
logger.Warn("ignored cmdline parse failure", zap.Error(err))
|
||||
}
|
||||
@ -103,15 +115,15 @@ func (ctrl *OperatorVIPConfigController) Run(ctx context.Context, r controller.R
|
||||
if len(devices) > 0 {
|
||||
for _, device := range devices {
|
||||
if device.Ignore() {
|
||||
ignoredInterfaces[device.Interface()] = struct{}{}
|
||||
ignoredInterfaces[linkNameResolver.Resolve(device.Interface())] = struct{}{}
|
||||
}
|
||||
|
||||
if _, ignore := ignoredInterfaces[device.Interface()]; ignore {
|
||||
if _, ignore := ignoredInterfaces[linkNameResolver.Resolve(device.Interface())]; ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
if device.VIPConfig() != nil {
|
||||
if spec, specErr := handleVIP(ctx, device.VIPConfig(), device.Interface(), logger); specErr != nil {
|
||||
if spec, specErr := handleVIP(ctx, device.VIPConfig(), linkNameResolver.Resolve(device.Interface()), logger); specErr != nil {
|
||||
specErrors = multierror.Append(specErrors, specErr)
|
||||
} else {
|
||||
specs = append(specs, spec)
|
||||
|
||||
@ -5,23 +5,17 @@
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller/runtime"
|
||||
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
|
||||
"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/siderolabs/go-pointer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
|
||||
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/container"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
@ -30,51 +24,35 @@ import (
|
||||
)
|
||||
|
||||
type OperatorVIPConfigSuite struct {
|
||||
suite.Suite
|
||||
|
||||
state state.State
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *OperatorVIPConfigSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
|
||||
suite.state = state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
var err error
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, zaptest.NewLogger(suite.T()))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.DeviceConfigController{}))
|
||||
}
|
||||
|
||||
func (suite *OperatorVIPConfigSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer suite.wg.Done()
|
||||
|
||||
suite.Assert().NoError(suite.runtime.Run(suite.ctx))
|
||||
}()
|
||||
ctest.DefaultSuite
|
||||
}
|
||||
|
||||
func (suite *OperatorVIPConfigSuite) assertOperators(
|
||||
requiredIDs []string,
|
||||
check func(*network.OperatorSpec, *assert.Assertions),
|
||||
) {
|
||||
assertResources(suite.ctx, suite.T(), suite.state, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
ctest.AssertResources(suite, requiredIDs, check, rtestutils.WithNamespace(network.ConfigNamespaceName))
|
||||
}
|
||||
|
||||
func (suite *OperatorVIPConfigSuite) TestMachineConfigurationVIP() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.OperatorVIPConfigController{}))
|
||||
for _, link := range []struct {
|
||||
name string
|
||||
aliases []string
|
||||
}{
|
||||
{
|
||||
name: "eth5",
|
||||
aliases: []string{"enxa"},
|
||||
},
|
||||
{
|
||||
name: "eth6",
|
||||
aliases: []string{"enxb"},
|
||||
},
|
||||
} {
|
||||
status := network.NewLinkStatus(network.NamespaceName, link.name)
|
||||
status.TypedSpec().AltNames = link.aliases
|
||||
|
||||
suite.startRuntime()
|
||||
suite.Create(status)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://foo:6443")
|
||||
suite.Require().NoError(err)
|
||||
@ -112,6 +90,13 @@ func (suite *OperatorVIPConfigSuite) TestMachineConfigurationVIP() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "enxa",
|
||||
DeviceDHCP: pointer.To(true),
|
||||
DeviceVIPConfig: &v1alpha1.DeviceVIPConfig{
|
||||
SharedIP: "2.3.4.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -126,13 +111,14 @@ func (suite *OperatorVIPConfigSuite) TestMachineConfigurationVIP() {
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, cfg))
|
||||
suite.Create(cfg)
|
||||
|
||||
suite.assertOperators(
|
||||
[]string{
|
||||
"configuration/vip/eth1",
|
||||
"configuration/vip/eth2",
|
||||
"configuration/vip/eth3.26",
|
||||
"configuration/vip/eth5",
|
||||
}, func(r *network.OperatorSpec, asrt *assert.Assertions) {
|
||||
asrt.Equal(network.OperatorVIP, r.TypedSpec().Operator)
|
||||
asrt.True(r.TypedSpec().RequireUp)
|
||||
@ -141,6 +127,9 @@ func (suite *OperatorVIPConfigSuite) TestMachineConfigurationVIP() {
|
||||
case "configuration/vip/eth1":
|
||||
asrt.Equal("eth1", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(netip.MustParseAddr("2.3.4.5"), r.TypedSpec().VIP.IP)
|
||||
case "configuration/vip/eth5":
|
||||
asrt.Equal("eth5", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(netip.MustParseAddr("2.3.4.5"), r.TypedSpec().VIP.IP)
|
||||
case "configuration/vip/eth2":
|
||||
asrt.Equal("eth2", r.TypedSpec().LinkName)
|
||||
asrt.EqualValues(
|
||||
@ -155,14 +144,16 @@ func (suite *OperatorVIPConfigSuite) TestMachineConfigurationVIP() {
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *OperatorVIPConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
}
|
||||
|
||||
func TestOperatorVIPConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(OperatorVIPConfigSuite))
|
||||
t.Parallel()
|
||||
|
||||
suite.Run(t, &OperatorVIPConfigSuite{
|
||||
DefaultSuite: ctest.DefaultSuite{
|
||||
Timeout: 5 * time.Second,
|
||||
AfterSetup: func(s *ctest.DefaultSuite) {
|
||||
s.Require().NoError(s.Runtime().RegisterController(&netctrl.DeviceConfigController{}))
|
||||
s.Require().NoError(s.Runtime().RegisterController(&netctrl.OperatorVIPConfigController{}))
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ func (ctrl *ResolverConfigController) parseCmdline(logger *zap.Logger) (spec net
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, network.NewEmptyLinkResolver())
|
||||
if err != nil {
|
||||
logger.Warn("ignoring error", zap.Error(err))
|
||||
|
||||
|
||||
@ -181,7 +181,7 @@ func (ctrl *RouteConfigController) parseCmdline(logger *zap.Logger) (routes []ne
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, network.NewEmptyLinkResolver())
|
||||
if err != nil {
|
||||
logger.Info("ignoring error", zap.Error(err))
|
||||
|
||||
|
||||
@ -171,7 +171,7 @@ func (ctrl *TimeServerConfigController) parseCmdline(logger *zap.Logger) (spec n
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline)
|
||||
settings, err := ParseCmdlineNetwork(ctrl.Cmdline, network.NewEmptyLinkResolver())
|
||||
if err != nil {
|
||||
logger.Warn("ignoring error", zap.Error(err))
|
||||
|
||||
|
||||
45
pkg/machinery/resources/network/link_name_resolver.go
Normal file
45
pkg/machinery/resources/network/link_name_resolver.go
Normal file
@ -0,0 +1,45 @@
|
||||
// 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 network
|
||||
|
||||
import "iter"
|
||||
|
||||
// LinkResolver resolves link names and aliases to actual link names.
|
||||
type LinkResolver struct {
|
||||
lookup map[string]string // map of link names/aliases to link names
|
||||
}
|
||||
|
||||
// Resolve resolves the link name or alias to the actual link name.
|
||||
//
|
||||
// If the link name or alias is not found in the lookup table, it is returned as is.
|
||||
func (r *LinkResolver) Resolve(name string) string {
|
||||
if resolved, ok := r.lookup[name]; ok {
|
||||
return resolved
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// NewLinkResolver creates a new link name resolver.
|
||||
func NewLinkResolver(f func() iter.Seq[*LinkStatus]) *LinkResolver {
|
||||
lookup := make(map[string]string)
|
||||
|
||||
for link := range f() {
|
||||
for alias := range AllLinkAliases(link) {
|
||||
lookup[alias] = link.Metadata().ID()
|
||||
}
|
||||
}
|
||||
|
||||
for link := range f() {
|
||||
lookup[link.Metadata().ID()] = link.Metadata().ID()
|
||||
}
|
||||
|
||||
return &LinkResolver{lookup: lookup}
|
||||
}
|
||||
|
||||
// NewEmptyLinkResolver creates a new link name resolver with an empty lookup table.
|
||||
func NewEmptyLinkResolver() *LinkResolver {
|
||||
return &LinkResolver{}
|
||||
}
|
||||
44
pkg/machinery/resources/network/link_name_resolver_test.go
Normal file
44
pkg/machinery/resources/network/link_name_resolver_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// 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 network_test
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
func TestLinkNameResolver(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
link1 := network.NewLinkStatus(network.NamespaceName, "eth0")
|
||||
link1.TypedSpec().Alias = "net0"
|
||||
link1.TypedSpec().AltNames = []string{"ext0"}
|
||||
|
||||
link2 := network.NewLinkStatus(network.NamespaceName, "eth1")
|
||||
|
||||
link3 := network.NewLinkStatus(network.NamespaceName, "eth2")
|
||||
link3.TypedSpec().AltNames = []string{"ext2"}
|
||||
|
||||
links := []*network.LinkStatus{
|
||||
link1,
|
||||
link2,
|
||||
link3,
|
||||
}
|
||||
|
||||
resolver := network.NewLinkResolver(func() iter.Seq[*network.LinkStatus] { return slices.Values(links) })
|
||||
|
||||
assert.Equal(t, "eth0", resolver.Resolve("eth0"))
|
||||
assert.Equal(t, "eth0", resolver.Resolve("net0"))
|
||||
assert.Equal(t, "eth0", resolver.Resolve("ext0"))
|
||||
assert.Equal(t, "eth1", resolver.Resolve("eth1"))
|
||||
assert.Equal(t, "eth2", resolver.Resolve("eth2"))
|
||||
assert.Equal(t, "eth2", resolver.Resolve("ext2"))
|
||||
assert.Equal(t, "eth3", resolver.Resolve("eth3"))
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"iter"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/resource/meta"
|
||||
"github.com/cosi-project/runtime/pkg/resource/protobuf"
|
||||
@ -66,6 +68,38 @@ func (s LinkStatusSpec) Physical() bool {
|
||||
return s.Type == nethelpers.LinkEther && s.Kind == ""
|
||||
}
|
||||
|
||||
// AllLinkNames returns all link names, including name, alias and altnames.
|
||||
func AllLinkNames(link *LinkStatus) iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
if !yield(link.Metadata().ID()) {
|
||||
return
|
||||
}
|
||||
|
||||
for alias := range AllLinkAliases(link) {
|
||||
if !yield(alias) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AllLinkAliases returns all link aliases (altnames and alias).
|
||||
func AllLinkAliases(link *LinkStatus) iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
if link.TypedSpec().Alias != "" {
|
||||
if !yield(link.TypedSpec().Alias) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, altName := range link.TypedSpec().AltNames {
|
||||
if !yield(altName) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewLinkStatus initializes a LinkStatus resource.
|
||||
func NewLinkStatus(namespace resource.Namespace, id resource.ID) *LinkStatus {
|
||||
return typed.NewResource[LinkStatusSpec, LinkStatusExtension](
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user