Skip to content

Commit

Permalink
fix: Render bridges correctly for v2 on sysconfig with set-name (#5674)
Browse files Browse the repository at this point in the history
When listing interfaces in v2 format, we should expect to be able to
reference other interfaces using the name in the configuration, not
the name the interface will eventually take. This was broken when
using `set-name`.

To fix this, we now store the configuration id alongside the eventual
name, and reference that instead of the name.

Fixes GH-5574
  • Loading branch information
TheRealFalcon authored Oct 11, 2024
1 parent 39f2742 commit a8f6940
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 27 deletions.
6 changes: 6 additions & 0 deletions cloudinit/net/eni.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
import os
import re
from contextlib import suppress
from typing import Optional

from cloudinit import performance, subp, util
Expand Down Expand Up @@ -421,6 +422,11 @@ def _render_route(self, route, indent=""):
return content

def _render_iface(self, iface, render_hwaddress=False):
iface = copy.deepcopy(iface)

# Remove irrelevant keys
with suppress(KeyError):
iface.pop("config_id")
sections = []
subnets = iface.get("subnets", {})
accept_ra = iface.pop("accept-ra", None)
Expand Down
29 changes: 10 additions & 19 deletions cloudinit/net/network_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ def handle_physical(self, command):
wakeonlan = util.is_true(wakeonlan)
iface.update(
{
"config_id": command.get("config_id"),
"name": command.get("name"),
"type": command.get("type"),
"mac_address": command.get("mac_address"),
Expand All @@ -424,7 +425,8 @@ def handle_physical(self, command):
"wakeonlan": wakeonlan,
}
)
self._network_state["interfaces"].update({command.get("name"): iface})
iface_key = command.get("config_id", command.get("name"))
self._network_state["interfaces"].update({iface_key: iface})
self.dump_network_state()

@ensure_command_keys(["name", "vlan_id", "vlan_link"])
Expand Down Expand Up @@ -712,6 +714,7 @@ def handle_ethernets(self, command):

for eth, cfg in command.items():
phy_cmd = {
"config_id": eth,
"type": "physical",
}
match = cfg.get("match", {})
Expand Down Expand Up @@ -800,26 +803,14 @@ def handle_wifis(self, command):
def _v2_common(self, cfg) -> None:
LOG.debug("v2_common: handling config:\n%s", cfg)
for iface, dev_cfg in cfg.items():
if "set-name" in dev_cfg:
set_name_iface = dev_cfg.get("set-name")
if set_name_iface:
iface = set_name_iface
if "nameservers" in dev_cfg:
search = dev_cfg.get("nameservers").get("search", [])
dns = dev_cfg.get("nameservers").get("addresses", [])
search = dev_cfg.get("nameservers").get("search")
dns = dev_cfg.get("nameservers").get("addresses")
name_cmd = {"type": "nameserver"}
if len(search) > 0:
name_cmd.update({"search": search})
if len(dns) > 0:
name_cmd.update({"address": dns})

mac_address: Optional[str] = dev_cfg.get("match", {}).get(
"macaddress"
)
if mac_address:
real_if_name = find_interface_name_from_mac(mac_address)
if real_if_name:
iface = real_if_name
if search:
name_cmd["search"] = search
if dns:
name_cmd["address"] = dns

self._handle_individual_nameserver(name_cmd, iface)

Expand Down
24 changes: 16 additions & 8 deletions cloudinit/net/sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
import os
import re
from typing import Mapping, Optional
from typing import Dict, Optional

from cloudinit import subp, util
from cloudinit.distros.parsers import networkmanager_conf, resolv_conf
Expand Down Expand Up @@ -720,7 +720,7 @@ def _render_physical_interfaces(
):
physical_filter = renderer.filter_by_physical
for iface in network_state.iter_interfaces(physical_filter):
iface_name = iface["name"]
iface_name = iface.get("config_id") or iface["name"]
iface_subnets = iface.get("subnets", [])
iface_cfg = iface_contents[iface_name]
route_cfg = iface_cfg.routes
Expand Down Expand Up @@ -925,7 +925,9 @@ def _render_networkmanager_conf(network_state, templates=None):
return out

@classmethod
def _render_bridge_interfaces(cls, network_state, iface_contents, flavor):
def _render_bridge_interfaces(
cls, network_state: NetworkState, iface_contents, flavor
):
bridge_key_map = {
old_k: new_k
for old_k, new_k in cls.cfg_key_maps[flavor].items()
Expand Down Expand Up @@ -1006,23 +1008,29 @@ def _render_ib_interfaces(cls, network_state, iface_contents, flavor):

@classmethod
def _render_sysconfig(
cls, base_sysconf_dir, network_state, flavor, templates=None
cls,
base_sysconf_dir,
network_state: NetworkState,
flavor,
templates=None,
):
"""Given state, return /etc/sysconfig files + contents"""
if not templates:
templates = cls.templates
iface_contents: Mapping[str, NetInterface] = {}
iface_contents: Dict[str, NetInterface] = {}
for iface in network_state.iter_interfaces():
if iface["type"] == "loopback":
continue
iface_name = iface["name"]
iface_cfg = NetInterface(iface_name, base_sysconf_dir, templates)
config_id: str = iface.get("config_id") or iface["name"]
iface_cfg = NetInterface(
iface["name"], base_sysconf_dir, templates
)
if flavor == "suse":
iface_cfg.drop("DEVICE")
# If type detection fails it is considered a bug in SUSE
iface_cfg.drop("TYPE")
cls._render_iface_shared(iface, iface_cfg, flavor)
iface_contents[iface_name] = iface_cfg
iface_contents[config_id] = iface_cfg
cls._render_physical_interfaces(network_state, iface_contents, flavor)
cls._render_bond_interfaces(network_state, iface_contents, flavor)
cls._render_vlan_interfaces(network_state, iface_contents, flavor)
Expand Down
85 changes: 85 additions & 0 deletions tests/unittests/net/network_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4912,4 +4912,89 @@
"""
),
},
"v2-bridges-set-name": {
"yaml": textwrap.dedent(
"""\
version: 2
ethernets:
baremetalport:
match:
macaddress: 52:54:00:bd:8f:cb
set-name: baremetal0
provisioningport:
match:
macaddress: 52:54:00:25:ae:12
set-name: provisioning0
bridges:
baremetal:
addresses:
- fc00:1:1::2/64
interfaces:
- baremetalport
provisioning:
addresses:
- fc00:1:2::2/64
interfaces:
- provisioningport
"""
),
"expected_sysconfig_rhel": {
"ifcfg-baremetal": textwrap.dedent(
"""\
# Created by cloud-init automatically, do not edit.
#
BOOTPROTO=none
DEVICE=baremetal
IPV6ADDR=fc00:1:1::2/64
IPV6INIT=yes
IPV6_AUTOCONF=no
IPV6_FORCE_ACCEPT_RA=no
ONBOOT=yes
TYPE=Bridge
USERCTL=no
"""
),
"ifcfg-baremetal0": textwrap.dedent(
"""\
# Created by cloud-init automatically, do not edit.
#
BOOTPROTO=none
BRIDGE=baremetal
DEVICE=baremetal0
HWADDR=52:54:00:bd:8f:cb
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
"""
),
"ifcfg-provisioning": textwrap.dedent(
"""\
# Created by cloud-init automatically, do not edit.
#
BOOTPROTO=none
DEVICE=provisioning
IPV6ADDR=fc00:1:2::2/64
IPV6INIT=yes
IPV6_AUTOCONF=no
IPV6_FORCE_ACCEPT_RA=no
ONBOOT=yes
TYPE=Bridge
USERCTL=no
"""
),
"ifcfg-provisioning0": textwrap.dedent(
"""\
# Created by cloud-init automatically, do not edit.
#
BOOTPROTO=none
BRIDGE=provisioning
DEVICE=provisioning0
HWADDR=52:54:00:25:ae:12
ONBOOT=yes
TYPE=Ethernet
USERCTL=no
"""
),
},
},
}
1 change: 1 addition & 0 deletions tests/unittests/test_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,7 @@ def test_config_with_explicit_loopback(self):
("dhcpv6_stateful", "yaml"),
("wakeonlan_disabled", "yaml_v2"),
("wakeonlan_enabled", "yaml_v2"),
("v2-bridges-set-name", "yaml"),
pytest.param(
"v1-dns",
"yaml",
Expand Down

0 comments on commit a8f6940

Please sign in to comment.