Merge pull request #127 from carlroth/roth_swl_3426

Restore boot loader config on loader upgrade, see SWL-3426
This commit is contained in:
Jeffrey Townsend
2016-11-10 08:37:53 -08:00
committed by GitHub
6 changed files with 323 additions and 151 deletions

View File

@@ -17,31 +17,9 @@ import time
from InstallUtils import InitrdContext
from InstallUtils import SubprocessMixin
from InstallUtils import ProcMountsParser
from ShellApp import Onie
from ShellApp import OnieBootContext
import ConfUtils, BaseInstall
class OnieHelper(Onie):
"""Unpack the initrd, but keep it around."""
UMOUNT = False
# leave self.onieDir mounted
ictx = None
def _runInitrdShell(self, initrd):
with InitrdContext(initrd, log=self.log) as self.ictx:
self.initrdDir = self.ictx.dir
self.ictx.detach()
def shutdown(self):
self.ictx.attach()
self.ictx.shutdown()
if self.dctx is not None:
self.dctx.attach()
self.dctx.shutdown()
class App(SubprocessMixin, object):
def __init__(self, url=None,
@@ -66,7 +44,7 @@ class App(SubprocessMixin, object):
self.nextUpdate = None
self.onieHelper = None
self.octx = None
def run(self):
@@ -160,15 +138,13 @@ class App(SubprocessMixin, object):
##self.log.info("using native GRUB")
##self.grubEnv = ConfUtils.GrubEnv(log=self.log.getChild("grub"))
self.onieHelper = OnieHelper(log=self.log)
code = self.onieHelper.run()
if code:
self.log.warn("cannot find ONIE initrd")
with OnieBootContext(log=self.log) as self.octx:
self.octx.detach()
if self.onieHelper.onieDir is not None:
self.log.info("using native ONIE initrd+chroot GRUB (%s)", self.onieHelper.onieDir)
self.grubEnv = ConfUtils.ChrootGrubEnv(self.onieHelper.initrdDir,
bootDir=self.onieHelper.onieDir,
if self.octx.onieDir is not None:
self.log.info("using native ONIE initrd+chroot GRUB (%s)", self.octx.onieDir)
self.grubEnv = ConfUtils.ChrootGrubEnv(self.octx.initrdDir,
bootDir=self.octx.onieDir,
path="/grub/grubenv",
log=self.log.getChild("grub"))
# direct access using ONIE initrd as a chroot
@@ -340,9 +316,10 @@ class App(SubprocessMixin, object):
if installer is not None:
installer.shutdown()
h, self.onieHelper = self.onieHelper, None
if h is not None:
h.shutdown()
ctx, self.octx = self.octx, None
if ctx:
ctx.attach()
ctx.shutdown()
def post_mortem(self):
self.log.info("re-attaching to tty")

View File

@@ -92,6 +92,10 @@ class Base:
self.log.error("not implemented")
return 1
def upgradeBootLoader(self):
self.log.error("not implemented")
return 1
def shutdown(self):
zf, self.zf = self.zf, None
if zf: zf.close()
@@ -524,19 +528,7 @@ class GrubInstaller(SubprocessMixin, Base):
def installLoader(self):
ctx = {}
kernel = self.im.platformConf['grub']['kernel']
ctx['kernel'] = kernel['='] if type(kernel) == dict else kernel
ctx['args'] = self.im.platformConf['grub']['args']
ctx['platform'] = self.im.installerConf.installer_platform
ctx['serial'] = self.im.platformConf['grub']['serial']
ctx['boot_menu_entry'] = sysconfig.installer.menu_name
ctx['boot_loading_name'] = sysconfig.installer.os_name
kernels = []
for f in set(os.listdir(self.im.installerConf.installer_dir) + self.zf.namelist()):
if 'kernel' in f:
kernels.append(f)
@@ -548,11 +540,10 @@ class GrubInstaller(SubprocessMixin, Base):
initrd = i
break
cf = GRUB_TPL % ctx
self.log.info("Installing kernel")
dev = self.blkidParts['ONL-BOOT']
self.log.info("Installing kernel to %s", dev.device)
with MountContext(dev.device, log=self.log) as ctx:
def _cp(b, dstname=None):
if dstname is None:
@@ -561,8 +552,32 @@ class GrubInstaller(SubprocessMixin, Base):
self.installerCopy(b, dst, optional=True)
[_cp(e) for e in kernels]
_cp(initrd, "%s.cpio.gz" % self.im.installerConf.installer_platform)
return 0
def installGrubCfg(self):
dev = self.blkidParts['ONL-BOOT']
self.log.info("Installing grub.cfg to %s", dev.device)
ctx = {}
kernel = self.im.platformConf['grub']['kernel']
ctx['kernel'] = kernel['='] if type(kernel) == dict else kernel
ctx['args'] = self.im.platformConf['grub']['args']
ctx['platform'] = self.im.installerConf.installer_platform
ctx['serial'] = self.im.platformConf['grub']['serial']
ctx['boot_menu_entry'] = sysconfig.installer.menu_name
ctx['boot_loading_name'] = sysconfig.installer.os_name
cf = GRUB_TPL % ctx
with MountContext(dev.device, log=self.log) as ctx:
d = os.path.join(ctx.dir, "grub")
self.makedirs(d)
if not os.path.exists(d):
self.makedirs(d)
dst = os.path.join(ctx.dir, 'grub/grub.cfg')
with open(dst, "w") as fd:
fd.write(cf)
@@ -624,6 +639,9 @@ class GrubInstaller(SubprocessMixin, Base):
code = self.installLoader()
if code: return code
code = self.installGrubCfg()
if code: return code
code = self.installBootConfig()
if code: return code
@@ -648,6 +666,16 @@ class GrubInstaller(SubprocessMixin, Base):
return 1
return self.installGpt()
def upgradeBootLoader(self):
"""Upgrade the boot loader settings."""
self.blkidParts = BlkidParser(log=self.log.getChild("blkid"))
code = self.installGrubCfg()
if code: return code
return 0
def shutdown(self):
Base.shutdown(self)
@@ -883,5 +911,17 @@ class UbootInstaller(SubprocessMixin, Base):
return self.installUboot()
def upgradeBootLoader(self):
"""Upgrade the boot loader settings as part of a loader upgrade."""
self.blkidParts = BlkidParser(log=self.log.getChild("blkid"))
# XXX boot-config (and saved boot-config) should be unchanged during loader upgrade
code = self.installUbootEnv()
if code: return code
return 0
def shutdown(self):
Base.shutdown(self)

View File

@@ -10,6 +10,8 @@ import tempfile
import string
import shutil
import Fit
class SubprocessMixin:
V1 = "V1"
@@ -229,8 +231,8 @@ class MountContext(SubprocessMixin):
self.label = label
self.fsType = fsType
self.dir = None
self.hostDir = None
self.mounted = False
self.hostDir = self.__hostDir = None
self.mounted = self.__mounted = False
self.log = log or logging.getLogger("mount")
if self.device and self.label:
@@ -238,9 +240,6 @@ class MountContext(SubprocessMixin):
if not self.device and not self.label:
raise ValueError("no device or label specified")
self._detachMounted = False
self._detachHostDir = None
def __enter__(self):
dev = self.device
if dev is None:
@@ -290,12 +289,12 @@ class MountContext(SubprocessMixin):
return False
def detach(self):
self.mounted, self._detachMounted = False, self.mounted
self.hostDir, self._detachHostdir = None, self.hostDir
self.__mounted, self.mounted = self.mounted, False
self.__hostDir, self.hostDir = self.hostDir, None
def attach(self):
self.mounted = self._detachMounted
self.hostDir = self._detachHostdir
self.mounted = self.__mounted
self.hostDir = self.__hostDir
class BlkidEntry:
@@ -697,8 +696,9 @@ class InitrdContext(SubprocessMixin):
self.ilog.setLevel(logging.INFO)
self.log = self.hlog
self.__initrd = None
self.__dir = None
self._hasDevTmpfs = False
self._detachInitrd = None
def _unpack(self):
self.dir = self.mkdtemp(prefix="chroot-",
@@ -837,21 +837,25 @@ class InitrdContext(SubprocessMixin):
def shutdown(self):
p = ProcMountsParser()
dirs = [e.dir for e in p.mounts if e.dir.startswith(self.dir)]
if self.dir is not None:
dirs = [e.dir for e in p.mounts if e.dir.startswith(self.dir)]
else:
dirs = []
# XXX probabaly also kill files here
# umount any nested mounts
self.log.debug("un-mounting mounts points in chroot %s", self.dir)
dirs.sort(reverse=True)
for p in dirs:
cmd = ('umount', p,)
self.check_call(cmd, vmode=self.V1)
if dirs:
self.log.debug("un-mounting mounts points in chroot %s", self.dir)
dirs.sort(reverse=True)
for p in dirs:
cmd = ('umount', p,)
self.check_call(cmd, vmode=self.V1)
if self.initrd is not None:
if self.initrd and self.dir:
self.log.debug("cleaning up chroot in %s", self.dir)
self.rmtree(self.dir)
else:
elif self.dir:
self.log.debug("saving chroot in %s", self.dir)
def __exit__(self, type, value, tb):
@@ -859,10 +863,12 @@ class InitrdContext(SubprocessMixin):
return False
def detach(self):
self.initrd, self._detachInitrd = None, self.initrd
self.__initrd, self.initrd = self.initrd, None
self.__dir, self.dir = self.dir, None
def attach(self):
self.initrd = self._detachInitrd
self.initrd = self.__initrd
self.dir = self.__dir
@classmethod
def mkChroot(cls, initrd, log=None):
@@ -873,6 +879,51 @@ class InitrdContext(SubprocessMixin):
# (it's inside this chroot anyway)
return initrdDir
class FitInitrdContext(SubprocessMixin):
def __init__(self, path, log=None):
self.fitPath = path
self.log = log or logging.getLogger(self.__class__.__name__)
self.initrd = self.__initrd = None
def __enter__(self):
self.log.debug("parsing FIT image in %s", self.fitPath)
p = Fit.Parser(path=self.fitPath, log=self.log)
node = p.getInitrdNode()
if node is None:
raise ValueError("cannot find initrd node in FDT")
prop = node.properties.get('data', None)
if prop is None:
raise ValueError("cannot find initrd data property in FDT")
with open(self.fitPath) as fd:
self.log.debug("reading initrd at [%x:%x]",
prop.offset, prop.offset+prop.sz)
fd.seek(prop.offset, 0)
buf = fd.read(prop.sz)
fno, self.initrd = tempfile.mkstemp(prefix="initrd-",
suffix=".img")
self.log.debug("+ cat > %s", self.initrd)
with os.fdopen(fno, "w") as fd:
fd.write(buf)
return self
def shutdown(self):
initrd, self.initrd = self.initrd, None
if initrd and os.path.exists(initrd):
self.unlink(initrd)
def __exit__(self, eType, eValue, eTrace):
self.shutdown()
return False
def detach(self):
self.__initrd, self.initrd = self.initrd, None
def attach(self):
self.initrd = self.__initrd
class ChrootSubprocessMixin:
chrootDir = None

View File

@@ -13,7 +13,7 @@ from InstallUtils import InitrdContext, MountContext
from InstallUtils import SubprocessMixin
from InstallUtils import ProcMountsParser, ProcMtdParser
from InstallUtils import BlkidParser
import Fit
from InstallUtils import FitInitrdContext
import onl.platform.current
from onl.sysconfig import sysconfig
@@ -47,30 +47,8 @@ class AppBase(SubprocessMixin, object):
return 0
def _runFitShell(self, device):
self.log.debug("parsing FIT image in %s", device)
p = Fit.Parser(path=device, log=self.log)
node = p.getInitrdNode()
if node is None:
self.log.error("cannot find initrd node in FDT")
return 1
prop = node.properties.get('data', None)
if prop is None:
self.log.error("cannot find initrd data property in FDT")
return 1
with open(device) as fd:
self.log.debug("reading initrd at [%x:%x]",
prop.offset, prop.offset+prop.sz)
fd.seek(prop.offset, 0)
buf = fd.read(prop.sz)
try:
fno, initrd = tempfile.mkstemp(prefix="initrd-",
suffix=".img")
self.log.debug("+ cat > %s", initrd)
with os.fdopen(fno, "w") as fd:
fd.write(buf)
return self._runInitrdShell(initrd)
finally:
self.unlink(initrd)
with FitInitrdContext(path=device, log=self.log) as ctx:
return self._runInitrdShell(ctx.initrd)
def shutdown(self):
pass
@@ -109,21 +87,26 @@ class AppBase(SubprocessMixin, object):
app.shutdown()
sys.exit(code)
class Onie(AppBase):
"""Application shell that uses the ONIE runtime."""
class OnieBootContext:
"""Find the ONIE initrd and umpack/mount it."""
PROG = "onie-shell"
def __init__(self, log=None):
self.log = log or logging.getLogger(self.__class__.__name__)
UNMOUNT = True
# default, unmount directories once the initrd is extracted
self.initrd = None
def run(self):
self.pm = self.blkid = self.mtd = None
self.ictx = self.dctx = self.fctx = None
self.onieDir = None
self.initrdDir = None
self.__ictx = self.__dctx = self.__fctx = None
def __enter__(self):
self.pm = ProcMountsParser()
self.blkid = BlkidParser(log=self.log.getChild("blkid"))
self.mtd = ProcMtdParser(log=self.log.getChild("mtd"))
self.dctx = None
self.onieDir = None
def _g(d):
pat = os.path.join(d, "onie/initrd.img*")
@@ -144,28 +127,29 @@ class Onie(AppBase):
self.log.debug("found ONIE boot mounted at %s", parts[0].dir)
initrd = _g(parts[0].dir)
if initrd is None:
self.log.warn("cannot find ONIE initrd on %s", parts[0].dir)
else:
self.onieDir = parts[0].dir
self.log.debug("found ONIE initrd at %s", initrd)
return self._runInitrdShell(initrd)
raise ValueError("cannot find ONIE initrd on %s" % parts[0].dir)
self.log.debug("found ONIE initrd at %s", initrd)
with InitrdContext(initrd=initrd, log=self.log) as self.ictx:
self.initrd = initrd
self.initrdDir = self.ictx.dir
self.ictx.detach()
return self
# else, try to mount the directory containing the initrd
with MountContext(dev, log=self.log) as self.dctx:
initrd = _g(self.dctx.dir)
if initrd is None:
self.log.warn("cannot find ONIE initrd on %s", dev)
else:
self.log.debug("found ONIE initrd at %s", initrd)
try:
return self._runInitrdShell(initrd)
finally:
if not self.UMOUNT:
self.onieDir = self.dctx.hostDir
self.dctx.detach()
raise ValueError("cannot find ONIE initrd on %s" % dev)
self.onieDir = self.dctx.dir
self.dctx.detach()
self.log.debug("found ONIE initrd at %s", initrd)
with InitrdContext(initrd=initrd, log=self.log) as self.ictx:
self.initrd = initrd
self.initrdDir = self.ictx.dir
self.ictx.detach()
return self
self.log.warn("cannot find an ONIE initrd")
return 1
raise ValueError("cannot find an ONIE initrd")
# try to find onie initrd on a mounted fs (GRUB);
# for ONIE images this is usually /mnt/onie-boot
@@ -178,7 +162,11 @@ class Onie(AppBase):
else:
self.onieDir = part.dir
self.log.debug("found ONIE initrd at %s", initrd)
return self._runInitrdShell(initrd)
with InitrdContext(initrd=initrd, log=self.log) as self.ictx:
self.initrd = initrd
self.initrdDir = self.ictx.dir
self.ictx.detach()
return self
# grovel through MTD devices (u-boot)
parts = [p for p in self.mtd.parts if p.label == "onie"]
@@ -186,13 +174,49 @@ class Onie(AppBase):
part = parts[0]
self.log.debug("found ONIE MTD device %s",
part.charDevice or part.blockDevice)
return self._runFitShell(part.blockDevice)
elif self.mtd.mounts:
self.log.error("cannot find ONIE MTD device")
return 1
with FitInitrdContext(part.blockDevice, log=self.log) as self.fctx:
with InitrdContext(initrd=self.fctx.initrd, log=self.log) as self.ictx:
self.initrd = self.fctx.initrd
self.fctx.detach()
self.initrdDir = self.ictx.dir
self.ictx.detach()
return self
self.log.error("cannot find ONIE initrd")
return 1
if self.mtd.mounts:
raise ValueError("cannot find ONIE MTD device")
raise ValueError("cannot find ONIE initrd")
def shutdown(self):
ctx, self.fctx = self.fctx, None
if ctx is not None: ctx.shutdown()
ctx, self.ictx = self.ictx, None
if ctx is not None: ctx.shutdown()
ctx, self.dctx = self.dctx, None
if ctx is not None: ctx.shutdown()
def __exit__(self, eType, eValue, eTrace):
self.shutdown()
return False
def detach(self):
self.__fctx, self.fctx = self.fctx, None
self.__ictx, self.ictx = self.ictx, None
self.__dctx, self.dctx = self.dctx, None
def attach(self):
self.fctx = self.__fctx
self.ictx = self.__ictx
self.dctx = self.__dctx
class Onie(AppBase):
"""XXX roth -- refactor in from loader.py code."""
PROG = "onie-shell"
def run(self):
with OnieBootContext(log=self.log) as ctx:
return self._runInitrdShell(ctx.initrd)
class Loader(AppBase):
"""Application shell that uses the (installed) loader runtime."""

View File

@@ -16,7 +16,7 @@ import subprocess
from onl.install.InstallUtils import InitrdContext
from onl.install.InstallUtils import ProcMountsParser
from onl.install.ConfUtils import MachineConf, InstallerConf
from onl.install.ShellApp import Onie, Upgrader
from onl.install.ShellApp import OnieBootContext, Upgrader
from onl.install.InstallUtils import SubprocessMixin
import onl.install.App
@@ -45,8 +45,6 @@ class App(SubprocessMixin):
self.force = force
self.onieHelper = None
def _runInitrd(self, helper, path):
with InitrdContext(initrd=path, log=self.log) as ctx:
@@ -55,22 +53,14 @@ class App(SubprocessMixin):
prefix="installer-", suffix=".d")
chroot_idir = abs_idir[len(ctx.dir):]
self.onieHelper = onl.install.App.OnieHelper(log=self.log)
code = self.onieHelper.run()
if code:
self.log.error("cannot find/unpack ONIE initrd")
return code
self.log.info("onie directory is %s", self.onieHelper.onieDir)
self.log.info("initrd directory is %s", self.onieHelper.initrdDir)
with OnieBootContext(log=self.log) as octx:
self.log.info("onie directory is %s", octx.onieDir)
self.log.info("initrd directory is %s", octx.initrdDir)
src = os.path.join(self.onieHelper.initrdDir, "etc/machine.conf")
dst = os.path.join(ctx.dir, "etc/machine.conf")
self.log.debug("+ /bin/cp %s %s", src, dst)
shutil.copy2(src, dst)
h, self.onieHelper = self.onieHelper, None
if h is not None:
h.shutdown()
src = os.path.join(octx.initrdDir, "etc/machine.conf")
dst = os.path.join(ctx.dir, "etc/machine.conf")
self.log.debug("+ /bin/cp %s %s", src, dst)
shutil.copy2(src, dst)
src = "/etc/fw_env.config"
if os.path.exists(src):
@@ -221,10 +211,7 @@ class App(SubprocessMixin):
return code
def shutdown(self):
h, self.onieHelper = self.onieHelper, None
if h is not None:
h.shutdown()
pass
@classmethod
def main(cls):

View File

@@ -11,6 +11,10 @@ import fnmatch
from onl.upgrade import ubase
from onl.sysconfig import sysconfig
from onl.mounts import OnlMountManager, OnlMountContextReadOnly, OnlMountContextReadWrite
from onl.install import BaseInstall, ConfUtils, InstallUtils
from onl.install.ShellApp import OnieBootContext
import onl.platform.current
import onl.versions
class LoaderUpgradeBase(ubase.BaseUpgrade):
name="loader"
@@ -57,9 +61,10 @@ class LoaderUpgradeBase(ubase.BaseUpgrade):
* A single reboot will be required to complete this upgrade.
"""
class LoaderUpgrade_Fit(LoaderUpgradeBase):
installer_klass = BaseInstall.UbootInstaller
def do_upgrade(self, forced=False):
fit_image = None
@@ -75,10 +80,49 @@ class LoaderUpgrade_Fit(LoaderUpgradeBase):
with OnlMountContextReadWrite("ONL-BOOT", self.logger) as d:
self.copyfile(fit_image, os.path.join(d.directory, "%s.itb" % (self.platform.platform())))
onlPlatform = onl.platform.current.OnlPlatform()
with OnieBootContext(log=self.logger) as octx:
path = os.path.join(octx.initrdDir, "etc/machine.conf")
machineConf = ConfUtils.MachineConf(path=path)
installerConf = ConfUtils.InstallerConf(path="/dev/null")
# start with an empty installerConf, fill it in piece by piece
installerConf.installer_platform = onlPlatform.platform()
installerConf.installer_arch = machineConf.onie_arch
installerConf.installer_platform_dir = os.path.join("/lib/platform-config",
onlPlatform.platform())
mfPath = os.path.join(sysconfig.upgrade.loader.package.dir, "manifest.json")
mf = onl.versions.OnlVersionManifest(mfPath)
installerConf.onl_version = mf.RELEASE_ID
grubEnv = ConfUtils.ProxyGrubEnv(installerConf,
bootDir="/mnt/onie-boot",
path="/grub/grubenv",
chroot=False,
log=self.logger.getChild("grub"))
ubootEnv = ConfUtils.UbootEnv(log=self.logger.getChild("u-boot"))
installer = self.installer_klass(machineConf=machineConf,
installerConf=installerConf,
platformConf=onlPlatform.platform_config,
grubEnv=grubEnv,
ubootEnv=ubootEnv,
force=True,
log=self.logger)
installer.upgradeBootLoader()
installer.shutdown()
self.reboot()
class LoaderUpgrade_x86_64(LoaderUpgradeBase):
class LoaderUpgrade_x86_64(LoaderUpgradeBase, InstallUtils.SubprocessMixin):
installer_klass = BaseInstall.GrubInstaller
def do_upgrade(self, forced=False):
@@ -110,6 +154,55 @@ class LoaderUpgrade_x86_64(LoaderUpgradeBase):
#if os.path.exists(src):
# self.copyfile(src, dst)
# installer assumes that partitions are unmounted
self.log = self.logger
# ha ha, SubprocessMixin api is different
pm = InstallUtils.ProcMountsParser()
for m in pm.mounts:
if m.dir.startswith('/mnt/onl'):
self.logger.warn("unmounting %s (--force)", m.dir)
self.check_call(('umount', m.dir,))
onlPlatform = onl.platform.current.OnlPlatform()
with OnieBootContext(log=self.logger) as octx:
path = os.path.join(octx.initrdDir, "etc/machine.conf")
machineConf = ConfUtils.MachineConf(path=path)
# hold on to the ONIE boot context for grub access
installerConf = ConfUtils.InstallerConf(path="/dev/null")
# XXX fill in installerConf fields
installerConf.installer_platform = onlPlatform.platform()
installerConf.installer_arch = machineConf.onie_arch
installerConf.installer_platform_dir = os.path.join("/lib/platform-config",
onlPlatform.platform())
mfPath = os.path.join(sysconfig.upgrade.loader.package.dir, "manifest.json")
mf = onl.versions.OnlVersionManifest(mfPath)
installerConf.onl_version = mf.RELEASE_ID
grubEnv = ConfUtils.ChrootGrubEnv(octx.initrdDir,
bootDir=octx.onieDir,
path="/grub/grubenv",
log=self.logger.getChild("grub"))
ubootEnv = None
installer = self.installer_klass(machineConf=machineConf,
installerConf=installerConf,
platformConf=onlPlatform.platform_config,
grubEnv=grubEnv,
ubootEnv=ubootEnv,
force=True,
log=self.logger)
installer.upgradeBootLoader()
installer.shutdown()
self.reboot()