summaryrefslogtreecommitdiff
path: root/cloudinit/sources
diff options
context:
space:
mode:
authorJames Falcon <james.falcon@canonical.com>2022-10-22 12:47:25 -0500
committerGitHub <noreply@github.com>2022-10-22 12:47:25 -0500
commite77c0bf84c867a05ce7947fc15548a2c8b261a24 (patch)
tree065f37c23794ce342c8e504ac4725f1bfd0175f5 /cloudinit/sources
parent41922bf0144ffe7ae3b3d3bc6378b921e076b3b1 (diff)
downloadcloud-init-git-e77c0bf84c867a05ce7947fc15548a2c8b261a24.tar.gz
Enable hotplug for LXD datasource (#1787)
When a NIC appears, check for a cloud-init.network-config and apply it.
Diffstat (limited to 'cloudinit/sources')
-rw-r--r--cloudinit/sources/DataSourceLXD.py97
-rw-r--r--cloudinit/sources/__init__.py6
2 files changed, 74 insertions, 29 deletions
diff --git a/cloudinit/sources/DataSourceLXD.py b/cloudinit/sources/DataSourceLXD.py
index 177137ab..d873cd3d 100644
--- a/cloudinit/sources/DataSourceLXD.py
+++ b/cloudinit/sources/DataSourceLXD.py
@@ -6,7 +6,6 @@ Notes:
still be detected on those images.
* Detect LXD datasource when /dev/lxd/sock is an active socket file.
* Info on dev-lxd API: https://linuxcontainers.org/lxd/docs/master/dev-lxd
- * TODO( Hotplug support using websockets API 1.0/events )
"""
import os
@@ -14,7 +13,7 @@ import socket
import stat
from enum import Flag, auto
from json.decoder import JSONDecodeError
-from typing import Any, Dict, Union, cast
+from typing import Any, Dict, List, Optional, Union, cast
import requests
from requests.adapters import HTTPAdapter
@@ -25,6 +24,7 @@ from urllib3.connectionpool import HTTPConnectionPool
from cloudinit import log as logging
from cloudinit import sources, subp, url_helper, util
+from cloudinit.net import find_fallback_nic
LOG = logging.getLogger(__name__)
@@ -43,18 +43,8 @@ CONFIG_KEY_ALIASES = {
}
-def generate_fallback_network_config() -> dict:
- """Return network config V1 dict representing instance network config."""
- network_v1: Dict[str, Any] = {
- "version": 1,
- "config": [
- {
- "type": "physical",
- "name": "eth0",
- "subnets": [{"type": "dhcp", "control": "auto"}],
- }
- ],
- }
+def _get_fallback_interface_name() -> str:
+ default_name = "eth0"
if subp.which("systemd-detect-virt"):
try:
virt_type, _ = subp.subp(["systemd-detect-virt"])
@@ -64,19 +54,43 @@ def generate_fallback_network_config() -> dict:
" Rendering default network config.",
err,
)
- return network_v1
+ return default_name
if virt_type.strip() in (
"kvm",
"qemu",
): # instance.type VIRTUAL-MACHINE
arch = util.system_info()["uname"][4]
if arch == "ppc64le":
- network_v1["config"][0]["name"] = "enp0s5"
+ return "enp0s5"
elif arch == "s390x":
- network_v1["config"][0]["name"] = "enc9"
+ return "enc9"
else:
- network_v1["config"][0]["name"] = "enp5s0"
- return network_v1
+ return "enp5s0"
+ return default_name
+
+
+def generate_network_config(
+ nics: Optional[List[str]] = None,
+) -> Dict[str, Any]:
+ """Return network config V1 dict representing instance network config."""
+ if not nics:
+ primary_nic = _get_fallback_interface_name()
+ elif len(nics) > 1:
+ fallback_nic = find_fallback_nic()
+ primary_nic = nics[0] if fallback_nic not in nics else fallback_nic
+ else:
+ primary_nic = nics[0]
+
+ return {
+ "version": 1,
+ "config": [
+ {
+ "type": "physical",
+ "name": primary_nic,
+ "subnets": [{"type": "dhcp", "control": "auto"}],
+ }
+ ],
+ }
class SocketHTTPConnection(HTTPConnection):
@@ -146,6 +160,12 @@ class DataSourceLXD(sources.DataSource):
"user.user-data",
)
+ skip_hotplug_detect = True
+
+ def _unpickle(self, ci_pkl_version: int) -> None:
+ super()._unpickle(ci_pkl_version)
+ self.skip_hotplug_detect = True
+
def _is_platform_viable(self) -> bool:
"""Check platform environment to report if this datasource may run."""
return is_platform_viable()
@@ -207,14 +227,33 @@ class DataSourceLXD(sources.DataSource):
if self._network_config == sources.UNSET:
if self._crawled_metadata == sources.UNSET:
self._get_data()
- if isinstance(
- self._crawled_metadata, dict
- ) and self._crawled_metadata.get("network-config"):
- self._network_config = self._crawled_metadata.get(
- "network-config", {}
- )
- else:
- self._network_config = generate_fallback_network_config()
+ if isinstance(self._crawled_metadata, dict):
+ if self._crawled_metadata.get("network-config"):
+ LOG.debug("LXD datasource using provided network config")
+ self._network_config = self._crawled_metadata[
+ "network-config"
+ ]
+ elif self._crawled_metadata.get("devices"):
+ # If no explicit network config, but we have net devices
+ # available to us, find the primary and set it up.
+ devices: List[str] = [
+ k
+ for k, v in self._crawled_metadata["devices"].items()
+ if v["type"] == "nic"
+ ]
+ LOG.debug(
+ "LXD datasource generating network config using "
+ "devices: %s",
+ ", ".join(devices),
+ )
+ self._network_config = generate_network_config(devices)
+ if self._network_config == sources.UNSET:
+ # We know nothing about network, so setup fallback
+ LOG.debug(
+ "LXD datasource generating network config using fallback."
+ )
+ self._network_config = generate_network_config()
+
return cast(dict, self._network_config)
@@ -338,13 +377,13 @@ class _MetaDataReader:
md.update(self._process_config(session))
if MetaDataKeys.DEVICES in metadata_keys:
url = url_helper.combine_url(self._version_url, "devices")
- md.update({"devices": _get_json_response(session, url)})
+ md["devices"] = _get_json_response(session, url)
return md
def read_metadata(
api_version: str = LXD_SOCKET_API_VERSION,
- metadata_keys: MetaDataKeys = MetaDataKeys.CONFIG | MetaDataKeys.META_DATA,
+ metadata_keys: MetaDataKeys = MetaDataKeys.ALL,
) -> dict:
"""Fetch metadata from the /dev/lxd/socket routes.
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 3a483c26..85e094ac 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -251,6 +251,10 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
"security-credentials",
)
+ # True on datasources that may not see hotplugged devices reflected
+ # in the updated metadata
+ skip_hotplug_detect = False
+
_ci_pkl_version = 1
def __init__(self, sys_cfg, distro: Distro, paths: Paths, ud_proc=None):
@@ -282,6 +286,8 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
self.vendordata2 = None
if not hasattr(self, "vendordata2_raw"):
self.vendordata2_raw = None
+ if not hasattr(self, "skip_hotplug_detect"):
+ self.skip_hotplug_detect = False
if hasattr(self, "userdata") and self.userdata is not None:
# If userdata stores MIME data, on < python3.6 it will be
# missing the 'policy' attribute that exists on >=python3.6.