From 478e4c149dde1e2cf83949def40aea7a5c234793 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Tue, 26 Apr 2016 21:13:29 +0300 Subject: [PATCH] Add bootloader do_action Partially implements blueprint: pluggable-do-actions Change-Id: I92c8864dc3021b04d0d0f898198007176a1e92fe --- bareon/actions/bootloader.py | 182 ++++++++++++++++ bareon/drivers/deploy/mixins.py | 15 +- bareon/drivers/deploy/nailgun.py | 122 +---------- bareon/tests/test_do_bootloader.py | 253 ++++++++++++++++++++++ bareon/tests/test_manager.py | 332 ----------------------------- setup.cfg | 1 + 6 files changed, 449 insertions(+), 456 deletions(-) create mode 100644 bareon/actions/bootloader.py create mode 100644 bareon/tests/test_do_bootloader.py diff --git a/bareon/actions/bootloader.py b/bareon/actions/bootloader.py new file mode 100644 index 0000000..cc9494e --- /dev/null +++ b/bareon/actions/bootloader.py @@ -0,0 +1,182 @@ +# Copyright 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from io import open +import os + +from oslo_config import cfg + +from bareon.actions import base +from bareon.drivers.deploy import mixins +from bareon import errors +from bareon.openstack.common import log as logging +from bareon.utils import grub as gu +from bareon.utils import utils + +opts = [ + cfg.IntOpt( + 'grub_timeout', + default=5, + help='Timeout in secs for GRUB' + ), + cfg.BoolOpt( + 'fix_udev_net_rules', + default=True, + help='Add udev rules for NIC remapping' + ), +] + +CONF = cfg.CONF +CONF.register_opts(opts) + +LOG = logging.getLogger(__name__) + + +# TODO(agordeev): rename to GrubBootLoaderAction ? +class BootLoaderAction(base.BaseAction, mixins.MountableMixin): + """BootLoaderAction + + installs and configures bootloader + """ + + def validate(self): + # TODO(agordeev): implement validate for bootloader + pass + + def execute(self): + self.do_bootloader() + + def do_bootloader(self): + LOG.debug('--- Installing bootloader (do_bootloader) ---') + chroot = '/tmp/target' + partition_scheme = self.driver.partition_scheme + with self.mount_target(chroot): + mount2uuid = {} + for fs in partition_scheme.fss: + mount2uuid[fs.mount] = utils.execute( + 'blkid', '-o', 'value', '-s', 'UUID', fs.device, + check_exit_code=[0])[0].strip() + + if '/' not in mount2uuid: + raise errors.WrongPartitionSchemeError( + 'Error: device with / mountpoint has not been found') + + grub = self.driver.grub + + guessed_version = gu.guess_grub_version(chroot=chroot) + if guessed_version != grub.version: + grub.version = guessed_version + LOG.warning('Grub version differs from which the operating ' + 'system should have by default. Found version in ' + 'image: %s', guessed_version) + boot_device = partition_scheme.boot_device(grub.version) + install_devices = [d.name for d in partition_scheme.parteds + if d.install_bootloader] + + grub.append_kernel_params('root=UUID=%s ' % mount2uuid['/']) + + kernel = grub.kernel_name or \ + gu.guess_kernel(chroot=chroot, regexp=grub.kernel_regexp) + + initrd = grub.initrd_name or \ + gu.guess_initrd(chroot=chroot, regexp=grub.initrd_regexp) + + if grub.version == 1: + gu.grub1_cfg(kernel=kernel, initrd=initrd, + kernel_params=grub.kernel_params, chroot=chroot, + grub_timeout=CONF.grub_timeout) + gu.grub1_install(install_devices, boot_device, chroot=chroot) + else: + # TODO(kozhukalov): implement which kernel to use by default + # Currently only grub1_cfg accepts kernel and initrd + # parameters. + gu.grub2_cfg(kernel_params=grub.kernel_params, chroot=chroot, + grub_timeout=CONF.grub_timeout) + gu.grub2_install(install_devices, chroot=chroot) + + # TODO(agordeev): move to separate actions? + + if CONF.fix_udev_net_rules: + # FIXME(agordeev) There's no convenient way to perfrom NIC + # remapping in Ubuntu, so injecting files prior the first boot + # should work + with open(chroot + '/etc/udev/rules.d/70-persistent-net.rules', + 'wt', encoding='utf-8') as f: + f.write(u'# Generated by bareon during provisioning: ' + u'BEGIN\n') + # pattern is aa:bb:cc:dd:ee:ff_eth0,aa:bb:cc:dd:ee:ff_eth1 + for mapping in self.driver.configdrive_scheme. \ + common.udevrules.split(','): + mac_addr, nic_name = mapping.split('_') + f.write(u'SUBSYSTEM=="net", ACTION=="add", ' + u'DRIVERS=="?*", ATTR{address}=="%s", ' + u'ATTR{type}=="1", KERNEL=="eth*", ' + u'NAME="%s"\n' % (mac_addr, nic_name)) + f.write( + u'# Generated by bareon during provisioning: END\n') + # FIXME(agordeev): Disable net-generator that adds new entries + # to 70-persistent-net.rules + with open(chroot + '/etc/udev/rules.d/' + '75-persistent-net-generator.rules', 'wt', + encoding='utf-8') as f: + f.write(u'# Generated by bareon during provisioning:\n' + u'# DO NOT DELETE. It is needed to disable ' + u'net-generator\n') + + # FIXME(kozhukalov): Prevent nailgun-agent from doing anything. + # This ugly hack is to be used together with the command removing + # this lock file not earlier than /etc/rc.local + # The reason for this hack to appear is to prevent nailgun-agent + # from changing mcollective config at the same time when cloud-init + # does the same. Otherwise, we can end up with corrupted + # mcollective config. + # For details see https://bugs.launchpad.net/fuel/+bug/1449186 + LOG.debug('Preventing nailgun-agent from doing ' + 'anything until it is unlocked') + utils.makedirs_if_not_exists(os.path.join(chroot, + 'etc/nailgun-agent')) + with open(os.path.join(chroot, 'etc/nailgun-agent/nodiscover'), + 'w'): + pass + + # FIXME(kozhukalov): When we have just os-root fs image and don't + # have os-var-log fs image while / and /var/log are supposed to be + # separate file systems and os-var-log is mounted into + # non-empty directory on the / file system, those files in /var/log + # directory become unavailable. + # The thing is that among those file there is /var/log/upstart + # where upstart daemon writes its logs. We have specific upstart + # job which is to flush open files once all file systems are + # mounted. + # This job needs upstart directory to be available on os-var-log + # file system. + # This is just a temporary fix and correct fix will be available + # soon via updates. + utils.execute('mkdir', '-p', chroot + '/var/log/upstart') + + with open(chroot + '/etc/fstab', 'wt', encoding='utf-8') as f: + for fs in self.driver.partition_scheme.fss: + # TODO(kozhukalov): Think of improving the logic so as to + # insert a meaningful fsck order value which is last zero + # at fstab line. Currently we set it into 0 which means + # a corresponding file system will never be checked. We + # assume puppet or other configuration tool will care of + # it. + if fs.mount == '/': + f.write(u'UUID=%s %s %s defaults,errors=panic 0 0\n' % + (mount2uuid[fs.mount], fs.mount, fs.type)) + else: + f.write(u'UUID=%s %s %s defaults 0 0\n' % + (mount2uuid[fs.mount], fs.mount, fs.type)) diff --git a/bareon/drivers/deploy/mixins.py b/bareon/drivers/deploy/mixins.py index 3655827..3963c32 100644 --- a/bareon/drivers/deploy/mixins.py +++ b/bareon/drivers/deploy/mixins.py @@ -28,7 +28,8 @@ LOG = logging.getLogger(__name__) class MountableMixin(object): - def _mount_target(self, mount_dir, os_id, pseudo=True, treat_mtab=True): + def _mount_target(self, mount_dir, os_id=None, pseudo=True, + treat_mtab=True): LOG.debug('Mounting target file systems: %s', mount_dir) # Here we are going to mount all file systems in partition schema. for fs in self.driver.partition_scheme.fs_sorted_by_depth(os_id): @@ -53,10 +54,11 @@ class MountableMixin(object): with open(mtab_path, 'wb') as f: f.write(mtab) - def _umount_target(self, mount_dir, os_id, pseudo=True): + def _umount_target(self, mount_dir, os_id=None, pseudo=True): LOG.debug('Umounting target file systems: %s', mount_dir) if pseudo: - for path in ('/proc', '/dev', '/sys'): + # umount fusectl (typically mounted at /sys/fs/fuse/connections) + for path in ('/proc', '/dev', '/sys/fs/fuse/connections', '/sys'): fu.umount_fs(os.path.join(mount_dir, path.strip(os.sep)), try_lazy_umount=True) for fs in self.driver.partition_scheme.fs_sorted_by_depth(os_id, @@ -66,13 +68,14 @@ class MountableMixin(object): fu.umount_fs(os.path.join(mount_dir, fs.mount.strip(os.sep))) @contextmanager - def mount_target(self, mount_dir, os_id, pseudo=True, treat_mtab=True): - self._mount_target(mount_dir, os_id, pseudo=pseudo, + def mount_target(self, mount_dir, os_id=None, pseudo=True, + treat_mtab=True): + self._mount_target(mount_dir, os_id=os_id, pseudo=pseudo, treat_mtab=treat_mtab) try: yield finally: - self._umount_target(mount_dir, os_id, pseudo) + self._umount_target(mount_dir, os_id=os_id, pseudo=pseudo) @contextmanager def _mount_bootloader(self, mount_dir): diff --git a/bareon/drivers/deploy/nailgun.py b/bareon/drivers/deploy/nailgun.py index f94f1ed..cd9b029 100644 --- a/bareon/drivers/deploy/nailgun.py +++ b/bareon/drivers/deploy/nailgun.py @@ -21,6 +21,7 @@ from oslo_config import cfg import six import yaml +from bareon.actions import bootloader from bareon.actions import configdrive from bareon.actions import copyimage from bareon.actions import partitioning @@ -29,7 +30,6 @@ from bareon import errors from bareon.openstack.common import log as logging from bareon.utils import build as bu from bareon.utils import fs as fu -from bareon.utils import grub as gu from bareon.utils import utils opts = [ @@ -132,6 +132,9 @@ class Manager(BaseDeployDriver): def do_copyimage(self): copyimage.CopyImageAction(self.driver).execute() + def do_bootloader(self): + bootloader.BootLoaderAction(self.driver).execute() + @staticmethod def _update_metadata_with_repos(metadata, repos): """Update action metadata with information about repositories @@ -394,123 +397,6 @@ class Manager(BaseDeployDriver): with open(meta_file, 'wt') as f: yaml.safe_dump(drop_data, stream=f, encoding='utf-8') - def do_bootloader(self): - LOG.debug('--- Installing bootloader (do_bootloader) ---') - chroot = '/tmp/target' - self.mount_target(chroot) - - mount2uuid = {} - for fs in self.driver.partition_scheme.fss: - mount2uuid[fs.mount] = utils.execute( - 'blkid', '-o', 'value', '-s', 'UUID', fs.device, - check_exit_code=[0])[0].strip() - - if '/' not in mount2uuid: - raise errors.WrongPartitionSchemeError( - 'Error: device with / mountpoint has not been found') - - grub = self.driver.grub - - guessed_version = gu.guess_grub_version(chroot=chroot) - if guessed_version != grub.version: - grub.version = guessed_version - LOG.warning('Grub version differs from which the operating system ' - 'should have by default. Found version in image: ' - '{0}'.format(guessed_version)) - boot_device = self.driver.partition_scheme.boot_device(grub.version) - install_devices = [d.name for d in self.driver.partition_scheme.parteds - if d.install_bootloader] - - grub.append_kernel_params('root=UUID=%s ' % mount2uuid['/']) - - kernel = grub.kernel_name or gu.guess_kernel(chroot=chroot, - regexp=grub.kernel_regexp) - - initrd = grub.initrd_name or gu.guess_initrd(chroot=chroot, - regexp=grub.initrd_regexp) - - if grub.version == 1: - gu.grub1_cfg(kernel=kernel, initrd=initrd, - kernel_params=grub.kernel_params, chroot=chroot, - grub_timeout=CONF.grub_timeout) - gu.grub1_install(install_devices, boot_device, chroot=chroot) - else: - # TODO(kozhukalov): implement which kernel to use by default - # Currently only grub1_cfg accepts kernel and initrd parameters. - gu.grub2_cfg(kernel_params=grub.kernel_params, chroot=chroot, - grub_timeout=CONF.grub_timeout) - gu.grub2_install(install_devices, chroot=chroot) - - if CONF.fix_udev_net_rules: - # FIXME(agordeev) There's no convenient way to perfrom NIC - # remapping in Ubuntu, so injecting files prior the first boot - # should work - with open(chroot + '/etc/udev/rules.d/70-persistent-net.rules', - 'wt', encoding='utf-8') as f: - f.write(u'# Generated by bareon during provisioning: ' - u'BEGIN\n') - # pattern is aa:bb:cc:dd:ee:ff_eth0,aa:bb:cc:dd:ee:ff_eth1 - for mapping in self.driver.configdrive_scheme. \ - common.udevrules.split(','): - mac_addr, nic_name = mapping.split('_') - f.write(u'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' - u'ATTR{address}=="%s", ATTR{type}=="1", ' - u'KERNEL=="eth*", NAME="%s"\n' % (mac_addr, - nic_name)) - f.write( - u'# Generated by bareon during provisioning: END\n') - # FIXME(agordeev): Disable net-generator that will add new etries - # to 70-persistent-net.rules - with open(chroot + '/etc/udev/rules.d/' - '75-persistent-net-generator.rules', 'wt', - encoding='utf-8') as f: - f.write(u'# Generated by bareon during provisioning:\n' - u'# DO NOT DELETE. It is needed to disable ' - u'net-generator\n') - - # FIXME(kozhukalov): Prevent nailgun-agent from doing anything. - # This ugly hack is to be used together with the command removing - # this lock file not earlier than /etc/rc.local - # The reason for this hack to appear is to prevent nailgun-agent from - # changing mcollective config at the same time when cloud-init - # does the same. Otherwise, we can end up with corrupted mcollective - # config. For details see https://bugs.launchpad.net/fuel/+bug/1449186 - LOG.debug('Preventing nailgun-agent from doing ' - 'anything until it is unlocked') - utils.makedirs_if_not_exists(os.path.join(chroot, 'etc/nailgun-agent')) - with open(os.path.join(chroot, 'etc/nailgun-agent/nodiscover'), 'w'): - pass - - # FIXME(kozhukalov): When we have just os-root fs image and don't have - # os-var-log fs image while / and /var/log are supposed to be - # separate file systems and os-var-log is mounted into - # non-empty directory on the / file system, those files in /var/log - # directory become unavailable. - # The thing is that among those file there is /var/log/upstart - # where upstart daemon writes its logs. We have specific upstart job - # which is to flush open files once all file systems are mounted. - # This job needs upstart directory to be available on os-var-log - # file system. - # This is just a temporary fix and correct fix will be available soon - # via updates. - utils.execute('mkdir', '-p', chroot + '/var/log/upstart') - - with open(chroot + '/etc/fstab', 'wt', encoding='utf-8') as f: - for fs in self.driver.partition_scheme.fss: - # TODO(kozhukalov): Think of improving the logic so as to - # insert a meaningful fsck order value which is last zero - # at fstab line. Currently we set it into 0 which means - # a corresponding file system will never be checked. We assume - # puppet or other configuration tool will care of it. - if fs.mount == '/': - f.write(u'UUID=%s %s %s defaults,errors=panic 0 0\n' % - (mount2uuid[fs.mount], fs.mount, fs.type)) - else: - f.write(u'UUID=%s %s %s defaults 0 0\n' % - (mount2uuid[fs.mount], fs.mount, fs.type)) - - self.umount_target(chroot) - def do_reboot(self): LOG.debug('--- Rebooting node (do_reboot) ---') utils.execute('reboot') diff --git a/bareon/tests/test_do_bootloader.py b/bareon/tests/test_do_bootloader.py new file mode 100644 index 0000000..a84d10a --- /dev/null +++ b/bareon/tests/test_do_bootloader.py @@ -0,0 +1,253 @@ +# Copyright 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import six +import unittest2 + +from bareon.actions import bootloader +from bareon.drivers.data import nailgun +from bareon import errors +from bareon import objects + +if six.PY2: + import mock +elif six.PY3: + import unittest.mock as mock + + +class TestBootLoaderAction(unittest2.TestCase): + + def setUp(self): + super(TestBootLoaderAction, self).setUp() + self.drv = mock.MagicMock(spec=nailgun.Nailgun) + self.action = bootloader.BootLoaderAction(self.drv) + self.action._mount_target = mock.Mock() + self.action._umount_target = mock.Mock() + self.drv.grub = objects.Grub( + kernel_params=' console=ttyS0,9600 console=tty0 ' + 'rootdelay=90 nomodeset') + root_fs = objects.FS('/dev/sda', mount='/') + self.drv.partition_scheme.fss = [root_fs] + self.drv.partition_scheme.boot_device.return_value = '/dev/sda3' + parteds = [objects.Parted('/dev/sd%s' % x, 'gpt', + install_bootloader=True) + for x in ['a', 'b', 'c']] + self.drv.partition_scheme.parteds = parteds + + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_grub1_kernel_initrd_guessed(self, mock_utils, + mock_gu, mock_open): + mock_utils.execute.return_value = ('fake_root_uuid', '') + mock_gu.guess_grub_version.return_value = 1 + # grub has kernel_name and initrd_name both set to None + self.drv.grub.kernel_name = None + self.drv.grub.initrd_name = None + self.drv.grub.kernel_params = 'fake_kernel_params' + self.drv.grub.kernel_regexp = 'fake_kernel_regexp' + self.drv.grub.initrd_regexp = 'fake_initrd_regexp' + mock_gu.guess_kernel.return_value = 'guessed_kernel' + mock_gu.guess_initrd.return_value = 'guessed_initrd' + self.action.execute() + self.assertFalse(mock_gu.grub2_cfg.called) + self.assertFalse(mock_gu.grub2_install.called) + mock_gu.grub1_cfg.assert_called_once_with( + kernel_params='fake_kernel_params root=UUID=fake_root_uuid ', + initrd='guessed_initrd', kernel='guessed_kernel', + chroot='/tmp/target', grub_timeout=5) + mock_gu.grub1_install.assert_called_once_with( + ['/dev/sda', '/dev/sdb', '/dev/sdc'], + '/dev/sda3', chroot='/tmp/target') + mock_gu.guess_initrd.assert_called_once_with( + regexp='fake_initrd_regexp', chroot='/tmp/target') + mock_gu.guess_kernel.assert_called_once_with( + regexp='fake_kernel_regexp', chroot='/tmp/target') + + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_grub1_kernel_initrd_set(self, mock_utils, + mock_gu, mock_open): + mock_utils.execute.return_value = ('', '') + mock_gu.guess_grub_version.return_value = 1 + self.drv.grub.kernel_params = 'fake_kernel_params' + # grub has kernel_name and initrd_name set + self.drv.grub.kernel_name = 'kernel_name' + self.drv.grub.initrd_name = 'initrd_name' + self.action.execute() + self.assertFalse(mock_gu.grub2_cfg.called) + self.assertFalse(mock_gu.grub2_install.called) + mock_gu.grub1_cfg.assert_called_once_with( + kernel_params='fake_kernel_params root=UUID= ', + initrd='initrd_name', kernel='kernel_name', chroot='/tmp/target', + grub_timeout=5) + mock_gu.grub1_install.assert_called_once_with( + ['/dev/sda', '/dev/sdb', '/dev/sdc'], + '/dev/sda3', chroot='/tmp/target') + self.assertFalse(mock_gu.guess_initrd.called) + self.assertFalse(mock_gu.guess_kernel.called) + + @mock.patch.object(objects, 'Grub', autospec=True) + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_rootfs_uuid(self, mock_utils, mock_gu, mock_open, + mock_grub): + def _fake_uuid(*args, **kwargs): + if len(args) >= 6 and args[5] == '/dev/sda': + return ('FAKE_ROOTFS_UUID', None) + else: + return ('FAKE_UUID', None) + mock_utils.execute.side_effect = _fake_uuid + mock_grub.version = 2 + mock_gu.guess_grub_version.return_value = 2 + mock_grub.kernel_name = 'fake_kernel_name' + mock_grub.initrd_name = 'fake_initrd_name' + mock_grub.kernel_params = 'fake_kernel_params' + self.drv.grub = mock_grub + self.action.execute() + mock_grub.append_kernel_params.assert_called_once_with( + 'root=UUID=FAKE_ROOTFS_UUID ') + self.assertEqual(2, mock_grub.version) + + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_rootfs_not_found(self, mock_utils): + mock_utils.execute.return_value = ('fake', 'fake') + self.drv.partition_scheme.fss = [ + objects.FS(device='fake', mount='/boot', fs_type='ext2'), + objects.FS(device='fake', mount='swap', fs_type='swap')] + self.assertRaises(errors.WrongPartitionSchemeError, + self.action.execute) + + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_grub_version_changes( + self, mock_utils, mock_gu, mock_open): + # actually covers only grub1 related logic + mock_utils.execute.return_value = ('fake_UUID\n', None) + mock_gu.guess_grub_version.return_value = 'expected_version' + self.action.execute() + mock_gu.guess_grub_version.assert_called_once_with( + chroot='/tmp/target') + self.assertEqual('expected_version', self.drv.grub.version) + + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_grub1(self, mock_utils, mock_gu, mock_open): + # actually covers only grub1 related logic + mock_utils.execute.return_value = ('fake_UUID\n', None) + mock_gu.guess_initrd.return_value = 'guessed_initrd' + mock_gu.guess_kernel.return_value = 'guessed_kernel' + mock_gu.guess_grub_version.return_value = 1 + self.action.execute() + mock_gu.guess_grub_version.assert_called_once_with( + chroot='/tmp/target') + mock_gu.grub1_cfg.assert_called_once_with( + kernel_params=' console=ttyS0,9600 console=tty0 rootdelay=90 ' + 'nomodeset root=UUID=fake_UUID ', + initrd='guessed_initrd', + chroot='/tmp/target', + kernel='guessed_kernel', + grub_timeout=5) + mock_gu.grub1_install.assert_called_once_with( + ['/dev/sda', '/dev/sdb', '/dev/sdc'], + '/dev/sda3', chroot='/tmp/target') + self.assertFalse(mock_gu.grub2_cfg.called) + self.assertFalse(mock_gu.grub2_install.called) + + @mock.patch.object(bootloader, 'open', create=True, + new_callable=mock.mock_open) + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_grub2(self, mock_utils, mock_gu, mock_open): + # actually covers only grub2 related logic + mock_utils.execute.return_value = ('fake_UUID\n', None) + mock_gu.guess_grub_version.return_value = 2 + self.action.execute() + mock_gu.guess_grub_version.assert_called_once_with( + chroot='/tmp/target') + mock_gu.grub2_cfg.assert_called_once_with( + kernel_params=' console=ttyS0,9600 console=tty0 rootdelay=90 ' + 'nomodeset root=UUID=fake_UUID ', + chroot='/tmp/target', grub_timeout=5) + mock_gu.grub2_install.assert_called_once_with( + ['/dev/sda', '/dev/sdb', '/dev/sdc'], + chroot='/tmp/target') + self.assertFalse(mock_gu.grub1_cfg.called) + self.assertFalse(mock_gu.grub1_install.called) + + @mock.patch.object(bootloader, 'gu', autospec=True) + @mock.patch.object(bootloader, 'utils', autospec=True) + def test_do_bootloader_writes(self, mock_utils, mock_gu): + # actually covers only write() calls + mock_utils.execute.return_value = ('fake_UUID\n', None) + self.drv.configdrive_scheme.common.udevrules = "08:00:27:79:da:80_"\ + "eth0,08:00:27:46:43:60_eth1,08:00:27:b1:d7:15_eth2" + self.drv.partition_scheme.fss = [ + objects.FS('device', mount='/boot', fs_type='ext2'), + objects.FS('device', mount='/tmp', fs_type='ext2'), + objects.FS('device', mount='/', fs_type='ext4'), + objects.FS('device', mount='swap', fs_type='swap'), + objects.FS('device', mount='/var/lib/glance')] + with mock.patch.object(bootloader, 'open', create=True) as mock_open: + file_handle_mock = mock_open.return_value.__enter__.return_value + self.action.execute() + expected_open_calls = [ + mock.call('/tmp/target/etc/udev/rules.d/70-persistent-net.' + 'rules', 'wt', encoding='utf-8'), + mock.call('/tmp/target/etc/udev/rules.d/75-persistent-net-' + 'generator.rules', 'wt', encoding='utf-8'), + mock.call('/tmp/target/etc/nailgun-agent/nodiscover', 'w'), + mock.call('/tmp/target/etc/fstab', 'wt', encoding='utf-8')] + self.assertEqual(expected_open_calls, mock_open.call_args_list) + expected_write_calls = [ + mock.call('# Generated by bareon during provisioning: ' + 'BEGIN\n'), + mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' + 'ATTR{address}=="08:00:27:79:da:80", ATTR{type}=="1"' + ', KERNEL=="eth*", NAME="eth0"\n'), + mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' + 'ATTR{address}=="08:00:27:46:43:60", ATTR{type}=="1"' + ', KERNEL=="eth*", NAME="eth1"\n'), + mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' + 'ATTR{address}=="08:00:27:b1:d7:15", ATTR{type}=="1"' + ', KERNEL=="eth*", NAME="eth2"\n'), + mock.call('# Generated by bareon during provisioning: ' + 'END\n'), + mock.call('# Generated by bareon during provisioning:\n# ' + 'DO NOT DELETE. It is needed to disable ' + 'net-generator\n'), + mock.call('UUID=fake_UUID /boot ext2 defaults 0 0\n'), + mock.call('UUID=fake_UUID /tmp ext2 defaults 0 0\n'), + mock.call( + 'UUID=fake_UUID / ext4 defaults,errors=panic 0 0\n'), + mock.call('UUID=fake_UUID swap swap defaults 0 0\n'), + mock.call('UUID=fake_UUID /var/lib/glance xfs defaults 0 0\n') + ] + self.assertEqual(expected_write_calls, + file_handle_mock.write.call_args_list) + self.action._mount_target.assert_called_once_with( + '/tmp/target', os_id=None, pseudo=True, treat_mtab=True) + mock_utils.makedirs_if_not_exists.assert_called_once_with( + '/tmp/target/etc/nailgun-agent') + self.action._umount_target.assert_called_once_with( + '/tmp/target', os_id=None, pseudo=True) diff --git a/bareon/tests/test_manager.py b/bareon/tests/test_manager.py index ea265ec..d39ceeb 100644 --- a/bareon/tests/test_manager.py +++ b/bareon/tests/test_manager.py @@ -21,11 +21,7 @@ import unittest2 import bareon from bareon.drivers.data import nailgun as nailgun_data -from bareon.drivers.deploy import nailgun as nailgun_deploy -from bareon import errors from bareon import objects -from bareon.tests import test_nailgun -from bareon.utils import hardware as hu from bareon.utils import utils if six.PY2: @@ -36,334 +32,6 @@ elif six.PY3: CONF = cfg.CONF -@unittest2.skip("Fix after cray rebase") -class TestManager(unittest2.TestCase): - - @mock.patch('bareon.drivers.data.nailgun.Nailgun.parse_image_meta', - return_value={}) - @mock.patch.object(hu, 'list_block_devices') - def setUp(self, mock_lbd, mock_image_meta): - super(TestManager, self).setUp() - mock_lbd.return_value = test_nailgun.LIST_BLOCK_DEVICES_SAMPLE - self.mgr = nailgun_deploy.Manager(test_nailgun.PROVISION_SAMPLE_DATA) - - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_grub1_kernel_initrd_guessed(self, mock_umount, - mock_mount, mock_utils, - mock_gu, mock_open): - mock_utils.execute.return_value = ('', '') - mock_gu.guess_grub_version.return_value = 1 - # grub has kernel_name and initrd_name both set to None - self.mgr.driver.grub.kernel_name = None - self.mgr.driver.grub.initrd_name = None - self.mgr.driver.grub.kernel_params = 'fake_kernel_params' - self.mgr.driver.grub.kernel_regexp = 'fake_kernel_regexp' - self.mgr.driver.grub.initrd_regexp = 'fake_initrd_regexp' - mock_gu.guess_kernel.return_value = 'guessed_kernel' - mock_gu.guess_initrd.return_value = 'guessed_initrd' - self.mgr.do_bootloader() - self.assertFalse(mock_gu.grub2_cfg.called) - self.assertFalse(mock_gu.grub2_install.called) - mock_gu.grub1_cfg.assert_called_once_with( - kernel_params='fake_kernel_params root=UUID= ', - initrd='guessed_initrd', kernel='guessed_kernel', - chroot='/tmp/target', grub_timeout=5) - mock_gu.grub1_install.assert_called_once_with( - ['/dev/sda', '/dev/sdb', '/dev/sdc'], - '/dev/sda3', chroot='/tmp/target') - mock_gu.guess_initrd.assert_called_once_with( - regexp='fake_initrd_regexp', chroot='/tmp/target') - mock_gu.guess_kernel.assert_called_once_with( - regexp='fake_kernel_regexp', chroot='/tmp/target') - - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_grub1_kernel_initrd_set(self, mock_umount, - mock_mount, mock_utils, - mock_gu, mock_open): - mock_utils.execute.return_value = ('', '') - mock_gu.guess_grub_version.return_value = 1 - self.mgr.driver.grub.kernel_params = 'fake_kernel_params' - # grub has kernel_name and initrd_name set - self.mgr.driver.grub.kernel_name = 'kernel_name' - self.mgr.driver.grub.initrd_name = 'initrd_name' - self.mgr.do_bootloader() - self.assertFalse(mock_gu.grub2_cfg.called) - self.assertFalse(mock_gu.grub2_install.called) - mock_gu.grub1_cfg.assert_called_once_with( - kernel_params='fake_kernel_params root=UUID= ', - initrd='initrd_name', kernel='kernel_name', chroot='/tmp/target', - grub_timeout=5) - mock_gu.grub1_install.assert_called_once_with( - ['/dev/sda', '/dev/sdb', '/dev/sdc'], - '/dev/sda3', chroot='/tmp/target') - self.assertFalse(mock_gu.guess_initrd.called) - self.assertFalse(mock_gu.guess_kernel.called) - - @mock.patch('bareon.objects.bootloader.Grub', autospec=True) - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_rootfs_uuid(self, mock_umount, mock_mount, - mock_utils, mock_gu, mock_open, - mock_grub): - def _fake_uuid(*args, **kwargs): - if len(args) >= 6 and args[5] == '/dev/mapper/os-root': - return ('FAKE_ROOTFS_UUID', None) - else: - return ('FAKE_UUID', None) - mock_utils.execute.side_effect = _fake_uuid - mock_grub.version = 2 - mock_gu.guess_grub_version.return_value = 2 - mock_grub.kernel_name = 'fake_kernel_name' - mock_grub.initrd_name = 'fake_initrd_name' - mock_grub.kernel_params = 'fake_kernel_params' - self.mgr.driver._grub = mock_grub - self.mgr.do_bootloader() - mock_grub.append_kernel_params.assert_called_once_with( - 'root=UUID=FAKE_ROOTFS_UUID ') - self.assertEqual(2, mock_grub.version) - - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - def test_do_bootloader_rootfs_not_found(self, mock_umount, mock_utils): - mock_utils.execute.return_value = ('fake', 'fake') - self.mgr.driver._partition_scheme = objects.PartitionScheme() - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/boot', fs_type='ext2') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='swap', fs_type='swap') - self.assertRaises(errors.WrongPartitionSchemeError, - self.mgr.do_bootloader) - - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_grub_version_changes( - self, mock_umount, mock_mount, mock_utils, mock_gu, mock_open): - # actually covers only grub1 related logic - mock_utils.execute.return_value = ('fake_UUID\n', None) - mock_gu.guess_grub_version.return_value = 'expected_version' - self.mgr.do_bootloader() - mock_gu.guess_grub_version.assert_called_once_with( - chroot='/tmp/target') - self.assertEqual('expected_version', self.mgr.driver.grub.version) - - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_grub1(self, mock_umount, mock_mount, mock_utils, - mock_gu, mock_open): - # actually covers only grub1 related logic - mock_utils.execute.return_value = ('fake_UUID\n', None) - mock_gu.guess_initrd.return_value = 'guessed_initrd' - mock_gu.guess_kernel.return_value = 'guessed_kernel' - mock_gu.guess_grub_version.return_value = 1 - self.mgr.do_bootloader() - mock_gu.guess_grub_version.assert_called_once_with( - chroot='/tmp/target') - mock_gu.grub1_cfg.assert_called_once_with( - kernel_params=' console=ttyS0,9600 console=tty0 rootdelay=90 ' - 'nomodeset root=UUID=fake_UUID ', - initrd='guessed_initrd', - chroot='/tmp/target', - kernel='guessed_kernel', - grub_timeout=5) - mock_gu.grub1_install.assert_called_once_with( - ['/dev/sda', '/dev/sdb', '/dev/sdc'], - '/dev/sda3', chroot='/tmp/target') - self.assertFalse(mock_gu.grub2_cfg.called) - self.assertFalse(mock_gu.grub2_install.called) - - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_grub2(self, mock_umount, mock_mount, mock_utils, - mock_gu, mock_open): - # actually covers only grub2 related logic - mock_utils.execute.return_value = ('fake_UUID\n', None) - mock_gu.guess_grub_version.return_value = 2 - self.mgr.do_bootloader() - mock_gu.guess_grub_version.assert_called_once_with( - chroot='/tmp/target') - mock_gu.grub2_cfg.assert_called_once_with( - kernel_params=' console=ttyS0,9600 console=tty0 rootdelay=90 ' - 'nomodeset root=UUID=fake_UUID ', - chroot='/tmp/target', grub_timeout=5) - mock_gu.grub2_install.assert_called_once_with( - ['/dev/sda', '/dev/sdb', '/dev/sdc'], - chroot='/tmp/target') - self.assertFalse(mock_gu.grub1_cfg.called) - self.assertFalse(mock_gu.grub1_install.called) - - @mock.patch('bareon.drivers.deploy.nailgun.gu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch.object(bareon.drivers.deploy.nailgun, 'mount_target') - @mock.patch.object(bareon.drivers.deploy.nailgun, 'umount_target') - def test_do_bootloader_writes(self, mock_umount, mock_mount, mock_utils, - mock_gu): - # actually covers only write() calls - mock_utils.execute.return_value = ('fake_UUID\n', None) - with mock.patch('bareon.drivers.deploy.nailgun.open', - create=True) as mock_open: - file_handle_mock = mock_open.return_value.__enter__.return_value - self.mgr.do_bootloader() - expected_open_calls = [ - mock.call('/tmp/target/etc/udev/rules.d/70-persistent-net.' - 'rules', 'wt', encoding='utf-8'), - mock.call('/tmp/target/etc/udev/rules.d/75-persistent-net-' - 'generator.rules', 'wt', encoding='utf-8'), - mock.call('/tmp/target/etc/nailgun-agent/nodiscover', 'w'), - mock.call('/tmp/target/etc/fstab', 'wt', encoding='utf-8')] - self.assertEqual(expected_open_calls, mock_open.call_args_list) - expected_write_calls = [ - mock.call('# Generated by bareon during provisioning: ' - 'BEGIN\n'), - mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' - 'ATTR{address}=="08:00:27:79:da:80", ATTR{type}=="1"' - ', KERNEL=="eth*", NAME="eth0"\n'), - mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' - 'ATTR{address}=="08:00:27:46:43:60", ATTR{type}=="1"' - ', KERNEL=="eth*", NAME="eth1"\n'), - mock.call('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' - 'ATTR{address}=="08:00:27:b1:d7:15", ATTR{type}=="1"' - ', KERNEL=="eth*", NAME="eth2"\n'), - mock.call('# Generated by bareon during provisioning: ' - 'END\n'), - mock.call('# Generated by bareon during provisioning:\n# ' - 'DO NOT DELETE. It is needed to disable ' - 'net-generator\n'), - mock.call('UUID=fake_UUID /boot ext2 defaults 0 0\n'), - mock.call('UUID=fake_UUID /tmp ext2 defaults 0 0\n'), - mock.call( - 'UUID=fake_UUID / ext4 defaults,errors=panic 0 0\n'), - mock.call('UUID=fake_UUID swap swap defaults 0 0\n'), - mock.call('UUID=fake_UUID /var/lib/glance xfs defaults 0 0\n') - ] - self.assertEqual(expected_write_calls, - file_handle_mock.write.call_args_list) - mock_umount.assert_called_once_with('/tmp/target') - mock_mount.assert_called_once_with('/tmp/target') - mock_utils.makedirs_if_not_exists.assert_called_once_with( - '/tmp/target/etc/nailgun-agent') - - @mock.patch('bareon.drivers.deploy.nailgun.fu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.os', create=True) - def test_mount_target_mtab_is_link(self, mock_os, mock_open, mock_utils, - mock_fu): - mock_os.path.islink.return_value = True - mock_utils.execute.return_value = (None, None) - self.mgr.driver._partition_scheme = objects.PartitionScheme() - self.mgr.mount_target('fake_chroot') - mock_open.assert_called_once_with('fake_chroot/etc/mtab', 'wt', - encoding='utf-8') - mock_os.path.islink.assert_called_once_with('fake_chroot/etc/mtab') - mock_os.remove.assert_called_once_with('fake_chroot/etc/mtab') - - @mock.patch('bareon.drivers.deploy.nailgun.fu', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.utils', create=True) - @mock.patch('bareon.drivers.deploy.nailgun.open', - create=True, new_callable=mock.mock_open) - @mock.patch('bareon.drivers.deploy.nailgun.os', create=True) - def test_mount_target(self, mock_os, mock_open, mock_utils, mock_fu): - mock_os.path.islink.return_value = False - self.mgr.driver._partition_scheme = objects.PartitionScheme() - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/var/lib', fs_type='xfs') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/', fs_type='ext4') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/boot', fs_type='ext2') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='swap', fs_type='swap') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/var', fs_type='ext4') - fake_mtab = """ -proc /proc proc rw,noexec,nosuid,nodev 0 0 -sysfs /sys sysfs rw,noexec,nosuid,nodev 0 0 -none /sys/fs/fuse/connections fusectl rw 0 0 -none /sys/kernel/debug debugfs rw 0 0 -none /sys/kernel/security securityfs rw 0 0 -udev /dev devtmpfs rw,mode=0755 0 0 -devpts /dev/pts devpts rw,noexec,nosuid,gid=5,mode=0620 0 0 -tmpfs /run tmpfs rw,noexec,nosuid,size=10%,mode=0755 0 0 -none /run/lock tmpfs rw,noexec,nosuid,nodev,size=5242880 0 0 -none /run/shm tmpfs rw,nosuid,nodev 0 0""" - mock_utils.execute.return_value = (fake_mtab, None) - self.mgr.mount_target('fake_chroot') - self.assertEqual([mock.call('fake_chroot/'), - mock.call('fake_chroot/boot'), - mock.call('fake_chroot/var'), - mock.call('fake_chroot/var/lib'), - mock.call('fake_chroot/sys'), - mock.call('fake_chroot/dev'), - mock.call('fake_chroot/proc')], - mock_utils.makedirs_if_not_exists.call_args_list) - self.assertEqual([mock.call('ext4', 'fake', 'fake_chroot/'), - mock.call('ext2', 'fake', 'fake_chroot/boot'), - mock.call('ext4', 'fake', 'fake_chroot/var'), - mock.call('xfs', 'fake', 'fake_chroot/var/lib')], - mock_fu.mount_fs.call_args_list) - self.assertEqual([mock.call('fake_chroot', '/sys'), - mock.call('fake_chroot', '/dev'), - mock.call('fake_chroot', '/proc')], - mock_fu.mount_bind.call_args_list) - file_handle = mock_open.return_value.__enter__.return_value - file_handle.write.assert_called_once_with(fake_mtab) - mock_open.assert_called_once_with('fake_chroot/etc/mtab', 'wt', - encoding='utf-8') - mock_os.path.islink.assert_called_once_with('fake_chroot/etc/mtab') - self.assertFalse(mock_os.remove.called) - - @mock.patch('bareon.drivers.deploy.nailgun.fu', create=True) - def test_umount_target(self, mock_fu): - self.mgr.driver._partition_scheme = objects.PartitionScheme() - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/var/lib', fs_type='xfs') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/', fs_type='ext4') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/boot', fs_type='ext2') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='swap', fs_type='swap') - self.mgr.driver.partition_scheme.add_fs( - device='fake', mount='/var', fs_type='ext4') - self.mgr.umount_target('fake_chroot') - self.assertEqual([mock.call('fake_chroot/proc'), - mock.call('fake_chroot/dev'), - mock.call('fake_chroot/sys/fs/fuse/connections'), - mock.call('fake_chroot/sys'), - mock.call('fake_chroot/var/lib'), - mock.call('fake_chroot/boot'), - mock.call('fake_chroot/var'), - mock.call('fake_chroot/')], - mock_fu.umount_fs.call_args_list) - - @unittest2.skip("Fix after cray rebase") class TestImageBuild(unittest2.TestCase): diff --git a/setup.cfg b/setup.cfg index e6a3d57..751d02c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,7 @@ bareon.actions = do_partitioning = bareon.actions.partitioning:PartitioningAction do_configdrive = bareon.actions.configdrive:ConfigDriveAction do_copyimage = bareon.actions.copyimage:CopyImageAction + do_bootloader = bareon.actions.bootloader:BootLoaderAction oslo.config.opts = bareon.manager = bareon.manager:list_opts