Skip to content

Commit

Permalink
Add new network configurers to bring up interfaces
Browse files Browse the repository at this point in the history
Currently _bring_up_interfaces() is a no-op for any distro using
renderers. We need to be able to support bringing up a single
interfaces, a list of interfaces, and all interfaces. This should be
independent of the renderers, as the network config is often
generated independent of the mechanism used to apply it.

Additionally, I included a refactor to remove
"_supported_write_network_config". We had a confusing call chain of
apply_network_config->_write_network_config->_supported_write_network_config.
The last two have been combined.
  • Loading branch information
TheRealFalcon committed Jun 14, 2021
1 parent 05b0e35 commit eb7d43d
Show file tree
Hide file tree
Showing 19 changed files with 257 additions and 117 deletions.
3 changes: 0 additions & 3 deletions cloudinit/cmd/devel/net_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,6 @@ def handle_args(name, args):
pre_ns = ovf.get_network_config_from_conf(config, False)

ns = network_state.parse_net_config_data(pre_ns)
if not ns:
raise RuntimeError("No valid network_state object created from"
" input data")

if args.debug:
sys.stderr.write('\n'.join(
Expand Down
39 changes: 17 additions & 22 deletions cloudinit/distros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
import string
import urllib.parse
from io import StringIO
from typing import Any, Mapping

from cloudinit import importer
from cloudinit import log as logging
from cloudinit import net
from cloudinit.net import configurers
from cloudinit.net import eni
from cloudinit.net import network_state
from cloudinit.net import renderers
from cloudinit.net.network_state import parse_net_config_data
from cloudinit import persistence
from cloudinit import ssh_util
from cloudinit import type_utils
Expand Down Expand Up @@ -71,7 +74,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
hostname_conf_fn = "/etc/hostname"
tz_zone_dir = "/usr/share/zoneinfo"
init_cmd = ['service'] # systemctl, service etc
renderer_configs = {}
renderer_configs = {} # type: Mapping[str, Mapping[str, Any]]
_preferred_ntp_clients = None
networking_cls = LinuxNetworking
# This is used by self.shutdown_command(), and can be overridden in
Expand Down Expand Up @@ -104,23 +107,20 @@ def install_packages(self, pkglist):
raise NotImplementedError()

def _write_network(self, settings):
raise RuntimeError(
"""Deprecated. Remove if/when arch and gentoo support renderers."""
raise NotImplementedError(
"Legacy function '_write_network' was called in distro '%s'.\n"
"_write_network_config needs implementation.\n" % self.name)

def _write_network_config(self, settings):
raise NotImplementedError()

def _supported_write_network_config(self, network_config):
def _write_network_state(self, network_state):
priority = util.get_cfg_by_path(
self._cfg, ('network', 'renderers'), None)

name, render_cls = renderers.select(priority=priority)
LOG.debug("Selected renderer '%s' from priority list: %s",
name, priority)
renderer = render_cls(config=self.renderer_configs.get(name))
renderer.render_network_config(network_config)
return []
renderer.render_network_state(network_state)

def _find_tz_file(self, tz):
tz_file = os.path.join(self.tz_zone_dir, str(tz))
Expand Down Expand Up @@ -172,6 +172,7 @@ def get_package_mirror_info(self, arch=None, data_source=None):
mirror_info=arch_info)

def apply_network(self, settings, bring_up=True):
"""Deprecated. Remove if/when arch and gentoo support renderers."""
# this applies network where 'settings' is interfaces(5) style
# it is obsolete compared to apply_network_config
# Write it out
Expand All @@ -186,6 +187,7 @@ def apply_network(self, settings, bring_up=True):
return False

def _apply_network_from_network_config(self, netconfig, bring_up=True):
"""Deprecated. Remove if/when arch and gentoo support renderers."""
distro = self.__class__
LOG.warning("apply_network_config is not currently implemented "
"for distribution '%s'. Attempting to use apply_network",
Expand All @@ -206,16 +208,18 @@ def apply_network_config(self, netconfig, bring_up=False):
# apply network config netconfig
# This method is preferred to apply_network which only takes
# a much less complete network config format (interfaces(5)).
network_state = parse_net_config_data(netconfig)
try:
dev_names = self._write_network_config(netconfig)
self._write_network_state(network_state)
except NotImplementedError:
# backwards compat until all distros have apply_network_config
return self._apply_network_from_network_config(
netconfig, bring_up=bring_up)

# Now try to bring them up
if bring_up:
return self._bring_up_interfaces(dev_names)
network_configurer = configurers.select_configurer()
network_configurer.bring_up_all_interfaces(network_state)
return False

def apply_network_config_names(self, netconfig):
Expand Down Expand Up @@ -391,20 +395,11 @@ def preferred_ntp_clients(self):
return self._preferred_ntp_clients

def _bring_up_interface(self, device_name):
cmd = ['ifup', device_name]
LOG.debug("Attempting to run bring up interface %s using command %s",
device_name, cmd)
try:
(_out, err) = subp.subp(cmd)
if len(err):
LOG.warning("Running %s resulted in stderr output: %s",
cmd, err)
return True
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
return False
"""Deprecated. Remove if/when arch and gentoo support renderers."""
raise NotImplementedError

def _bring_up_interfaces(self, device_names):
"""Deprecated. Remove if/when arch and gentoo support renderers."""
am_failed = 0
for d in device_names:
if not self._bring_up_interface(d):
Expand Down
13 changes: 0 additions & 13 deletions cloudinit/distros/alpine.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,6 @@ def install_packages(self, pkglist):
self.update_package_sources()
self.package_command('add', pkgs=pkglist)

def _write_network_config(self, netconfig):
return self._supported_write_network_config(netconfig)

def _bring_up_interfaces(self, device_names):
use_all = False
for d in device_names:
if d == 'all':
use_all = True
if use_all:
return distros.Distro._bring_up_interface(self, '-a')
else:
return distros.Distro._bring_up_interfaces(self, device_names)

def _write_hostname(self, your_hostname, out_fn):
conf = None
try:
Expand Down
4 changes: 2 additions & 2 deletions cloudinit/distros/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ def install_packages(self, pkglist):
self.update_package_sources()
self.package_command('', pkgs=pkglist)

def _write_network_config(self, netconfig):
def _write_network_state(self, network_state):
try:
return self._supported_write_network_config(netconfig)
super()._write_network_state(network_state)
except RendererNotFoundError as e:
# Fall back to old _write_network
raise NotImplementedError from e
Expand Down
3 changes: 0 additions & 3 deletions cloudinit/distros/bsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,6 @@ def package_command(self, command, args=None, pkgs=None):
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, env=self._get_pkg_cmd_environ(), capture=False)

def _write_network_config(self, netconfig):
return self._supported_write_network_config(netconfig)

def set_timezone(self, tz):
distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))

