Skip to content

Commit

Permalink
fix: Render bridges correctly for v2 on sysconfig with set-name
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 committed Sep 6, 2024
1 parent 8bc3e42 commit 6cf4d30
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 23 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 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
21 changes: 6 additions & 15 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", [])
name_cmd = {"type": "nameserver"}
if len(search) > 0:
name_cmd.update({"search": search})
name_cmd["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
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 @@ -705,7 +705,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 @@ -910,7 +910,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 @@ -991,23 +993,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 6cf4d30

Please sign in to comment.