summaryrefslogtreecommitdiff
path: root/cloudinit/net/activators.py
diff options
context:
space:
mode:
authorJames Falcon <therealfalcon@gmail.com>2021-07-01 14:43:07 -0500
committerGitHub <noreply@github.com>2021-07-01 14:43:07 -0500
commit81299de5fe3b6e491a965a6ebef66c6b8bf2c037 (patch)
treec4d2277a7f6240e306f6222769d349357510414a /cloudinit/net/activators.py
parent78e89b03ecb29e7df3181b1219a0b5f44b9d7532 (diff)
downloadvyos-cloud-init-81299de5fe3b6e491a965a6ebef66c6b8bf2c037.tar.gz
vyos-cloud-init-81299de5fe3b6e491a965a6ebef66c6b8bf2c037.zip
Add new network activators to bring up interfaces (#919)
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.
Diffstat (limited to 'cloudinit/net/activators.py')
-rw-r--r--cloudinit/net/activators.py156
1 files changed, 156 insertions, 0 deletions
diff --git a/cloudinit/net/activators.py b/cloudinit/net/activators.py
new file mode 100644
index 00000000..34fee3bf
--- /dev/null
+++ b/cloudinit/net/activators.py
@@ -0,0 +1,156 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+import logging
+import os
+from abc import ABC, abstractmethod
+from typing import Iterable, List, Type
+
+from cloudinit import subp
+from cloudinit import util
+from cloudinit.net.eni import available as eni_available
+from cloudinit.net.netplan import available as netplan_available
+from cloudinit.net.network_state import NetworkState
+from cloudinit.net.sysconfig import NM_CFG_FILE
+
+
+LOG = logging.getLogger(__name__)
+
+
+class NetworkActivator(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()]
+ )
+
+
+class IfUpDownActivator(NetworkActivator):
+ # 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
+
+
+class NetworkManagerActivator(NetworkActivator):
+ @staticmethod
+ def available(target=None) -> bool:
+ config_present = os.path.isfile(
+ subp.target_path(target, path=NM_CFG_FILE)
+ )
+ nmcli_present = subp.which('nmcli', target=target)
+ return config_present and bool(nmcli_present)
+
+ @staticmethod
+ def bring_up_interface(device_name: str) -> bool:
+ try:
+ subp.subp(['nmcli', 'connection', 'up', device_name])
+ except subp.ProcessExecutionError:
+ util.logexc(LOG, "nmcli failed to bring up {}".format(device_name))
+ return False
+ return True
+
+
+class NetplanActivator(NetworkActivator):
+ @staticmethod
+ def available(target=None) -> bool:
+ return netplan_available(target=target)
+
+ @staticmethod
+ def _apply_netplan():
+ LOG.debug('Applying current netplan config')
+ try:
+ subp.subp(['netplan', 'apply'], capture=True)
+ except subp.ProcessExecutionError:
+ util.logexc(LOG, "netplan apply failed")
+ return False
+ return True
+
+ @staticmethod
+ def bring_up_interface(device_name: str) -> bool:
+ LOG.debug("Calling 'netplan apply' rather than "
+ "bringing up individual interfaces")
+ return NetplanActivator._apply_netplan()
+
+ @staticmethod
+ def bring_up_interfaces(device_names: Iterable[str]) -> bool:
+ LOG.debug("Calling 'netplan apply' rather than "
+ "bringing up individual interfaces")
+ return NetplanActivator._apply_netplan()
+
+ @staticmethod
+ def bring_up_all_interfaces(network_state: NetworkState) -> bool:
+ return NetplanActivator._apply_netplan()
+
+
+# This section is mostly copied and pasted from renderers.py. An abstract
+# version to encompass both seems overkill at this point
+DEFAULT_PRIORITY = [
+ IfUpDownActivator,
+ NetworkManagerActivator,
+ NetplanActivator,
+]
+
+
+def search_activator(
+ priority=None, target=None
+) -> List[Type[NetworkActivator]]:
+ if priority is None:
+ priority = DEFAULT_PRIORITY
+
+ unknown = [i for i in priority if i not in DEFAULT_PRIORITY]
+ if unknown:
+ raise ValueError(
+ "Unknown activators provided in priority list: %s" % unknown)
+
+ return [activator for activator in priority if activator.available(target)]
+
+
+def select_activator(priority=None, target=None) -> Type[NetworkActivator]:
+ found = search_activator(priority, target)
+ if not found:
+ if priority is None:
+ priority = DEFAULT_PRIORITY
+ tmsg = ""
+ if target and target != "/":
+ tmsg = " in target=%s" % target
+ raise RuntimeError(
+ "No available network activators found%s. Searched "
+ "through list: %s" % (tmsg, priority))
+ return found[0]