diff options
author | Alberto Contreras <alberto.contreras@canonical.com> | 2022-08-24 20:28:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-24 13:28:10 -0500 |
commit | f1d901c9b21fcf1073d663f4190badce662ff3da (patch) | |
tree | 48cbd97fd0d2dd059136233344d2d7952a7672c3 /cloudinit/net | |
parent | 5d12b43499ac6dac81af21aa96bd243729e6769c (diff) | |
download | cloud-init-git-f1d901c9b21fcf1073d663f4190badce662ff3da.tar.gz |
net: Passthough v2 netconfigs in netplan systems (#1650)
Adhere to Netplan Passthrough documented behavior,
not limiting v2 netplan configs to the subset of
props that cloud-init supports.
LP: #1978543
Diffstat (limited to 'cloudinit/net')
-rw-r--r-- | cloudinit/net/bsd.py | 9 | ||||
-rw-r--r-- | cloudinit/net/eni.py | 9 | ||||
-rw-r--r-- | cloudinit/net/netplan.py | 9 | ||||
-rw-r--r-- | cloudinit/net/network_manager.py | 9 | ||||
-rw-r--r-- | cloudinit/net/network_state.py | 64 | ||||
-rw-r--r-- | cloudinit/net/networkd.py | 9 | ||||
-rw-r--r-- | cloudinit/net/renderer.py | 13 | ||||
-rw-r--r-- | cloudinit/net/sysconfig.py | 9 |
8 files changed, 107 insertions, 24 deletions
diff --git a/cloudinit/net/bsd.py b/cloudinit/net/bsd.py index ff5c7413..e0f18366 100644 --- a/cloudinit/net/bsd.py +++ b/cloudinit/net/bsd.py @@ -1,11 +1,13 @@ # This file is part of cloud-init. See LICENSE file for license information. import re +from typing import Optional from cloudinit import log as logging from cloudinit import net, subp, util from cloudinit.distros import bsd_utils from cloudinit.distros.parsers.resolv_conf import ResolvConf +from cloudinit.net.network_state import NetworkState from . import renderer @@ -156,7 +158,12 @@ class BSDRenderer(renderer.Renderer): 0o644, ) - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: if target: self.target = target self._ifconfig_entries(settings=network_state) diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py index b0ec67bd..ea0b8e4a 100644 --- a/cloudinit/net/eni.py +++ b/cloudinit/net/eni.py @@ -4,10 +4,12 @@ import copy import glob import os import re +from typing import Optional from cloudinit import log as logging from cloudinit import subp, util from cloudinit.net import subnet_is_ipv6 +from cloudinit.net.network_state import NetworkState from . import ParserError, renderer @@ -561,7 +563,12 @@ class Renderer(renderer.Renderer): return "\n\n".join(["\n".join(s) for s in sections]) + "\n" - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: fpeni = subp.target_path(target, self.eni_path) util.ensure_dir(os.path.dirname(fpeni)) header = self.eni_header if self.eni_header else "" diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py index d63d86d8..7b91077d 100644 --- a/cloudinit/net/netplan.py +++ b/cloudinit/net/netplan.py @@ -3,7 +3,7 @@ import copy import os import textwrap -from typing import cast +from typing import Optional, cast from cloudinit import log as logging from cloudinit import safeyaml, subp, util @@ -240,7 +240,12 @@ class Renderer(renderer.Renderer): LOG.debug("Failed to list features from netplan info: %s", e) return self._features - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: # check network state for version # if v2, then extract network_state.config # else render_v2_from_state diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py index 5457d1b2..8053511c 100644 --- a/cloudinit/net/network_manager.py +++ b/cloudinit/net/network_manager.py @@ -11,10 +11,12 @@ import io import itertools import os import uuid +from typing import Optional from cloudinit import log as logging from cloudinit import subp, util from cloudinit.net import is_ipv6_address, subnet_is_ipv6 +from cloudinit.net.network_state import NetworkState from . import renderer @@ -342,7 +344,12 @@ class Renderer(renderer.Renderer): # Well, what can we do... return con_id - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: # First pass makes sure there's NMConnections for all known # interfaces that have UUIDs that can be linked to from related # interfaces diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py index 80f2b108..fd92bf0e 100644 --- a/cloudinit/net/network_state.py +++ b/cloudinit/net/network_state.py @@ -7,7 +7,7 @@ import copy import functools import logging -from typing import Any, Dict +from typing import TYPE_CHECKING, Any, Dict, Optional from cloudinit import safeyaml, util from cloudinit.net import ( @@ -22,6 +22,9 @@ from cloudinit.net import ( net_prefix_to_ipv4_mask, ) +if TYPE_CHECKING: + from cloudinit.net.renderer import Renderer + LOG = logging.getLogger(__name__) NETWORK_STATE_VERSION = 1 @@ -136,14 +139,16 @@ class CommandHandlerMeta(type): class NetworkState(object): - def __init__(self, network_state, version=NETWORK_STATE_VERSION): + def __init__( + self, network_state: dict, version: int = NETWORK_STATE_VERSION + ): self._network_state = copy.deepcopy(network_state) self._version = version self.use_ipv6 = network_state.get("use_ipv6", False) self._has_default_route = None @property - def config(self): + def config(self) -> dict: return self._network_state["config"] @property @@ -204,6 +209,20 @@ class NetworkState(object): route.get("prefix") == 0 and route.get("network") in default_nets ) + @classmethod + def to_passthrough(cls, network_state: dict) -> "NetworkState": + """Instantiates a `NetworkState` without interpreting its data. + + That means only `config` and `version` are copied. + + :param network_state: Network state data. + :return: Instance of `NetworkState`. + """ + kwargs = {} + if "version" in network_state: + kwargs["version"] = network_state["version"] + return cls({"config": network_state}, **kwargs) + class NetworkStateInterpreter(metaclass=CommandHandlerMeta): @@ -218,16 +237,27 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta): "config": None, } - def __init__(self, version=NETWORK_STATE_VERSION, config=None): + def __init__( + self, + version=NETWORK_STATE_VERSION, + config=None, + renderer=None, # type: Optional[Renderer] + ): self._version = version self._config = config self._network_state = copy.deepcopy(self.initial_network_state) self._network_state["config"] = config self._parsed = False - self._interface_dns_map = {} + self._interface_dns_map: dict = {} + self._renderer = renderer @property - def network_state(self): + def network_state(self) -> NetworkState: + from cloudinit.net.netplan import Renderer as NetplanRenderer + + if self._version == 2 and isinstance(self._renderer, NetplanRenderer): + LOG.debug("Passthrough netplan v2 config") + return NetworkState.to_passthrough(self._config) return NetworkState(self._network_state, version=self._version) @property @@ -268,10 +298,6 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta): def as_dict(self): return {"version": self._version, "config": self._config} - def get_network_state(self): - ns = self.network_state - return ns - def parse_config(self, skip_broken=True): if self._version == 1: self.parse_config_v1(skip_broken=skip_broken) @@ -316,6 +342,12 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta): } def parse_config_v2(self, skip_broken=True): + from cloudinit.net.netplan import Renderer as NetplanRenderer + + if isinstance(self._renderer, NetplanRenderer): + # Nothing to parse as we are going to perform a Netplan passthrough + return + for command_type, command in self._config.items(): if command_type in ["version", "renderer"]: continue @@ -1044,7 +1076,11 @@ def _normalize_subnets(subnets): return [_normalize_subnet(s) for s in subnets] -def parse_net_config_data(net_config, skip_broken=True) -> NetworkState: +def parse_net_config_data( + net_config: dict, + skip_broken: bool = True, + renderer=None, # type: Optional[Renderer] +) -> NetworkState: """Parses the config, returns NetworkState object :param net_config: curtin network config dict @@ -1058,9 +1094,11 @@ def parse_net_config_data(net_config, skip_broken=True) -> NetworkState: config = net_config if version and config is not None: - nsi = NetworkStateInterpreter(version=version, config=config) + nsi = NetworkStateInterpreter( + version=version, config=config, renderer=renderer + ) nsi.parse_config(skip_broken=skip_broken) - state = nsi.get_network_state() + state = nsi.network_state if not state: raise RuntimeError( diff --git a/cloudinit/net/networkd.py b/cloudinit/net/networkd.py index 7d7d82c2..4a52d5dd 100644 --- a/cloudinit/net/networkd.py +++ b/cloudinit/net/networkd.py @@ -8,9 +8,11 @@ # This file is part of cloud-init. See LICENSE file for license information. from collections import OrderedDict +from typing import Optional from cloudinit import log as logging from cloudinit import subp, util +from cloudinit.net.network_state import NetworkState from . import renderer @@ -217,7 +219,12 @@ class Renderer(renderer.Renderer): util.write_file(net_fn, conf) util.chownbyname(net_fn, net_fn_owner, net_fn_owner) - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: network_dir = self.network_conf_dir if target: network_dir = subp.target_path(target) + network_dir diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py index da154731..d7bc19b1 100644 --- a/cloudinit/net/renderer.py +++ b/cloudinit/net/renderer.py @@ -7,6 +7,7 @@ import abc import io +from typing import Optional from cloudinit.net.network_state import NetworkState, parse_net_config_data from cloudinit.net.udev import generate_udev_rule @@ -49,11 +50,19 @@ class Renderer(object): return content.getvalue() @abc.abstractmethod - def render_network_state(self, network_state, templates=None, target=None): + def render_network_state( + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: """Render network state.""" def render_network_config( - self, network_config, templates=None, target=None + self, + network_config: dict, + templates: Optional[dict] = None, + target=None, ): return self.render_network_state( network_state=parse_net_config_data(network_config), diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py index 698724ab..d5789fb0 100644 --- a/cloudinit/net/sysconfig.py +++ b/cloudinit/net/sysconfig.py @@ -4,7 +4,7 @@ import copy import io import os import re -from typing import Mapping +from typing import Mapping, Optional from cloudinit import log as logging from cloudinit import subp, util @@ -980,8 +980,11 @@ class Renderer(renderer.Renderer): return contents def render_network_state( - self, network_state: NetworkState, templates=None, target=None - ): + self, + network_state: NetworkState, + templates: Optional[dict] = None, + target=None, + ) -> None: if not templates: templates = self.templates file_mode = 0o644 |