Skip to content

Commit c8561ee

Browse files
committed
feat: implement bridge multi-document config
Fixes #10962 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent f4ad307 commit c8561ee

File tree

15 files changed

+1068
-7
lines changed

15 files changed

+1068
-7
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func (ctrl *LinkConfigController) processDevicesConfiguration(
356356
}
357357

358358
if device.Bridge() != nil {
359-
if err := SetBridgeMaster(linkMap[deviceInterface], device.Bridge()); err != nil {
359+
if err := SetBridgeMasterLegacy(linkMap[deviceInterface], device.Bridge()); err != nil {
360360
logger.Error("error parsing bridge config", zap.Error(err))
361361
}
362362
}
@@ -463,6 +463,22 @@ func (ctrl *LinkConfigController) processLinkConfigs(logger *zap.Logger, linkMap
463463

464464
SetBondSlave(linkMap[slaveLinkName], ordered.MakePair(linkName, idx))
465465
}
466+
case talosconfig.NetworkBridgeConfig:
467+
SetBridgeMaster(linkMap[linkName], specificLinkConfig)
468+
469+
bridgedLinks := xslices.Map(specificLinkConfig.Links(), linkNameResolver.Resolve)
470+
471+
for _, slaveLinkName := range bridgedLinks {
472+
if _, exists := linkMap[slaveLinkName]; !exists {
473+
linkMap[slaveLinkName] = &network.LinkSpecSpec{
474+
Name: slaveLinkName,
475+
Up: true,
476+
ConfigLayer: network.ConfigMachineConfiguration,
477+
}
478+
}
479+
480+
SetBridgeSlave(linkMap[slaveLinkName], linkName)
481+
}
466482
default:
467483
logger.Error("unknown link config type", zap.String("linkName", linkName), zap.String("type", fmt.Sprintf("%T", specificLinkConfig)))
468484
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,12 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
484484
bc1.BondLinks = []string{"dummy2", "dummy3"}
485485
bc1.BondUpDelay = pointer.To(uint32(200))
486486

487-
ctr, err := container.New(dc1, lc1, vl1, dc2, dc3, bc1)
487+
br1 := networkcfg.NewBridgeConfigV1Alpha1("br0")
488+
br1.BridgeLinks = []string{"enp0s2", "eth1"}
489+
br1.BridgeSTP.BridgeSTPEnabled = pointer.To(true)
490+
br1.BridgeVLAN.BridgeVLANFiltering = pointer.To(true)
491+
492+
ctr, err := container.New(dc1, lc1, vl1, dc2, dc3, bc1, br1)
488493
suite.Require().NoError(err)
489494

490495
cfg := config.NewMachineConfig(ctr)
@@ -513,6 +518,7 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
513518
"configuration/dummy3",
514519
"configuration/dummy1.100",
515520
"configuration/bond357",
521+
"configuration/br0",
516522
}, func(r *network.LinkSpec, asrt *assert.Assertions) {
517523
asrt.Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
518524

@@ -521,6 +527,10 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
521527
asrt.True(r.TypedSpec().Up)
522528
asrt.False(r.TypedSpec().Logical)
523529
asrt.EqualValues(9001, r.TypedSpec().MTU)
530+
asrt.Equal("br0", r.TypedSpec().BridgeSlave.MasterName)
531+
case "eth1":
532+
asrt.True(r.TypedSpec().Up)
533+
asrt.Equal("br0", r.TypedSpec().BridgeSlave.MasterName)
524534
case "dummy1", "dummy2", "dummy3":
525535
asrt.True(r.TypedSpec().Up)
526536
asrt.True(r.TypedSpec().Logical)
@@ -546,6 +556,13 @@ func (suite *LinkConfigSuite) TestMachineConfigurationNewStyle() {
546556
asrt.Equal(network.LinkKindBond, r.TypedSpec().Kind)
547557
asrt.Equal(nethelpers.BondModeActiveBackup, r.TypedSpec().BondMaster.Mode)
548558
asrt.EqualValues(200, r.TypedSpec().BondMaster.UpDelay)
559+
case "br0":
560+
asrt.True(r.TypedSpec().Up)
561+
asrt.True(r.TypedSpec().Logical)
562+
asrt.Equal(nethelpers.LinkEther, r.TypedSpec().Type)
563+
asrt.Equal(network.LinkKindBridge, r.TypedSpec().Kind)
564+
asrt.True(r.TypedSpec().BridgeMaster.STP.Enabled)
565+
asrt.True(r.TypedSpec().BridgeMaster.VLAN.FilteringEnabled)
549566
}
550567
},
551568
)

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ func SetBridgeSlave(link *network.LinkSpecSpec, bridge string) {
160160
}
161161
}
162162

