From 23e08ef984ba784d9d74332ffe2d70125ce756e0 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 2 Jun 2014 10:53:29 +0000 Subject: Add initramfs write extension This creates a gzipped cpio archive that may be used as an initramfs. It is hard-coded to use gzip to compress the initramfs, since it's the most common way to do it. This is unfortunate, since the busybox gzip utility only allows maximum compression, which is rather slow and doesn't give progress reporting, so you can easily think it's gotten stuck. It's possible to use other compression formats, but they need the kernel to be built with them supported, and in the case of lz4, unusual userland tools to create it, since the version of lz4 supported in the kernel is not what the standard lz4 tools produce. --- morphlib/exts/initramfs.write | 27 +++++++++++++++++++++++++++ morphlib/exts/initramfs.write.help | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100755 morphlib/exts/initramfs.write create mode 100644 morphlib/exts/initramfs.write.help diff --git a/morphlib/exts/initramfs.write b/morphlib/exts/initramfs.write new file mode 100755 index 00000000..815772f2 --- /dev/null +++ b/morphlib/exts/initramfs.write @@ -0,0 +1,27 @@ +#!/bin/sh +# Copyright (C) 2014 Codethink Limited +# +# 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; version 2 of the License. +# +# 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. +# +# =*= License: GPL-2 =*= + +set -e + +ROOTDIR="$1" +INITRAMFS_PATH="$2" + +(cd "$ROOTDIR" && + find . -print0 | + cpio -0 -H newc -o | + gzip -c) >"$INITRAMFS_PATH" diff --git a/morphlib/exts/initramfs.write.help b/morphlib/exts/initramfs.write.help new file mode 100644 index 00000000..29a9d266 --- /dev/null +++ b/morphlib/exts/initramfs.write.help @@ -0,0 +1,35 @@ +help: | + Create an initramfs for a system by taking an existing system and + converting it to the appropriate format. + + The system must have a `/init` executable as the userland entry-point. + This can have a different path, if `rdinit=$path` is added to + the kernel command line. This can be added to the `rawdisk`, + `virtualbox-ssh` and `kvm` write extensions with the `KERNEL_CMDLINE` + option. + + It is possible to use a ramfs as the final rootfs without a `/init` + executable, by setting `root=/dev/mem`, or `rdinit=/sbin/init`, + but this is beyond the scope for the `initramfs.write` extension. + + The intended use of initramfs.write is to be part of a nested + deployment, so the parent system has an initramfs stored as + `/boot/initramfs.gz`. See the following example: + + name: initramfs-test + kind: cluster + systems: + - morph: minimal-system-x86_64-generic + deploy: + system: + type: rawdisk + location: initramfs-system-x86_64.img + DISK_SIZE: 1G + HOSTNAME: initramfs-system + INITRAMFS_PATH: boot/initramfs.gz + subsystems: + - morph: initramfs-x86_64 + deploy: + initramfs: + type: initramfs + location: boot/initramfs.gz -- cgit v1.2.1 From 1ab034dc56b8eb3fff8f8f2abf730450048e9dfc Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 2 Jun 2014 11:09:29 +0000 Subject: Add initramfs support to write extensions that produce disks If INITRAMFS_PATH is specified and the file exists, then the produced kernel command line will use root=UUID=$uuid_of_created_disk rather than root=/dev/sda, which may be incorrect. Help files have been updated to mention the new option. This leads to an unfortunate duplication of the path to the initramfs, in both the location field of the nested deployment and the INITRAMFS_PATH of the disk image creation. However, an initramfs could be produced by a chunk and put in the same place, so it doesn't make sense to couple the rawdisk and initramfs write extensions to remove this duplication. Similarly, there may be multiple valid initramfs in the rootfs e.g. extlinux loads a hypervisor, which is Linux + initramfs, and the initramfs then boots a guest Linux system, which uses a different initramfs. This makes it important to explicitly let the rootfs write extensions know which to use, or not as the case may be. util-linux's blkid is required, since the busybox version ignores the options to filter its output, and parsing the output is undesirable. Because syslinux's btrfs subvolume support is limited to being able to use a non-0 default subvolume, the initramfs has to be copied out of the run-time rootfs subvolume and into the boot subvolume. This pushed the required disk space of a minimal system over the 512M threshold because we do not have the userland tooling support to be able to do a btrfs file contents clone. --- morphlib/exts/kvm.write.help | 4 +++ morphlib/exts/rawdisk.write.help | 4 +++ morphlib/exts/virtualbox-ssh.write.help | 4 +++ morphlib/writeexts.py | 62 ++++++++++++++++++++++++++++----- 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 morphlib/exts/kvm.write.help create mode 100644 morphlib/exts/virtualbox-ssh.write.help diff --git a/morphlib/exts/kvm.write.help b/morphlib/exts/kvm.write.help new file mode 100644 index 00000000..8b5053a5 --- /dev/null +++ b/morphlib/exts/kvm.write.help @@ -0,0 +1,4 @@ +help: | + The INITRAMFS_PATH option can be used to specify the location of an + initramfs for syslinux to tell Linux to use, rather than booting + the rootfs directly. diff --git a/morphlib/exts/rawdisk.write.help b/morphlib/exts/rawdisk.write.help index a514a4e8..298d441c 100644 --- a/morphlib/exts/rawdisk.write.help +++ b/morphlib/exts/rawdisk.write.help @@ -5,3 +5,7 @@ help: | The `location` argument is a pathname to the image to be created or upgraded. + + The INITRAMFS_PATH option can be used to specify the location of an + initramfs for syslinux to tell Linux to use, rather than booting + the rootfs directly. diff --git a/morphlib/exts/virtualbox-ssh.write.help b/morphlib/exts/virtualbox-ssh.write.help new file mode 100644 index 00000000..8b5053a5 --- /dev/null +++ b/morphlib/exts/virtualbox-ssh.write.help @@ -0,0 +1,4 @@ +help: | + The INITRAMFS_PATH option can be used to specify the location of an + initramfs for syslinux to tell Linux to use, rather than booting + the rootfs directly. diff --git a/morphlib/writeexts.py b/morphlib/writeexts.py index b4912db1..334dc15c 100644 --- a/morphlib/writeexts.py +++ b/morphlib/writeexts.py @@ -120,7 +120,8 @@ class WriteExtension(cliapp.Application): raise try: self.create_btrfs_system_layout( - temp_root, mp, version_label='factory') + temp_root, mp, version_label='factory', + disk_uuid=self.get_uuid(raw_disk)) except BaseException, e: sys.stderr.write('Error creating Btrfs system layout') self.unmount(mp) @@ -186,6 +187,13 @@ class WriteExtension(cliapp.Application): '''Create a btrfs filesystem on the disk.''' self.status(msg='Creating btrfs filesystem') cliapp.runcmd(['mkfs.btrfs', '-L', 'baserock', location]) + + def get_uuid(self, location): + '''Get the UUID of a block device's file system.''' + # Requires util-linux blkid; busybox one ignores options and + # lies by exiting successfully. + return cliapp.runcmd(['blkid', '-s', 'UUID', '-o', 'value', + location]).strip() def mount(self, location): '''Mount the filesystem so it can be tweaked. @@ -212,10 +220,12 @@ class WriteExtension(cliapp.Application): cliapp.runcmd(['umount', mount_point]) os.rmdir(mount_point) - def create_btrfs_system_layout(self, temp_root, mountpoint, version_label): + def create_btrfs_system_layout(self, temp_root, mountpoint, version_label, + disk_uuid=None): '''Separate base OS versions from state using subvolumes. ''' + initramfs = self.find_initramfs(temp_root) version_root = os.path.join(mountpoint, 'systems', version_label) state_root = os.path.join(mountpoint, 'state') @@ -238,7 +248,12 @@ class WriteExtension(cliapp.Application): if self.bootloader_is_wanted(): self.install_kernel(version_root, temp_root) self.install_syslinux_menu(mountpoint, version_root) - self.install_extlinux(mountpoint) + if initramfs is not None: + self.install_initramfs(initramfs, version_root) + self.install_extlinux(mountpoint, disk_uuid) + else: + self.install_extlinux(mountpoint) + def create_orig(self, version_root, temp_root): '''Create the default "factory" system.''' @@ -322,6 +337,29 @@ class WriteExtension(cliapp.Application): fstab.write() return state_dirs_to_create + def find_initramfs(self, temp_root): + '''Check whether the rootfs has an initramfs. + + Uses the INITRAMFS_PATH option to locate it. + ''' + if 'INITRAMFS_PATH' in os.environ: + initramfs = os.path.join(temp_root, os.environ['INITRAMFS_PATH']) + if not os.path.exists(initramfs): + raise morphlib.Error('INITRAMFS_PATH specified, ' + 'but file does not exist') + return initramfs + return None + + def install_initramfs(self, initramfs_path, version_root): + '''Install the initramfs outside of 'orig' or 'run' subvolumes. + + This is required because syslinux doesn't traverse subvolumes when + loading the kernel or initramfs. + ''' + self.status(msg='Installing initramfs') + initramfs_dest = os.path.join(version_root, 'initramfs') + cliapp.runcmd(['cp', '-a', initramfs_path, initramfs_dest]) + def install_kernel(self, version_root, temp_root): '''Install the kernel outside of 'orig' or 'run' subvolumes''' @@ -337,20 +375,28 @@ class WriteExtension(cliapp.Application): def get_extra_kernel_args(self): return os.environ.get('KERNEL_ARGS', '') - def install_extlinux(self, real_root): + def install_extlinux(self, real_root, disk_uuid=None): '''Install extlinux on the newly created disk image.''' self.status(msg='Creating extlinux.conf') config = os.path.join(real_root, 'extlinux.conf') - kernel_args = self.get_extra_kernel_args() + kernel_args = ( + 'rw ' # ro ought to work, but we don't test that regularly + 'init=/sbin/init ' # default, but it doesn't hurt to be explicit + 'rootfstype=btrfs ' # required when using initramfs, also boots + # faster when specified without initramfs + 'rootflags=subvol=systems/default/run ') # boot runtime subvol + kernel_args += 'root=%s ' % ('/dev/sda' if disk_uuid is None + else 'UUID=%s' % disk_uuid) + kernel_args += self.get_extra_kernel_args() with open(config, 'w') as f: f.write('default linux\n') f.write('timeout 1\n') f.write('label linux\n') f.write('kernel /systems/default/kernel\n') - f.write('append root=/dev/sda ' - 'rootflags=subvol=systems/default/run ' - '%s init=/sbin/init rw\n' % (kernel_args)) + if disk_uuid is not None: + f.write('initrd /systems/default/initramfs\n') + f.write('append %s\n' % kernel_args) self.status(msg='Installing extlinux') cliapp.runcmd(['extlinux', '--install', real_root]) -- cgit v1.2.1