From d582dc90bdc68a4d84f4f74965d97c4d445a4f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Sch=C3=A4fer?= Date: Thu, 23 Nov 2017 11:56:55 +0100 Subject: [PATCH] Implementation of dracut kiwi-dump module Provide the capabilities of the oem install code as dracut module. The implementation covers the result of the installiso="true" configuration. Installation from remote sources still needs to be done and will follow in an extra pull request. This addresses Issue #576 --- .../modules.d/90kiwi-dump/kiwi-dump-image.sh | 311 ++++++++++++++++++ dracut/modules.d/90kiwi-dump/module-setup.sh | 23 ++ .../90kiwi-dump/parse-kiwi-install.sh | 31 ++ .../modules.d/99kiwi-lib/kiwi-dialog-lib.sh | 44 ++- dracut/modules.d/99kiwi-lib/module-setup.sh | 2 +- kiwi/boot/image/base.py | 3 +- kiwi/boot/image/builtin_kiwi.py | 11 +- kiwi/boot/image/dracut.py | 9 +- kiwi/bootloader/config/grub2.py | 12 +- kiwi/bootloader/config/isolinux.py | 13 +- kiwi/builder/disk.py | 44 +-- kiwi/builder/install.py | 76 ++++- kiwi/defaults.py | 7 + test/unit/boot_image_builtin_kiwi_test.py | 9 + test/unit/boot_image_dracut_test.py | 17 + test/unit/builder_disk_test.py | 45 +-- test/unit/builder_install_test.py | 67 +++- 17 files changed, 637 insertions(+), 87 deletions(-) create mode 100755 dracut/modules.d/90kiwi-dump/kiwi-dump-image.sh create mode 100755 dracut/modules.d/90kiwi-dump/module-setup.sh create mode 100755 dracut/modules.d/90kiwi-dump/parse-kiwi-install.sh diff --git a/dracut/modules.d/90kiwi-dump/kiwi-dump-image.sh b/dracut/modules.d/90kiwi-dump/kiwi-dump-image.sh new file mode 100755 index 00000000..0d70767e --- /dev/null +++ b/dracut/modules.d/90kiwi-dump/kiwi-dump-image.sh @@ -0,0 +1,311 @@ +#!/bin/bash +type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh +type setup_debug >/dev/null 2>&1 || . /lib/kiwi-lib.sh +type run_dialog >/dev/null 2>&1 || . /lib/kiwi-dialog-lib.sh +type get_block_device_kbsize >/dev/null 2>&1 || . /lib/kiwi-partitions-lib.sh + +#====================================== +# Functions +#-------------------------------------- +function scan_softraid_devices { + # TODO: run ataraid scan so that they appear in lsblk + : +} + +function scan_multipath_devices { + # TODO: run multipath scan/daemon so that they appear in lsblk + : +} + +function get_disk_list { + declare kiwi_oemdevicefilter=${kiwi_oemdevicefilter} + declare kiwi_oemataraid_scan=${kiwi_oemataraid_scan} + declare kiwi_oemmultipath_scan=${kiwi_oemmultipath_scan} + declare kiwi_devicepersistency=${kiwi_devicepersistency} + local disk_id="by-id" + local disk_size + local disk_device + local disk_device_by_id + local disk_meta + local item_status=on + local list_items + if [ ! -z "${kiwi_devicepersistency}" ];then + disk_id=${kiwi_devicepersistency} + fi + if [ ! -z "${kiwi_oemataraid_scan}" ];then + scan_softraid_devices + fi + if [ ! -z "${kiwi_oemmultipath_scan}" ];then + scan_multipath_devices + fi + for disk_meta in $( + lsblk -n -r -o NAME,SIZE,TYPE | grep disk | tr ' ' ":" + );do + disk_device="/dev/$(echo "${disk_meta}" | cut -f1 -d:)" + disk_size=$(echo "${disk_meta}" | cut -f2 -d:) + disk_device_by_id=$( + get_persistent_device_from_unix_node "${disk_device}" "${disk_id}" + ) + if [ ! -z "${disk_device_by_id}" ];then + disk_device=${disk_device_by_id} + fi + # check for static filter rules + if [[ ${disk_device} =~ ^/dev/fd ]];then + # ignore floppy disk devices + continue + fi + # check for custom filter rule + if [ ! -z "${kiwi_oemdevicefilter}" ];then + if [[ ${disk_device} =~ ${kiwi_oemdevicefilter} ]];then + info "${disk_device} filtered out by: ${kiwi_oemdevicefilter}" + continue + fi + fi + list_items="${list_items} ${disk_device} ${disk_size} ${item_status}" + item_status=off + done + if [ -z "${list_items}" ];then + local no_device_text="No device(s) for installation found" + run_dialog --msgbox "\"${no_device_text}\"" 5 60 + die "${no_device_text}" + fi + echo "${list_items}" +} + +function get_selected_disk { + declare kiwi_oemunattended=${kiwi_oemunattended} + declare kiwi_oemunattended_id=${kiwi_oemunattended_id} + local disk_list + local device_array + disk_list=$(get_disk_list) + if [ ! -z "${disk_list}" ];then + local count=0 + local device_index=0 + for entry in ${disk_list};do + if [ $((count % 3)) -eq 0 ];then + device_array[${device_index}]=${entry} + device_index=$((device_index + 1)) + fi + count=$((count + 1)) + done + if [ "${device_index}" -eq 1 ];then + # one single disk device found, use it + echo "${device_array[0]}" + elif [ ! -z "${kiwi_oemunattended}" ];then + if [ -z "${kiwi_oemunattended_id}" ];then + # unattended mode requested but no target specifier, + # thus use first device from list + echo "${device_array[0]}" + else + # unattended mode requested with target specifier + # use this device if present + local device + for device in ${device_array[*]}; do + if [[ ${device} =~ ${kiwi_oemunattended_id} ]];then + echo "${device}" + return + fi + done + fi + else + # manually select from storage list + if ! run_dialog \ + --radiolist "\"Select Installation Disk\"" 20 75 15 \ + "$(get_disk_list)" + then + die "System installation canceled" + fi + get_dialog_result + fi + fi +} + +function export_image_metadata { + local image_source_files=$1 + export checksum + export blocks + export blocksize + export zblocks + export zblocksize + local meta_file + meta_file="$(echo "${image_source_files}" | cut -f2 -d\|)" + if ! read -r checksum blocks blocksize zblocks zblocksize < "${meta_file}" + then + die "Reading ${meta_file} failed" + fi + echo "Image checksum: ${checksum}" + echo "Image blocks: ${blocks} / blocksize: ${blocksize}" + if [ ! -z "${zblocks}" ];then + echo "Image compressed blocks: ${zblocks} / blocksize: ${zblocksize}" + fi +} + +function check_image_fits_target { + local image_target=$1 + local need_mbytes + local have_mbytes + need_mbytes=$((blocks * blocksize / 1048576)) + have_mbytes=$(($(get_block_device_kbsize "${image_target}") / 1024)) + echo "Have size: ${image_target} -> ${have_mbytes} MB" + echo "Need size: ${image_source} -> ${need_mbytes} MB" + if [ ${need_mbytes} -gt ${have_mbytes} ];then + die "Not enough space available for this image" + fi +} + +function dump_local_image { + declare kiwi_oemsilentinstall=${kiwi_oemsilentinstall} + declare kiwi_oemunattended=${kiwi_oemunattended} + local image_source_files=$1 + local image_target=$2 + local image_source + local image_basename + image_source="$(echo "${image_source_files}" | cut -f1 -d\|)" + image_basename=$(basename "${image_source}") + local progress=/dev/install_progress + local load_text="Loading ${image_basename}..." + local title_text="Installation..." + + read_local_image_metadata "${image_source}" + + check_image_fits_target "${image_target}" + + if [ -z "${kiwi_oemunattended}" ];then + local ack_dump_text="Destroying ALL data on ${image_target}, continue ?" + if ! run_dialog --yesno "\"${ack_dump_text}\"" 5 80; then + local install_cancel_text="System installation canceled" + run_dialog --msgbox "\"${install_cancel_text}\"" 5 60 + die "${install_cancel_text}" + fi + fi + + echo "${load_text} [${image_target}]..." + if command -v pv &>/dev/null && [ -z "${kiwi_oemsilentinstall}" ];then + # dump with dialog based progress information + setup_progress_fifo ${progress} + #run_progress_dialog "${load_text}" "${title_text}" & + ( + pv -n "${image_source}" | dd bs=32k of="${image_target}" &>/dev/null + ) 2>${progress} & + run_progress_dialog "${load_text}" "${title_text}" + #stop_dialog + else + # dump with silently blocked console + if ! dd if="${image_source}" bs=32k of="${image_target}" &>/dev/null + then + die "Failed to install image" + fi + fi +} + +function dump_remote_image { + # TODO + : +} + +function check_image_integrity { + declare kiwi_oemskipverify=${kiwi_oemskipverify} + declare kiwi_oemsilentverify=${kiwi_oemsilentverify} + local image_target=$1 + local progress=/dev/install_verify_progress + local verify_text="Verifying ${image_target}" + local title_text="Installation..." + local verify_result=/dumped_image.md5 + if [ ! -z "${kiwi_oemskipverify}" ];then + # no verification wanted + return + fi + if command -v pv &>/dev/null && [ -z "${kiwi_oemsilentverify}" ];then + # verify with dialog based progress information + setup_progress_fifo ${progress} + run_progress_dialog "${verify_text}" "${title_text}" & + ( + pv --size $((blocks * blocksize)) --stop-at-size \ + -n "${image_target}" | md5sum - > ${verify_result} + ) 2>${progress} + stop_dialog + else + # verify with silently blocked console + head --bytes=$((blocks * blocksize)) "${image_target}" |\ + md5sum - > ${verify_result} + fi + local checksum_dumped_image + local checksum_fileref + read -r checksum_dumped_image checksum_fileref < ${verify_result} + echo "Dumped Image checksum: ${checksum_dumped_image}/${checksum_fileref}" + if [ "${checksum}" != "${checksum_dumped_image}" ];then + die "Image checksum test failed" + fi +} + +function get_local_image_source_files { + declare root=${root} + local iso_device="${root#install:}" + local iso_mount_point=/run/install + local image_mount_point=/run/image + local image_source + local image_md5 + mkdir -m 0755 -p "${iso_mount_point}" + if ! mount -n "${iso_device}" "${iso_mount_point}"; then + die "Failed to mount install ISO device" + fi + mkdir -m 0755 -p "${image_mount_point}" + if ! mount -n "${iso_mount_point}"/*.squashfs ${image_mount_point};then + die "Failed to mount install image squashfs filesystem" + fi + image_source="$(echo "${image_mount_point}"/*.raw)" + image_md5="$(echo "${image_mount_point}"/*.md5)" + echo "${image_source}|${image_md5}" +} + +function get_remote_image_source_files { + # TODO: + : +} + +function boot_installed_system { + local boot_options + boot_options="$(cat /config.bootoptions)" + if getargbool 0 rd.kiwi.debug; then + boot_options="${boot_options} rd.kiwi.debug" + fi + kexec -l /run/install/boot/*/loader/linux \ + --initrd /run/install/initrd.system_image \ + --command-line "${boot_options}" + if ! kexec -e; then + die "Failed to boot system" + fi +} + +#====================================== +# Perform image dump/install operations +#-------------------------------------- +setup_debug + +udev_pending + +stop_plymouth + +image_target=$(get_selected_disk) + +if getargbool 0 rd.kiwi.install.pxe; then + image_source_files=$(get_remote_image_source_files) +else + image_source_files=$(get_local_image_source_files) +fi + +export_image_metadata "${image_source_files}" + +if getargbool 0 rd.kiwi.install.pxe; then + dump_remote_image "${image_source_files}" "${image_target}" +else + dump_local_image "${image_source_files}" "${image_target}" +fi + +check_image_integrity "${image_target}" + +boot_installed_system + +emergency_shell + +exit 0 diff --git a/dracut/modules.d/90kiwi-dump/module-setup.sh b/dracut/modules.d/90kiwi-dump/module-setup.sh new file mode 100755 index 00000000..66a39cae --- /dev/null +++ b/dracut/modules.d/90kiwi-dump/module-setup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# called by dracut +depends() { + echo rootfs-block dm kiwi-lib + return 0 +} + +# called by dracut +installkernel() { + instmods squashfs loop iso9660 +} + +# called by dracut +install() { + declare moddir=${moddir} + inst_multiple \ + tr lsblk dd md5sum head pv kexec + inst_hook cmdline 30 "${moddir}/parse-kiwi-install.sh" + inst_hook pre-mount 30 "${moddir}/kiwi-dump-image.sh" + inst_rules 60-cdrom_id.rules + dracut_need_initqueue +} diff --git a/dracut/modules.d/90kiwi-dump/parse-kiwi-install.sh b/dracut/modules.d/90kiwi-dump/parse-kiwi-install.sh new file mode 100755 index 00000000..e00e76b4 --- /dev/null +++ b/dracut/modules.d/90kiwi-dump/parse-kiwi-install.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# install images are specified with +# root=install:CDLABEL=label +type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh + +[ -z "${root}" ] && root=$(getarg root=) + +if [ "${root%%:*}" = "install" ] ; then + installroot=${root} +fi + +[ "${installroot%%:*}" = "install" ] || return 1 + +modprobe -q loop + +case "${installroot}" in + install:CDLABEL=*|CDLABEL=*) \ + root="${root#install:}" + root="$(echo "${root}" | sed 's,/,\\x2f,g')" + root="install:/dev/disk/by-label/${root#CDLABEL=}" + rootok=1 ;; +esac + +[ "$rootok" = "1" ] || return 1 + +info "root was ${installroot}, is now ${root}" + +# make sure that init doesn't complain +[ -z "${root}" ] && root="install" + +return 0 diff --git a/dracut/modules.d/99kiwi-lib/kiwi-dialog-lib.sh b/dracut/modules.d/99kiwi-lib/kiwi-dialog-lib.sh index c4509280..301e47b2 100644 --- a/dracut/modules.d/99kiwi-lib/kiwi-dialog-lib.sh +++ b/dracut/modules.d/99kiwi-lib/kiwi-dialog-lib.sh @@ -13,24 +13,57 @@ function run_dialog { local dialog_exit_code=/tmp/dialog_code { echo "dialog $* 2>$dialog_result" - echo -n \$? >$dialog_exit_code + echo "echo -n \$? >$dialog_exit_code" } >/bin/dracut-interactive _run_interactive return "$(cat $dialog_exit_code)" } +function setup_progress_fifo { + local fifo=$1 + export current_progress_fifo="${fifo}" + [ -e "${fifo}" ] || mkfifo "${fifo}" +} + +function stop_dialog { + _stop_interactive +} + +function run_progress_dialog { + # """ + # Run the gauge dialog via systemd reading progress + # status information from stdin + # """ + declare fifo=${current_progress_fifo} + local title=$1 + local backtitle=$2 + local progress_at="${fifo}" + if [ ! -e "${progress_at}" ];then + return + fi + { + echo -n "cat ${fifo} | dialog --backtitle \"${backtitle}\" " + echo -n "--gauge \"${title}\"" 7 65 0 + echo + } >/bin/dracut-interactive + _run_interactive +} + function get_dialog_result { local dialog_result=/tmp/dialog_result [ -e "${dialog_result}" ] && cat ${dialog_result}; rm -f ${dialog_result} } +function stop_plymouth { + plymouth --quit --wait +} + #========================================= # Methods considered private #----------------------------------------- function _setup_interactive_service { local service=/usr/lib/systemd/system/dracut-run-interactive.service local script=/bin/dracut-interactive - local unicode_font=/lib/b16.pcf.gz [ -e ${service} ] && return { echo "[Unit]" @@ -45,7 +78,7 @@ function _setup_interactive_service { echo "Environment=NEWROOT=/sysroot" echo "WorkingDirectory=/" if _fbiterm_ok; then - echo "ExecStart=fbiterm -m ${unicode_font} -- /bin/bash ${script}" + echo "ExecStart=fbiterm -- /bin/bash ${script} " else echo "ExecStart=/bin/bash ${script}" fi @@ -55,7 +88,6 @@ function _setup_interactive_service { echo "StandardError=inherit" echo "KillMode=process" echo "IgnoreSIGPIPE=no" - echo "TaskMax=infinity" echo "KillSignal=SIGHUP" } > ${service} } @@ -65,6 +97,10 @@ function _run_interactive { systemctl start dracut-run-interactive.service } +function _stop_interactive { + systemctl stop dracut-run-interactive.service +} + function _fbiterm_ok { if [ ! -e /dev/fb0 ];then # no framebuffer device found diff --git a/dracut/modules.d/99kiwi-lib/module-setup.sh b/dracut/modules.d/99kiwi-lib/module-setup.sh index ff4d5c58..2133cb86 100755 --- a/dracut/modules.d/99kiwi-lib/module-setup.sh +++ b/dracut/modules.d/99kiwi-lib/module-setup.sh @@ -20,7 +20,7 @@ install() { btrfs xfs_growfs resize2fs \ e2fsck btrfsck xfs_repair \ vgs vgchange lvextend lvcreate lvresize pvresize \ - mdadm cryptsetup + mdadm cryptsetup dialog if [[ "$(uname -m)" =~ s390 ]];then inst_multiple fdasd fi diff --git a/kiwi/boot/image/base.py b/kiwi/boot/image/base.py index 8af8c45b..004c3591 100644 --- a/kiwi/boot/image/base.py +++ b/kiwi/boot/image/base.py @@ -129,11 +129,12 @@ class BootImageBase(object): """ raise NotImplementedError - def create_initrd(self, mbrid=None): + def create_initrd(self, mbrid=None, basename=None): """ Implements creation of the initrd :param object mbrid: instance of ImageIdentifier + :param string basename: base initrd file name """ raise NotImplementedError diff --git a/kiwi/boot/image/builtin_kiwi.py b/kiwi/boot/image/builtin_kiwi.py index 2dfe63ae..f4b908cc 100644 --- a/kiwi/boot/image/builtin_kiwi.py +++ b/kiwi/boot/image/builtin_kiwi.py @@ -104,11 +104,12 @@ class BootImageKiwi(BootImageBase): self.setup.call_image_script() self.setup.create_init_link_from_linuxrc() - def create_initrd(self, mbrid=None): + def create_initrd(self, mbrid=None, basename=None): """ Create initrd from prepared boot system tree and compress the result :param object mbrid: instance of ImageIdentifier + :param string basename: base initrd file name """ if self.is_prepared(): log.info('Creating initrd cpio archive') @@ -119,6 +120,10 @@ class BootImageKiwi(BootImageBase): # boot directory should not be changed because we rely # on other data in boot/ e.g the kernel to be available # for the entire image building process + if basename: + kiwi_initrd_basename = basename + else: + kiwi_initrd_basename = self.initrd_base_name temp_boot_root_directory = mkdtemp( prefix='kiwi_boot_root_copy.' ) @@ -143,7 +148,7 @@ class BootImageKiwi(BootImageBase): mbrid.write(image_identifier) cpio = ArchiveCpio( - os.sep.join([self.target_dir, self.initrd_base_name]) + os.sep.join([self.target_dir, kiwi_initrd_basename]) ) # the following is a list of directories which were needed # during the process of creating an image but not when the @@ -165,7 +170,7 @@ class BootImageKiwi(BootImageBase): '--> xz compressing archive' ) compress = Compress( - os.sep.join([self.target_dir, self.initrd_base_name]) + os.sep.join([self.target_dir, kiwi_initrd_basename]) ) compress.xz( ['--check=crc32', '--lzma2=dict=1MiB', '--threads=0'] diff --git a/kiwi/boot/image/dracut.py b/kiwi/boot/image/dracut.py index cf502412..e5df1b7f 100644 --- a/kiwi/boot/image/dracut.py +++ b/kiwi/boot/image/dracut.py @@ -57,18 +57,23 @@ class BootImageDracut(BootImageBase): self.dracut_options.append('--install') self.dracut_options.append('/.profile') - def create_initrd(self, mbrid=None): + def create_initrd(self, mbrid=None, basename=None): """ Call dracut as chroot operation to create the initrd and move the result into the image build target directory :param object mbrid: unused + :param string basename: base initrd file name """ if self.is_prepared(): log.info('Creating generic dracut initrd archive') kernel_info = Kernel(self.boot_root_directory) kernel_details = kernel_info.get_kernel(raise_on_not_found=True) - dracut_initrd_basename = self.initrd_base_name + '.xz' + if basename: + dracut_initrd_basename = basename + else: + dracut_initrd_basename = self.initrd_base_name + dracut_initrd_basename += '.xz' Command.run( [ 'chroot', self.boot_root_directory, diff --git a/kiwi/bootloader/config/grub2.py b/kiwi/bootloader/config/grub2.py index 828aae90..c19cbcc4 100644 --- a/kiwi/bootloader/config/grub2.py +++ b/kiwi/bootloader/config/grub2.py @@ -146,6 +146,10 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase): 'root=live:CDLABEL={0}'.format(self.volume_id), 'rd.live.image' ] + self.install_boot_options = [ + 'root=install:CDLABEL={0}'.format(Defaults.get_install_volume_id()), + 'loglevel=0' + ] if self.xml_state.build_type.get_hybridpersistent(): self.live_boot_options += \ Defaults.get_live_iso_persistent_boot_options( @@ -326,8 +330,12 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase): 'default_boot': self.get_install_image_boot_default(), 'kernel_file': kernel, 'initrd_file': initrd, - 'boot_options': self.cmdline, - 'failsafe_boot_options': self.cmdline_failsafe, + 'boot_options': ' '.join( + [self.cmdline] + self.install_boot_options + ), + 'failsafe_boot_options': ' '.join( + [self.cmdline_failsafe] + self.install_boot_options + ), 'gfxmode': self.gfxmode, 'theme': self.theme, 'boot_timeout': self.timeout, diff --git a/kiwi/bootloader/config/isolinux.py b/kiwi/bootloader/config/isolinux.py index f4316f8f..69eb49dc 100644 --- a/kiwi/bootloader/config/isolinux.py +++ b/kiwi/bootloader/config/isolinux.py @@ -98,6 +98,11 @@ class BootLoaderConfigIsoLinux(BootLoaderConfigBase): 'root=live:CDLABEL={0}'.format(self.volume_id), 'rd.live.image' ] + self.install_boot_options = [ + 'root=install:CDLABEL={0}'.format(Defaults.get_install_volume_id()), + 'loglevel=0' + ] + if self.xml_state.build_type.get_hybridpersistent(): self.live_boot_options += \ Defaults.get_live_iso_persistent_boot_options( @@ -160,8 +165,12 @@ class BootLoaderConfigIsoLinux(BootLoaderConfigBase): 'default_boot': self.get_install_image_boot_default('isolinux'), 'kernel_file': kernel, 'initrd_file': initrd, - 'boot_options': self.cmdline, - 'failsafe_boot_options': self.cmdline_failsafe, + 'boot_options': ' '.join( + [self.cmdline] + self.install_boot_options + ), + 'failsafe_boot_options': ' '.join( + [self.cmdline_failsafe] + self.install_boot_options + ), 'gfxmode': self.gfxmode, 'boot_timeout': self.timeout, 'title': self.get_menu_entry_install_title() diff --git a/kiwi/builder/disk.py b/kiwi/builder/disk.py index d000dd64..1dc5b9a2 100644 --- a/kiwi/builder/disk.py +++ b/kiwi/builder/disk.py @@ -28,7 +28,6 @@ from kiwi.bootloader.config import BootLoaderConfig from kiwi.bootloader.install import BootLoaderInstall from kiwi.system.identifier import SystemIdentifier from kiwi.boot.image import BootImage -from kiwi.boot.image import BootImageKiwi from kiwi.storage.setup import DiskSetup from kiwi.storage.loop_device import LoopDevice from kiwi.firmware import FirmWare @@ -392,31 +391,6 @@ class DiskBuilder(object): # prepare for install media if requested if self.install_media: - if self.initrd_system == 'dracut': - # for the installation process we need a kiwi initrd - # Therefore an extra install boot root system needs to - # be prepared if dracut was set as the initrd system - # to boot the system image - log.info('Preparing extra install boot system') - - self.xml_state.build_type.set_initrd_system('kiwi') - self.initrd_system = self.xml_state.get_initrd_system() - - self.boot_image = BootImageKiwi( - self.xml_state, self.target_dir, - signing_keys=self.signing_keys - ) - - self.boot_image.prepare() - - # apply disk builder metadata also needed in the install initrd - self._write_partition_id_config_to_boot_image() - self._write_recovery_metadata_to_boot_image() - self._write_raid_config_to_boot_image() - self.system_setup.export_modprobe_setup( - self.boot_image.boot_root_directory - ) - log.info('Saving boot image instance to file') self.boot_image.dump( self.target_dir + '/boot_image.pickledump' @@ -677,6 +651,7 @@ class DiskBuilder(object): 'dracut_rescue_image="no"' ] dracut_modules = [] + dracut_modules_omit = ['kiwi-dump'] if self.root_filesystem_is_overlay: dracut_modules.append('kiwi-overlay') if self.build_type_name == 'oem': @@ -685,6 +660,9 @@ class DiskBuilder(object): dracut_config.append( 'add_dracutmodules+=" {0} "'.format(' '.join(dracut_modules)) ) + dracut_config.append( + 'omit_dracutmodules+=" {0} "'.format(' '.join(dracut_modules_omit)) + ) with open(dracut_config_file, 'w') as config: for entry in dracut_config: config.write(entry + os.linesep) @@ -832,6 +810,20 @@ class DiskBuilder(object): ) self.bootloader_config.write() + log.info('Creating config.bootoptions') + filename = ''.join( + [self.boot_image.boot_root_directory, '/config.bootoptions'] + ) + kexec_boot_options = ' '.join( + [ + self.bootloader_config.get_boot_cmdline(root_uuid) + ] + boot_options + ) + with open(filename, 'w') as boot_options: + boot_options.write( + '{0}{1}'.format(kexec_boot_options, os.linesep) + ) + partition_id_map = self.disk.get_public_partition_id_map() boot_partition_id = partition_id_map['kiwi_RootPart'] if 'kiwi_BootPart' in partition_id_map: diff --git a/kiwi/builder/install.py b/kiwi/builder/install.py index 399a8e29..9901f7e2 100644 --- a/kiwi/builder/install.py +++ b/kiwi/builder/install.py @@ -15,8 +15,10 @@ # You should have received a copy of the GNU General Public License # along with kiwi. If not, see # +import os from tempfile import mkdtemp import platform +import shutil # project from kiwi.command import Command @@ -72,6 +74,7 @@ class InstallImageBuilder(object): self.target_dir = target_dir self.boot_image_task = boot_image_task self.xml_state = xml_state + self.initrd_system = xml_state.get_initrd_system() self.firmware = FirmWare(xml_state) self.diskname = ''.join( [ @@ -100,6 +103,9 @@ class InstallImageBuilder(object): '.install.tar.xz' ] ) + self.dracut_config_file = ''.join( + [self.root_dir, '/etc/dracut.conf.d/02-kiwi.conf'] + ) self.squashed_diskname = ''.join( [xml_state.xml_data.get_name(), '.raw'] ) @@ -133,7 +139,7 @@ class InstallImageBuilder(object): # custom iso metadata self.custom_iso_args = { 'create_options': [ - '-V', '"KIWI Installation System"', + '-V', Defaults.get_install_volume_id(), '-A', self.mbrid.get_id() ] } @@ -146,8 +152,10 @@ class InstallImageBuilder(object): checksum = Checksum(self.diskname) checksum.md5(self.squashed_contents + '/' + self.md5name) - # the kiwi initrd code triggers the install by trigger files - self._create_iso_install_trigger_files() + # the system image name is stored in a config file + self._write_install_image_info_to_iso_image() + if self.initrd_system == 'kiwi': + self._write_install_image_info_to_boot_image() # the system image is stored as squashfs embedded file log.info('Creating squashfs embedded disk image') @@ -203,6 +211,9 @@ class InstallImageBuilder(object): log.info('Creating install image boot image') self._create_iso_install_kernel_and_initrd() + # the system image initrd is stored to allow kexec + self._copy_system_image_initrd_to_iso_image() + # create iso filesystem from media_dir log.info('Creating ISO filesystem') iso_image = FileSystemIsoFs( @@ -263,8 +274,9 @@ class InstallImageBuilder(object): checksum = Checksum(self.diskname) checksum.md5(pxe_md5_filename) - # the kiwi initrd code triggers the install by trigger files - self._create_pxe_install_trigger_files() + # the install image name is stored in a config file + if self.initrd_system == 'kiwi': + self._write_install_image_info_to_boot_image() # create pxe config append information # this information helps to configure the boot server correctly @@ -311,7 +323,9 @@ class InstallImageBuilder(object): 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) - self.boot_image_task.create_initrd(self.mbrid) + if self.initrd_system == 'dracut': + self._create_dracut_install_config() + self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install') Command.run( [ 'mv', self.boot_image_task.initrd_filename, @@ -338,7 +352,10 @@ class InstallImageBuilder(object): 'No hypervisor in boot image tree %s found' % self.boot_image_task.boot_root_directory ) - self.boot_image_task.create_initrd(self.mbrid) + if self.initrd_system == 'dracut': + self._create_dracut_install_config() + self._add_system_image_boot_options_to_boot_image() + self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install') Command.run( [ 'mv', self.boot_image_task.initrd_filename, @@ -346,23 +363,56 @@ class InstallImageBuilder(object): ] ) - def _create_iso_install_trigger_files(self): - initrd_trigger = \ - self.boot_image_task.boot_root_directory + '/config.vmxsystem' + def _add_system_image_boot_options_to_boot_image(self): + filename = ''.join( + [self.boot_image_task.boot_root_directory, '/config.bootoptions'] + ) + self.boot_image_task.include_file( + os.sep + os.path.basename(filename) + ) + + def _copy_system_image_initrd_to_iso_image(self): + system_image_initrd = self.root_dir + '/boot/initrd' + shutil.copy( + system_image_initrd, self.media_dir + '/initrd.system_image' + ) + + def _write_install_image_info_to_iso_image(self): iso_trigger = self.media_dir + '/config.isoclient' - with open(initrd_trigger, 'w') as vmx_system: - vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname) with open(iso_trigger, 'w') as iso_system: iso_system.write('IMAGE="%s"\n' % self.squashed_diskname) - def _create_pxe_install_trigger_files(self): + def _write_install_image_info_to_boot_image(self): initrd_trigger = \ self.boot_image_task.boot_root_directory + '/config.vmxsystem' with open(initrd_trigger, 'w') as vmx_system: vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname) + def _create_dracut_install_config(self): + dracut_config = [ + 'hostonly="no"', + 'dracut_rescue_image="no"' + ] + dracut_modules = ['kiwi-lib', 'kiwi-dump'] + dracut_modules_omit = ['kiwi-overlay', 'kiwi-repart'] + dracut_config.append( + 'add_dracutmodules+=" {0} "'.format(' '.join(dracut_modules)) + ) + dracut_config.append( + 'omit_dracutmodules+=" {0} "'.format(' '.join(dracut_modules_omit)) + ) + with open(self.dracut_config_file, 'w') as config: + for entry in dracut_config: + config.write(entry + os.linesep) + + def _delete_dracut_install_config(self): + if os.path.exists(self.dracut_config_file): + os.remove(self.dracut_config_file) + def __del__(self): log.info('Cleaning up %s instance', type(self).__name__) + if self.initrd_system == 'dracut': + self._delete_dracut_install_config() if self.media_dir: Path.wipe(self.media_dir) if self.pxe_dir: diff --git a/kiwi/defaults.py b/kiwi/defaults.py index 35d1bb62..2ca9e1f1 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -204,6 +204,13 @@ class Defaults(object): """ return 'CDROM' + @classmethod + def get_install_volume_id(self): + """ + Implements default value for ISO volume ID for install media + """ + return 'INSTALL' + @classmethod def get_default_video_mode(self): """ diff --git a/test/unit/boot_image_builtin_kiwi_test.py b/test/unit/boot_image_builtin_kiwi_test.py index a4557f00..44a552f1 100644 --- a/test/unit/boot_image_builtin_kiwi_test.py +++ b/test/unit/boot_image_builtin_kiwi_test.py @@ -138,6 +138,15 @@ class TestBootImageKiwi(object): compress.xz.assert_called_once_with( ['--check=crc32', '--lzma2=dict=1MiB', '--threads=0'] ) + mock_cpio.reset_mock() + mock_compress.reset_mock() + self.boot_image.create_initrd(mbrid, 'foo') + mock_cpio.assert_called_once_with( + self.boot_image.target_dir + '/foo' + ) + mock_compress.assert_called_once_with( + self.boot_image.target_dir + '/foo' + ) @patch('kiwi.boot.image.base.Path.wipe') @patch('os.path.exists') diff --git a/test/unit/boot_image_dracut_test.py b/test/unit/boot_image_dracut_test.py index 24282b52..6dbbf038 100644 --- a/test/unit/boot_image_dracut_test.py +++ b/test/unit/boot_image_dracut_test.py @@ -70,3 +70,20 @@ class TestBootImageKiwi(object): 'some-target-dir' ]) ] + mock_command.reset_mock() + self.boot_image.create_initrd(basename='foo') + assert mock_command.call_args_list == [ + call([ + 'chroot', 'system-directory', + 'dracut', '--force', '--no-hostonly', + '--no-hostonly-cmdline', '--xz', + '--install', 'system-directory/etc/foo', + '--install', '/system-directory/var/lib/bar', + 'foo.xz', '1.2.3' + ]), + call([ + 'mv', + 'system-directory/foo.xz', + 'some-target-dir' + ]) + ] diff --git a/test/unit/builder_disk_test.py b/test/unit/builder_disk_test.py index fc6ea386..93545456 100644 --- a/test/unit/builder_disk_test.py +++ b/test/unit/builder_disk_test.py @@ -134,6 +134,9 @@ class TestDiskBuilder(object): return_value=self.bootloader_install ) self.bootloader_config = mock.Mock() + self.bootloader_config.get_boot_cmdline = mock.Mock( + return_value='boot_cmdline' + ) kiwi.builder.disk.BootLoaderConfig = mock.MagicMock( return_value=self.bootloader_config ) @@ -159,11 +162,6 @@ class TestDiskBuilder(object): kiwi.builder.disk.SystemSetup = mock.Mock( return_value=self.setup ) - self.boot_image_kiwi = mock.Mock() - self.boot_image_kiwi.boot_root_directory = 'boot_dir_kiwi' - kiwi.builder.disk.BootImageKiwi = mock.Mock( - return_value=self.boot_image_kiwi - ) self.install_image = mock.Mock() kiwi.builder.disk.InstallImageBuilder = mock.Mock( return_value=self.install_image @@ -336,15 +334,17 @@ class TestDiskBuilder(object): 'image', '.profile', '.kconfig', '.buildenv', 'var/cache/kiwi', 'boot/*', 'boot/.*', 'boot/efi/*', 'boot/efi/.*' ]) - assert mock_open.call_args_list[0:3] == [ + assert mock_open.call_args_list[0:4] == [ call('boot_dir/config.partids', 'w'), call('root_dir/boot/mbrid', 'w'), + call('boot_dir/config.bootoptions', 'w'), call('/dev/some-loop', 'wb') ] assert self.file_mock.write.call_args_list == [ call('kiwi_BootPart="1"\n'), call('kiwi_RootPart="1"\n'), call('0x0f0f0f0f\n'), + call('boot_cmdline\n'), call(bytes(b'\x0f\x0f\x0f\x0f')), ] assert mock_command.call_args_list == [ @@ -379,13 +379,6 @@ class TestDiskBuilder(object): self.disk_builder.create_disk() self.setup.create_recovery_archive.assert_called_once_with() - call = self.setup.export_modprobe_setup.call_args_list[0] - assert self.setup.export_modprobe_setup.call_args_list[0] == \ - call('boot_dir') - call = self.setup.export_modprobe_setup.call_args_list[1] - assert self.setup.export_modprobe_setup.call_args_list[1] == \ - call('boot_dir_kiwi') - self.setup.set_selinux_file_contexts.assert_called_once_with( '/etc/selinux/targeted/contexts/files/file_contexts' ) @@ -423,10 +416,7 @@ class TestDiskBuilder(object): 'target_dir/LimeJeOS-openSUSE-13.2.x86_64-1.13.2.raw', '/dev/boot-device' ) - - self.boot_image_kiwi.prepare.assert_called_once_with() self.boot_image_task.prepare.assert_called_once_with() - call = filesystem.create_on_device.call_args_list[0] assert filesystem.create_on_device.call_args_list[0] == \ call(label='EFI') @@ -453,8 +443,8 @@ class TestDiskBuilder(object): call('boot_dir/config.partids', 'w'), call('root_dir/boot/mbrid', 'w'), call('root_dir/etc/dracut.conf.d/02-kiwi.conf', 'w'), - call('/dev/some-loop', 'wb'), - call('boot_dir_kiwi/config.partids', 'w') + call('boot_dir/config.bootoptions', 'w'), + call('/dev/some-loop', 'wb') ] assert self.file_mock.write.call_args_list == [ call('kiwi_BootPart="1"\n'), @@ -463,14 +453,13 @@ class TestDiskBuilder(object): call('hostonly="no"\n'), call('dracut_rescue_image="no"\n'), call('add_dracutmodules+=" kiwi-lib kiwi-repart "\n'), - call(bytes(b'\x0f\x0f\x0f\x0f')), - call('kiwi_BootPart="1"\n'), - call('kiwi_RootPart="1"\n') + call('omit_dracutmodules+=" kiwi-dump "\n'), + call('boot_cmdline\n'), + call(bytes(b'\x0f\x0f\x0f\x0f')) ] assert mock_command.call_args_list == [ call(['cp', 'root_dir/recovery.partition.size', 'boot_dir']), - call(['mv', 'initrd', 'root_dir/boot/initramfs-1.2.3.img']), - call(['cp', 'root_dir/recovery.partition.size', 'boot_dir_kiwi']) + call(['mv', 'initrd', 'root_dir/boot/initramfs-1.2.3.img']) ] self.setup.export_package_list.assert_called_once_with( 'target_dir' @@ -478,6 +467,7 @@ class TestDiskBuilder(object): self.setup.export_package_verification.assert_called_once_with( 'target_dir' ) + print(self.boot_image_task.include_file.call_args_list) assert self.boot_image_task.include_file.call_args_list == [ call('/config.partids'), call('/recovery.partition.size') @@ -576,9 +566,9 @@ class TestDiskBuilder(object): call('hostonly="no"\n'), call('dracut_rescue_image="no"\n'), call('add_dracutmodules+=" kiwi-overlay kiwi-lib kiwi-repart "\n'), - call(b'\x0f\x0f\x0f\x0f'), - call('kiwi_BootPart="1"\n'), - call('kiwi_RootPart="1"\n') + call('omit_dracutmodules+=" kiwi-dump "\n'), + call('boot_cmdline\n'), + call(b'\x0f\x0f\x0f\x0f') ] @patch('kiwi.builder.disk.FileSystem') @@ -724,7 +714,8 @@ class TestDiskBuilder(object): 'root_dir/etc/crypttab' ) assert self.boot_image_task.include_file.call_args_list == [ - call('/config.partids'), call('/etc/crypttab') + call('/config.partids'), + call('/etc/crypttab') ] @patch('kiwi.builder.disk.FileSystem') diff --git a/test/unit/builder_install_test.py b/test/unit/builder_install_test.py index de6957c5..c8b64de5 100644 --- a/test/unit/builder_install_test.py +++ b/test/unit/builder_install_test.py @@ -56,6 +56,9 @@ class TestInstallImageBuilder(object): return_value=self.kernel ) self.xml_state = mock.Mock() + self.xml_state.get_initrd_system = mock.Mock( + return_value='kiwi' + ) self.xml_state.xml_data.get_name = mock.Mock( return_value='result-image' ) @@ -90,13 +93,15 @@ class TestInstallImageBuilder(object): ) assert install_image.arch == 'ix86' + @patch('kiwi.builder.install.shutil.copy') @patch('kiwi.builder.install.mkdtemp') @patch_open @patch('kiwi.builder.install.Command.run') @patch('kiwi.builder.install.Iso.create_hybrid') @patch('kiwi.builder.install.Defaults.get_grub_boot_directory_name') def test_create_install_iso( - self, mock_grub_dir, mock_hybrid, mock_command, mock_open, mock_dtemp + self, mock_grub_dir, mock_hybrid, mock_command, mock_open, + mock_dtemp, mock_copy ): tmpdir_name = ['temp-squashfs', 'temp_media_dir'] @@ -118,9 +123,12 @@ class TestInstallImageBuilder(object): self.checksum.md5.assert_called_once_with( 'temp-squashfs/result-image.md5' ) + mock_copy.assert_called_once_with( + 'root_dir/boot/initrd', 'temp_media_dir/initrd.system_image' + ) assert mock_open.call_args_list == [ - call('initrd_dir/config.vmxsystem', 'w'), - call('temp_media_dir/config.isoclient', 'w') + call('temp_media_dir/config.isoclient', 'w'), + call('initrd_dir/config.vmxsystem', 'w') ] assert file_mock.write.call_args_list == [ call('IMAGE="result-image.raw"\n'), @@ -141,7 +149,7 @@ class TestInstallImageBuilder(object): call(), call() ] self.boot_image_task.create_initrd.assert_called_once_with( - self.mbrid + self.mbrid, 'initrd_kiwi_install' ) self.kernel.copy_kernel.assert_called_once_with( 'temp_media_dir/boot/x86_64/loader', '/linux' @@ -170,6 +178,28 @@ class TestInstallImageBuilder(object): 'uefi' ) + tmpdir_name = ['temp-squashfs', 'temp_media_dir'] + file_mock.write.reset_mock() + mock_open.reset_mock() + self.install_image.initrd_system = 'dracut' + + self.install_image.create_install_iso() + + self.boot_image_task.include_file.assert_called_once_with( + '/config.bootoptions' + ) + assert mock_open.call_args_list == [ + call('temp_media_dir/config.isoclient', 'w'), + call('root_dir/etc/dracut.conf.d/02-kiwi.conf', 'w') + ] + assert file_mock.write.call_args_list == [ + call('IMAGE="result-image.raw"\n'), + call('hostonly="no"\n'), + call('dracut_rescue_image="no"\n'), + call('add_dracutmodules+=" kiwi-lib kiwi-dump "\n'), + call('omit_dracutmodules+=" kiwi-overlay kiwi-repart "\n') + ] + @patch('kiwi.builder.install.mkdtemp') @patch_open @patch('kiwi.builder.install.Command.run') @@ -277,7 +307,7 @@ class TestInstallImageBuilder(object): 'tmpdir', '/pxeboot.xen.gz' ) self.boot_image_task.create_initrd.assert_called_once_with( - self.mbrid + self.mbrid, 'initrd_kiwi_install' ) assert mock_command.call_args_list[1] == call( ['mv', 'initrd', 'tmpdir/pxeboot.initrd.xz'] @@ -289,12 +319,37 @@ class TestInstallImageBuilder(object): 'tmpdir', xz_options=None ) + file_mock.write.reset_mock() + mock_open.reset_mock() + self.install_image.initrd_system = 'dracut' + + self.install_image.create_install_pxe_archive() + + assert mock_open.call_args_list == [ + call('tmpdir/result-image.append', 'w'), + call('root_dir/etc/dracut.conf.d/02-kiwi.conf', 'w') + ] + assert file_mock.write.call_args_list == [ + call('pxe=1 custom_kernel_options\n'), + call('hostonly="no"\n'), + call('dracut_rescue_image="no"\n'), + call('add_dracutmodules+=" kiwi-lib kiwi-dump "\n'), + call('omit_dracutmodules+=" kiwi-overlay kiwi-repart "\n') + ] + @patch('kiwi.builder.install.Path.wipe') - def test_destructor(self, mock_wipe): + @patch('os.path.exists') + @patch('os.remove') + def test_destructor(self, mock_remove, mock_exists, mock_wipe): + mock_exists.return_value = True + self.install_image.initrd_system = 'dracut' self.install_image.pxe_dir = 'pxe-dir' self.install_image.media_dir = 'media-dir' self.install_image.squashed_contents = 'squashed-dir' self.install_image.__del__() + mock_remove.assert_called_once_with( + 'root_dir/etc/dracut.conf.d/02-kiwi.conf' + ) assert mock_wipe.call_args_list == [ call('media-dir'), call('pxe-dir'), call('squashed-dir') ]