154 lines
5.8 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
#
# Copyright 2015 Red Hat, Inc.
# All Rights Reserved.
#
# 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 os
import shlex
import shutil
import tempfile
from oslo_concurrency import processutils
from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent import hardware
from ironic_python_agent.openstack.common import log
from ironic_python_agent import utils
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})
try:
# Try to tell the kernel to re-read the partition table
try:
utils.execute('partx', '-u', device, attempts=3,
delay_on_retry=True)
except processutils.ProcessExecutionError:
LOG.warning("Couldn't re-read the partition table "
"on device %s" % device)
report = utils.execute('lsblk', '-PbioKNAME,UUID,TYPE', device)[0]
for line in report.split('\n'):
part = {}
# Split into KEY=VAL pairs
vals = shlex.split(line)
for key, val in (v.split('=', 1) for v in vals):
part[key] = val.strip()
# Ignore non partition
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})
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})
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 '
'device %(dev)s failed with %(err)s' %
{'uuid': root_uuid, 'dev': device, 'err': e})
LOG.error(error_msg)
raise errors.CommandExecutionError(error_msg)
def _install_grub2(device, root_uuid):
"""Install GRUB2 bootloader on a given device."""
LOG.debug("Installing GRUB2 bootloader on device %s", device)
root_partition = _get_root_partition(device, root_uuid)
try:
# Mount the partition and binds
path = tempfile.mkdtemp()
utils.execute('mount', root_partition, path)
for fs in BIND_MOUNTS:
utils.execute('mount', '-o', 'bind', fs, path + fs)
binary_name = "grub"
if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
binary_name = "grub2"
# 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)
# 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)
LOG.info("GRUB2 successfully installed on %s", device)
except processutils.ProcessExecutionError as e:
error_msg = ('Installing GRUB2 boot loader to device %(dev)s '
'failed with %(err)s. Attempted 3 times.' %
{'dev': device, 'err': e})
LOG.error(error_msg)
raise errors.CommandExecutionError(error_msg)
finally:
umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"
# Umount binds and partition
umount_binds_fail = False
for fs in BIND_MOUNTS:
try:
utils.execute('umount', path + fs, attempts=3,
delay_on_retry=True)
except processutils.ProcessExecutionError as e:
umount_binds_fail = True
LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e})
# If umounting the binds succeed then we can try to delete it
if not umount_binds_fail:
try:
utils.execute('umount', path, attempts=3, delay_on_retry=True)
except processutils.ProcessExecutionError as e:
LOG.warning(umount_warn_msg, {'path': path, 'error': e})
else:
# After everything is umounted we can then remove the
# temporary directory
shutil.rmtree(path)
class ImageExtension(base.BaseAgentExtension):
@base.sync_command('install_bootloader')
def install_bootloader(self, root_uuid):
"""Install the GRUB2 bootloader on the image.
:param root_uuid: The UUID of the root partition.
: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)