From aa0cd62c81866d632522bbc54dc03eb4fa7fd913 Mon Sep 17 00:00:00 2001 From: Alberto Contreras Date: Thu, 27 Apr 2023 21:11:07 +0200 Subject: gce: activate network discovery on every boot (#2128) Google wants to allow users to make changes on nics while the instance is stopped. Activate network discovery on every boot. Additionally, skip the call to `netplan generate` if the rendered config is the same on subsequent boots. --- cloudinit/net/netplan.py | 28 +++++++++++++++++++++++----- cloudinit/net/renderer.py | 2 +- cloudinit/sources/DataSourceGCE.py | 7 +++++++ cloudinit/util.py | 14 +++++++++++++- 4 files changed, 44 insertions(+), 7 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py index 1c28e16e..8b52a641 100644 --- a/cloudinit/net/netplan.py +++ b/cloudinit/net/netplan.py @@ -1,6 +1,7 @@ # This file is part of cloud-init. See LICENSE file ... import copy +import io import ipaddress import os import textwrap @@ -277,31 +278,48 @@ class Renderer(renderer.Renderer): fpnplan = os.path.join(subp.target_path(target), self.netplan_path) util.ensure_dir(os.path.dirname(fpnplan)) - header = self.netplan_header if self.netplan_header else "" # render from state content = self._render_content(network_state) + # normalize header + header = self.netplan_header if self.netplan_header else "" if not header.endswith("\n"): header += "\n" + content = header + content - mode = 0o600 if features.NETPLAN_CONFIG_ROOT_READ_ONLY else 0o644 + # determine if existing config files have the same content + same_content = False if os.path.exists(fpnplan): + hashed_content = util.hash_buffer(io.BytesIO(content.encode())) + with open(fpnplan, "rb") as f: + hashed_original_content = util.hash_buffer(f) + if hashed_content == hashed_original_content: + same_content = True + + mode = 0o600 if features.NETPLAN_CONFIG_ROOT_READ_ONLY else 0o644 + if not same_content and os.path.exists(fpnplan): current_mode = util.get_permissions(fpnplan) if current_mode & mode == current_mode: # preserve mode if existing perms are more strict than default mode = current_mode - util.write_file(fpnplan, header + content, mode=mode) + util.write_file(fpnplan, content, mode=mode) if self.clean_default: _clean_default(target=target) - self._netplan_generate(run=self._postcmds) + self._netplan_generate(run=self._postcmds, same_content=same_content) self._net_setup_link(run=self._postcmds) - def _netplan_generate(self, run=False): + def _netplan_generate(self, run: bool = False, same_content: bool = False): if not run: LOG.debug("netplan generate postcmd disabled") return + if same_content: + LOG.debug( + "skipping call to `netplan generate`." + " reason: identical netplan config" + ) + return subp.subp(self.NETPLAN_GENERATE, capture=True) def _net_setup_link(self, run=False): diff --git a/cloudinit/net/renderer.py b/cloudinit/net/renderer.py index 72813e32..c429d068 100644 --- a/cloudinit/net/renderer.py +++ b/cloudinit/net/renderer.py @@ -24,7 +24,7 @@ def filter_by_attr(match_name): filter_by_physical = filter_by_type("physical") -class Renderer: +class Renderer(abc.ABC): def __init__(self, config=None): pass diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py index 041c8914..27d6089a 100644 --- a/cloudinit/sources/DataSourceGCE.py +++ b/cloudinit/sources/DataSourceGCE.py @@ -11,6 +11,7 @@ from cloudinit import dmi from cloudinit import log as logging from cloudinit import sources, url_helper, util from cloudinit.distros import ug_util +from cloudinit.event import EventScope, EventType from cloudinit.net.ephemeral import EphemeralDHCPv4 from cloudinit.sources import DataSourceHostname @@ -63,6 +64,12 @@ class DataSourceGCE(sources.DataSource): dsname = "GCE" perform_dhcp_setup = False + default_update_events = { + EventScope.NETWORK: { + EventType.BOOT_NEW_INSTANCE, + EventType.BOOT, + } + } def __init__(self, sys_cfg, distro, paths): sources.DataSource.__init__(self, sys_cfg, distro, paths) diff --git a/cloudinit/util.py b/cloudinit/util.py index fc777b82..2eb79d33 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1725,7 +1725,7 @@ def logexc(log, msg, *args): log.debug(msg, exc_info=exc_info, *args) -def hash_blob(blob, routine, mlen=None): +def hash_blob(blob, routine: str, mlen=None) -> str: hasher = hashlib.new(routine) hasher.update(encode_text(blob)) digest = hasher.hexdigest() @@ -1736,6 +1736,18 @@ def hash_blob(blob, routine, mlen=None): return digest +def hash_buffer(f: io.BufferedIOBase) -> bytes: + """Hash the content of a binary buffer using SHA1. + + @param f: buffered binary stream to hash. + @return: digested data as bytes. + """ + hasher = hashlib.sha1() + for chunk in iter(lambda: f.read(io.DEFAULT_BUFFER_SIZE), b""): + hasher.update(chunk) + return hasher.digest() + + def is_user(name): try: if pwd.getpwnam(name): -- cgit v1.2.1