Expand Down
14 changes: 2 additions & 12 deletions cloudinit/distros/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,9 @@ def install_packages(self, pkglist):
self.update_package_sources()
self.package_command('install', pkgs=pkglist)

def _write_network_config(self, netconfig):
def _write_network_state(self, network_state):
_maybe_remove_legacy_eth0()
return self._supported_write_network_config(netconfig)

def _bring_up_interfaces(self, device_names):
use_all = False
for d in device_names:
if d == 'all':
use_all = True
if use_all:
return distros.Distro._bring_up_interface(self, '--all')
else:
return distros.Distro._bring_up_interfaces(self, device_names)
return super()._write_network_state(network_state)

def _write_hostname(self, your_hostname, out_fn):
conf = None
Expand Down
9 changes: 0 additions & 9 deletions cloudinit/distros/opensuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,6 @@ def update_package_sources(self):
self._runner.run("update-sources", self.package_command,
['refresh'], freq=PER_INSTANCE)

def _bring_up_interfaces(self, device_names):
if device_names and 'all' in device_names:
raise RuntimeError(('Distro %s can not translate '
'the device name "all"') % (self.name))
return distros.Distro._bring_up_interfaces(self, device_names)

def _read_hostname(self, filename, default=None):
if self.uses_systemd() and filename.endswith('/previous-hostname'):
return util.load_file(filename).strip()
Expand Down Expand Up @@ -175,9 +169,6 @@ def _write_hostname(self, hostname, out_fn):
conf.set_hostname(hostname)
util.write_file(out_fn, str(conf), 0o644)

def _write_network_config(self, netconfig):
return self._supported_write_network_config(netconfig)

