From 3a8ab731abb12a38f3e9acba333a5a0aef9e0a6d Mon Sep 17 00:00:00 2001 From: Sujin Kang Date: Wed, 27 Oct 2021 12:18:55 -0700 Subject: [PATCH] [fwutil] Add `fwutil update all` to support the automatic platform component fw updates (#1242) - What I did Add the support for the automatic platform component fw updates - How I did it Add fwutil auto_update interfaces - How to verify it Work with dell to verify the auto_update interfaces with ssd firmware update. - Previous command output (if the output of a command-line utility has changed) - New command output (if the output of a command-line utility has changed) New added command outputs are available in the following HLD: Azure/SONiC#648 --- fwutil/lib.py | 291 +++++++++++++++++++++++++++++++++++++++++--- fwutil/log.py | 63 +++++++++- fwutil/main.py | 115 +++++++++++++++-- scripts/fast-reboot | 35 +++++- scripts/reboot | 28 ++++- 5 files changed, 494 insertions(+), 38 deletions(-) diff --git a/fwutil/lib.py b/fwutil/lib.py index d4623cf7cebc..8e994d35144e 100755 --- a/fwutil/lib.py +++ b/fwutil/lib.py @@ -5,11 +5,14 @@ # try: + import glob import os import json + import shutil import socket import subprocess import time + import tarfile from collections import OrderedDict from urllib.parse import urlparse from urllib.request import urlopen, urlretrieve @@ -29,6 +32,13 @@ EMPTY = "" NA = "N/A" NEWLINE = "\n" +PLATFORM_COMPONENTS_FILE = "platform_components.json" +FIRMWARE_UPDATE_DIR = "/var/platform/" +FWUPDATE_FWPACKAGE_DIR = os.path.join(FIRMWARE_UPDATE_DIR, "fwpackage/") +FIRMWARE_AU_STATUS_DIR = "/tmp/firmwareupdate/" +FW_AU_TASK_FILE_REGEX = "*_fw_au_task" +FW_AU_STATUS_FILE = "fw_au_status" +FW_AU_STATUS_FILE_PATH = os.path.join(FIRMWARE_AU_STATUS_DIR, FW_AU_STATUS_FILE) # ========================= Variables ========================================== @@ -281,17 +291,54 @@ def umount_next_image_fs(self): next_image = property(fget=get_next_image) +class FWPackage(object): + """ + FWPackage + """ + fwupdate_fwimage_dir = None + fwupdate_package_name = None + + def __init__(self, fwpackage): + self.fwupdate_package_name = fwpackage + if not os.path.isdir(FIRMWARE_UPDATE_DIR): + os.mkdir(FIRMWARE_UPDATE_DIR) + if os.path.isdir(FWUPDATE_FWPACKAGE_DIR): + shutil.rmtree(FWUPDATE_FWPACKAGE_DIR) + os.mkdir(FWUPDATE_FWPACKAGE_DIR) + + def untar_fwpackage(self): + if self.fwupdate_package_name is not None: + fwupdate_tar = tarfile.open(self.fwupdate_package_name) + fwupdate_tar.extractall(FWUPDATE_FWPACKAGE_DIR) + fwupdate_tar.close() + return True + return False + + def get_fw_package_path(self): + for r, d, f in os.walk(FWUPDATE_FWPACKAGE_DIR): + for file in f: + if PLATFORM_COMPONENTS_FILE in file: + self.fwupdate_fwimage_dir = os.path.join(r, os.path.dirname(file)) + log_helper.print_warning("fwupdate_fwimage_dir: {}".format(self.fwupdate_fwimage_dir)) + return self.fwupdate_fwimage_dir + + def cleanup_tmp_fwpackage(self): + shutil.rmtree(FWUPDATE_FWPACKAGE_DIR) + return + + class PlatformComponentsParser(object): """ PlatformComponentsParser """ - PLATFORM_COMPONENTS_FILE = "platform_components.json" PLATFORM_COMPONENTS_PATH_TEMPLATE = "{}/usr/share/sonic/device/{}/{}" + PLATFORM_COMPONENTS_FILE_PATH = None CHASSIS_KEY = "chassis" MODULE_KEY = "module" COMPONENT_KEY = "component" FIRMWARE_KEY = "firmware" + UTILITY_KEY = "utility" VERSION_KEY = "version" UTF8_ENCODING = "utf-8" @@ -302,11 +349,15 @@ def __init__(self, is_modular_chassis): self.__module_component_map = OrderedDict() def __get_platform_components_path(self, root_path): - return self.PLATFORM_COMPONENTS_PATH_TEMPLATE.format( - root_path, - device_info.get_platform(), - self.PLATFORM_COMPONENTS_FILE - ) + if "{}".format(root_path).startswith(FWUPDATE_FWPACKAGE_DIR): + self.PLATFORM_COMPONENTS_FILE_PATH = os.path.join(root_path, PLATFORM_COMPONENTS_FILE) + else: + self.PLATFORM_COMPONENTS_FILE_PATH = self.PLATFORM_COMPONENTS_PATH_TEMPLATE.format( + root_path, + device_info.get_platform(), + PLATFORM_COMPONENTS_FILE + ) + return self.PLATFORM_COMPONENTS_FILE_PATH def __is_str(self, obj): return isinstance(obj, str) @@ -315,7 +366,7 @@ def __is_dict(self, obj): return isinstance(obj, dict) def __parser_fail(self, msg): - raise RuntimeError("Failed to parse \"{}\": {}".format(self.PLATFORM_COMPONENTS_FILE, msg)) + raise RuntimeError("Failed to parse \"{}\": {}".format(PLATFORM_COMPONENTS_FILE, msg)) def __parser_platform_fail(self, msg): self.__parser_fail("invalid platform schema: {}".format(msg)) @@ -348,15 +399,12 @@ def __parse_component_section(self, section, component, is_module_component=Fals self.__module_component_map[section][key1] = OrderedDict() if value1: - if len(value1) != 1 and len(value1) != 2: + if len(value1) < 1 or len(value1) > 3: self.__parser_component_fail("unexpected number of records: key={}".format(key1)) if self.FIRMWARE_KEY not in value1: missing_key = self.FIRMWARE_KEY break - elif len(value1) == 2 and self.VERSION_KEY not in value1: - missing_key = self.VERSION_KEY - break for key2, value2 in value1.items(): if not self.__is_str(value2): @@ -484,6 +532,10 @@ class ComponentUpdateProvider(PlatformDataProvider): def __init__(self, root_path=None): PlatformDataProvider.__init__(self) + if not os.path.isdir(FIRMWARE_UPDATE_DIR): + os.mkdir(FIRMWARE_UPDATE_DIR) + if not os.path.isdir(FIRMWARE_AU_STATUS_DIR): + os.mkdir(FIRMWARE_AU_STATUS_DIR) self.__root_path = root_path @@ -530,12 +582,15 @@ def __validate_platform_schema(self, pcp): pcp.module_component_map ) - def get_status(self): + def get_updates_status(self): status_table = [ ] + auto_update_status_table = [ ] append_chassis_name = self.is_chassis_has_components() append_module_na = not self.is_modular_chassis() module_name = NA + update_utility = NA + is_chassis_component = True for chassis_name, chassis_component_map in self.chassis_component_map.items(): for chassis_component_name, chassis_component in chassis_component_map.items(): @@ -564,6 +619,9 @@ def get_status(self): else: status = self.FW_STATUS_UP_TO_DATE + if self.__pcp.UTILITY_KEY in component: + update_utility = component[self.__pcp.UTILITY_KEY] + status_table.append( [ chassis_name if append_chassis_name else EMPTY, @@ -575,6 +633,19 @@ def get_status(self): ] ) + auto_update_status_table.append( + [ + is_chassis_component, + chassis_name, + module_name, + chassis_component_name, + firmware_path, + firmware_version, + update_utility, + status + ] + ) + if append_chassis_name: append_chassis_name = False @@ -585,6 +656,7 @@ def get_status(self): chassis_name = self.chassis.get_name() if self.is_modular_chassis(): + is_chassis_component = False for module_name, module_component_map in self.module_component_map.items(): append_module_name = True @@ -614,6 +686,9 @@ def get_status(self): else: status = self.FW_STATUS_UP_TO_DATE + if self.__pcp.UTILITY_KEY in component: + update_utility = component[self.__pcp.UTILITY_KEY] + status_table.append( [ chassis_name if append_chassis_name else EMPTY, @@ -625,19 +700,45 @@ def get_status(self): ] ) + auto_update_status_table.append( + [ + is_chassis_component, + chassis_name, + module_name, + module_component_name, + firmware_path, + firmware_version, + update_utility, + status + ] + ) + if append_chassis_name: append_chassis_name = False if append_module_name: append_module_name = False + return status_table, auto_update_status_table + + def get_status(self): + status_table, auto_update_status_table = self.get_updates_status() if not status_table: return None return tabulate(status_table, self.STATUS_HEADER, tablefmt=self.FORMAT) + def get_update_available_components(self): + update_available_components = [] + status_table, auto_update_status_table = self.get_updates_status() + for component_status in auto_update_status_table: + if component_status[-1] is self.FW_STATUS_UPDATE_REQUIRED: + update_available_components.append(component_status) + + return update_available_components + def get_notification(self, chassis_name, module_name, component_name): - if self.is_modular_chassis(): + if module_name is not None: component = self.module_component_map[module_name][component_name] parser = self.__pcp.module_component_map[module_name][component_name] else: @@ -655,7 +756,7 @@ def get_notification(self, chassis_name, module_name, component_name): return component.get_firmware_update_notification(firmware_path) def update_firmware(self, chassis_name, module_name, component_name): - if self.is_modular_chassis(): + if module_name is not None: component = self.module_component_map[module_name][component_name] parser = self.__pcp.module_component_map[module_name][component_name] @@ -687,8 +788,128 @@ def update_firmware(self, chassis_name, module_name, component_name): log_helper.log_fw_update_end(component_path, firmware_path, False, e) raise + def update_au_status_file(self, au_info_data, filename=FW_AU_STATUS_FILE_PATH): + with open(filename, 'w') as f: + json.dump(au_info_data, f) + + def read_au_status_file_if_exists(self, filename=FW_AU_STATUS_FILE_PATH): + data = None + if os.path.exists(filename): + with open(filename) as au_status_file: + data = json.load(au_status_file) + return data + + def set_firmware_auto_update_status(self, component_path, fw_version, boot, rt_code): + data = self.read_au_status_file_if_exists(FW_AU_STATUS_FILE_PATH) + if data is None: + data = {} + if boot not in data: + data[boot] = [] + + au_status = data[boot] + + comp_au_status = {} + if rt_code < -1: + status = False + else: + status = True + + if rt_code == 0: + info = "reserved" + elif rt_code == 1: + info = "installed" + elif rt_code == 2: + info = "updated" + elif rt_code == 3: + info = "scheduled" + elif rt_code == -1: + info = "err_boot_type" + elif rt_code == -2: + info = "err_image" + elif rt_code == -3: + info = "err_others" + else: + info = "err_unknown" + + comp_au_status['comp'] = component_path + comp_au_status['status'] = status + comp_au_status['version'] = fw_version + comp_au_status['info'] = info + + au_status.append(comp_au_status) + + self.update_au_status_file(data, FW_AU_STATUS_FILE_PATH) + return (status, info) + + def auto_update_firmware(self, component_au_info, boot): + is_chassis_component = component_au_info[0] + chassis_name = component_au_info[1] + module_name = component_au_info[2] + component_name = component_au_info[3] + fw_version = component_au_info[5] + utility = component_au_info[6] + + if is_chassis_component: + component = self.chassis_component_map[chassis_name][component_name] + parser = self.__pcp.chassis_component_map[chassis_name][component_name] + component_path = "{}/{}".format(chassis_name, component_name) + else: + component = self.module_component_map[module_name][component_name] + parser = self.__pcp.module_component_map[module_name][component_name] + component_path = "{}/{}/{}".format(chassis_name, module_name, component_name) + + if not parser: + return + + firmware_path = parser[self.__pcp.FIRMWARE_KEY] + + if self.__root_path is not None: + firmware_path = self.__root_path + firmware_path + + if self.__root_path is not None and utility is not None: + utility = self.__root_path + utility + + try: + click.echo("{} firmware auto-update starting: {} with boot_type {}".format(component_path, firmware_path, boot)) + log_helper.log_fw_auto_update_start(component_path, firmware_path, boot) + if os.path.isfile(utility) and os.access(utility, os.X_OK): + cmd = "{} -a {} {}".format( + utility, + firmware_path, + boot + ) + click.echo("firmware auto-update starting:utility cmd {}".format(cmd)) + rt_code = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True, text=True) + rt_code = int(rt_code.strip()) + else: + rt_code = component.auto_update_firmware(firmware_path, boot) + click.echo("{} firmware auto-update status return_code: {}".format(component_path, int(rt_code))) + (status, info) = self.set_firmware_auto_update_status(component_path, fw_version, boot, rt_code) + log_helper.log_fw_auto_update_end(component_path, firmware_path, boot, status, info) + except KeyboardInterrupt: + log_helper.log_fw_auto_update_end(component_path, firmware_path, boot, False, "Keyboard interrupt") + raise + except Exception as e: + log_helper.log_fw_auto_update_end(component_path, firmware_path, boot, False, e) + raise + + + def is_first_auto_update(self, boot): + task_file = None + status_file = None + for task_file in glob.glob(os.path.join(FIRMWARE_AU_STATUS_DIR, FW_AU_TASK_FILE_REGEX)): + if task_file is not None: + click.echo("{} firmware auto-update is already performed, {} firmware auto update is not allowed any more".format(task_file, boot)) + return False + for status_file in glob.glob(os.path.join(FIRMWARE_AU_STATUS_DIR, FW_AU_STATUS_FILE)): + if status_file is not None: + click.echo("{} firmware auto-update is already performed, {} firmware auto update is not allowed any more".format(status_file, boot)) + return False + click.echo("Firmware auto-update for boot_type {} is allowed".format(boot)) + return True + def is_firmware_update_available(self, chassis_name, module_name, component_name): - if self.is_modular_chassis(): + if module_name is not None: component = self.__pcp.module_component_map[module_name][component_name] else: component = self.__pcp.chassis_component_map[chassis_name][component_name] @@ -699,7 +920,7 @@ def is_firmware_update_available(self, chassis_name, module_name, component_name return True def is_firmware_update_required(self, chassis_name, module_name, component_name): - if self.is_modular_chassis(): + if module_name is not None: component = self.module_component_map[module_name][component_name] parser = self.__pcp.module_component_map[module_name][component_name] else: @@ -729,11 +950,19 @@ class ComponentStatusProvider(PlatformDataProvider): ComponentStatusProvider """ HEADER = [ "Chassis", "Module", "Component", "Version", "Description" ] + AU_STATUS_HEADER = [ "Component", "Version", "Status", "Info" ] FORMAT = "simple" + INFO_KEY = "info" def __init__(self): PlatformDataProvider.__init__(self) + def __is_dict(self, obj): + return isinstance(obj, dict) + + def __parser_fail_fw_au_status(self, msg): + raise RuntimeError("Failed to parse \"{}\": {}".format(FW_AU_STATUS_FILE_PATH, msg)) + def get_status(self): status_table = [ ] @@ -790,3 +1019,33 @@ def get_status(self): append_module_name = False return tabulate(status_table, self.HEADER, tablefmt=self.FORMAT) + + def read_au_status_file_if_exists(self, filename=FW_AU_STATUS_FILE_PATH): + data = None + if os.path.exists(filename): + with open(filename) as au_status_file: + data = json.load(au_status_file) + return data + + def get_au_status(self): + au_status = [] + auto_updated_status_table = [] + data = self.read_au_status_file_if_exists(FW_AU_STATUS_FILE_PATH) + + if data is None: + return None + + boot_type = list(data.keys())[0] + click.echo("Firmware auto-update performed for {} reboot".format(boot_type)) + + au_status = data[boot_type] + for comp_au_status in au_status: + r = [] + r.append(comp_au_status['comp'] if 'comp' in comp_au_status else "") + r.append(comp_au_status['version'] if 'version' in comp_au_status else "") + r.append(comp_au_status['status'] if 'status' in comp_au_status else "") + r.append(comp_au_status['info'] if 'info' in comp_au_status else "") + auto_updated_status_table.append(r) + + return tabulate(auto_updated_status_table, self.AU_STATUS_HEADER, tablefmt=self.FORMAT) + diff --git a/fwutil/log.py b/fwutil/log.py index f9dc03345325..69463f5e11fc 100755 --- a/fwutil/log.py +++ b/fwutil/log.py @@ -29,6 +29,7 @@ class LogHelper(object): FW_ACTION_DOWNLOAD = "download" FW_ACTION_INSTALL = "install" FW_ACTION_UPDATE = "update" + FW_ACTION_AUTO_UPDATE = "auto-update" STATUS_SUCCESS = "success" STATUS_FAILURE = "failure" @@ -45,6 +46,19 @@ def __log_fw_action_start(self, action, component, firmware): ) ) + def __log_fw_au_action_start(self, action, component, firmware, boot): + caption = "Firmware {} started".format(action) + template = "{}: component={}, firmware={}, for boot_type={}" + + log.log_info( + template.format( + caption, + component, + firmware, + boot + ) + ) + def __log_fw_action_end(self, action, component, firmware, status, exception=None): caption = "Firmware {} ended".format(action) @@ -61,26 +75,54 @@ def __log_fw_action_end(self, action, component, firmware, status, exception=Non ) ) else: - if exception is None: + if exception: log.log_error( - status_template.format( + exception_template.format( caption, component, firmware, - self.STATUS_FAILURE + self.STATUS_FAILURE, + str(exception) ) ) else: log.log_error( - exception_template.format( + status_template.format( caption, component, firmware, - self.STATUS_FAILURE, - str(exception) + self.STATUS_FAILURE ) ) + def __log_fw_au_action_end(self, action, component, firmware, boot, status, info=None): + caption = "Firmware {} ended".format(action) + + status_template = "{}: component={}, firmware={}, boot={}, status={}" + status_info_template = "{}: component={}, firmware={}, boot={}, status={}, info={}" + + if info: + log.log_info( + status_info_template.format( + caption, + component, + firmware, + boot, + self.STATUS_SUCCESS, + info + ) + ) + else: + log.log_info( + status_template.format( + caption, + component, + firmware, + boot, + self.STATUS_FAILURE, + ) + ) + def log_fw_download_start(self, component, firmware): self.__log_fw_action_start(self.FW_ACTION_DOWNLOAD, component, firmware) @@ -99,6 +141,15 @@ def log_fw_update_start(self, component, firmware): def log_fw_update_end(self, component, firmware, status, exception=None): self.__log_fw_action_end(self.FW_ACTION_UPDATE, component, firmware, status, exception) + def log_fw_auto_update_start(self, component, firmware, boot): + self.__log_fw_au_action_start(self.FW_ACTION_AUTO_UPDATE, component, firmware, boot) + + def log_fw_auto_update_end(self, component, firmware, status, boot, exception=None): + self.__log_fw_au_action_end(self.FW_ACTION_AUTO_UPDATE, component, firmware, status, exception, boot) + + def log_fw_auto_update_fail(self, component, firmware, status, boot, exception=None): + self.__log_fw_au_action_end(self.FW_ACTION_AUTO_UPDATE, component, firmware, status, exception, boot) + def print_error(self, msg): click.echo("Error: {}.".format(msg)) diff --git a/fwutil/main.py b/fwutil/main.py index 4e4b1276425a..2f378da3066f 100755 --- a/fwutil/main.py +++ b/fwutil/main.py @@ -10,7 +10,7 @@ import click from .lib import PlatformDataProvider, ComponentStatusProvider, ComponentUpdateProvider - from .lib import URL, SquashFs + from .lib import URL, SquashFs, FWPackage from .log import LogHelper except ImportError as e: raise ImportError("Required module not found: {}".format(str(e))) @@ -89,6 +89,14 @@ def update(ctx): ctx.obj[COMPONENT_PATH_CTX_KEY] = [ ] +# 'auto_update' group +@click.group() +@click.pass_context +def auto_update(ctx): + """Auto-update platform firmware""" + pass + + def chassis_handler(ctx): ctx.obj[CHASSIS_NAME_CTX_KEY] = pdp.chassis.get_name() ctx.obj[COMPONENT_PATH_CTX_KEY].append(pdp.chassis.get_name()) @@ -334,6 +342,60 @@ def fw_update(ctx, yes, force, image): cli_abort(ctx, str(e)) +# 'fw' subcommand +@auto_update.command(name='fw') +@click.option('-i', '--image', 'image', type=click.Choice(["current", "next"]), default="current", show_default=True, help="Update firmware using current/next SONiC image") +@click.option('-f', '--fw_image', 'fw_image', help="Custom FW package path") +@click.option('-b', '--boot', 'boot', type=click.Choice(["any", "cold", "fast", "warm", "none"]), default="none", show_default=True, help="Necessary boot option after the firmware update") +@click.pass_context +def fw_auto_update(ctx, boot, image=None, fw_image=None): + """Update firmware from SONiC image""" + squashfs = None + fwpackage = None + cup = None + + try: + + if fw_image is not None: + fwpackage = FWPackage(fw_image) + + if fwpackage.untar_fwpackage(): + fs_path = fwpackage.get_fw_package_path() + cup = ComponentUpdateProvider(fs_path) + else: + log_helper.print_warning("Cannot open the firmware package") + else: + if image == IMAGE_NEXT: + squashfs = SquashFs() + + if squashfs.is_next_boot_set(): + fs_path = squashfs.mount_next_image_fs() + cup = ComponentUpdateProvider(fs_path) + else: + log_helper.print_warning("Next boot is set to current: fallback to defaults") + cup = ComponentUpdateProvider() + else: + cup = ComponentUpdateProvider() + + if cup is not None: + au_component_list = cup.get_update_available_components() + if au_component_list: + if cup.is_first_auto_update(boot): + for au_component in au_component_list: + cup.auto_update_firmware(au_component, boot) + log_helper.print_warning("All firmware auto-update has been performed") + click.echo("All firmware auto-update has been performed") + else: + log_helper.print_warning("All components: {}".format(cup.FW_STATUS_UP_TO_DATE)) + else: + log_helper.print_warning("compoenet update package is not available") + finally: + if squashfs is not None: + squashfs.umount_next_image_fs() + if fwpackage is not None: + fwpackage.cleanup_tmp_fwpackage() + + # 'show' subgroup @cli.group() def show(): @@ -344,24 +406,36 @@ def show(): # 'updates' subcommand @show.command() @click.option('-i', '--image', 'image', type=click.Choice(["current", "next"]), default="current", show_default=True, help="Show updates using current/next SONiC image") +@click.option('-f', '--fw_image', 'fw_image', help="Custom FW package path") @click.pass_context -def updates(ctx, image): +def updates(ctx, image=None, fw_image=None): """Show available updates""" try: squashfs = None + fwpackage = None + cup = None try: - if image == IMAGE_NEXT: - squashfs = SquashFs() + if fw_image is not None: + fwpackage = FWPackage(fw_image) - if squashfs.is_next_boot_set(): - fs_path = squashfs.mount_next_image_fs() + if fwpackage.untar_fwpackage(): + fs_path = fwpackage.get_fw_package_path() cup = ComponentUpdateProvider(fs_path) else: - log_helper.print_warning("Next boot is set to current: fallback to defaults") - cup = ComponentUpdateProvider() + log_helper.print_warning("Cannot open the firmware package") else: - cup = ComponentUpdateProvider() + if image == IMAGE_NEXT: + squashfs = SquashFs() + + if squashfs.is_next_boot_set(): + fs_path = squashfs.mount_next_image_fs() + cup = ComponentUpdateProvider(fs_path) + else: + log_helper.print_warning("Next boot is set to current: fallback to defaults") + cup = ComponentUpdateProvider() + else: + cup = ComponentUpdateProvider() status = cup.get_status() if status is not None: @@ -387,17 +461,40 @@ def status(ctx): cli_abort(ctx, str(e)) +# 'updates' subcommand +@click.group() +@click.pass_context +def show_update(ctx): + """status : Show platform components auto_update status""" + pass + + +# 'status' subcommand +@show_update.command(name='status') +@click.pass_context +def update_status(ctx): + """Show platform components auto_update status""" + try: + csp = ComponentStatusProvider() + click.echo(csp.get_au_status()) + except Exception as e: + cli_abort(ctx, str(e)) + + # 'version' subcommand @show.command() def version(): """Show utility version""" click.echo("fwutil version {0}".format(VERSION)) +show.add_command(show_update, name='update') + install.add_command(chassis_install, name='chassis') install.add_command(module_install, name='module') update.add_command(chassis_update, name='chassis') update.add_command(module_update, name='module') +update.add_command(auto_update, name='all') chassis_install.add_command(component_install, name='component') module_install.add_command(component_install, name='component') diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 0dc42b07de17..869a56373d5b 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -22,6 +22,7 @@ DEVPATH="/usr/share/sonic/device" PLATFORM=$(sonic-cfggen -H -v DEVICE_METADATA.localhost.platform) PLATFORM_PLUGIN="${REBOOT_TYPE}_plugin" LOG_SSD_HEALTH="/usr/local/bin/log_ssd_health" +PLATFORM_FWUTIL_AU_REBOOT_HANDLE="platform_fw_au_reboot_handle" SSD_FW_UPDATE="ssd-fw-upgrade" TAG_LATEST=yes @@ -42,6 +43,7 @@ EXIT_COUNTERPOLL_DELAY_FAILURE=14 EXIT_DB_INTEGRITY_FAILURE=15 EXIT_NO_CONTROL_PLANE_ASSISTANT=20 EXIT_SONIC_INSTALLER_VERIFY_REBOOT=21 +EXIT_PLATFORM_FW_AU_FAILURE=22 function error() { @@ -456,6 +458,25 @@ function check_warm_restart_in_progress() { done } +function check_conflict_boot_in_fw_update() { + # Make sure firmware auto update is not scheduled for a different reboot + FIRMWARE_AU_STATUS_DIR="/tmp/firmwareupdate" + FW_AU_TASK_FILE_REGEX="${FIRMWARE_AU_STATUS_DIR}/*_fw_au_task" + case "$REBOOT_TYPE" in + "fast-reboot") + FW_AU_TASK_FILE_EXP="${FIRMWARE_AU_STATUS_DIR}/fast_fw_au_task" + ;; + "warm-reboot") + FW_AU_TASK_FILE_EXP="${FIRMWARE_AU_STATUS_DIR}/warm_fw_au_task" + ;; + esac + FW_AU_TASK_FILE=$(compgen -G ${FW_AU_TASK_FILE_REGEX}) || true + if [[ -n "${FW_AU_TASK_FILE}" ]] && [[ ! -f "${FW_AU_TASK_FILE_EXP}" ]]; then + error "Firmware auto update scheduled for a different reboot: ${FW_AU_TASK_FILE}" + exit "${EXIT_FAILURE}" + fi +} + # main starts here parseOptions $@ @@ -466,6 +487,8 @@ then exit "${EXIT_FAILURE}" fi +check_conflict_boot_in_fw_update + sonic_asic_type=$(sonic-cfggen -y /etc/sonic/sonic_version.yml -v asic_type) # Check reboot type supported @@ -731,11 +754,17 @@ if [ -x /sbin/hwclock ]; then /sbin/hwclock -w || /bin/true fi -if [ -x ${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ]; then - debug "updating ssd fw for${REBOOT_TYPE}" - ${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ${REBOOT_TYPE} + +if [[ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ]]; then + debug "Handling task file for boot type ${REBOOT_TYPE}" + ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ${REBOOT_TYPE} || PLATFORM_FW_AU_RC=$? + if [[ $PLATFORM_FW_AU_RC -ne 0 ]]; then + debug "ERROR: Failed to handle the platform firmware auto-update for ${REBOOT_TYPE} Exit code: $PLATFORM_FW_AU_RC" + exit "${EXIT_PLATFORM_FW_AU_FAILURE}" + fi fi + # Enable Watchdog Timer if [ -x ${WATCHDOG_UTIL} ]; then debug "Enabling Watchdog before ${REBOOT_TYPE}" diff --git a/scripts/reboot b/scripts/reboot index 4b6a3583ddbf..7746ee7d69b2 100755 --- a/scripts/reboot +++ b/scripts/reboot @@ -28,7 +28,8 @@ SUBTYPE=$(sonic-cfggen -d -v DEVICE_METADATA.localhost.subtype) VERBOSE=no EXIT_NEXT_IMAGE_NOT_EXISTS=4 EXIT_SONIC_INSTALLER_VERIFY_REBOOT=21 -SSD_FW_UPDATE="ssd-fw-upgrade" +EXIT_PLATFORM_FW_AU_FAILURE=22 +PLATFORM_FWUTIL_AU_REBOOT_HANDLE="platform_fw_au_reboot_handle" REBOOT_SCRIPT_NAME=$(basename $0) REBOOT_TYPE="${REBOOT_SCRIPT_NAME}" TAG_LATEST=yes @@ -135,6 +136,19 @@ function reboot_pre_check() fi } +function check_conflict_boot_in_fw_update() +{ + # Make sure firmware auto update is not scheduled for a different reboot + FIRMWARE_AU_STATUS_DIR="/tmp/firmwareupdate" + FW_AU_TASK_FILE_REGEX="${FIRMWARE_AU_STATUS_DIR}/*_fw_au_task" + FW_AU_TASK_FILE_EXP="${FIRMWARE_AU_STATUS_DIR}/cold_fw_au_task" + FW_AU_TASK_FILE=$(compgen -G ${FW_AU_TASK_FILE_REGEX}) || true + if [[ -n "${FW_AU_TASK_FILE}" ]] && [[ ! -f "${FW_AU_TASK_FILE_EXP}" ]]; then + VERBOSE=yes debug "Firmware auto update scheduled for a different reboot: ${FW_AU_TASK_FILE}" + exit 1 + fi +} + function parse_options() { while getopts "h?v" opt; do @@ -162,6 +176,8 @@ fi debug "User requested rebooting device ..." +check_conflict_boot_in_fw_update + setup_reboot_variables reboot_pre_check @@ -186,9 +202,13 @@ if [ -x /sbin/hwclock ]; then /sbin/hwclock -w || /bin/true fi -if [ -x ${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ]; then - debug "updating ssd fw for${REBOOT_TYPE}" - ${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ${REBOOT_TYPE} +if [[ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ]]; then + debug "Handling task file for boot type ${REBOOT_TYPE}" + ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ${REBOOT_TYPE} || PLATFORM_FW_AU_RC=$? + if [[ $PLATFORM_FW_AU_RC -ne 0 ]]; then + debug "ERROR: Failed to handle the platform firmware auto-update for ${REBOOT_TYPE} Exit code: $PLATFORM_FW_AU_RC" + exit "${EXIT_PLATFORM_FW_AU_FAILURE}" + fi fi if [ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_UPDATE_REBOOT_CAUSE} ]; then