fuel-web/fuel_agent/fuel_agent/utils/grub.py

258 lines
9.0 KiB
Python

# Copyright 2014 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 os
import re
import shutil
from oslo.config import cfg
from fuel_agent import errors
from fuel_agent.openstack.common import log as logging
from fuel_agent.utils import utils
LOG = logging.getLogger(__name__)
gu_opts = [
cfg.IntOpt(
'grub_timeout',
default=5,
help='Timeout in secs for GRUB'
),
]
CONF = cfg.CONF
CONF.register_opts(gu_opts)
def guess_grub2_conf(chroot=''):
for filename in ('/boot/grub/grub.cfg', '/boot/grub2/grub.cfg'):
if os.path.isdir(os.path.dirname(chroot + filename)):
return filename
def guess_grub2_default(chroot=''):
for filename in ('/etc/default/grub', '/etc/sysconfig/grub'):
if os.path.isfile(chroot + filename):
return filename
def guess_grub2_mkconfig(chroot=''):
for grub_mkconfig in \
('/sbin/grub-mkconfig', '/sbin/grub2-mkconfig',
'/usr/sbin/grub-mkconfig', '/usr/sbin/grub2-mkconfig'):
if os.path.isfile(chroot + grub_mkconfig):
return grub_mkconfig
def guess_grub_version(chroot=''):
grub_install = guess_grub_install(chroot=chroot)
LOG.debug('Trying to run %s --version' % grub_install)
cmd = [grub_install, '--version']
if chroot:
cmd[:0] = ['chroot', chroot]
result = utils.execute(*cmd)
version = 1 if result[0].find('0.97') > 0 else 2
LOG.debug('Looks like grub version is %s' % version)
return version
def guess_grub(chroot=''):
for grub in ('/sbin/grub', '/usr/sbin/grub'):
LOG.debug('Looking for grub: trying %s' % grub)
if os.path.isfile(chroot + grub):
LOG.debug('grub found: %s' % grub)
return grub
raise errors.GrubUtilsError('grub not found')
def guess_grub_install(chroot=''):
for grub_install in ('/sbin/grub-install', '/sbin/grub2-install',
'/usr/sbin/grub-install', '/usr/sbin/grub2-install'):
LOG.debug('Looking for grub-install: trying %s' % grub_install)
if os.path.isfile(chroot + grub_install):
LOG.debug('grub-install found: %s' % grub_install)
return grub_install
raise errors.GrubUtilsError('grub-install not found')
def guess_grub1_datadir(chroot='', arch='x86_64'):
LOG.debug('Looking for grub data directory')
for d in os.listdir(chroot + '/usr/share/grub'):
if arch in d:
LOG.debug('Looks like grub data directory '
'is /usr/share/grub/%s' % d)
return '/usr/share/grub/' + d
def guess_kernel(chroot='', regexp=None):
"""Tries to guess kernel by regexp
:param chroot: Path to chroot
:param regexp: (String) Regular expression (must have python syntax).
Default is r'^vmlinuz.*'
"""
kernel = utils.guess_filename(
path=os.path.join(chroot, 'boot'),
regexp=(regexp or r'^vmlinuz.*'))
if kernel:
return kernel
raise errors.GrubUtilsError('Error while trying to find kernel: '
'regexp=%s' % regexp)
def guess_initrd(chroot='', regexp=None):
"""Tries to guess initrd by regexp
:param chroot: Path to chroot
:param regexp: (String) Regular expression (must have python syntax).
Default is r'^(initrd|initramfs).*'
"""
initrd = utils.guess_filename(
path=os.path.join(chroot, 'boot'),
regexp=(regexp or r'^(initrd|initramfs).*'))
if initrd:
return initrd
raise errors.GrubUtilsError('Error while trying to find initrd: '
'regexp=%s' % regexp)
def grub1_install(install_devices, boot_device, chroot=''):
match = re.search(r'(.+?)(p?)(\d*)$', boot_device)
# Checking whether boot device is a partition
# !!! It must be a partition not a whole disk. !!!
if not match.group(3):
raise errors.GrubUtilsError(
'Error while installing legacy grub: '
'boot device must be a partition')
boot_disk = match.group(1)
boot_part = str(int(match.group(3)) - 1)
grub1_stage1(chroot=chroot)
for install_device in install_devices:
grub1_mbr(install_device, boot_disk, boot_part, chroot=chroot)
def grub1_mbr(install_device, boot_disk, boot_part, chroot=''):
# The device on which we are going to install
# stage1 needs to be mapped as hd0, otherwise system won't be able to boot.
batch = 'device (hd0) {0}\n'.format(install_device)
# That is much easier to use grub-install, but unfortunately
# it is not able to install bootloader on huge disks.
# Instead we set drive geometry manually to avoid grub register
# overlapping. We set it so as to make grub
# thinking that disk size is equal to 1G.
# 130 cylinders * (16065 * 512 = 8225280 bytes) = 1G
# We also assume that boot partition is in the beginning
# of disk between 0 and 1G.
batch += 'geometry (hd0) 130 255 63\n'
if boot_disk != install_device:
batch += 'device (hd1) {0}\n'.format(boot_disk)
batch += 'geometry (hd1) 130 255 63\n'
batch += 'root (hd1,{0})\n'.format(boot_part)
else:
batch += 'root (hd0,{0})\n'.format(boot_part)
batch += 'setup (hd0)\n'
batch += 'quit\n'
with open(chroot + '/tmp/grub.batch', 'wb') as f:
LOG.debug('Grub batch content: \n%s' % batch)
f.write(batch)
script = 'cat /tmp/grub.batch | {0} --no-floppy --batch'.format(
guess_grub(chroot=chroot))
with open(chroot + '/tmp/grub.sh', 'wb') as f:
LOG.debug('Grub script content: \n%s' % script)
f.write(script)
os.chmod(chroot + '/tmp/grub.sh', 0o755)
cmd = ['/tmp/grub.sh']
if chroot:
cmd[:0] = ['chroot', chroot]
stdout, stderr = utils.execute(*cmd, run_as_root=True, check_exit_code=[0])
LOG.debug('Grub script stdout: \n%s' % stdout)
LOG.debug('Grub script stderr: \n%s' % stderr)
def grub1_stage1(chroot=''):
LOG.debug('Installing grub stage1 files')
for f in os.listdir(chroot + '/boot/grub'):
if f in ('stage1', 'stage2') or 'stage1_5' in f:
LOG.debug('Removing: %s' % chroot + os.path.join('/boot/grub', f))
os.remove(chroot + os.path.join('/boot/grub', f))
grub1_datadir = guess_grub1_datadir(chroot=chroot)
for f in os.listdir(chroot + grub1_datadir):
if f in ('stage1', 'stage2') or 'stage1_5' in f:
LOG.debug('Copying %s from %s to /boot/grub' % (f, grub1_datadir))
shutil.copy(chroot + os.path.join(grub1_datadir, f),
chroot + os.path.join('/boot/grub', f))
def grub1_cfg(kernel=None, initrd=None,
kernel_params='', chroot='', grub_timeout=CONF.grub_timeout):
if not kernel:
kernel = guess_kernel(chroot=chroot)
if not initrd:
initrd = guess_initrd(chroot=chroot)
config = """
default=0
timeout={grub_timeout}
title Default ({kernel})
kernel /{kernel} {kernel_params}
initrd /{initrd}
""".format(kernel=kernel, initrd=initrd,
kernel_params=kernel_params,
grub_timeout=grub_timeout)
with open(chroot + '/boot/grub/grub.conf', 'wb') as f:
f.write(config)
def grub2_install(install_devices, chroot=''):
grub_install = guess_grub_install(chroot=chroot)
for install_device in install_devices:
cmd = [grub_install, install_device]
if chroot:
cmd[:0] = ['chroot', chroot]
utils.execute(*cmd, run_as_root=True, check_exit_code=[0])
def grub2_cfg(kernel_params='', chroot='', grub_timeout=CONF.grub_timeout):
grub_defaults = chroot + guess_grub2_default(chroot=chroot)
rekerparams = re.compile(r'^.*GRUB_CMDLINE_LINUX=.*')
retimeout = re.compile(r'^.*GRUB_HIDDEN_TIMEOUT=.*')
new_content = ''
with open(grub_defaults) as f:
for line in f:
line = rekerparams.sub(
'GRUB_CMDLINE_LINUX="{kernel_params}"'.
format(kernel_params=kernel_params), line)
line = retimeout.sub('GRUB_HIDDEN_TIMEOUT={grub_timeout}'.
format(grub_timeout=grub_timeout), line)
new_content += line
# NOTE(agordeev): explicitly add record fail timeout, in order to
# prevent user confirmation appearing if unexpected reboot occured.
new_content += '\nGRUB_RECORDFAIL_TIMEOUT={grub_timeout}\n'.\
format(grub_timeout=grub_timeout)
with open(grub_defaults, 'wb') as f:
f.write(new_content)
cmd = [guess_grub2_mkconfig(chroot), '-o', guess_grub2_conf(chroot)]
if chroot:
cmd[:0] = ['chroot', chroot]
utils.execute(*cmd, run_as_root=True)