@property
def preferred_ntp_clients(self):
"""The preferred ntp client is dependent on the version."""
Expand Down
9 changes: 0 additions & 9 deletions cloudinit/distros/rhel.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ def __init__(self, name, cfg, paths):
def install_packages(self, pkglist):
self.package_command('install', pkgs=pkglist)

def _write_network_config(self, netconfig):
return self._supported_write_network_config(netconfig)

def apply_locale(self, locale, out_fn=None):
if self.uses_systemd():
if not out_fn:
Expand Down Expand Up @@ -118,12 +115,6 @@ def _read_hostname(self, filename, default=None):
else:
return default

def _bring_up_interfaces(self, device_names):
if device_names and 'all' in device_names:
raise RuntimeError(('Distro %s can not translate '
'the device name "all"') % (self.name))
return distros.Distro._bring_up_interfaces(self, device_names)

def set_timezone(self, tz):
tz_file = self._find_tz_file(tz)
if self.uses_systemd():
Expand Down
31 changes: 31 additions & 0 deletions cloudinit/net/configurer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This file is part of cloud-init. See LICENSE file for license information.
from abc import ABC, abstractmethod
from typing import Iterable

from cloudinit.net.network_state import NetworkState


class NetworkConfigurer(ABC):
@staticmethod
@abstractmethod
def available() -> bool:
raise NotImplementedError()

@staticmethod
@abstractmethod
def bring_up_interface(device_name: str) -> bool:
raise NotImplementedError()

@classmethod
def bring_up_interfaces(cls, device_names: Iterable[str]) -> bool:
all_succeeded = True
for device in device_names:
if not cls.bring_up_interface(device):
all_succeeded = False
return all_succeeded

@classmethod
def bring_up_all_interfaces(cls, network_state: NetworkState) -> bool:
return cls.bring_up_interfaces(
[i['name'] for i in network_state.iter_interfaces()]
)
48 changes: 48 additions & 0 deletions cloudinit/net/configurers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This file is part of cloud-init. See LICENSE file for license information.

# This file is mostly copied and pasted from renderers.py. An abstract
# version to encompass both seems overkill at this point
from typing import List, Type

from cloudinit.net.configurer import NetworkConfigurer
from cloudinit.net.ifupdown import IfUpDownConfigurer
from cloudinit.net.netplan import NetplanConfigurer
from cloudinit.net.network_manager import NetworkManagerConfigurer

DEFAULT_PRIORITY = [
IfUpDownConfigurer,
NetworkManagerConfigurer,
NetplanConfigurer,
]


def search_configurer(
priority=None, target=None
) -> List[Type[NetworkConfigurer]]:
if priority is None:
priority = DEFAULT_PRIORITY

unknown = [i for i in priority if i not in DEFAULT_PRIORITY]
if unknown:
raise ValueError(
"Unknown configurers provided in priority list: %s" % unknown)

found = []
for configurer in priority:
if configurer.available(target):
found.append(configurer)
return found


def select_configurer(priority=None, target=None) -> Type[NetworkConfigurer]:
found = search_configurer(priority, target)
if not found:
if priority is None:
priority = DEFAULT_PRIORITY
tmsg = ""
if target and target != "/":
tmsg = " in target=%s" % target
raise ValueError(
"No available network configurers found%s. Searched "
"through list: %s" % (tmsg, priority))
return found[0]
35 changes: 35 additions & 0 deletions cloudinit/net/ifupdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging

from cloudinit import subp
from cloudinit import util
from cloudinit.net.configurer import NetworkConfigurer
from cloudinit.net.eni import available as eni_available

LOG = logging.getLogger(__name__)


class IfUpDownConfigurer(NetworkConfigurer):
# Note that we're not overriding bring_up_interfaces to pass something
# like ifup --all because it isn't supported everywhere.
# E.g., NetworkManager has a ifupdown plugin that requires the name
# of a specific connection.
@staticmethod
def available(target=None) -> bool:
"""Return true if ifupdown can be used on this system."""
return eni_available(target=target)

@staticmethod
def bring_up_interface(device_name: str) -> bool:
"""Bring up interface using ifup."""
cmd = ['ifup', device_name]
LOG.debug("Attempting to run bring up interface %s using command %s",
device_name, cmd)
try:
(_out, err) = subp.subp(cmd)
if len(err):
LOG.warning("Running %s resulted in stderr output: %s",
cmd, err)
return True
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
return False
Loading

0 comments on commit eb7d43d

Please sign in to comment.