Skip to content

Commit be028b6

Browse files
committed
feat: add support for multi-doc VLAN config
Fixes #10961 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent f3df0f8 commit be028b6

File tree

17 files changed

+931
-12
lines changed

17 files changed

+931
-12
lines changed

internal/app/machined/pkg/controllers/network/link_config.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ func (ctrl *LinkConfigController) processLinkConfigs(logger *zap.Logger, linkMap
443443
// nothing specific for physical links
444444
case talosconfig.NetworkDummyLinkConfig:
445445
dummyLink(linkMap[linkName])
446+
case talosconfig.NetworkVLANConfig:
447+
vlanLink(linkMap[linkName], linkName, specificLinkConfig.ParentLink(), networkVLANConfigToVlaner{specificLinkConfig})
446448
default:
447449
logger.Error("unknown link config type", zap.String("linkName", linkName), zap.String("type", fmt.Sprintf("%T", specificLinkConfig)))
448450
}
@@ -465,20 +467,37 @@ func (ctrl *LinkConfigController) processLinkConfigs(logger *zap.Logger, linkMap
465467

466468
type vlaner interface {
467469
ID() uint16
468-
MTU() uint32
470+
Mode() nethelpers.VLANProtocol
471+
}
472+
473+
type networkVLANConfigToVlaner struct {
474+
talosconfig.NetworkVLANConfig
475+
}
476+
477+
func (v networkVLANConfigToVlaner) ID() uint16 {
478+
return v.VLANID()
479+
}
480+
481+
func (v networkVLANConfigToVlaner) Mode() nethelpers.VLANProtocol {
482+
return v.VLANMode().ValueOr(nethelpers.VLANProtocol8021Q)
469483
}
470484

471485
func vlanLink(link *network.LinkSpecSpec, vlanName, linkName string, vlan vlaner) {
472486
link.Name = vlanName
473487
link.Logical = true
474488
link.Up = true
475-
link.MTU = vlan.MTU()
489+
490+
// only legacy config specifies MTUs on VLANs this way
491+
if mtuConfig, ok := vlan.(interface{ MTU() uint32 }); ok {
492+
link.MTU = mtuConfig.MTU()
493+
}
494+
476495
link.Kind = network.LinkKindVLAN
477496
link.Type = nethelpers.LinkEther
478497
link.ParentName = linkName
479498
link.VLAN = network.VLANSpec{
480499
VID: vlan.ID(),
481-
Protocol: nethelpers.VLANProtocol8021Q,
500+
Protocol: vlan.Mode(),
482501
}
483502
}
484503

internal/app/machined/pkg/controllers/network/link_config_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5-
//nolint:goconst
5+
//nolint:goconst,dupl
66
package network_test
77

88
import (
@@ -469,7 +469,14 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
469469
dc1.HardwareAddressConfig = nethelpers.HardwareAddr{0x02, 0x42, 0xac, 0x11, 0x00, 0x02}
470470
dc1.LinkUp = pointer.To(true)
471471

472-
ctr, err := container.New(dc1, lc1)
472+
vl1 := networkcfg.NewVLANConfigV1Alpha1("dummy1.100")
473+
vl1.VLANIDConfig = 100
474+
vl1.ParentLinkConfig = "dummy1"
475+
vl1.VLANModeConfig = pointer.To(nethelpers.VLANProtocol8021AD)
476+
vl1.LinkMTU = 200
477+
vl1.LinkUp = pointer.To(true)
478+
479+
ctr, err := container.New(dc1, lc1, vl1)
473480
suite.Require().NoError(err)
474481

475482
cfg := config.NewMachineConfig(ctr)
@@ -494,6 +501,7 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
494501
[]string{
495502
"configuration/eth0",
496503
"configuration/dummy1",
504+
"configuration/dummy1.100",
497505
}, func(r *network.LinkSpec, asrt *assert.Assertions) {
498506
asrt.Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
499507

@@ -507,6 +515,15 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
507515
asrt.True(r.TypedSpec().Logical)
508516
asrt.Equal(nethelpers.LinkEther, r.TypedSpec().Type)
509517
asrt.Equal("dummy", r.TypedSpec().Kind)
518+
case "dummy1.100":
519+
asrt.True(r.TypedSpec().Up)
520+
asrt.True(r.TypedSpec().Logical)
521+
asrt.Equal(nethelpers.LinkEther, r.TypedSpec().Type)
522+
asrt.Equal(network.LinkKindVLAN, r.TypedSpec().Kind)
523+
asrt.Equal("dummy1", r.TypedSpec().ParentName)
524+
asrt.Equal(nethelpers.VLANProtocol8021AD, r.TypedSpec().VLAN.Protocol)
525+
asrt.EqualValues(100, r.TypedSpec().VLAN.VID)
526+
asrt.EqualValues(200, r.TypedSpec().MTU)
510527
}
511528
},
512529
)

