From 78f8e6657ba4fef04919dad889257d7f341c035b Mon Sep 17 00:00:00 2001 From: Neil Williams Date: Thu, 31 Dec 2015 12:59:01 +0000 Subject: Add support for systemd-networkd When masking udev/systemd predictable network interface names, the initramfs must be updated or the mask will not be effective. Add support for systemd-networkd using predictable network interface names - can be extended using customisation scripts. --- bin/vmdebootstrap | 53 +++++++++------------- vmdebootstrap/base.py | 11 ----- vmdebootstrap/filesystem.py | 8 +++- vmdebootstrap/network.py | 105 ++++++++++++++++++++++++++++++++++++++++++++ yarns/200-fast-tests.yarn | 7 +++ 5 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 vmdebootstrap/network.py diff --git a/bin/vmdebootstrap b/bin/vmdebootstrap index 0a08eb9..805b347 100755 --- a/bin/vmdebootstrap +++ b/bin/vmdebootstrap @@ -35,8 +35,9 @@ from vmdebootstrap.extlinux import ExtLinux from vmdebootstrap.codenames import Codenames from vmdebootstrap.filesystem import Filesystem from vmdebootstrap.uefi import Uefi +from vmdebootstrap.network import Networking -__version__ = '1.3' +__version__ = '1.4' # pylint: disable=invalid-name,line-too-long,missing-docstring @@ -56,6 +57,7 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth GrubHandler.name: GrubHandler(), ExtLinux.name: ExtLinux(), Filesystem.name: Filesystem(), + Networking.name: Networking(), } def add_settings(self): @@ -123,8 +125,11 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.settings.boolean(['pkglist'], 'Create a list of package names ' 'included in the image.') self.settings.boolean(['no-acpid'], 'do not install the acpid package', default=False) - self.settings.boolean(['no-update-initramfs'], 'Skip running update-initramfs', default=False) + self.settings.boolean(['update-initramfs'], + 'Run update-initramfs after customisation', default=True) self.settings.boolean(['convert-qcow2'], 'Convert final image to qcow2', default=False) + self.settings.boolean(['systemd-networkd'], 'Use Predictable Network ' + 'Interface Names', default=True) self.settings.boolean(['dry-run'], 'do not build, just test the options', default=False) def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements @@ -161,6 +166,13 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth if self.settings['squash'] and self.settings['tarball']: raise cliapp.AppException( 'Use --squash or --tarball, not both.') + if not distro.was_oldstable(datetime.date(2015, 4, 26)): + if not self.settings['systemd-networkd'] and\ + self.settings['no-update-initramfs']: + raise cliapp.AppException( + 'Disabling systemd-networkd for jessie and later ' + 'requires updating the initramfs.') + uefi = self.handlers[Uefi.name] oldstable = distro.was_oldstable(datetime.date(2015, 4, 26)) uefi.check_settings(oldstable=oldstable) @@ -234,7 +246,7 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth # only append for wheezy (which became oldstable on 2015.04.25) if distro.was_oldstable(datetime.date(2015, 4, 26)): base.append_serial_console(rootdir) - elif self.settings['serial-console']: + elif self.settings['serial-console'] and not self.settings['grub']: base.message("Skipping setting serial console- wheezy only.") self.optimize_image(rootdir) @@ -258,7 +270,11 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth base.set_root_password(rootdir) base.create_users(rootdir) filesystem.remove_udev_persistent_rules() - self.setup_networking(rootdir) + if distro.was_oldstable(datetime.date(2015, 4, 26)): + network.setup_wheezy_networking(rootdir) + else: + network.setup_networking(rootdir) + network.systemd_support(rootdir) filesystem.configure_apt() base.customize(rootdir) cleanup_apt_cache(rootdir) @@ -462,35 +478,6 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros, 'bs=1M']) runcmd(['rm', '-f', zeros]) - def setup_networking(self, rootdir): - base = self.handlers[Base.name] - base.message('Setting up networking') - distro = self.handlers[Codenames.name] - ifc_file = os.path.join(rootdir, 'etc', 'network', 'interfaces') - ifc_d = os.path.join(rootdir, 'etc', 'network', 'interfaces.d') - - # unconditionally write for wheezy (which became oldstable on 2015.04.25) - if distro.was_oldstable(datetime.date(2015, 4, 26)): - with open(ifc_file, 'w') as netfile: - netfile.write('source /etc/network/interfaces.d/*\n') - elif not os.path.exists(ifc_file): - with open(ifc_file, 'a') as netfile: - netfile.write('source-directory /etc/network/interfaces.d\n') - - if not os.path.exists(ifc_d): - os.mkdir(ifc_d) - ethpath = os.path.join(ifc_d, 'setup') - with open(ethpath, 'w') as eth: - eth.write('auto lo\n') - eth.write('iface lo inet loopback\n') - - if self.settings['enable-dhcp']: - eth.write('\n') - eth.write('auto eth0\n') - eth.write('iface eth0 inet dhcp\n') - # force predictable interface names - base.mask_udev_predictable_rules(rootdir) - def cleanup_system(self): base = self.handlers[Base.name] # Clean up after any errors. diff --git a/vmdebootstrap/base.py b/vmdebootstrap/base.py index 4ce5236..43d5a3e 100644 --- a/vmdebootstrap/base.py +++ b/vmdebootstrap/base.py @@ -204,14 +204,3 @@ class Base(object): if self.settings['kernel-package']: packages.append(self.settings['kernel-package']) return packages - - def mask_udev_predictable_rules(self, rootdir): - """ - This can be reset later but to get networking using immediately - on boot, the interface we're going to use must be known - http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ - """ - self.message('Disabling systemd predictable interface names') - udev_path = os.path.join( - 'etc', 'udev', 'rules.d', '80-net-setup-link.rules') - runcmd(['chroot', rootdir, 'ln', '-s', '/dev/null', udev_path]) diff --git a/vmdebootstrap/filesystem.py b/vmdebootstrap/filesystem.py index 106fa56..ad7575c 100644 --- a/vmdebootstrap/filesystem.py +++ b/vmdebootstrap/filesystem.py @@ -70,11 +70,15 @@ class Filesystem(Base): runcmd(["chown", "-R", self.settings["owner"], filename]) def update_initramfs(self): + if not rootdir: + raise cliapp.AppException("rootdir not set") + if not os.path.exists( + os.path.join(rootdir, 'usr', 'sbin', 'update-initramfs')): + self.message("Error: Unable to run update-initramfs.") + return if not self.settings['no-update-initramfs']: return rootdir = self.devices['rootdir'] - if not rootdir: - raise cliapp.AppException("rootdir not set") cmd = os.path.join('usr', 'sbin', 'update-initramfs') if os.path.exists(os.path.join(str(rootdir), cmd)): self.message("Updating the initramfs") diff --git a/vmdebootstrap/network.py b/vmdebootstrap/network.py new file mode 100644 index 0000000..740dede --- /dev/null +++ b/vmdebootstrap/network.py @@ -0,0 +1,105 @@ +""" + Wrapper for network support +""" +# -*- coding: utf-8 -*- +# +# network.py +# +# Copyright 2015 Neil Williams +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +import os +from vmdebootstrap.base import ( + Base, + runcmd, +) + +# pylint: disable=missing-docstring + + +class Networking(Base): + + name = 'networking' + + def _write_network_interfaces(self, rootdir, line): + self.message('Setting up networking') + ifc_d = os.path.join(rootdir, 'etc', 'network', 'interfaces.d') + ifc_file = os.path.join(rootdir, 'etc', 'network', 'interfaces') + with open(ifc_file, 'w') as netfile: + netfile.write(line) + if not os.path.exists(ifc_d): + os.mkdir(ifc_d) + with open(ethpath, 'w') as eth: + eth.write('auto lo\n') + eth.write('iface lo inet loopback\n') + if self.settings['enable-dhcp']: + eth.write('\n') + eth.write('auto eth0\n') + eth.write('iface eth0 inet dhcp\n') + + def setup_wheezy_networking(self, rootdir): + """ + unconditionally write for wheezy + (which became oldstable on 2015.04.25) + """ + self._write_network_interfaces( + rootdir, 'source /etc/network/interfaces.d/*\n') + + def setup_networking(self, rootdir): + self._write_network_interfaces( + rootdir, 'source-directory /etc/network/interfaces.d\n') + + def systemd_support(self, rootdir): + """ + Handle the systemd-networkd setting + """ + if self.settings['systemd-networkd']: + self.enable_systemd_networkd(rootdir) + else: + self.mask_udev_predictable_rules(rootdir) + + def mask_udev_predictable_rules(self, rootdir): + """ + This can be reset later but to get networking using eth0 + immediately upon boot, the interface we're going to use must + be known and must update the initramfs after setting up the + mask. + """ + self.message('Disabling systemd predictable interface names') + udev_path = os.path.join( + 'etc', 'udev', 'rules.d', '80-net-setup-link.rules') + runcmd(['chroot', rootdir, 'ln', '-s', '/dev/null', udev_path]) + + def enable_systemd_networkd(self, rootdir): + """ + Get networking working immediately on boot, allow any en* interface + to be enabled by systemd-networkd using DHCP + https://coreos.com/os/docs/latest/network-config-with-networkd.html + http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ + """ + if not self.settings['enable-dhcp']: + return + self.message('Enabling systemd-networkd for DHCP') + ethpath = os.path.join(rootdir, 'etc', 'systemd', 'network', '99-dhcp.network') + with open(ethpath, 'w') as eth: + eth.write('[Match]\n') + eth.write('Name=en*\n') + eth.write('\n[Network]\n') + eth.write('DHCP=yes\n') + runcmd(['chroot', rootdir, 'systemctl', 'enable', 'systemd-networkd']) diff --git a/yarns/200-fast-tests.yarn b/yarns/200-fast-tests.yarn index 796535e..803d09d 100644 --- a/yarns/200-fast-tests.yarn +++ b/yarns/200-fast-tests.yarn @@ -167,3 +167,10 @@ verify that vmdebootstrap parses the command line correctly. ... --squash=FOO --tarball=FOO --arch=amd64 --dry-run THEN vmdebootstrap exited with a non-zero exit code AND vmdebootstrap wrote an error message matching not both + + SCENARIO masking systemd-networkd without updating initramfs + ASSUMING fast tests are requested + WHEN user attempts to run vmdebootstrap + ... --image=FOO --no-systemd-networkd --no-update-initramfs --dry-run + THEN vmdebootstrap exited with a non-zero exit code + AND vmdebootstrap wrote an error message matching requires updating the -- cgit v1.2.1