diff --git a/packages/base/all/vendor-config-onl/src/boot.d/61.upgrade-onie b/packages/base/all/vendor-config-onl/src/boot.d/61.upgrade-onie new file mode 100755 index 00000000..5f0e8883 --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/boot.d/61.upgrade-onie @@ -0,0 +1,55 @@ +#!/usr/bin/python -u +from onl.upgrade import ubase + +class ONIE_Upgrade(ubase.BaseOnieUpgrade): + name="onie" + Name="ONIE" + title="ONIE Upgrade Check" + atype="An ONIE" + + current_version_key="Current ONIE Version" + next_version_key="Next ONIE Version" + + def init_versions(self): + + # Get the current platform ONIE version + self.current_version = self.platform.onie_version() + self.next_version = None + self.updater = None + + (udir, um, data) = self.platform.upgrade_manifest("onie") + self.udir = udir + self.data = data + + if data: + self.next_version = data.get('onie-version', None) + + if data: + self.updater = data.get('onie-updater', None) + + if self.updater is None: + self.finish("No ONIE updater available for the current platform.") + + def summarize(self): + self.logger.info("Current ONIE Version: %s" % self.current_version) + self.logger.info(" Next ONIE Version: %s" % self.next_version) + self.logger.info(" Force-Update: %s" % self.data['force-update']) + self.logger.info(" Updater: %s" % self.updater) + self.logger.info("") + + def upgrade_notes(self): + return """ + * The system will reboot into ONIE to complete the update, and then reboot to return to Switch Light +""" + + def do_upgrade(self, forced=False): + self.install_onie_updater(self.udir, self.updater) + self.initiate_onie_update() + + def do_no_upgrade(self): + self.clean_onie_updater() + + +if __name__ == '__main__': + ONIE_Upgrade().main() + diff --git a/packages/base/all/vendor-config-onl/src/boot.d/62.upgrade-loader b/packages/base/all/vendor-config-onl/src/boot.d/62.upgrade-loader new file mode 100755 index 00000000..dad90f70 --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/boot.d/62.upgrade-loader @@ -0,0 +1,147 @@ +#!/usr/bin/python +############################################################ +# +# ONL Loader Upgrade +# +############################################################ +import os +import fnmatch +from onl.upgrade import ubase + +class Loader_Upgrade(ubase.BaseUpgrade): + name="loader" + Name="Loader" + title="Loader Upgrade Check" + atype="A Loader" + + current_version_key="Current Loader Version" + next_version_key="Next Loader Version" + + def init_versions(self): + # + # Current Loader version file. + # If this file doesn't exist then in-place upgrade is not supported. + # + ETC_LOADER_VERSIONS_JSON = "/etc/onl/loader/versions.json" + + # Upgrade Loader Version file. + NEXT_LOADER_VERSIONS_JSON = "/etc/onl/upgrade/%s/manifest.json" % self.arch + + self.current_version = self.load_json(ETC_LOADER_VERSIONS_JSON, + "RELEASE_ID", + None) + + self.next_version = self.load_json(NEXT_LOADER_VERSIONS_JSON, + "version", {}).get('RELEASE_ID', None) + + # These will be used by the derived class's implementation. + (udir, um, data) = self.platform.upgrade_manifest("loader") + self.manifest = data + + + def summarize(self): + self.logger.info("Current Loader Version: %s" % self.current_version) + self.logger.info(" Next Loader Version: %s" % self.next_version) + self.logger.info("") + + + def upgrade_notes(self): + return """ + * A single reboot will be required to complete this upgrade. +""" + +class Loader_Upgrade_ppc(Loader_Upgrade): + + PPC_FIT_UPGRADE_IMAGE_PLATFORM="/etc/onl/upgrade/ppc/%s.itb" + PPC_FIT_UPGRADE_IMAGE_ALL="/etc/onl/upgrade/ppc/all.itb" + PPC_FIT_LOADER_IMAGE_NAME="switchlight-loader" + + def do_upgrade(self, forced=False): + + # + # The upgrade/ppc directory must have a FIT image called $PPC_FIT_UPGRADE_IMAGE (see constants above) + # This is obviously janky. + # + fit_image = None + + if os.path.exists(self.PPC_FIT_UPGRADE_IMAGE_PLATFORM % self.swlp.platform()): + fit_image = self.PPC_FIT_UPGRADE_IMAGE_PLATFORM % self.swlp.platform() + elif os.path.exists(self.PPC_FIT_UPGRADE_IMAGE_ALL): + fit_image = self.PPC_FIT_UPGRADE_IMAGE_ALL + else: + self.abort("The PPC Upgrade FIT image is missing. Upgrade cannot continue.") + + # + # The manifest says which partition contains the loader, and whether + # the loader is stored as a file in that partition or written raw + # directly to it. + # + partition = self.manifest.get('partition', None) + if not partition: + self.abort("The platform upgrade manifest does not contain a valid partition key.") + + if self.manifest.get('raw', False): + # + # The loader file is written raw to the given partition. + # + print "Writing %s to %s..." % (fit_image, partition) + if os.system("dd of=/dev/%s if=%s" % (partition, fit_image)) != 0: + self.abort("Failure writing loader data to partition." % (partition)) + + else: + # + # Mount the loader partition and rewrite the loader image. + # + mdir="/mnt/upgrade/loader" + self.mount(mdir, partition=partition) + self.copyfile(fit_image, os.path.join(mdir, self.PPC_FIT_LOADER_IMAGE_NAME)) + self.umount(mdir) + + self.reboot() + + + +class Loader_Upgrade_x86_64(Loader_Upgrade): + + X86_64_UPGRADE_DIR="/etc/onl/upgrade/x86_64/" + X86_64_UPGRADE_PATTERNS = [ "kernel-*", "initrd-*" ] + + def do_upgrade(self, forced=False): + # + # Mount the ONL-BOOT partition + # + mdir="/mnt/onl-boot" + self.mount(mdir, label="ONL-BOOT") + + for f in os.listdir(self.X86_64_UPGRADE_DIR): + for pattern in self.X86_64_UPGRADE_PATTERNS: + if fnmatch.fnmatch(f, pattern): + self.copyfile(os.path.join(self.X86_64_UPGRADE_DIR, f), os.path.join(mdir, f)) + + src = "/lib/platform-config/current/onl/boot/grub.cfg" + dst = os.path.join(mdir, "grub/grub.cfg") + if os.path.exists(src): + self.copyfile(src, dst) + + self.umount(mdir) + self.reboot() + + + + + +if __name__ == '__main__': + import platform + + arch = platform.machine() + klass = None + + if arch =='ppc': + klass = Loader_Upgrade_ppc + elif arch == 'x86_64': + klass = Loader_Upgrade_x86_64 + else: + raise Exception("The current architecture (%s) is not supported for upgrade." % arch) + + klass().main() + diff --git a/packages/base/all/vendor-config-onl/src/python/onl/upgrade/__init__.py b/packages/base/all/vendor-config-onl/src/python/onl/upgrade/__init__.py new file mode 100644 index 00000000..c71a523a --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/python/onl/upgrade/__init__.py @@ -0,0 +1,9 @@ +############################################################ +# +# +# Copyright 2013, 2014 BigSwitch Networks, Inc. +# +# +# +# +############################################################ diff --git a/packages/base/all/vendor-config-onl/src/python/onl/upgrade/ubase.py b/packages/base/all/vendor-config-onl/src/python/onl/upgrade/ubase.py new file mode 100644 index 00000000..faebe6cd --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/python/onl/upgrade/ubase.py @@ -0,0 +1,427 @@ +############################################################ +# +# Upgrade Base Classes +# +############################################################ +import logging +import logging.handlers +import platform as pp +import subprocess +import os +import sys +import shutil +import json +import string +import argparse +from time import sleep +from onl.platform.current import OnlPlatform + +class BaseUpgrade(object): + + # Customized by deriving class + name = None + Name = None + atype = None + current_version_key = None + next_version_key = None + def __init__(self): + + if not (self.name and self.Name and self.atype and self.title and + self.current_version_key and self.next_version_key): + raise Exception("Name descriptors must be provided by deriving class.") + + self.init_logger() + self.init_argparser() + self.load_config() + self.arch = pp.machine() + self.platform = OnlPlatform() + + # + # TODO. + # + # DEFAULT_CONFIG = { + # "auto-upgrade" : "advisory", + # } + # + # CONFIG_FILES = [ + # "/etc/boot.d/upgrade/.upgrade-config.json", + # "/mnt/flash/override-upgrade-config.json" + # ] + # + # def load_config(self): + # self.config = self.DEFAULT_CONFIG + # for f in self.CONFIG_FILES: + # if os.path.exists(f): + # self.config.update(json.load(file(f))) + # + # self.logger.debug("Loaded Configuration:\n%s\n" % (json.dumps(self.config, indent=2))) + # + # if self.name in self.config: + # self.config = self.config['name'] + # + # self.logger.debug("Final Configuration:\n%s\n" % (json.dumps(self.config, indent=2))) + # + def load_config(self): + pass + + + def init_logger(self): + fmt = '%(asctime)s.%(msecs)d %(levelname)s %(name)s: %(message)s' + datefmt="%Y-%m-%d %H:%M:%S" + formatter = logging.Formatter(fmt, datefmt) + + logging.basicConfig(format=fmt, datefmt=datefmt) + + self.logger = logging.getLogger(string.rjust("%s-upgrade" % self.name, 16)) + self.logger.setLevel(logging.INFO) + if os.getenv("DEBUG"): + self.logger.setLevel(logging.DEBUG) + + def init_argparser(self): + self.ap = argparse.ArgumentParser("%s-upgrade" % self.name) + self.ap.add_argument("--enable", action='store_true', help="Enable updates.") + self.ap.add_argument("--force", action='store_true', help="Force update.") + self.ap.add_argument("--no-reboot", action='store_true', help="Don't reboot.") + self.ap.add_argument("--check", action='store_true', help="Check only.") + self.ap.add_argument("--auto-upgrade", help="Override auto-upgrade mode.", default='advisory') + self.ap.add_argument("--summarize", action='store_true', help="Summarize only, no upgrades.") + + def banner(self): + self.logger.info("************************************************************") + self.logger.info("* %s" % self.title) + self.logger.info("************************************************************") + self.logger.info("") + + def finish(self, message=None, rc=0): + if message: + self.logger.info("") + if rc == 0: + self.logger.info(message) + else: + self.logger.error(message) + self.logger.info("") + self.logger.info("************************************************************") + self.update_upgrade_status(self.current_version_key, self.current_version) + self.update_upgrade_status(self.next_version_key, self.next_version) + # Flush stdout + sleep(.1) + sys.exit(rc) + + def abort(self, message=None, rc=1): + if message: + message = "Error: %s" % message + self.finish(message, rc) + + def fw_getenv(self, var): + FW_PRINTENV="/usr/bin/fw_printenv" + if os.path.exists(FW_PRINTENV): + try: + if var: + return subprocess.check_output("%s -n %s" % FW_PRINTENV, stderr=subprocess.STDOUT); + else: + variables = {} + for v in subprocess.check_output("/usr/bin/fw_printenv", shell=True).split('\n'): + (name, eq, value) = v.partition('=') + variables[name] = value + return variables + except Exception, e: + return None + else: + return None + + def fw_setenv(self, var, value): + FW_SETENV="/usr/bin/fw_setenv" + if os.system("%s %s %s" % (FW_SETENV, var, value)) != 0: + self.abort("Error setting environment variable %s=%s. Upgrade cannot continue." % (var, value)) + + def copyfile(self, src, dst): + self.logger.info("Installing %s -> %s..." % (src, dst)) + if not os.path.exists(src): + self.abort("Source file '%s' does not exist." % src) + + try: + shutil.copyfile(src, dst) + except Exception, e: + self.abort("Exception while copying: %s" % e) + + def reboot(self): + if self.ops.no_reboot: + self.finish("[ No Reboot ]") + elif self.ops.check: + self.finish("[ Check Abort ]") + else: + self.logger.info("The system will reboot to complete the update.") + sleep(1) + os.system("sync") + sleep(1) + while True: + os.system("reboot -f") + sleep(30) + self.abort("The system did not reboot as expected.") + + + def load_json(self, fname, key=None, default=None): + if os.path.exists(fname): + with open(fname) as f: + data = json.load(f) + if key: + return data.get(key, default) + else: + return data + else: + return default + + + UPGRADE_STATUS_JSON = "/lib/platform-config/current/upgrade.json" + + def update_upgrade_status(self, key, value): + data = {} + if os.path.exists(self.UPGRADE_STATUS_JSON): + with open(self.UPGRADE_STATUS_JSON) as f: + data = json.load(f) + data[key] = value + with open(self.UPGRADE_STATUS_JSON, "w") as f: + json.dump(data, f) + + + # + # Initialize self.current_version, self.next_Version + # and anything required for summarize() + # + def init_versions(self): + raise Exception("init_versions() must be provided by the deriving class.") + + + # + # Perform actual upgrade. Provided by derived class. + # + def do_upgrade(self, forced=False): + raise Exception("do_upgrade() must be provided by the deriving class.") + + # + # Perform any clean up necessary if the system is current (no upgrade required.) + # + def do_no_upgrade(self): + pass + + + def init_upgrade(self): + self.current_version = None + self.next_version = None + self.init_versions() + self.update_upgrade_status(self.current_version_key, self.current_version) + self.update_upgrade_status(self.next_version_key, self.next_version) + + def summarize(self): + pass + + + def __upgrade_prompt(self, instructions, default="yes"): + valid = {"yes": True, "y": True, "ye": True, + "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + notes = self.upgrade_notes() + if notes: + instructions = instructions + "\n" + notes + "\n" + + instructions = "\n\n" + instructions + "\n\nStart the upgrade? " + + while True: + sys.stdout.write(instructions + prompt) + choice = raw_input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " + "(or 'y' or 'n').\n") + + def upgrade_prompt(self, instructions, default='yes'): + try: + return self.__upgrade_prompt(instructions, default) + except Exception, e: + self.logger.error("") + self.logger.error("Exception: %s" % e) + self.abort("No upgrade will be performed.") + return False + except KeyboardInterrupt: + return False + + def upgrade_notes(self): + raise Exception("Must be provided by derived class.") + + def __upgrade_advisory(self): + if not self.ops.enable: + self.finish("%s updates are not enabled." % self.Name) + self.__do_upgrade() + self.finish() + + def __upgrade_force(self): + self.logger.info("This system is configured for automatic %s updates." % self.Name) + self.__do_upgrade() + self.finish() + + def __do_upgrade(self): + if self.ops.check: + self.finish("[ Check abort. ]") + self.do_upgrade() + + + def __upgrade_optional(self): + instructions = """%s upgrade should be performed before continuing for optimal Switch Light +performance on this system. + +While it is recommended that you perform this upgrade it is optional and you +may continue booting Switch Light for testing purposes at this time. + +Please note that you will be asked again each time at boot to perform +this upgrade. Automatic booting will not be possible until the upgrade +is performed.""" % self.atype + + if self.upgrade_prompt(instructions) == True: + self.__do_upgrade() + self.finish() + else: + self.finish("Upgrade cancelled.") + + def __upgrade_required(self): + instructions = """%s upgrade must be performed before Switch Light can run on this system. +If you choose not to perform this upgrade Switch Light cannot continue +booting.""" % self.atype + + if self.upgrade_prompt(instructions) == True: + self.__do_upgrade() + self.finish() + else: + self.logger.info("The system cannot continue without performing %s update. The system will now reboot if you would like to take alternative actions." % self.atype) + self.reboot() + + + def __upgrade(self): + self.logger.info("%s upgrade is required." % self.atype) + + auto_upgrade = self.ops.auto_upgrade + + # + # auto-upgrade modes are: + # advisory : Advisory only. This is the default. + # force : Perform the upgrade automatically. + # optional : Ask the user whether an upgrade should be performed. + # required : Ask the user whether an upgrade should be performed. + # Reboot if the answer is 'no'. + # + if auto_upgrade == 'advisory': + self.__upgrade_advisory() + elif auto_upgrade == 'force': + self.__upgrade_force(); + elif auto_upgrade == 'optional': + self.__upgrade_optional() + elif auto_upgrade == 'required': + self.__upgrade_required() + else: + self.abort("auto-upgrade mode '%s' is not supported." % auto_upgrade) + + + def upgrade_check(self): + + if self.current_version == self.next_version: + # Versions match. Only continue if forced. + if self.ops.force: + self.logger.info("%s version %s is current." % (self.Name, + self.current_version)) + self.logger.info("[ Upgrade Forced. ]") + else: + self.logger.info("%s version %s is current." % (self.Name, + self.current_version)) + self.do_no_upgrade() + self.finish() + + if self.next_version: + self.__upgrade() + + + def mount(self, location, partition=None, label=None): + if not os.path.isdir(location): + os.makedirs(location) + + if partition: + cmd = "mount /dev/%s %s " % (partition,location) + name = partition + + if label: + cmd = "mount LABEL=%s %s" % (label, location) + name = label + + if os.system(cmd) != 0: + self.abort("Could not mount %s @ %s. Upgrade cannot continue." % (name, location)) + + def umount(self, location): + if os.system("umount %s" % location) != 0: + self.abort("Could not unmount %s. Upgrade cannot continue." % location) + + + def main(self): + self.ops = self.ap.parse_args() + self.banner() + self.init_upgrade() + self.summarize() + if not self.ops.summarize: + self.upgrade_check() + + + + +class BaseOnieUpgrade(BaseUpgrade): + + ONIE_UPDATER_PATH = "/mnt/flash2/onie-updater" + + def install_onie_updater(self, src_dir, updater): + if type(updater) is list: + # Copy all files in the list to /mnt/flash2 + for f in updater: + src = os.path.join(src_dir, f) + dst = os.path.join("/mnt/flash2", f) + self.copyfile(src, dst) + else: + # Copy single updater to /mnt/flash2/onie-updater + src = os.path.join(src_dir, updater) + self.copyfile(src, self.ONIE_UPDATER_PATH) + + + def initiate_onie_update(self): + self.logger.info("Initiating %s Update." % self.Name) + if self.arch == 'ppc': + # Initiate update + self.fw_setenv('onie_boot_reason', 'update') + self.reboot() + + elif self.arch == 'x86_64': + OB = "/mnt/onie-boot" + self.mount(OB, label="ONIE-BOOT") + if os.system("/mnt/onie-boot/onie/tools/bin/onie-boot-mode -o update") != 0: + self.abort("Could not set ONIE Boot Mode to Update. Upgrade cannot continue.") + self.umount(OB) + + SL = "/mnt/sl-boot" + self.mount(SL, label="SL-BOOT") + with open("/mnt/sl-boot/grub/grub.cfg", "a") as f: + f.write("set default=ONIE\n") + self.umount(SL) + self.reboot() + + else: + self.abort("Architecture %s unhandled." % self.arch) + + def clean_onie_updater(self): + if os.path.exists(self.ONIE_UPDATER_PATH): + self.logger.info("Removing previous onie-updater.") + os.remove(self.ONIE_UPDATER_PATH)