internal/integration/api/network-config.go

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,6 @@ func (suite *NetworkConfigSuite) TestLinkAliasConfig() {
301301

302302
// TestVirtualIPConfig tests configuring virtual IPs.
303303
func (suite *NetworkConfigSuite) TestVirtualIPConfig() {
304-
suite.T().Skip("[TODO]: this test causes kube-apiserver to restart causing random failure")
305-
306304
if suite.Cluster == nil || suite.Cluster.Provisioner() != base.ProvisionerQEMU {
307305
suite.T().Skip("skipping if cluster is not qemu")
308306
}
@@ -328,12 +326,13 @@ func (suite *NetworkConfigSuite) TestVirtualIPConfig() {
328326

329327
suite.Require().NotEmpty(linkName, "expected to find at least one physical link")
330328

331-
virtualIP := "fd13:1234::34"
329+
// using link-local address to avoid kube-apiserver picking it up
330+
virtualIP := "169.254.100.100"
332331

333332
cfg := network.NewLayer2VIPConfigV1Alpha1(virtualIP)
334333
cfg.LinkName = linkName
335334

336-
addressID := linkName + "/" + virtualIP + "/128"
335+
addressID := linkName + "/" + virtualIP + "/32"
337336

338337
suite.PatchMachineConfig(nodeCtx, cfg)
339338

@@ -348,6 +347,88 @@ func (suite *NetworkConfigSuite) TestVirtualIPConfig() {
348347
rtestutils.AssertNoResource[*networkres.AddressStatus](nodeCtx, suite.T(), suite.Client.COSI, addressID)
349348
}
350349

350+
// TestVLANConfig tests creation of VLAN interfaces.
351+
func (suite *NetworkConfigSuite) TestVLANConfig() {
352+
if suite.Cluster == nil {
353+
suite.T().Skip("skipping if cluster is not qemu/docker")
354+
}
355+
356+
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker)
357+
nodeCtx := client.WithNode(suite.ctx, node)
358+
359+
suite.T().Logf("testing on node %q", node)
360+
361+
dummyName := fmt.Sprintf("dummy%d", rand.IntN(10000))
362+
363+
dummy := network.NewDummyLinkConfigV1Alpha1(dummyName)
364+
dummy.LinkUp = pointer.To(true)
365+
dummy.LinkMTU = 9000
366+
367+
vlanName := dummyName + ".v"
368+
369+
vlan := network.NewVLANConfigV1Alpha1(vlanName)
370+
vlan.VLANIDConfig = 100
371+
vlan.LinkMTU = 2000
372+
vlan.ParentLinkConfig = dummyName
373+
vlan.LinkAddresses = []network.AddressConfig{
374+
{
375+
AddressAddress: netip.MustParsePrefix("fd13:1234::1/64"),
376+
AddressPriority: pointer.To[uint32](100),
377+
},
378+
}
379+
vlan.LinkRoutes = []network.RouteConfig{
380+
{
381+
RouteDestination: network.Prefix{Prefix: netip.MustParsePrefix("fd13:1235::/64")},
382+
RouteGateway: network.Addr{Addr: netip.MustParseAddr("fd13:1234::ffff")},
383+
},
384+
}
385+
386+
addressID := vlanName + "/fd13:1234::1/64"
387+
routeID := vlanName + "/inet6/fd13:1234::ffff/fd13:1235::/64/1024"
388+
addressRouteID := vlanName + "/inet6//fd13:1234::/64/100"
389+
390+
suite.PatchMachineConfig(nodeCtx, dummy, vlan)
391+
392+
rtestutils.AssertResource(nodeCtx, suite.T(), suite.Client.COSI, dummyName,
393+
func(link *networkres.LinkStatus, asrt *assert.Assertions) {
394+
asrt.Equal("dummy", link.TypedSpec().Kind)
395+
asrt.Equal(dummy.LinkMTU, link.TypedSpec().MTU)
396+
},
397+
)
398+
399+
rtestutils.AssertResource(nodeCtx, suite.T(), suite.Client.COSI, vlanName,
400+
func(link *networkres.LinkStatus, asrt *assert.Assertions) {
401+
asrt.Equal("vlan", link.TypedSpec().Kind)
402+
asrt.Equal(vlan.LinkMTU, link.TypedSpec().MTU)
403+
asrt.NotZero(link.TypedSpec().LinkIndex)
404+
asrt.Equal(vlan.VLANIDConfig, link.TypedSpec().VLAN.VID)
405+
asrt.Equal(nethelpers.VLANProtocol8021Q, link.TypedSpec().VLAN.Protocol)
406+
},
407+
)
408+
409+
rtestutils.AssertResource(nodeCtx, suite.T(), suite.Client.COSI, addressID,
410+
func(addr *networkres.AddressStatus, asrt *assert.Assertions) {
411+
asrt.Equal(vlanName, addr.TypedSpec().LinkName)
412+
},
413+
)
414+
415+
rtestutils.AssertResources(nodeCtx, suite.T(), suite.Client.COSI,
416+
[]resource.ID{routeID, addressRouteID},
417+
func(route *networkres.RouteStatus, asrt *assert.Assertions) {
418+
asrt.Equal(vlanName, route.TypedSpec().OutLinkName)
419+
},
420+
)
421+
422+
suite.RemoveMachineConfigDocumentsByName(nodeCtx, network.VLANKind, vlanName)
423+
suite.RemoveMachineConfigDocumentsByName(nodeCtx, network.DummyLinkKind, dummyName)
424+
425+
rtestutils.AssertNoResource[*networkres.LinkStatus](nodeCtx, suite.T(), suite.Client.COSI, dummyName)
426+
rtestutils.AssertNoResource[*networkres.LinkStatus](nodeCtx, suite.T(), suite.Client.COSI, vlanName)
427+
rtestutils.AssertNoResource[*networkres.AddressStatus](nodeCtx, suite.T(), suite.Client.COSI, addressID)
428+
rtestutils.AssertNoResource[*networkres.RouteStatus](nodeCtx, suite.T(), suite.Client.COSI, addressRouteID)
429+
rtestutils.AssertNoResource[*networkres.RouteStatus](nodeCtx, suite.T(), suite.Client.COSI, routeID)
430+
}
431+
351432
func init() {
352433
allSuites = append(allSuites, new(NetworkConfigSuite))
353434
}

pkg/machinery/config/config/machine.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ type Vlan interface {
284284
DHCP() bool
285285
ID() uint16
286286
MTU() uint32
287+
Mode() nethelpers.VLANProtocol
287288
VIPConfig() VIPConfig
288289
DHCPOptions() DHCPOptions
289290
}

pkg/machinery/config/config/network.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,13 @@ type NetworkHCloudVIPConfig interface {
209209
NetworkVirtualIPConfig
210210
HCloudAPIToken() string
211211
}
212+
213+
// NetworkVLANConfig defines a VLAN link configuration.
214+
type NetworkVLANConfig interface {
215+
NamedDocument
216+
NetworkCommonLinkConfig
217+
VLANConfig()
218+
VLANID() uint16
219+
ParentLink() string
220+
VLANMode() optional.Optional[nethelpers.VLANProtocol]
221+
}

pkg/machinery/config/schemas/config.schema.json

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,103 @@
17631763
],
17641764
"description": "StaticHostConfig is a config document to set /etc/hosts entries."
17651765
},
1766+
"network.VLANConfigV1Alpha1": {
1767+
"properties": {
1768+
"apiVersion": {
1769+
"enum": [
1770+
"v1alpha1"
1771+
],
1772+
"title": "apiVersion",
1773+
"description": "apiVersion is the API version of the resource.\n",
1774+
"markdownDescription": "apiVersion is the API version of the resource.",
1775+
"x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
1776+
},
1777+
"kind": {
1778+
"enum": [
1779+
"VLANConfig"
1780+
],
1781+
"title": "kind",
1782+
"description": "kind is the kind of the resource.\n",
1783+
"markdownDescription": "kind is the kind of the resource.",
1784+
"x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
1785+
},
1786+
"name": {
1787+
"type": "string",
1788+
"title": "name",
1789+
"description": "Name of the VLAN link (interface) to be created.\n",
1790+
"markdownDescription": "Name of the VLAN link (interface) to be created.",
1791+
"x-intellij-html-description": "\u003cp\u003eName of the VLAN link (interface) to be created.\u003c/p\u003e\n"
1792+
},
1793+
"vlanID": {
1794+
"type": "integer",
1795+
"title": "vlanID",
1796+
"description": "VLAN ID to be used for the VLAN link.\n",
1797+
"markdownDescription": "VLAN ID to be used for the VLAN link.",
1798+
"x-intellij-html-description": "\u003cp\u003eVLAN ID to be used for the VLAN link.\u003c/p\u003e\n"
1799+
},
1800+
"vlanMode": {
1801+
"enum": [
1802+
"802.1q",
1803+
"802.1ad"
1804+
],
1805+
"title": "vlanMode",
1806+
"description": "Set the VLAN mode to use.\nIf not set, defaults to ‘802.1q’.\n",
1807+
"markdownDescription": "Set the VLAN mode to use.\nIf not set, defaults to '802.1q'.",
1808+
"x-intellij-html-description": "\u003cp\u003eSet the VLAN mode to use.\nIf not set, defaults to \u0026lsquo;802.1q\u0026rsquo;.\u003c/p\u003e\n"
1809+
},
1810+
"parent": {
1811+
"type": "string",
1812+
"title": "parent",
1813+
"description": "Name of the parent link (interface) on which the VLAN link will be created.\n",
1814+
"markdownDescription": "Name of the parent link (interface) on which the VLAN link will be created.",
1815+
"x-intellij-html-description": "\u003cp\u003eName of the parent link (interface) on which the VLAN link will be created.\u003c/p\u003e\n"
1816+
},
1817+
"up": {
1818+
"type": "boolean",
1819+
"title": "up",
1820+
"description": "Bring the link up or down.\n\nIf not specified, the link will be brought up.\n",
1821+
"markdownDescription": "Bring the link up or down.\n\nIf not specified, the link will be brought up.",
1822+
"x-intellij-html-description": "\u003cp\u003eBring the link up or down.\u003c/p\u003e\n\n\u003cp\u003eIf not specified, the link will be brought up.\u003c/p\u003e\n"
1823+
},
1824+
"mtu": {
1825+
"type": "integer",
1826+
"title": "mtu",
1827+
"description": "Configure LinkMTU (Maximum Transmission Unit) for the link.\n\nIf not specified, the system default LinkMTU will be used (usually 1500).\n",
1828+
"markdownDescription": "Configure LinkMTU (Maximum Transmission Unit) for the link.\n\nIf not specified, the system default LinkMTU will be used (usually 1500).",
1829+
"x-intellij-html-description": "\u003cp\u003eConfigure LinkMTU (Maximum Transmission Unit) for the link.\u003c/p\u003e\n\n\u003cp\u003eIf not specified, the system default LinkMTU will be used (usually 1500).\u003c/p\u003e\n"
1830+
},
1831+
"addresses": {
1832+
"items": {
1833+
"$ref": "#/$defs/network.AddressConfig"
1834+
},
1835+
"type": "array",
1836+
"title": "addresses",
1837+
"description": "Configure addresses to be statically assigned to the link.\n",
1838+
"markdownDescription": "Configure addresses to be statically assigned to the link.",
1839+
"x-intellij-html-description": "\u003cp\u003eConfigure addresses to be statically assigned to the link.\u003c/p\u003e\n"
1840+
},
1841+
"routes": {
1842+
"items": {
1843+
"$ref": "#/$defs/network.RouteConfig"
1844+
},
1845+
"type": "array",
1846+
"title": "routes",
1847+
"description": "Configure routes to be statically created via the link.\n",
1848+
"markdownDescription": "Configure routes to be statically created via the link.",
1849+
"x-intellij-html-description": "\u003cp\u003eConfigure routes to be statically created via the link.\u003c/p\u003e\n"
1850+
}
1851+
},
1852+
"additionalProperties": false,
1853+
"type": "object",
1854+
"required": [
1855+
"apiVersion",
1856+
"kind",
1857+
"name",
1858+
"parent",
1859+
"vlanID"
1860+
],
1861+
"description": "VLANConfig is a config document to create a VLAN (virtual LAN) over a parent link."
1862+
},
17661863
"runtime.EventSinkV1Alpha1": {
17671864
"properties": {
17681865
"apiVersion": {
@@ -5001,6 +5098,9 @@
50015098
{
50025099
"$ref": "#/$defs/network.StaticHostConfigV1Alpha1"
50035100
},
5101+
{
5102+
"$ref": "#/$defs/network.VLANConfigV1Alpha1"
5103+
},
50045104
{
50055105
"$ref": "#/$defs/runtime.EventSinkV1Alpha1"
50065106
},

pkg/machinery/config/types/network/deep_copy.generated.go

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/machinery/config/types/network/link.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func conflictingLinkKinds(selfKind string) []string {
4040
return xslices.Filter([]string{
4141
DummyLinkKind,
4242
LinkKind,
43+
VLANKind,
4344
}, func(kind string) bool {
4445
return kind != selfKind
4546
})

0 commit comments

Comments
 (0)