summaryrefslogtreecommitdiff
path: root/cloudinit/net
diff options
context:
space:
mode:
authorAlberto Contreras <alberto.contreras@canonical.com>2022-08-24 20:28:10 +0200
committerGitHub <noreply@github.com>2022-08-24 13:28:10 -0500
commitf1d901c9b21fcf1073d663f4190badce662ff3da (patch)
tree48cbd97fd0d2dd059136233344d2d7952a7672c3 /cloudinit/net
parent5d12b43499ac6dac81af21aa96bd243729e6769c (diff)
downloadcloud-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.py9
-rw-r--r--cloudinit/net/eni.py9
-rw-r--r--cloudinit/net/netplan.py9
-rw-r--r--cloudinit/net/network_manager.py9
-rw-r--r--cloudinit/net/network_state.py64
-rw-r--r--cloudinit/net/networkd.py9
-rw-r--r--cloudinit/net/renderer.py13
-rw-r--r--cloudinit/net/sysconfig.py9
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