163-
// SetBridgeMaster sets the bridge master spec.
164-
func SetBridgeMaster(link *network.LinkSpecSpec, bridge talosconfig.Bridge) error {
163+
// SetBridgeMasterLegacy sets the bridge master spec.
164+
func SetBridgeMasterLegacy(link *network.LinkSpecSpec, bridge talosconfig.Bridge) error {
165165
link.Logical = true
166166
link.Kind = network.LinkKindBridge
167167
link.Type = nethelpers.LinkEther
@@ -179,3 +179,19 @@ func SetBridgeMaster(link *network.LinkSpecSpec, bridge talosconfig.Bridge) erro
179179

180180
return nil
181181
}
182+
183+
// SetBridgeMaster sets the bridge master spec.
184+
func SetBridgeMaster(link *network.LinkSpecSpec, bridge talosconfig.NetworkBridgeConfig) {
185+
link.Logical = true
186+
link.Kind = network.LinkKindBridge
187+
link.Type = nethelpers.LinkEther
188+
189+
link.BridgeMaster = network.BridgeMasterSpec{
190+
STP: network.STPSpec{
191+
Enabled: bridge.STP().Enabled().ValueOrZero(),
192+
},
193+
VLAN: network.BridgeVLANSpec{
194+
FilteringEnabled: bridge.VLAN().FilteringEnabled().ValueOrZero(),
195+
},
196+
}
197+
}

internal/integration/api/network-config.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ func (suite *NetworkConfigSuite) TestBondConfig() {
494494
rtestutils.AssertResource(nodeCtx, suite.T(), suite.Client.COSI, bondName,
495495
func(link *networkres.LinkStatus, asrt *assert.Assertions) {
496496
asrt.Equal("bond", link.TypedSpec().Kind)
497+
asrt.Equal(nethelpers.OperStateUp, link.TypedSpec().OperationalState)
497498
asrt.Equal(bond.LinkMTU, link.TypedSpec().MTU)
498499
asrt.Equal(nethelpers.BondMode8023AD, link.TypedSpec().BondMaster.Mode)
499500
asrt.Equal(bond.HardwareAddressConfig, link.TypedSpec().HardwareAddr)
@@ -526,6 +527,60 @@ func (suite *NetworkConfigSuite) TestBondConfig() {
526527
rtestutils.AssertNoResource[*networkres.RouteStatus](nodeCtx, suite.T(), suite.Client.COSI, routeID)
527528
}
528529

530+
// TestBridgeConfig tests creation of bridge interfaces.
531+
func (suite *NetworkConfigSuite) TestBridgeConfig() {
532+
if suite.Cluster == nil {
533+
suite.T().Skip("skipping if cluster is not qemu/docker")
534+
}
535+
536+
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker)
537+
nodeCtx := client.WithNode(suite.ctx, node)
538+
539+
suite.T().Logf("testing on node %q", node)
540+
541+
dummyNames := xslices.Map([]int{0, 1}, func(int) string {
542+
return fmt.Sprintf("dummy%d", rand.IntN(10000))
543+
})
544+
545+
dummyConfigs := xslices.Map(dummyNames, func(name string) any {
546+
return network.NewDummyLinkConfigV1Alpha1(name)
547+
})
548+
549+
bridgeName := "bridge." + strconv.Itoa(rand.IntN(10000))
550+
551+
bridge := network.NewBridgeConfigV1Alpha1(bridgeName)
552+
bridge.BridgeLinks = dummyNames
553+
bridge.BridgeSTP.BridgeSTPEnabled = pointer.To(true)
554+
bridge.HardwareAddressConfig = nethelpers.HardwareAddr{0x02, 0x00, 0x00, 0x00, byte(rand.IntN(256)), byte(rand.IntN(256))}
555+
bridge.LinkUp = pointer.To(true)
556+
557+
suite.PatchMachineConfig(nodeCtx, append(dummyConfigs, bridge)...)
558+
559+
rtestutils.AssertResources(nodeCtx, suite.T(), suite.Client.COSI, dummyNames,
560+
func(link *networkres.LinkStatus, asrt *assert.Assertions) {
561+
asrt.Equal("dummy", link.TypedSpec().Kind)
562+
asrt.NotZero(link.TypedSpec().MasterIndex)
563+
},
564+
)
565+
566+
rtestutils.AssertResource(nodeCtx, suite.T(), suite.Client.COSI, bridgeName,
567+
func(link *networkres.LinkStatus, asrt *assert.Assertions) {
568+
asrt.Equal("bridge", link.TypedSpec().Kind)
569+
asrt.Equal(pointer.SafeDeref(bridge.BridgeSTP.BridgeSTPEnabled), link.TypedSpec().BridgeMaster.STP.Enabled)
570+
asrt.Equal(bridge.HardwareAddressConfig, link.TypedSpec().HardwareAddr)
571+
},
572+
)
573+
574+
suite.RemoveMachineConfigDocumentsByName(nodeCtx, network.BridgeKind, bridgeName)
575+
suite.RemoveMachineConfigDocumentsByName(nodeCtx, network.DummyLinkKind, dummyNames...)
576+
577+
for _, dummyName := range dummyNames {
578+
rtestutils.AssertNoResource[*networkres.LinkStatus](nodeCtx, suite.T(), suite.Client.COSI, dummyName)
579+
}
580+
581+
rtestutils.AssertNoResource[*networkres.LinkStatus](nodeCtx, suite.T(), suite.Client.COSI, bridgeName)
582+
}
583+
529584
func init() {
530585
allSuites = append(allSuites, new(NetworkConfigSuite))
531586
}

pkg/machinery/config/config/network.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,24 @@ type NetworkBondConfig interface {
257257
PeerNotifyDelay() optional.Optional[uint32]
258258
MissedMax() optional.Optional[uint8]
259259
}
260+
261+
// NetworkBridgeConfig defines a bridge link configuration.
262+
type NetworkBridgeConfig interface {
263+
NamedDocument
264+
NetworkCommonLinkConfig
265+
NetworkHardwareAddressConfig
266+
BridgeConfig()
267+
Links() []string
268+
STP() BridgeSTPConfig
269+
VLAN() BridgeVLANConfig
270+
}
271+
272+
// BridgeSTPConfig is a bridge STP (Spanning Tree Protocol) configuration.
273+
type BridgeSTPConfig interface {
274+
Enabled() optional.Optional[bool]
275+
}
276+
277+
// BridgeVLANConfig is a bridge VLAN configuration.
278+
type BridgeVLANConfig interface {
279+
FilteringEnabled() optional.Optional[bool]
280+
}

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

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,138 @@
10891089
],
10901090
"description": "BondConfig is a config document to create a bond (link aggregation) over a set of links."
10911091
},
1092+
"network.BridgeConfigV1Alpha1": {
1093+
"properties": {
1094+
"apiVersion": {
1095+
"enum": [
1096+
"v1alpha1"
1097+
],
1098+
"title": "apiVersion",
1099+
"description": "apiVersion is the API version of the resource.\n",
1100+
"markdownDescription": "apiVersion is the API version of the resource.",
1101+
"x-intellij-html-description": "\u003cp\u003eapiVersion is the API version of the resource.\u003c/p\u003e\n"
1102+
},
1103+
"kind": {
1104+
"enum": [
1105+
"BridgeConfig"
1106+
],
1107+
"title": "kind",
1108+
"description": "kind is the kind of the resource.\n",
1109+
"markdownDescription": "kind is the kind of the resource.",
1110+
"x-intellij-html-description": "\u003cp\u003ekind is the kind of the resource.\u003c/p\u003e\n"
1111+
},
1112+
"name": {
1113+
"type": "string",
1114+
"title": "name",
1115+
"description": "Name of the bridge link (interface) to be created.\n",
1116+
"markdownDescription": "Name of the bridge link (interface) to be created.",
1117+
"x-intellij-html-description": "\u003cp\u003eName of the bridge link (interface) to be created.\u003c/p\u003e\n"
1118+
},
1119+
"hardwareAddr": {
1120+
"type": "string",
1121+
"pattern": "^[0-9a-f:]+$",
1122+
"title": "hardwareAddr",
1123+
"description": "Override the hardware (MAC) address of the link.\n",
1124+
"markdownDescription": "Override the hardware (MAC) address of the link.",
1125+
"x-intellij-html-description": "\u003cp\u003eOverride the hardware (MAC) address of the link.\u003c/p\u003e\n"
1126+
},
1127+
"links": {
1128+
"items": {
1129+
"type": "string"
1130+
},
1131+
"type": "array",
1132+
"title": "links",
1133+
"description": "Names of the links (interfaces) to be aggregated.\nLink aliases can be used here as well.\n",
1134+
"markdownDescription": "Names of the links (interfaces) to be aggregated.\nLink aliases can be used here as well.",
1135+
"x-intellij-html-description": "\u003cp\u003eNames of the links (interfaces) to be aggregated.\nLink aliases can be used here as well.\u003c/p\u003e\n"
1136+
},
1137+
"stp": {
1138+
"$ref": "#/$defs/network.BridgeSTPConfig",
1139+
"title": "stp",
1140+
"description": "Bridge STP (Spanning Tree Protocol) configuration.\n",
1141+
"markdownDescription": "Bridge STP (Spanning Tree Protocol) configuration.",
1142+
"x-intellij-html-description": "\u003cp\u003eBridge STP (Spanning Tree Protocol) configuration.\u003c/p\u003e\n"
1143+
},
1144+
"vlan": {
1145+
"$ref": "#/$defs/network.BridgeVLANConfig",
1146+
"title": "vlan",
1147+
"description": "Bridge VLAN configuration.\n",
1148+
"markdownDescription": "Bridge VLAN configuration.",
1149+
"x-intellij-html-description": "\u003cp\u003eBridge VLAN configuration.\u003c/p\u003e\n"
1150+
},
1151+
"up": {
1152+
"type": "boolean",
1153+
"title": "up",
1154+
"description": "Bring the link up or down.\n\nIf not specified, the link will be brought up.\n",
1155+
"markdownDescription": "Bring the link up or down.\n\nIf not specified, the link will be brought up.",
1156+
"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"
1157+
},
1158+
"mtu": {
1159+
"type": "integer",
1160+
"title": "mtu",
1161+
"description": "Configure LinkMTU (Maximum Transmission Unit) for the link.\n\nIf not specified, the system default LinkMTU will be used (usually 1500).\n",
1162+
"markdownDescription": "Configure LinkMTU (Maximum Transmission Unit) for the link.\n\nIf not specified, the system default LinkMTU will be used (usually 1500).",
1163+
"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"
1164+
},
1165+
"addresses": {
1166+
"items": {
1167+
"$ref": "#/$defs/network.AddressConfig"
1168+
},
1169+
"type": "array",
1170+
"title": "addresses",
1171+
"description": "Configure addresses to be statically assigned to the link.\n",
1172+
"markdownDescription": "Configure addresses to be statically assigned to the link.",
1173+
"x-intellij-html-description": "\u003cp\u003eConfigure addresses to be statically assigned to the link.\u003c/p\u003e\n"
1174+
},
1175+
"routes": {
1176+
"items": {
1177+
"$ref": "#/$defs/network.RouteConfig"
1178+
},
1179+
"type": "array",
1180+
"title": "routes",
1181+
"description": "Configure routes to be statically created via the link.\n",
1182+
"markdownDescription": "Configure routes to be statically created via the link.",
1183+
"x-intellij-html-description": "\u003cp\u003eConfigure routes to be statically created via the link.\u003c/p\u003e\n"
1184+
}
1185+
},
1186+
"additionalProperties": false,
1187+
"type": "object",
1188+
"required": [
1189+
"apiVersion",
1190+
"kind",
1191+
"links",
1192+
"name"
1193+
],
1194+
"description": "BridgeConfig is a config document to create a Bridge (link aggregation) over a set of links."
1195+
},
1196+
"network.BridgeSTPConfig": {
1197+
"properties": {
1198+
"enabled": {
1199+
"type": "boolean",
1200+
"title": "enabled",
1201+
"description": "Enable or disable STP on the bridge.\n",
1202+
"markdownDescription": "Enable or disable STP on the bridge.",
1203+
"x-intellij-html-description": "\u003cp\u003eEnable or disable STP on the bridge.\u003c/p\u003e\n"
1204+
}
1205+
},
1206+
"additionalProperties": false,
1207+
"type": "object",
1208+
"description": "BridgeSTPConfig is a bridge STP (Spanning Tree Protocol) configuration."
1209+
},
1210+
"network.BridgeVLANConfig": {
1211+
"properties": {
1212+
"filtering": {
1213+
"type": "boolean",
1214+
"title": "filtering",
1215+
"description": "Enable or disable VLAN filtering on the bridge.\n",
1216+
"markdownDescription": "Enable or disable VLAN filtering on the bridge.",
1217+
"x-intellij-html-description": "\u003cp\u003eEnable or disable VLAN filtering on the bridge.\u003c/p\u003e\n"
1218+
}
1219+
},
1220+
"additionalProperties": false,
1221+
"type": "object",
1222+
"description": "BridgeVLANConfig is a bridge VLAN configuration."
1223+
},
10921224
"network.CommonLinkConfig": {
10931225
"properties": {
10941226
"up": {
@@ -5393,6 +5525,9 @@
53935525
{
53945526
"$ref": "#/$defs/network.BondConfigV1Alpha1"
53955527
},
5528+
{
5529+
"$ref": "#/$defs/network.BridgeConfigV1Alpha1"
5530+
},
53965531
{
53975532
"$ref": "#/$defs/network.DefaultActionConfigV1Alpha1"
53985533
},

0 commit comments

Comments
 (0)