summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRamakrishnan G <rameshg87@gmail.com>2015-03-12 09:15:49 +0000
committerRamakrishnan G <rameshg87@gmail.com>2015-03-17 18:21:22 +0000
commitac4e9901d688a6efd39ddc9b20c724e6b24082bf (patch)
treee6220b3853638079120d41106f4d93c2d3dd94fd
parentbe44ac8d4d1793b86ade6eef6f3b0f620779fc7e (diff)
downloadironic-python-agent-ac4e9901d688a6efd39ddc9b20c724e6b24082bf.tar.gz
Add uefi support in image extension
This commit adds support for uefi systems in the image extension so that grub can be installed onto efi system partition for uefi machines. Implements: blueprint local-boot-support-with-partition-images Change-Id: I8fbb4b2ebdff991d41c7b618a4d654af26311a56
-rw-r--r--ironic_python_agent/extensions/image.py71
-rw-r--r--ironic_python_agent/tests/extensions/image.py157
2 files changed, 191 insertions, 37 deletions
diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py
index 1158eae4..6cd51ccc 100644
--- a/ironic_python_agent/extensions/image.py
+++ b/ironic_python_agent/extensions/image.py
@@ -34,10 +34,10 @@ LOG = log.getLogger(__name__)
BIND_MOUNTS = ('/dev', '/sys', '/proc')
-def _get_root_partition(device, root_uuid):
- """Find the root partition of a given device."""
- LOG.debug("Find the root partition %(uuid)s on device %(dev)s",
- {'dev': device, 'uuid': root_uuid})
+def _get_partition(device, uuid):
+ """Find the partition of a given device."""
+ LOG.debug("Find the partition %(uuid)s on device %(dev)s",
+ {'dev': device, 'uuid': uuid})
try:
# Try to tell the kernel to re-read the partition table
@@ -59,50 +59,69 @@ def _get_root_partition(device, root_uuid):
if part.get('TYPE') != 'part':
continue
- if part.get('UUID') == root_uuid:
- LOG.debug("Root partition %(uuid)s found on device "
- "%(dev)s", {'uuid': root_uuid, 'dev': device})
+ if part.get('UUID') == uuid:
+ LOG.debug("Partition %(uuid)s found on device "
+ "%(dev)s", {'uuid': uuid, 'dev': device})
return '/dev/' + part.get('KNAME')
else:
- error_msg = ("No root partition with UUID %(uuid)s found on "
- "device %(dev)s" % {'uuid': root_uuid, 'dev': device})
+ error_msg = ("No partition with UUID %(uuid)s found on "
+ "device %(dev)s" % {'uuid': uuid, 'dev': device})
LOG.error(error_msg)
raise errors.DeviceNotFound(error_msg)
except processutils.ProcessExecutionError as e:
- error_msg = ('Finding the root partition with UUID %(uuid)s on '
+ error_msg = ('Finding the partition with UUID %(uuid)s on '
'device %(dev)s failed with %(err)s' %
- {'uuid': root_uuid, 'dev': device, 'err': e})
+ {'uuid': uuid, 'dev': device, 'err': e})
LOG.error(error_msg)
raise errors.CommandExecutionError(error_msg)
-def _install_grub2(device, root_uuid):
+def _install_grub2(device, root_uuid, efi_system_part_uuid=None):
"""Install GRUB2 bootloader on a given device."""
LOG.debug("Installing GRUB2 bootloader on device %s", device)
- root_partition = _get_root_partition(device, root_uuid)
+ root_partition = _get_partition(device, uuid=root_uuid)
try:
# Mount the partition and binds
path = tempfile.mkdtemp()
+
+ if efi_system_part_uuid:
+ efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
+ efi_partition_mount_point = os.path.join(path, "boot/efi")
+ else:
+ efi_partition = None
+ efi_partition_mount_point = None
+
utils.execute('mount', root_partition, path)
for fs in BIND_MOUNTS:
utils.execute('mount', '-o', 'bind', fs, path + fs)
+ if efi_partition:
+ if not os.path.exists(efi_partition_mount_point):
+ os.makedirs(efi_partition_mount_point)
+ utils.execute('mount', efi_partition, efi_partition_mount_point)
+
binary_name = "grub"
if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
binary_name = "grub2"
+ # Add /bin to PATH variable as grub requires it to find efibootmgr
+ # when running in uefi boot mode.
+ path_variable = os.environ.get('PATH', '')
+ path_variable = '%s:/bin' % path_variable
+
# Install grub
utils.execute('chroot %(path)s /bin/bash -c '
'"/usr/sbin/%(bin)s-install %(dev)s"' %
{'path': path, 'bin': binary_name, 'dev': device},
- shell=True)
+ shell=True, env_variables={'PATH': path_variable})
# Generate the grub configuration file
utils.execute('chroot %(path)s /bin/bash -c '
'"/usr/sbin/%(bin)s-mkconfig -o '
'/boot/%(bin)s/grub.cfg"' %
- {'path': path, 'bin': binary_name}, shell=True)
+ {'path': path, 'bin': binary_name}, shell=True,
+ env_variables={'PATH': path_variable})
LOG.info("GRUB2 successfully installed on %s", device)
@@ -117,6 +136,19 @@ def _install_grub2(device, root_uuid):
umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"
# Umount binds and partition
umount_binds_fail = False
+
+ # If umount fails for efi partition, then we cannot be sure that all
+ # the changes were written back to the filesystem.
+ try:
+ if efi_partition:
+ utils.execute('umount', efi_partition_mount_point, attempts=3,
+ delay_on_retry=True)
+ except processutils.ProcessExecutionError as e:
+ error_msg = ('Umounting efi system partition failed. '
+ 'Attempted 3 times. Error: %s' % e)
+ LOG.error(error_msg)
+ raise errors.CommandExecutionError(error_msg)
+
for fs in BIND_MOUNTS:
try:
utils.execute('umount', path + fs, attempts=3,
@@ -140,14 +172,19 @@ def _install_grub2(device, root_uuid):
class ImageExtension(base.BaseAgentExtension):
@base.sync_command('install_bootloader')
- def install_bootloader(self, root_uuid):
+ def install_bootloader(self, root_uuid, efi_system_part_uuid=None):
"""Install the GRUB2 bootloader on the image.
:param root_uuid: The UUID of the root partition.
+ :param efi_system_part_uuid: The UUID of the efi system partition.
+ To be used only for uefi boot mode. For uefi boot mode, the
+ boot loader will be installed here.
:raises: CommandExecutionError if the installation of the
bootloader fails.
:raises: DeviceNotFound if the root partition is not found.
"""
device = hardware.dispatch_to_managers('get_os_install_device')
- _install_grub2(device, root_uuid)
+ _install_grub2(device,
+ root_uuid=root_uuid,
+ efi_system_part_uuid=efi_system_part_uuid)
diff --git a/ironic_python_agent/tests/extensions/image.py b/ironic_python_agent/tests/extensions/image.py
index fc23e71a..76712c94 100644
--- a/ironic_python_agent/tests/extensions/image.py
+++ b/ironic_python_agent/tests/extensions/image.py
@@ -16,6 +16,7 @@
# under the License.
import mock
+import os
import shutil
import tempfile
@@ -38,20 +39,41 @@ class TestImageExtension(test_base.BaseTestCase):
super(TestImageExtension, self).setUp()
self.agent_extension = image.ImageExtension()
self.fake_dev = '/dev/fake'
+ self.fake_efi_system_part = '/dev/fake1'
self.fake_root_part = '/dev/fake2'
self.fake_root_uuid = '11111111-2222-3333-4444-555555555555'
+ self.fake_efi_system_part_uuid = '45AB-2312'
self.fake_dir = '/tmp/fake-dir'
@mock.patch.object(image, '_install_grub2')
- def test_install_bootloader(self, mock_grub2, mock_execute, mock_dispatch):
+ def test_install_bootloader_bios(self, mock_grub2, mock_execute,
+ mock_dispatch):
mock_dispatch.return_value = self.fake_dev
self.agent_extension.install_bootloader(root_uuid=self.fake_root_uuid)
mock_dispatch.assert_called_once_with('get_os_install_device')
- mock_grub2.assert_called_once_with(self.fake_dev, self.fake_root_uuid)
+ mock_grub2.assert_called_once_with(
+ self.fake_dev, root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=None)
- @mock.patch.object(image, '_get_root_partition')
- def test__install_grub2(self, mock_get_root, mock_execute, mock_dispatch):
- mock_get_root.return_value = self.fake_root_part
+ @mock.patch.object(image, '_install_grub2')
+ def test_install_bootloader_uefi(self, mock_grub2, mock_execute,
+ mock_dispatch):
+ mock_dispatch.return_value = self.fake_dev
+ self.agent_extension.install_bootloader(
+ root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid)
+ mock_dispatch.assert_called_once_with('get_os_install_device')
+ mock_grub2.assert_called_once_with(
+ self.fake_dev,
+ root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid)
+
+ @mock.patch.object(os, 'environ')
+ @mock.patch.object(image, '_get_partition')
+ def test__install_grub2(self, mock_get_part_uuid, environ_mock,
+ mock_execute, mock_dispatch):
+ mock_get_part_uuid.return_value = self.fake_root_part
+ environ_mock.get.return_value = '/sbin'
image._install_grub2(self.fake_dev, self.fake_root_uuid)
expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
@@ -63,11 +85,13 @@ class TestImageExtension(test_base.BaseTestCase):
self.fake_dir + '/proc'),
mock.call(('chroot %s /bin/bash -c '
'"/usr/sbin/grub-install %s"' %
- (self.fake_dir, self.fake_dev)), shell=True),
+ (self.fake_dir, self.fake_dev)), shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
mock.call(('chroot %s /bin/bash -c '
'"/usr/sbin/grub-mkconfig -o '
'/boot/grub/grub.cfg"' % self.fake_dir),
- shell=True),
+ shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
mock.call('umount', self.fake_dir + '/dev',
attempts=3, delay_on_retry=True),
mock.call('umount', self.fake_dir + '/sys',
@@ -77,30 +101,123 @@ class TestImageExtension(test_base.BaseTestCase):
mock.call('umount', self.fake_dir, attempts=3,
delay_on_retry=True)]
mock_execute.assert_has_calls(expected)
- mock_get_root.assert_called_once_with(self.fake_dev,
- self.fake_root_uuid)
+ mock_get_part_uuid.assert_called_once_with(self.fake_dev,
+ uuid=self.fake_root_uuid)
+ self.assertFalse(mock_dispatch.called)
+
+ @mock.patch.object(os, 'environ')
+ @mock.patch.object(os, 'makedirs')
+ @mock.patch.object(image, '_get_partition')
+ def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock,
+ environ_mock, mock_execute,
+ mock_dispatch):
+ mock_get_part_uuid.side_effect = [self.fake_root_part,
+ self.fake_efi_system_part]
+ environ_mock.get.return_value = '/sbin'
+
+ image._install_grub2(
+ self.fake_dev, root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid)
+
+ expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
+ mock.call('mount', '-o', 'bind', '/dev',
+ self.fake_dir + '/dev'),
+ mock.call('mount', '-o', 'bind', '/sys',
+ self.fake_dir + '/sys'),
+ mock.call('mount', '-o', 'bind', '/proc',
+ self.fake_dir + '/proc'),
+ mock.call('mount', self.fake_efi_system_part,
+ self.fake_dir + '/boot/efi'),
+ mock.call(('chroot %s /bin/bash -c '
+ '"/usr/sbin/grub-install %s"' %
+ (self.fake_dir, self.fake_dev)), shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
+ mock.call(('chroot %s /bin/bash -c '
+ '"/usr/sbin/grub-mkconfig -o '
+ '/boot/grub/grub.cfg"' % self.fake_dir),
+ shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
+ mock.call('umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/dev',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/sys',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/proc',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir, attempts=3,
+ delay_on_retry=True)]
+ mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
+ mock_execute.assert_has_calls(expected)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_root_uuid)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_efi_system_part_uuid)
self.assertFalse(mock_dispatch.called)
- @mock.patch.object(image, '_get_root_partition')
- def test__install_grub2_command_fail(self, mock_get_root, mock_execute,
+ @mock.patch.object(os, 'environ')
+ @mock.patch.object(os, 'makedirs')
+ @mock.patch.object(image, '_get_partition')
+ def test__install_grub2_uefi_umount_fails(
+ self, mock_get_part_uuid, mkdir_mock, environ_mock,
+ mock_execute, mock_dispatch):
+ mock_get_part_uuid.side_effect = [self.fake_root_part,
+ self.fake_efi_system_part]
+
+ def umount_raise_func(*args, **kwargs):
+ if args[0] == 'umount':
+ raise processutils.ProcessExecutionError('error')
+
+ mock_execute.side_effect = umount_raise_func
+ environ_mock.get.return_value = '/sbin'
+ self.assertRaises(errors.CommandExecutionError,
+ image._install_grub2,
+ self.fake_dev, root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid)
+
+ expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
+ mock.call('mount', '-o', 'bind', '/dev',
+ self.fake_dir + '/dev'),
+ mock.call('mount', '-o', 'bind', '/sys',
+ self.fake_dir + '/sys'),
+ mock.call('mount', '-o', 'bind', '/proc',
+ self.fake_dir + '/proc'),
+ mock.call('mount', self.fake_efi_system_part,
+ self.fake_dir + '/boot/efi'),
+ mock.call(('chroot %s /bin/bash -c '
+ '"/usr/sbin/grub-install %s"' %
+ (self.fake_dir, self.fake_dev)), shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
+ mock.call(('chroot %s /bin/bash -c '
+ '"/usr/sbin/grub-mkconfig -o '
+ '/boot/grub/grub.cfg"' % self.fake_dir),
+ shell=True,
+ env_variables={'PATH': '/sbin:/bin'}),
+ mock.call('umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True)]
+ mock_execute.assert_has_calls(expected)
+
+ @mock.patch.object(image, '_get_partition')
+ def test__install_grub2_command_fail(self, mock_get_part_uuid,
+ mock_execute,
mock_dispatch):
- mock_get_root.return_value = self.fake_root_part
+ mock_get_part_uuid.return_value = self.fake_root_part
mock_execute.side_effect = processutils.ProcessExecutionError('boom')
self.assertRaises(errors.CommandExecutionError, image._install_grub2,
self.fake_dev, self.fake_root_uuid)
- mock_get_root.assert_called_once_with(self.fake_dev,
- self.fake_root_uuid)
+ mock_get_part_uuid.assert_called_once_with(self.fake_dev,
+ uuid=self.fake_root_uuid)
self.assertFalse(mock_dispatch.called)
- def test__get_root_partition(self, mock_execute, mock_dispatch):
+ def test__get_partition(self, mock_execute, mock_dispatch):
lsblk_output = ('''KNAME="test" UUID="" TYPE="disk"
KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part"
KNAME="test2" UUID="%s" TYPE="part"''' % self.fake_root_uuid)
mock_execute.side_effect = (None, [lsblk_output])
- root_part = image._get_root_partition(self.fake_dev,
+ root_part = image._get_partition(self.fake_dev,
self.fake_root_uuid)
self.assertEqual('/dev/test2', root_part)
expected = [mock.call('partx', '-u', self.fake_dev, attempts=3,
@@ -109,7 +226,7 @@ class TestImageExtension(test_base.BaseTestCase):
mock_execute.assert_has_calls(expected)
self.assertFalse(mock_dispatch.called)
- def test__get_root_partition_no_device_found(self, mock_execute,
+ def test__get_partition_no_device_found(self, mock_execute,
mock_dispatch):
lsblk_output = ('''KNAME="test" UUID="" TYPE="disk"
KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part"
@@ -117,7 +234,7 @@ class TestImageExtension(test_base.BaseTestCase):
mock_execute.side_effect = (None, [lsblk_output])
self.assertRaises(errors.DeviceNotFound,
- image._get_root_partition, self.fake_dev,
+ image._get_partition, self.fake_dev,
self.fake_root_uuid)
expected = [mock.call('partx', '-u', self.fake_dev, attempts=3,
delay_on_retry=True),
@@ -125,12 +242,12 @@ class TestImageExtension(test_base.BaseTestCase):
mock_execute.assert_has_calls(expected)
self.assertFalse(mock_dispatch.called)
- def test__get_root_partition_command_fail(self, mock_execute,
+ def test__get_partition_command_fail(self, mock_execute,
mock_dispatch):
mock_execute.side_effect = (None,
processutils.ProcessExecutionError('boom'))
self.assertRaises(errors.CommandExecutionError,
- image._get_root_partition, self.fake_dev,
+ image._get_partition, self.fake_dev,
self.fake_root_uuid)
expected = [mock.call('partx', '-u', self.fake_dev, attempts=3,