# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves <bmr@redhat.com>
#
# osprofile_tests.py - Boom OS profile tests.
#
# This file is part of the boom project.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU Lesser 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 unittest
import logging
from sys import stdout
from os import listdir, makedirs, mknod, unlink
from os.path import abspath, exists, join
from stat import S_IFBLK, S_IFCHR
import shutil
# Test suite paths
from tests import *
import boom
from boom.bootloader import *
from boom.osprofile import *
from boom.hostprofile import *
from boom import Selection
# Override default BOOM_ROOT and BOOT_ROOT
# NOTE: with test fixtures that use the sandbox, this path is further
# overridden by the class setUp() method to point to the appropriate
# sandbox location.
boom.set_boot_path(BOOT_ROOT_TEST)
log = logging.getLogger()
log.level = logging.DEBUG
log.addHandler(logging.FileHandler("test.log"))
_test_osp = None
class BootParamsTests(unittest.TestCase):
def test_BootParams_no_version_raises(self):
with self.assertRaises(ValueError) as cm:
# A version string is required
bp = BootParams(None)
def test_BootParams_conflicting_btrfs_raises(self):
with self.assertRaises(ValueError) as cm:
# Only one of subvol_id or subvol_path is allowed
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5",
btrfs_subvol_path="/snapshots/snap-1",
btrfs_subvol_id="232")
def test_BootParams_plain__str__and__repr__(self):
# Plain root_device
bp = BootParams(version="1.1.1.x86_64", root_device="/dev/sda5")
xstr = "1.1.1.x86_64, root_device=/dev/sda5"
xrepr = 'BootParams("1.1.1.x86_64", root_device="/dev/sda5")'
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
def test_BootParams_lvm__str__and__repr__(self):
# LVM logical volume and no root_device
bp = BootParams(version="1.1.1.x86_64", lvm_root_lv="vg00/lvol0")
xstr = ("1.1.1.x86_64, root_device=/dev/vg00/lvol0, "
"lvm_root_lv=vg00/lvol0")
xrepr = ('BootParams("1.1.1.x86_64", root_device="/dev/vg00/lvol0", '
'lvm_root_lv="vg00/lvol0")')
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
# LVM logical volume and root_device override
bp = BootParams(version="1.1.1.x86_64",
root_device="/dev/mapper/vg00-lvol0",
lvm_root_lv="vg00/lvol0")
xstr = ("1.1.1.x86_64, root_device=/dev/mapper/vg00-lvol0, "
"lvm_root_lv=vg00/lvol0")
xrepr = ('BootParams("1.1.1.x86_64", '
'root_device="/dev/mapper/vg00-lvol0", '
'lvm_root_lv="vg00/lvol0")')
self.assertFalse(bp.has_btrfs())
self.assertTrue(bp.has_lvm2())
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
def test_BootParams_btrfs__str__and__repr__(self):
# BTRFS subvol path and root_device
bp = BootParams(version="1.1.1.x86_64",
root_device="/dev/sda5",
btrfs_subvol_path="/snapshots/snap-1")
xstr = ("1.1.1.x86_64, root_device=/dev/sda5, "
"btrfs_subvol_path=/snapshots/snap-1")
xrepr = ('BootParams("1.1.1.x86_64", root_device="/dev/sda5", '
'btrfs_subvol_path="/snapshots/snap-1")')
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
# BTRFS subvol ID and root_device
bp = BootParams(version="1.1.1.x86_64",
root_device="/dev/sda5",
btrfs_subvol_id="232")
xstr = ("1.1.1.x86_64, root_device=/dev/sda5, "
"btrfs_subvol_id=232")
xrepr = ('BootParams("1.1.1.x86_64", root_device="/dev/sda5", '
'btrfs_subvol_id="232")')
self.assertTrue(bp.has_btrfs())
self.assertFalse(bp.has_lvm2())
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
def test_BootParams_lvm_btrfs__str__and__repr__(self):
# BTRFS subvol path and LVM root_device
bp = BootParams(version="1.1.1.x86_64", lvm_root_lv="vg00/lvol0",
btrfs_subvol_path="/snapshots/snap-1")
xstr = ("1.1.1.x86_64, root_device=/dev/vg00/lvol0, "
"lvm_root_lv=vg00/lvol0, "
"btrfs_subvol_path=/snapshots/snap-1")
xrepr = ('BootParams("1.1.1.x86_64", root_device="/dev/vg00/lvol0", '
'lvm_root_lv="vg00/lvol0", '
'btrfs_subvol_path="/snapshots/snap-1")')
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
# BTRFS subvol id and LVM root_device
bp = BootParams(version="1.1.1.x86_64", lvm_root_lv="vg00/lvol0",
btrfs_subvol_id="232")
xstr = ("1.1.1.x86_64, root_device=/dev/vg00/lvol0, "
"lvm_root_lv=vg00/lvol0, btrfs_subvol_id=232")
xrepr = ('BootParams("1.1.1.x86_64", root_device="/dev/vg00/lvol0", '
'lvm_root_lv="vg00/lvol0", btrfs_subvol_id="232")')
self.assertEqual(str(bp), xstr)
self.assertEqual(repr(bp), xrepr)
def _reset_test_osprofile():
global _test_osp
# Some tests modify the OsProfile: recycle it each time it is used
if _test_osp:
_test_osp.delete_profile()
osp = OsProfile(name="Distribution", short_name="distro",
version="1 (Workstation Edition)", version_id="1")
osp.uname_pattern = "di1"
osp.kernel_pattern = "/vmlinuz-%{version}"
osp.initramfs_pattern = "/initramfs-%{version}.img"
osp.root_opts_lvm2 = "rd.lvm.lv=%{lvm_root_lv}"
osp.root_opts_btrfs = "rootflags=%{btrfs_subvolume}"
osp.options = "root=%{root_device} %{root_opts} rhgb quiet"
_test_osp = osp
class MockBootEntry(object):
boot_id = "1234567890abcdef"
version = "1.1.1"
expand_options = "root=/dev/mapper/rhel-root ro rhgb quiet"
_osp = None
_entry_data = {}
class BootEntryBasicTests(unittest.TestCase):
"""Tests for the BootEntry class that do not depend on external
test data.
"""
# DELETEME
test_version = "1.1.1-1.qux.x86_64"
test_lvm2_root_device = "/dev/vg00/lvol0"
test_lvm_root_lv = "vg00/lvol0"
test_btrfs_root_device = "/dev/sda5"
test_btrfs_subvol_path = "/snapshots/snap1"
test_btrfs_subvol_id = "232"
# Sandbox paths
# Master BLS loader directory for sandbox
loader_path = join(BOOT_ROOT_TEST, "loader")
# Master boom configuration path for sandbox
boom_path = join(BOOT_ROOT_TEST, "boom")
def setUp(self):
reset_sandbox()
# Sandbox paths
boot_sandbox = join(SANDBOX_PATH, "boot")
boom_sandbox = join(SANDBOX_PATH, "boot/boom")
loader_sandbox = join(SANDBOX_PATH, "boot/loader")
# Initialise sandbox from master
makedirs(boot_sandbox)
shutil.copytree(self.boom_path, boom_sandbox)
shutil.copytree(self.loader_path, loader_sandbox)
# Set boom paths
boom.set_boot_path(boot_sandbox)
# Reset profiles, entries, and host profiles to known state.
load_profiles()
load_entries()
load_host_profiles()
def tearDown(self):
# Drop any in-memory entries and profiles modified by tests
drop_entries()
drop_profiles()
drop_host_profiles()
# Clear sandbox data
rm_sandbox()
reset_boom_paths()
# BootEntry tests
def test_BootEntry__str__(self):
be = BootEntry(title="title", machine_id="ffffffff", osprofile=None,
allow_no_dev=True)
xstr = ('title title\nmachine-id ffffffff\n'
'linux /vmlinuz-%{version}\n'
'initrd /initramfs-%{version}.img')
self.assertEqual(str(be), xstr)
def test_BootEntry__repr__(self):
be = BootEntry(title="title", machine_id="ffffffff", osprofile=None,
allow_no_dev=True)
xrepr = ('BootEntry(entry_data={BOOM_ENTRY_TITLE: "title", '
'BOOM_ENTRY_MACHINE_ID: "ffffffff", '
'BOOM_ENTRY_LINUX: "/vmlinuz-%{version}", '
'BOOM_ENTRY_INITRD: "/initramfs-%{version}.img", '
'BOOM_ENTRY_BOOT_ID: '
'"40c7c3158e626ed25cc2066b7c308fca0cb57be2"})')
self.assertEqual(repr(be), xrepr)
def test_BootEntry(self):
# Test BootEntry init from kwargs
with self.assertRaises(ValueError) as cm:
be = BootEntry(title=None, machine_id="ffffffff", osprofile=None,
allow_no_dev=True)
# Empty machine-id is now allowed for Red Hat BLS entries...
#with self.assertRaises(ValueError) as cm:
# be = BootEntry(title="title", machine_id=None, osprofile=None,
# allow_no_dev=True)
with self.assertRaises(ValueError) as cm:
be = BootEntry(title=None, machine_id=None, osprofile=None,
allow_no_dev=True)
be = BootEntry(title="title", machine_id="ffffffff")
self.assertTrue(be)
def test_BootEntry_from_entry_data(self):
# Pull in all the BOOM_ENTRY_* constants to the local namespace.
from boom.bootloader import (
BOOM_ENTRY_TITLE, BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_VERSION,
BOOM_ENTRY_LINUX, BOOM_ENTRY_EFI, BOOM_ENTRY_INITRD,
BOOM_ENTRY_OPTIONS
)
with self.assertRaises(ValueError) as cm:
# Missing BOOM_ENTRY_TITLE
be = BootEntry(entry_data={BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_LINUX: "/vmlinuz-1.1.1",
BOOM_ENTRY_INITRD: "/initramfs-1.1.1.img",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"})
# Valid entry
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_LINUX: "/vmlinuz-1.1.1",
BOOM_ENTRY_INITRD: "/initramfs-1.1.1.img",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"})
with self.assertRaises(ValueError) as cm:
# Missing BOOM_ENTRY_LINUX or BOOM_ENTRY_EFI
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_INITRD: "/initramfs-1.1.1.img",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"})
# Valid Linux entry
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_LINUX: "/vmlinuz",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"})
# Valid EFI entry
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_EFI: "/some.efi.thing",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"})
def test_BootEntry_with_boot_params(self):
from boom.bootloader import (
BOOM_ENTRY_TITLE, BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_VERSION,
BOOM_ENTRY_LINUX, BOOM_ENTRY_EFI, BOOM_ENTRY_INITRD,
BOOM_ENTRY_OPTIONS
)
bp = BootParams(version="2.2.2", lvm_root_lv="vg00/lvol0")
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_LINUX: "/vmlinuz-1.1.1",
BOOM_ENTRY_INITRD: "/initramfs-1.1.1.img",
BOOM_ENTRY_OPTIONS: "root=/dev/vg_root/root "
"rd.lvm.lv=vg_root/root"},
boot_params=bp, allow_no_dev=True)
# boot_params overrides BootEntry
self.assertEqual(be.version, bp.version)
self.assertNotEqual(be.version, "1.1.1")
def test_BootEntry_empty_osprofile(self):
# Assert that key properties of a BootEntry with no attached osprofile
# return None.
from boom.bootloader import (
BOOM_ENTRY_TITLE, BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_VERSION,
BOOM_ENTRY_LINUX, BOOM_ENTRY_EFI, BOOM_ENTRY_INITRD,
BOOM_ENTRY_OPTIONS
)
bp = BootParams(version="2.2.2", lvm_root_lv="vg00/lvol0")
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_LINUX: "/vmlinuz",
BOOM_ENTRY_VERSION: "1.1.1"}, boot_params=bp,
allow_no_dev=True)
xoptions = "root=/dev/vg00/lvol0 ro rd.lvm.lv=vg00/lvol0"
self.assertEqual(be.options, xoptions)
class BootEntryTests(unittest.TestCase):
"""Tests for the BootEntry class using on-disk entry and profile
data.
"""
test_version = "1.1.1-1.qux.x86_64"
test_lvm2_root_device = "/dev/vg00/lvol0"
test_lvm_root_lv = "vg00/lvol0"
test_btrfs_root_device = "/dev/sda5"
test_btrfs_subvol_path = "/snapshots/snap1"
test_btrfs_subvol_id = "232"
# Standard test OsProfile. Tests must not modify this.
test_osp = None
# Standard test BootParams. Tests must not modify this.
test_bp = None
# Standard test BootEntry. Tests must not modify this.
test_be = None
# Master BLS loader directory for sandbox
loader_path = join(BOOT_ROOT_TEST, "loader")
# Master boom configuration path for sandbox
boom_path = join(BOOT_ROOT_TEST, "boom")
# Test fixture init/cleanup
def setUp(self):
"""Set up a test fixture for the BootEntryTests class.
Defines standard OsProfile, BootParams, and BootEntry
objects for use in these tests.
"""
reset_sandbox()
# Sandbox paths
boot_sandbox = join(SANDBOX_PATH, "boot")
boom_sandbox = join(SANDBOX_PATH, "boot/boom")
loader_sandbox = join(SANDBOX_PATH, "boot/loader")
# Initialise sandbox from master
makedirs(boot_sandbox)
shutil.copytree(self.boom_path, boom_sandbox)
shutil.copytree(self.loader_path, loader_sandbox)
# Set PATH environment variable
set_mock_path()
# Set boom paths
boom.set_boot_path(boot_sandbox)
# Load test OsProfile and BootEntry data
load_profiles()
load_entries()
# Define a new, test OsProfile that is never included in the
# standard set distributed with boom. To be used only for
# formatting BootEntry objects for testing.
osp = OsProfile(name="Distribution", short_name="distro",
version="1 (Workstation Edition)", version_id="1")
osp.uname_pattern = "di1"
osp.kernel_pattern = "/vmlinuz-%{version}"
osp.initramfs_pattern = "/initramfs-%{version}.img"
osp.root_opts_lvm2 = "rd.lvm.lv=%{lvm_root_lv}"
osp.root_opts_btrfs = "rootflags=%{btrfs_subvolume}"
osp.options = "root=%{root_device} %{root_opts} rhgb quiet"
self.test_osp = osp
# Define a standard set of test BootParams
bp = BootParams("1.1.1.fc24", root_device="/dev/vg/lv",
lvm_root_lv="vg/lv")
self.test_bp = bp
# Define a synthetic BootEntry for testing
be = BootEntry(title="title", machine_id="ffffffff",
boot_params=bp, osprofile=osp, allow_no_dev=True)
self.test_be = be
def tearDown(self):
"""Tear down the standard test profiles and entries used by the
BootEntryTests class.
"""
# Drop any in-memory entries and profiles modified by tests
drop_entries()
drop_profiles()
# Clear sandbox data
rm_sandbox()
reset_boom_paths()
self.test_osp = None
self.test_bp = None
# BootParams recovery tests
def test_BootParams_from_entry_no_opts(self):
osp = self.test_osp
osp.options = "root=%{root_device}"
be = MockBootEntry()
be.options = ""
be.expand_options = ""
be._osp = osp
# Logs warning (no root_device)
bp = BootParams.from_entry(be)
# Assert that root_device is empty and that other parameters
# are still recovered.
self.assertEqual(bp.root_device, "")
self.assertEqual(bp.version, "1.1.1")
def test_BootParams_from_entry_no_root_device(self):
osp = self.test_osp
be = MockBootEntry()
be.options = "ro rd.lvm.lv=vg00/lvol0 rhgb quiet"
be._osp = osp
self.assertTrue(BootParams.from_entry(be))
def test_BootEntry_fixed_options_string(self):
# Assert that key properties of a BootEntry with empty format keys
# return the empty string.
from boom.bootloader import (
BOOM_ENTRY_TITLE, BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_VERSION,
BOOM_ENTRY_LINUX, BOOM_ENTRY_EFI, BOOM_ENTRY_INITRD,
BOOM_ENTRY_OPTIONS
)
osp = self.test_osp
# Clear the OsProfile.options format key
osp.options = "root=/dev/myroot"
bp = BootParams(version="2.2.2", lvm_root_lv="vg00/lvol0")
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_LINUX: "/vmlinuz-1.1.1",
BOOM_ENTRY_INITRD: "/initramfs-1.1.1.img"},
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.options, "root=/dev/myroot")
def test_BootEntry_write(self):
# Use a real OsProfile here: the entry will be written to disk, and
# may be seen during entry loading (to avoid the entry being moved
# to the Null profile).
osp = find_profiles(Selection(os_id="d4439b7"))[0]
bp = BootParams("1.1.1-1.fc26", root_device="/dev/vg00/lvol0",
lvm_root_lv="vg00/lvol0")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
osprofile=osp, allow_no_dev=True)
boot_id = be.boot_id
be.write_entry()
load_entries()
be2 = find_entries(Selection(boot_id=boot_id))[0]
self.assertEqual(be.title, be2.title)
self.assertEqual(be.boot_id, be2.boot_id)
self.assertEqual(be.version, be2.version)
## Create on-disk entry and add to list of known entries
#be.write_entry()
# Profile and entry are non-persistent
be2.delete_entry()
def test_BootEntry_profile_kernel_version(self):
osp = self.test_osp
be = BootEntry(title="title", machine_id="ffffffff", osprofile=osp)
be.version = "1.1.1-17.qux.x86_64"
self.assertEqual(be.linux, "/vmlinuz-1.1.1-17.qux.x86_64")
self.assertEqual(be.initrd, "/initramfs-1.1.1-17.qux.x86_64.img")
def test_BootEntry_profile_root_lvm2(self):
osp = self.test_osp
bp = BootParams("1.1", lvm_root_lv="vg00/lvol0")
be = BootEntry(title="title", machine_id="ffffffff",
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.root_opts, "rd.lvm.lv=vg00/lvol0")
self.assertEqual(be.options, "root=/dev/vg00/lvol0 "
"rd.lvm.lv=vg00/lvol0 rhgb quiet")
def test_BootEntry_profile_root_btrfs_id(self):
osp = self.test_osp
bp = BootParams("1.1", root_device="/dev/sda5", btrfs_subvol_id="232")
be = BootEntry(title="title", machine_id="ffffffff",
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.root_opts, "rootflags=subvolid=232")
self.assertEqual(be.options, "root=/dev/sda5 "
"rootflags=subvolid=232 rhgb quiet")
def test_BootEntry_profile_root_btrfs_path(self):
osp = self.test_osp
bp = BootParams("1.1", root_device="/dev/sda5",
btrfs_subvol_path="/snapshots/20170523-1")
be = BootEntry(title="title", machine_id="ffffffff",
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.root_opts,
"rootflags=subvol=/snapshots/20170523-1")
self.assertEqual(be.options, "root=/dev/sda5 "
"rootflags=subvol=/snapshots/20170523-1 rhgb quiet")
def test_BootEntry_boot_id(self):
xboot_id = 'f0a46b7a6e982cab4163af6b45087e87691a0c43'
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
self.assertEqual(xboot_id, be.boot_id)
def test_BootEntry_root_opts_no_values(self):
from boom.bootloader import (
BOOM_ENTRY_TITLE, BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_VERSION,
BOOM_ENTRY_LINUX, BOOM_ENTRY_EFI, BOOM_ENTRY_INITRD,
BOOM_ENTRY_OPTIONS
)
osp = self.test_osp
xroot_opts = ""
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_LINUX: "/vmlinuz",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_OPTIONS: "root=/dev/sda5 ro"
}, allow_no_dev=True)
self.assertEqual(xroot_opts, be.root_opts)
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(entry_data={BOOM_ENTRY_TITLE: "title",
BOOM_ENTRY_LINUX: "/vmlinuz",
BOOM_ENTRY_MACHINE_ID: "ffffffff",
BOOM_ENTRY_VERSION: "1.1.1",
BOOM_ENTRY_OPTIONS: "root=%{root_device} %{root_opts}"},
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(xroot_opts, be.root_opts)
# BootEntry properties get/set tests
# Simple properties: direct set to self._entry_data.
def test_BootEntry_options_set_get(self):
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
xoptions = "testoptions root=%{root_device}"
be.options = xoptions
self.assertEqual(xoptions, be.options)
def test_BootEntry_linux_set_get(self):
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
xlinux = "/vmlinuz"
be.linux = xlinux
self.assertEqual(xlinux, be.linux)
def test_BootEntry_initrd_set_get(self):
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
xinitrd = "/initrd.img"
be.initrd = xinitrd
self.assertEqual(xinitrd, be.initrd)
def test_BootEntry_efi_set_get(self):
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
xefi = "/some.efi.img"
be.efi = xefi
self.assertEqual(xefi, be.efi)
def test_BootEntry_devicetree_set_get(self):
bp = BootParams("1.1.1.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
xdevicetree = "/tegra20-paz00.dtb"
be.devicetree = xdevicetree
self.assertEqual(xdevicetree, be.devicetree)
def test_BootEntry_optional_keys(self):
osp = self.test_osp
osp.optional_keys = "grub_users grub_arg grub_class"
bp = BootParams("1.1", lvm_root_lv="vg00/lvol0")
be = BootEntry(title="title", machine_id="ffffffff",
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.root_opts, "rd.lvm.lv=vg00/lvol0")
self.assertEqual(be.options, "root=/dev/vg00/lvol0 "
"rd.lvm.lv=vg00/lvol0 rhgb quiet")
be.grub_users = "test_user"
be.grub_arg = "--test-arg"
be.grub_class = "test_class"
def test_BootEntry_optional_keys_not_set(self):
osp = self.test_osp
osp.optional_keys = ""
bp = BootParams("1.1", lvm_root_lv="vg00/lvol0")
be = BootEntry(title="title", machine_id="ffffffff",
osprofile=osp, boot_params=bp, allow_no_dev=True)
self.assertEqual(be.root_opts, "rd.lvm.lv=vg00/lvol0")
self.assertEqual(be.options, "root=/dev/vg00/lvol0 "
"rd.lvm.lv=vg00/lvol0 rhgb quiet")
with self.assertRaises(ValueError) as cm:
be.grub_users = "test_user"
def test_match_OsProfile_to_BootEntry(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
xos_id = "6bf746bb7231693b2903585f171e4290ff0602b5"
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
self.assertEqual(be._osp.os_id, xos_id)
def test_BootEntry__getitem__(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
from boom.bootloader import (BOOM_ENTRY_VERSION, BOOM_ENTRY_TITLE,
BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_LINUX,
BOOM_ENTRY_INITRD, BOOM_ENTRY_OPTIONS,
BOOM_ENTRY_DEVICETREE)
xtitle = "title"
xmachine_id = "ffffffff"
xversion = "4.11.5-100.fc24.x86_64"
xlinux = "/vmlinuz-4.11.5-100.fc24.x86_64"
xinitrd = "/initramfs-4.11.5-100.fc24.x86_64.img"
xoptions = "root=/dev/sda5 ro rhgb quiet"
xdevicetree = "device.tree"
bp = BootParams(xversion, root_device="/dev/sda5")
be = BootEntry(title=xtitle, machine_id=xmachine_id, boot_params=bp,
allow_no_dev=True)
be.devicetree = xdevicetree
self.assertEqual(be[BOOM_ENTRY_VERSION], "4.11.5-100.fc24.x86_64")
self.assertEqual(be[BOOM_ENTRY_TITLE], "title")
self.assertEqual(be[BOOM_ENTRY_MACHINE_ID], "ffffffff")
self.assertEqual(be[BOOM_ENTRY_LINUX], xlinux)
self.assertEqual(be[BOOM_ENTRY_INITRD], xinitrd)
self.assertEqual(be[BOOM_ENTRY_OPTIONS], xoptions)
self.assertEqual(be[BOOM_ENTRY_DEVICETREE], xdevicetree)
def test_BootEntry__getitem__bad_key_raises(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp)
with self.assertRaises(TypeError) as cm:
be[123]
def test_BootEntry__setitem__(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
from boom.bootloader import (BOOM_ENTRY_VERSION, BOOM_ENTRY_TITLE,
BOOM_ENTRY_MACHINE_ID, BOOM_ENTRY_LINUX,
BOOM_ENTRY_INITRD, BOOM_ENTRY_OPTIONS,
BOOM_ENTRY_DEVICETREE)
xtitle = "title"
xmachine_id = "ffffffff"
xversion = "4.11.5-100.fc24.x86_64"
xlinux = "/vmlinuz-4.11.5-100.fc24.x86_64"
xinitrd = "/initramfs-4.11.5-100.fc24.x86_64.img"
xoptions = "root=/dev/sda5 ro rhgb quiet"
xdevicetree = "device.tree"
bp = BootParams(xversion, root_device="/dev/sda5")
be = BootEntry(title="qux", machine_id="11111111", boot_params=bp,
allow_no_dev=True)
be.devicetree = xdevicetree
be[BOOM_ENTRY_VERSION] = xversion
be[BOOM_ENTRY_TITLE] = xtitle
be[BOOM_ENTRY_MACHINE_ID] = xmachine_id
be[BOOM_ENTRY_LINUX] = xlinux
be[BOOM_ENTRY_INITRD] = xinitrd
be[BOOM_ENTRY_DEVICETREE] = xdevicetree
self.assertEqual(be.version, "4.11.5-100.fc24.x86_64")
self.assertEqual(be.title, "title")
self.assertEqual(be.machine_id, "ffffffff")
self.assertEqual(be.linux, xlinux)
self.assertEqual(be.initrd, xinitrd)
self.assertEqual(be.options, xoptions)
self.assertEqual(be.devicetree, xdevicetree)
def test_BootEntry__getitem__bad_key_raises(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
with self.assertRaises(TypeError) as cm:
be[123] = "qux"
def test_BootEntry_keys(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
xkeys = [
'BOOM_ENTRY_TITLE', 'BOOM_ENTRY_MACHINE_ID',
'BOOM_ENTRY_ARCHITECTURE', 'BOOM_ENTRY_LINUX', 'BOOM_ENTRY_INITRD',
'BOOM_ENTRY_OPTIONS', 'BOOM_ENTRY_VERSION'
]
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
self.assertEqual(be.keys(), xkeys)
def test_BootEntry_values(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
xvalues = [
'title',
'ffffffff',
'x64',
'/vmlinuz-4.11.5-100.fc24.x86_64',
'/initramfs-4.11.5-100.fc24.x86_64.img',
'root=/dev/sda5 ro rhgb quiet',
'4.11.5-100.fc24.x86_64'
]
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
architecture='x64', allow_no_dev=True)
# Ignore ordering
be_set = set(be.values())
xvalues_set = set(xvalues)
self.assertEqual(be_set, xvalues_set)
def test_BootEntry_items(self):
from boom.osprofile import OsProfile, load_profiles
load_profiles()
os_id = "9cb53ddda889d6285fd9ab985a4c47025884999f"
osp = boom.osprofile.get_os_profile_by_id(os_id)
xkeys = [
'BOOM_ENTRY_TITLE', 'BOOM_ENTRY_MACHINE_ID',
'BOOM_ENTRY_ARCHITECTURE', 'BOOM_ENTRY_LINUX',
'BOOM_ENTRY_INITRD', 'BOOM_ENTRY_OPTIONS', 'BOOM_ENTRY_VERSION'
]
xvalues = [
'title',
'ffffffff',
'',
'/vmlinuz-4.11.5-100.fc24.x86_64',
'/initramfs-4.11.5-100.fc24.x86_64.img',
'root=/dev/sda5 ro rhgb quiet',
'4.11.5-100.fc24.x86_64'
]
xitems = list(zip(xkeys, xvalues))
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
osprofile=osp, allow_no_dev=True)
be_set = set(be.items())
xitems_set = set(xitems)
self.assertEqual(be_set, xitems_set)
def test_BootEntry_eq_no_boot_id(self):
class NotABootEntry(object):
i_have_no_boot_id = True
osp = self.test_osp
be = self.test_be
self.assertFalse(be == NotABootEntry())
def test__add_entry_loads_entries(self):
boom.bootloader._entries = None
osp = self.test_osp
be = self.test_be
boom.bootloader._add_entry(be)
self.assertTrue(boom.bootloader._entries)
self.assertTrue(boom.osprofile._profiles)
def test__del_entry_deletes_entry(self):
boom.bootloader.load_entries()
be = boom.bootloader._entries[0]
self.assertTrue(be in boom.bootloader._entries)
boom.bootloader._del_entry(be)
self.assertFalse(be in boom.bootloader._entries)
def test_load_entries_loads_profiles(self):
import boom.osprofile
boom.osprofile._profiles = []
boom.osprofile._profiles_by_id = {}
boom.osprofile._profiles = [boom.osprofile.OsProfile("","","","","")]
boom.osprofile._profiles_loaded = False
boom.bootloader.load_entries()
self.assertTrue(boom.osprofile._profiles)
self.assertTrue(boom.bootloader._entries)
def test_find_entries_loads_entries(self):
boom.bootloader._entries = None
boom.bootloader.find_entries()
self.assertTrue(boom.osprofile._profiles)
self.assertTrue(boom.bootloader._entries)
def test_find_entries_by_boot_id(self):
boot_id = "12a2696bf85cc33f42f0449fab5da64dac7aa10a"
boom.bootloader._entries = None
bes = boom.bootloader.find_entries(Selection(boot_id=boot_id))
self.assertEqual(len(bes), 1)
def test_find_entries_by_title(self):
title = "Red Hat Enterprise Linux 7.2 (Maipo) 3.10-23.el7"
boom.bootloader._entries = None
bes = boom.bootloader.find_entries(Selection(title=title))
self.assertEqual(len(bes), 1)
def test_find_entries_by_version(self):
version = "4.10.17-100.fc24.x86_64"
boom.bootloader._entries = None
bes = boom.bootloader.find_entries(Selection(version=version))
path = boom_entries_path()
nr = len([p for p in listdir(path) if version in p])
self.assertEqual(len(bes), nr)
def test_find_entries_by_root_device(self):
entries_path = boom_entries_path()
root_device = "/dev/vg_root/root"
boom.bootloader._entries = None
bes = boom.bootloader.find_entries(Selection(root_device=root_device))
xentries = 0
for e in listdir(entries_path):
if e.endswith(".conf"):
with open(join(entries_path, e)) as f:
for l in f.readlines():
if root_device in l:
xentries +=1
self.assertEqual(len(bes), xentries)
def test_find_entries_by_lvm_root_lv(self):
entries_path = boom_entries_path()
boom.bootloader._entries = None
lvm_root_lv = "vg_root/root"
bes = boom.bootloader.find_entries(Selection(lvm_root_lv=lvm_root_lv))
xentries = 0
for e in listdir(entries_path):
if e.endswith(".conf"):
with open(join(entries_path, e)) as f:
for l in f.readlines():
if "rd.lvm.lv=" + lvm_root_lv in l:
xentries +=1
self.assertEqual(len(bes), xentries)
def test_find_entries_by_btrfs_subvol_id(self):
entries_path = boom_entries_path()
boom.bootloader._entries = None
btrfs_subvol_id = "23"
nr = 0
# count entries
for p in listdir(entries_path):
with open(join(entries_path, p), "r") as f:
for l in f.readlines():
if "subvolid=23" in l:
nr += 1
select = Selection(btrfs_subvol_id=btrfs_subvol_id)
bes = boom.bootloader.find_entries(select)
self.assertEqual(len(bes), nr)
def test_find_entries_by_btrfs_subvol_path(self):
entries_path = boom_entries_path()
btrfs_subvol_path = "/snapshot/today"
boom.bootloader._entries = None
select = Selection(btrfs_subvol_path=btrfs_subvol_path)
bes = boom.bootloader.find_entries(select)
nr = 0
# count entries
for p in listdir(entries_path):
with open(join(entries_path, p), "r") as f:
for l in f.readlines():
if "/snapshot/today" in l:
nr += 1
self.assertEqual(len(bes), nr)
def test_delete_unwritten_BootEntry_raises(self):
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
with self.assertRaises(ValueError) as cm:
be.delete_entry()
def test_delete_BootEntry_deletes(self):
bp = BootParams("4.11.5-100.fc24.x86_64", root_device="/dev/sda5")
be = BootEntry(title="title", machine_id="ffffffff", boot_params=bp,
allow_no_dev=True)
be.write_entry()
be.delete_entry()
self.assertFalse(exists(be._entry_path))
def test_BootEntry_expanded_vars(self):
# A boot entry with Grub2 environment variable references
be = find_entries(Selection(boot_id="526f54a"))[0]
xstr = ("title grub args\n" +
"machine-id 653b444d513a43239c37deae4f5fe644\n" +
"version 5.4.7-100.fc30.x86_64\n" +
"linux /vmlinuz-5.4.7-100.fc30.x86_64\n" +
"initrd /initramfs-5.4.7-100.fc30.x86_64.img\n" +
"options root=/dev/vg_hex/root ro rd.lvm.lv=vg_hex/root\n" +
"grub_users root\n"
"grub_arg kernel\n"
"grub_class --unrestricted")
self.assertEqual(xstr, be.expanded())
def test_BootEntry_expanded_no_vars(self):
# A boot entry with no Grub2 environment variable references
be = find_entries(Selection(boot_id="b3389d2"))[0]
xstr = str(be)
self.assertEqual(xstr, be.expanded())
class BootLoaderBasicTests(unittest.TestCase):
def test_import(self):
import boom.bootloader
class BootLoaderTests(unittest.TestCase):
"""Class for bootloader module-level tests.
"""
# Master BLS loader directory for sandbox
loader_path = join(BOOT_ROOT_TEST, "loader")
# Master boom configuration path for sandbox
boom_path = join(BOOT_ROOT_TEST, "boom")
# Test fixture init/cleanup
def setUp(self):
"""setUp()
Set up a test fixture for the BootEntryTests class.
Defines standard OsProfile, BootParams, and BootEntry
objects for use in these tests.
"""
reset_sandbox()
# Sandbox paths
boot_sandbox = join(SANDBOX_PATH, "boot")
boom_sandbox = join(SANDBOX_PATH, "boot/boom")
loader_sandbox = join(SANDBOX_PATH, "boot/loader")
# Initialise sandbox from master
makedirs(boot_sandbox)
shutil.copytree(self.boom_path, boom_sandbox)
shutil.copytree(self.loader_path, loader_sandbox)
# Set boom paths
boom.set_boot_path(boot_sandbox)
# Load test OsProfile and BootEntry data
load_profiles()
load_entries()
def tearDown(self):
"""tearDown()
Tear down the standard test profiles and entries used by the
BootEntryTests class.
"""
# Drop any in-memory entries and profiles modified by tests
drop_entries()
drop_profiles()
# Clear sandbox data
rm_sandbox()
reset_boom_paths()
# Module tests
def _nr_machine_id(self, machine_id):
entries = boom.bootloader._entries
match = [e for e in entries if e.machine_id == machine_id]
return len(match)
# Profile store tests
def test_load_entries(self):
# Test that loading the test entries succeeds.
boom.bootloader.load_entries()
entry_count = 0
for entry in listdir(boom_entries_path()):
if entry.endswith(".conf"):
entry_count += 1
self.assertEqual(len(boom.bootloader._entries), entry_count)
def test_load_entries_with_machine_id(self):
# Test that loading the test entries by machine_id succeeds,
# and returns the expected number of profiles.
machine_id = "ffffffff"
boom.bootloader.load_entries(machine_id=machine_id)
entry_count = 0
for entry in listdir(boom_entries_path()):
if entry.startswith(machine_id) and entry.endswith(".conf"):
entry_count += 1
self.assertEqual(len(boom.bootloader._entries), entry_count)
def test_write_entries(self):
boom.bootloader.load_entries()
boom.bootloader.write_entries()
def test_find_boot_entries(self):
boom.bootloader.load_entries()
find_entries = boom.bootloader.find_entries
entries = find_entries()
self.assertEqual(len(entries), len(boom.bootloader._entries))
entries = find_entries(Selection(machine_id="ffffffff"))
self.assertEqual(len(entries), self._nr_machine_id("ffffffff"))
def test_Selection_no_osp_match(self):
s = Selection(os_id="12345")
self.assertFalse(find_entries(s))
@unittest.skipIf(not have_root(), "requires root privileges")
class BootLoaderTestsCheckRoot(unittest.TestCase):
"""Base class for BootLoaderTests that validate a chosen root
device.
Subclasses define the lists of devices that must be present
or absent, and the superclass initialises these in the setUp()
and tearDown() methods.
The format of the ``add_dev`` and ``del_dev`` lists is a
tuple ``(devname, type)``, where ``type`` is a string type
containing the character 'c' (char), or 'b' (block).
Device types in the del_devs list are currently ignored.
Tests using this base class require root privileges in order
to manipulate device nodes in the test sanbox. These tests
are automatically skipped if the suite is run as a normal
user.
"""
add_devs = []
del_devs = []
def setUp(self):
reset_sandbox()
dev_path = join(SANDBOX_PATH, "dev")
makedirs(dev_path)
for dev in self.add_devs:
mode = 0o600
if dev[1] == 'b':
mode |= S_IFBLK
if dev[1] == 'c':
mode |= S_IFCHR
mknod(join(dev_path, dev[0]), mode)
for dev in self.del_devs:
try:
unlink(join(dev_path, dev[0]))
except OSError as e:
if e.errno != 2:
raise
def tearDown(self):
rm_sandbox()
class BootLoaderTestsCheckRootReal(BootLoaderTestsCheckRoot):
"""Check root device with valid block device node.
"""
add_devs = [("sda", "b")]
def test_check_root_device_real(self):
# Real block device node
boom.bootloader.check_root_device("tests/sandbox/dev/sda")
class BootLoaderTestsCheckRootNonex(BootLoaderTestsCheckRoot):
"""Check root device with invalid block device node.
"""
del_devs = [("sdb", "b")]
def test_check_root_device_nonex(self):
# Non-existent device node
with self.assertRaises(BoomRootDeviceError) as cm:
boom.bootloader.check_root_device("tests/dev/sdb")
class BootLoaderTestsCheckRootNonblock(BootLoaderTestsCheckRoot):
"""Check root device with non-block device node.
"""
add_devs = [("null", "c")]
def test_check_root_device_nonblock(self):
# Non-existent device node
with self.assertRaises(BoomRootDeviceError) as cm:
boom.bootloader.check_root_device("tests/dev/null")
class BootLoaderTestsWithData(unittest.TestCase):
"""Base class for BootLoaderTests that require specific on-disk
test fixture data.
Each subclass sets ``bootloader_data`` to the subdirectory of
the ``bootloader_tests`` directory that contains the correct
set of configuration files.
"""
# Set by subclass
bootloader_config = None
# Common to all subclasses
configs = join(BOOT_ROOT_TEST, "bootloader_configs")
def setUp(self):
rm_sandbox()
if not self.bootloader_config:
raise ValueError("bootloader_config is undefined")
config_path = join(self.configs, self.bootloader_config)
shutil.copytree(config_path, join(SANDBOX_PATH))
boom.set_boot_path(join(BOOT_ROOT_TEST, "sandbox/boot"))
def tearDown(self):
rm_sandbox()
reset_boom_paths()
def bootloader_config_check(self, value):
self.assertEqual(check_bootloader(), value)
class BootLoaderTestsBoomOn(BootLoaderTestsWithData):
# Boot config with boom disabled
bootloader_config = "boom_on"
def test_check_bootloader(self):
self.bootloader_config_check(True)
class BootLoaderTestsBoomOff(BootLoaderTestsWithData):
# Boot config with boom disabled
bootloader_config = "boom_off"
def test_check_bootloader(self):
self.bootloader_config_check(True)
class BootLoaderTestsNoBoom(BootLoaderTestsWithData):
# Boot config with no boom configuration
bootloader_config = "no_boom"
def test_check_bootloader(self):
self.bootloader_config_check(False)
class BootLoaderTestsNoGrubD(BootLoaderTestsWithData):
# Boot config with no boom configuration
bootloader_config = "no_grub_d"
def test_check_bootloader(self):
self.bootloader_config_check(False)
# vim: set et ts=4 sw=4 :