[Mellanox] Add ONIE and SSD platform components. (#4758)

Signed-off-by: Nazarii Hnydyn <nazariig@mellanox.com>
This commit is contained in:
Nazarii Hnydyn 2020-06-15 14:25:49 +03:00 committed by GitHub
parent 0a750a6b54
commit 1db64a3bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 748 additions and 283 deletions

View File

@ -1,120 +1,205 @@
#!/bin/sh #!/bin/bash
# Copyright (C) 2019 Mellanox Technologies Ltd. # Copyright (C) 2019 Mellanox Technologies Ltd.
# Copyright (C) 2019 Michael Shych <michaelsh@mellanox.com> # Copyright (C) 2019 Michael Shych <michaelsh@mellanox.com>
# #
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
this_script=${ONIE_FWPKG_PROGRAM_NAME:-$(basename $(realpath $0))} this_script="$(basename $(realpath ${0}))"
lock_file="/var/run/${this_script%.*}.lock"
onie_mount=/mnt/onie-boot onie_mount=/mnt/onie-boot
onie_lib=/lib/onie
os_boot=/host os_boot=/host
onie_partition=
export ONIE_FWPKG_PROGRAM_NAME=$(basename $(realpath $0)) print_help() {
usage()
{
cat <<EOF cat <<EOF
update update
The 'update' command will reboot system to ONIE update mode The 'update' command will reboot system to ONIE update mode
and ONIE will perform automatically update of previously and ONIE will perform automatically update of previously
added (i.e. pending) FW (ONIE itself, BIOS or CPLD) image. added (i.e. pending) FW (ONIE itself, BIOS or CPLD) image.
EOF EOF
} }
enable_onie_access() enable_onie_access() {
{ if [[ ! -d "${onie_mount}" ]]; then
onie_partition=$(fdisk -l | grep "ONIE boot" | awk '{print $1}') mkdir ${onie_mount}
if [ ! -d $onie_mount ]; then fi
mkdir /mnt/onie-boot
fi if ! mountpoint -q "${onie_mount}"; then
mount $onie_partition /mnt/onie-boot mount LABEL="ONIE-BOOT" ${onie_mount}
if [ ! -e /lib/onie ]; then fi
ln -s /mnt/onie-boot/onie/tools/lib/onie /lib/onie
fi if [[ ! -e "${onie_lib}" ]]; then
PATH=/sbin:/usr/sbin:/bin:/usr/bin:$onie_mount/onie/tools/bin/ ln -s ${onie_mount}/onie/tools/lib/onie ${onie_lib}
export PATH fi
} }
clean_onie_access() disable_onie_access() {
{ if [[ -e "${onie_lib}" ]]; then
rm -f /lib/onie unlink ${onie_lib}
umount $onie_partition fi
if mountpoint -q "${onie_mount}"; then
umount -rf ${onie_mount}
fi
if [[ -d "${onie_mount}" ]]; then
rmdir ${onie_mount}
fi
} }
change_grub_boot_order() enable_onie_fw_update_mode() {
{ if [[ ! -f ${os_boot}/grub/grubenv || ! -f ${onie_mount}/grub/grubenv ]]; then
grub-editenv $os_boot/grub/grubenv set onie_entry=ONIE return 1
grub-editenv $onie_mount/grub/grubenv set onie_mode=update fi
return 0
register_terminate_handler
grub-editenv ${os_boot}/grub/grubenv set onie_entry="ONIE" || return $?
grub-editenv ${onie_mount}/grub/grubenv set onie_mode="update" || return $?
return 0
} }
show_pending() disable_onie_fw_update_mode() {
{ if [[ ! -f ${os_boot}/grub/grubenv || ! -f ${onie_mount}/grub/grubenv ]]; then
curr_dir=$(pwd) return 1
cd $onie_mount/onie/update/pending || return 0 fi
num=$(find . -type f | wc -l)
if [ $num -ge 1 ]; then
echo "Number of FW update pending files are: "$num
ls -l * | awk {'print $9" "$5" "$7"-"$6" "$8'}
else
echo "There is no pending files for FW update."
fi
cd $curr_dir
return $num grub-editenv ${os_boot}/grub/grubenv unset onie_entry || return $?
grub-editenv ${onie_mount}/grub/grubenv set onie_mode="install" || return $?
return 0
} }
show_pending() {
if [[ ! -d ${onie_mount}/onie/update/pending ]]; then
return 0
fi
num=$(find ${onie_mount}/onie/update/pending -type f | wc -l)
if [[ ${num} -ge 1 ]]; then
${onie_mount}/onie/tools/bin/onie-fwpkg show-pending
fi
return ${num}
}
system_reboot() {
echo "INFO: Rebooting in 5 sec..."
# Give user some time to cancel the update
sleep 5s
# Use SONiC reboot scenario
/usr/bin/reboot
}
terminate_handler() {
local -r _rc="$?"
local -r _sig="${1}"
echo
echo "WARNING: Interrupted by ${_sig}: disable ONIE firmware update mode"
echo
enable_onie_access
disable_onie_fw_update_mode
rc=$?
disable_onie_access
if [[ ${rc} -ne 0 ]]; then
echo "ERROR: failed to disable ONIE firmware update mode"
exit ${rc}
fi
exit ${_rc}
}
register_terminate_handler() {
trap "terminate_handler SIGHUP" SIGHUP
trap "terminate_handler SIGINT" SIGINT
trap "terminate_handler SIGQUIT" SIGQUIT
trap "terminate_handler SIGTERM" SIGTERM
}
unlock_handler() {
/usr/bin/flock -u ${1}
}
register_unlock_handler() {
trap "unlock_handler ${1}" EXIT
}
unlock_script_state_change() {
/usr/bin/flock -u ${lock_fd}
}
lock_script_state_change(){
exec {lock_fd}>${lock_file}
/usr/bin/flock -x ${lock_fd}
register_unlock_handler ${lock_fd}
}
# Multiprocessing synchronization
lock_script_state_change
# Process command arguments # Process command arguments
cmd=$1 cmd="${1}"
# optional argument
name="$2"
if [ -z "$cmd" ] ; then # Optional argument
# Default to 'show' if no command is specified. arg="${2}"
cmd="show"
if [[ -z "${cmd}" ]]; then
# Default to 'show' if no command is specified.
cmd="show"
fi fi
case "$cmd" in case "${cmd}" in
add | remove) add|remove)
[ -z "$name" ] && { if [[ -z "${arg}" ]]; then
echo "ERROR: This command requires a firmware update file name." echo "ERROR: This command requires a firmware update file name"
echo "Run '$this_script help' for complete details." echo "Run: '${this_script} help' for complete details"
exit 1 exit 1
} fi
;; ;;
update) update)
enable_onie_access enable_onie_access
show_pending show_pending
rc=$? rc=$?
if [ $rc -ne 0 ]; then if [[ ${rc} -ne 0 ]]; then
change_grub_boot_order enable_onie_fw_update_mode
rc=$? rc=$?
clean_onie_access disable_onie_access
exit $rc if [[ ${rc} -eq 0 ]]; then
else system_reboot
echo "ERROR: NO FW images for update." else
echo "Run: $this_script add <image> before update." echo "ERROR: failed to enable ONIE firmware update mode"
clean_onie_access exit ${rc}
exit 1 fi
fi else
;; echo "ERROR: No firmware images for update"
purge | show | show-results | show-log | show-pending | help) echo "Run: '${this_script} add <image>' before update"
;; disable_onie_access
*) exit 1
echo "Unknown command: $cmd" fi
exit 1 ;;
;; purge|show-pending|show-results|show|show-log|help)
;;
*)
echo "ERROR: Unknown command: ${cmd}"
exit 1
;;
esac esac
enable_onie_access enable_onie_access
$onie_mount/onie/tools/bin/onie-fwpkg "$@" ${onie_mount}/onie/tools/bin/onie-fwpkg "$@"
rc=$? rc=$?
if [ $cmd = "help" ]; then if [[ "${cmd}" = "help" ]]; then
usage print_help
fi fi
clean_onie_access disable_onie_access
exit $rc exit ${rc}

View File

@ -149,7 +149,9 @@ class Chassis(ChassisBase):
def initialize_components(self): def initialize_components(self):
# Initialize component list # Initialize component list
from sonic_platform.component import ComponentBIOS, ComponentCPLD from sonic_platform.component import ComponentONIE, ComponentSSD, ComponentBIOS, ComponentCPLD
self._component_list.append(ComponentONIE())
self._component_list.append(ComponentSSD())
self._component_list.append(ComponentBIOS()) self._component_list.append(ComponentBIOS())
self._component_list.extend(ComponentCPLD.get_component_list()) self._component_list.extend(ComponentCPLD.get_component_list())

View File

@ -6,19 +6,308 @@
# implementation of new platform api # implementation of new platform api
############################################################################# #############################################################################
from __future__ import print_function from __future__ import print_function
try: try:
from sonic_platform_base.component_base import ComponentBase
from sonic_device_util import get_machine_info
from glob import glob
import subprocess
import io
import os import os
import io
import re import re
import glob
import tempfile
import subprocess
import ConfigParser
from sonic_platform_base.component_base import ComponentBase
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
ZERO = '0'
NEWLINE = '\n' class MPFAManager(object):
MPFA_EXTENSION = '.mpfa'
MPFA_EXTRACT_COMMAND = 'tar xzf {} -C {}'
MPFA_CLEANUP_COMMAND = 'rm -rf {}'
def __init__(self, mpfa_path):
self.__mpfa_path = mpfa_path
self.__contents_path = None
self.__metadata = None
def __enter__(self):
self.extract()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.cleanup()
def __validate_path(self, mpfa_path):
if not os.path.isfile(mpfa_path):
raise RuntimeError("MPFA doesn't exist: path={}".format(mpfa_path))
name, ext = os.path.splitext(mpfa_path)
if ext != self.MPFA_EXTENSION:
raise RuntimeError("MPFA doesn't have valid extension: path={}".format(mpfa_path))
def __extract_contents(self, mpfa_path):
contents_path = tempfile.mkdtemp(prefix='mpfa-')
cmd = self.MPFA_EXTRACT_COMMAND.format(mpfa_path, contents_path)
subprocess.check_call(cmd.split())
self.__contents_path = contents_path
def __parse_metadata(self, contents_path):
metadata_path = os.path.join(contents_path, 'metadata.ini')
if not os.path.isfile(metadata_path):
raise RuntimeError("MPFA metadata doesn't exist: path={}".format(metadata_path))
cp = ConfigParser.ConfigParser()
with io.open(metadata_path, 'r') as metadata_ini:
cp.readfp(metadata_ini)
self.__metadata = cp
def extract(self):
if self.is_extracted():
return
self.__validate_path(self.__mpfa_path)
self.__extract_contents(self.__mpfa_path)
self.__parse_metadata(self.__contents_path)
def cleanup(self):
if os.path.exists(self.__contents_path):
cmd = self.MPFA_CLEANUP_COMMAND.format(self.__contents_path)
subprocess.check_call(cmd.split())
self.__contents_path = None
self.__metadata = None
def get_path(self):
return self.__contents_path
def get_metadata(self):
return self.__metadata
def is_extracted(self):
return self.__contents_path is not None and os.path.exists(self.__contents_path)
class ONIEUpdater(object):
ONIE_FW_UPDATE_CMD_ADD = '/usr/bin/mlnx-onie-fw-update.sh add {}'
ONIE_FW_UPDATE_CMD_REMOVE = '/usr/bin/mlnx-onie-fw-update.sh remove {}'
ONIE_FW_UPDATE_CMD_UPDATE = '/usr/bin/mlnx-onie-fw-update.sh update'
ONIE_FW_UPDATE_CMD_SHOW_PENDING = '/usr/bin/mlnx-onie-fw-update.sh show-pending'
ONIE_VERSION_PARSE_PATTERN = '([0-9]{4})\.([0-9]{2})-([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)'
ONIE_VERSION_BASE_PARSE_PATTERN = '([0-9]+)\.([0-9]+)\.([0-9]+)'
ONIE_VERSION_REQUIRED = '5.2.0016'
ONIE_VERSION_ATTR = 'onie_version'
ONIE_NO_PENDING_UPDATES_ATTR = 'No pending firmware updates present'
ONIE_IMAGE_INFO_COMMAND = '/bin/bash {} -q -i'
def __mount_onie_fs(self):
fs_mountpoint = '/mnt/onie-fs'
onie_path = '/lib/onie'
if os.path.lexists(onie_path) or os.path.exists(fs_mountpoint):
self.__umount_onie_fs()
cmd = "fdisk -l | grep 'ONIE boot' | awk '{print $1}'"
fs_path = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).rstrip('\n')
os.mkdir(fs_mountpoint)
cmd = "mount -n -r -t ext4 {} {}".format(fs_path, fs_mountpoint)
subprocess.check_call(cmd, shell=True)
fs_onie_path = os.path.join(fs_mountpoint, 'onie/tools/lib/onie')
os.symlink(fs_onie_path, onie_path)
return fs_mountpoint
def __umount_onie_fs(self):
fs_mountpoint = '/mnt/onie-fs'
onie_path = '/lib/onie'
if os.path.islink(onie_path):
os.unlink(onie_path)
if os.path.ismount(fs_mountpoint):
cmd = "umount -rf {}".format(fs_mountpoint)
subprocess.check_call(cmd, shell=True)
if os.path.exists(fs_mountpoint):
os.rmdir(fs_mountpoint)
def __stage_update(self, image_path):
cmd = self.ONIE_FW_UPDATE_CMD_ADD.format(image_path)
try:
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to stage firmware update: {}".format(str(e)))
def __unstage_update(self, image_path):
cmd = self.ONIE_FW_UPDATE_CMD_REMOVE.format(os.path.basename(image_path))
try:
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to unstage firmware update: {}".format(str(e)))
def __trigger_update(self):
cmd = self.ONIE_FW_UPDATE_CMD_UPDATE
try:
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to trigger firmware update: {}".format(str(e)))
def __is_update_staged(self, image_path):
cmd = self.ONIE_FW_UPDATE_CMD_SHOW_PENDING
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get pending firmware updates: {}".format(str(e)))
basename = os.path.basename(image_path)
for line in output.splitlines():
if line.startswith(basename):
return True
return False
def parse_onie_version(self, version, is_base=False):
onie_year = None
onie_month = None
onie_major = None
onie_minor = None
onie_release = None
onie_baudrate = None
if is_base:
pattern = self.ONIE_VERSION_BASE_PARSE_PATTERN
m = re.search(pattern, version)
if not m:
raise RuntimeError("Failed to parse ONIE version: pattern={}, version={}".format(pattern, version))
onie_major = m.group(1)
onie_minor = m.group(2)
onie_release = m.group(3)
return onie_year, onie_month, onie_major, onie_minor, onie_release, onie_baudrate
pattern = self.ONIE_VERSION_PARSE_PATTERN
m = re.search(pattern, version)
if not m:
raise RuntimeError("Failed to parse ONIE version: pattern={}, version={}".format(pattern, version))
onie_year = m.group(1)
onie_month = m.group(2)
onie_major = m.group(3)
onie_minor = m.group(4)
onie_release = m.group(5)
onie_baudrate = m.group(6)
return onie_year, onie_month, onie_major, onie_minor, onie_release, onie_baudrate
def get_onie_required_version(self):
return self.ONIE_VERSION_REQUIRED
def get_onie_version(self):
version = None
try:
fs_mountpoint = self.__mount_onie_fs()
machine_conf_path = os.path.join(fs_mountpoint, 'onie/grub/grub-machine.cfg')
with open(machine_conf_path, 'r') as machine_conf:
for line in machine_conf:
if line.startswith(self.ONIE_VERSION_ATTR):
items = line.rstrip('\n').split('=')
if len(items) != 2:
raise RuntimeError("Failed to parse ONIE info: line={}".format(line))
version = items[1]
break
if version is None:
raise RuntimeError("Failed to parse ONIE version")
finally:
self.__umount_onie_fs()
return version
def get_onie_firmware_info(self, image_path):
firmware_info = { }
try:
self.__mount_onie_fs()
cmd = self.ONIE_IMAGE_INFO_COMMAND.format(image_path)
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get ONIE firmware info: {}".format(str(e)))
for line in output.splitlines():
items = line.split('=')
if len(items) != 2:
raise RuntimeError("Failed to parse ONIE firmware info: line={}".format(line))
firmware_info[items[0]] = items[1]
finally:
self.__umount_onie_fs()
return firmware_info
def update_firmware(self, image_path):
cmd = self.ONIE_FW_UPDATE_CMD_SHOW_PENDING
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get pending firmware updates: {}".format(str(e)))
no_pending_updates = False
for line in output.splitlines():
if line.startswith(self.ONIE_NO_PENDING_UPDATES_ATTR):
no_pending_updates = True
break
if not no_pending_updates:
raise RuntimeError("Failed to complete firmware update: pending updates are present")
try:
self.__stage_update(image_path)
self.__trigger_update()
except:
if self.__is_update_staged(image_path):
self.__unstage_update(image_path)
raise
def is_non_onie_firmware_update_supported(self):
current_version = self.get_onie_version()
_, _, major1, minor1, release1, _ = self.parse_onie_version(current_version)
version1 = int("{}{}{}".format(major1, minor1, release1))
required_version = self.get_onie_required_version()
_, _, major2, minor2, release2, _ = self.parse_onie_version(required_version, True)
version2 = int("{}{}{}".format(major2, minor2, release2))
return version1 >= version2
class Component(ComponentBase): class Component(ComponentBase):
def __init__(self): def __init__(self):
@ -26,27 +315,12 @@ class Component(ComponentBase):
self.description = None self.description = None
self.image_ext_name = None self.image_ext_name = None
def get_name(self): def get_name(self):
"""
Retrieves the name of the component
Returns:
A string containing the name of the component
"""
return self.name return self.name
def get_description(self): def get_description(self):
"""
Retrieves the description of the component
Returns:
A string containing the description of the component
"""
return self.description return self.description
@staticmethod @staticmethod
def _read_generic_file(filename, len, ignore_errors=False): def _read_generic_file(filename, len, ignore_errors=False):
""" """
@ -63,7 +337,6 @@ class Component(ComponentBase):
return result return result
@staticmethod @staticmethod
def _get_command_result(cmdline): def _get_command_result(cmdline):
try: try:
@ -79,149 +352,246 @@ class Component(ComponentBase):
return result return result
def _check_file_validity(self, image_path): def _check_file_validity(self, image_path):
# check whether the image file exists
if not os.path.isfile(image_path): if not os.path.isfile(image_path):
print("ERROR: File {} doesn't exist or is not a file".format(image_path)) print("ERROR: File {} doesn't exist or is not a file".format(image_path))
return False return False
name_list = os.path.splitext(image_path)
if self.image_ext_name is not None: if self.image_ext_name is not None:
name_list = os.path.splitext(image_path)
if name_list[1] != self.image_ext_name: if name_list[1] != self.image_ext_name:
print("ERROR: Extend name of file {} is wrong. Image for {} should have extend name {}".format(image_path, self.name, self.image_ext_name)) print("ERROR: Extend name of file {} is wrong. Image for {} should have extend name {}".format(image_path, self.name, self.image_ext_name))
return False return False
else:
if name_list[1]:
print("ERROR: Extend name of file {} is wrong. Image for {} shouldn't have extension".format(image_path, self.name))
return False
return True return True
class ComponentONIE(Component):
COMPONENT_NAME = 'ONIE'
COMPONENT_DESCRIPTION = 'ONIE - Open Network Install Environment'
ONIE_IMAGE_VERSION_ATTR = 'image_version'
def __init__(self):
super(ComponentONIE, self).__init__()
self.name = self.COMPONENT_NAME
self.description = self.COMPONENT_DESCRIPTION
self.onie_updater = ONIEUpdater()
def __install_firmware(self, image_path):
if not self._check_file_validity(image_path):
return False
try:
print("INFO: Staging {} firmware update with ONIE updater".format(self.name))
self.onie_updater.update_firmware(image_path)
except Exception as e:
print("ERROR: Failed to update {} firmware: {}".format(self.name, str(e)))
return False
return True
def get_firmware_version(self):
return self.onie_updater.get_onie_version()
def get_available_firmware_version(self, image_path):
firmware_info = self.onie_updater.get_onie_firmware_info(image_path)
if self.ONIE_IMAGE_VERSION_ATTR not in firmware_info:
raise RuntimeError("Failed to get {} available firmware version".format(self.name))
return firmware_info[self.ONIE_IMAGE_VERSION_ATTR]
def get_firmware_update_notification(self, image_path):
return "Immediate cold reboot is required to complete {} firmware update".format(self.name)
def install_firmware(self, image_path):
return self.__install_firmware(image_path)
def update_firmware(self, image_path):
self.__install_firmware(image_path)
class ComponentSSD(Component):
COMPONENT_NAME = 'SSD'
COMPONENT_DESCRIPTION = 'SSD - Solid-State Drive'
COMPONENT_FIRMWARE_EXTENSION = '.pkg'
FIRMWARE_VERSION_ATTR = 'Firmware Version'
AVAILABLE_FIRMWARE_VERSION_ATTR = 'Available Firmware Version'
POWER_CYCLE_REQUIRED_ATTR = 'Power Cycle Required'
UPGRADE_REQUIRED_ATTR = 'Upgrade Required'
SSD_INFO_COMMAND = "/usr/bin/mlnx-ssd-fw-update.sh -q"
SSD_FIRMWARE_INFO_COMMAND = "/usr/bin/mlnx-ssd-fw-update.sh -q -i {}"
SSD_FIRMWARE_UPDATE_COMMAND = "/usr/bin/mlnx-ssd-fw-update.sh -y -u -i {}"
def __init__(self):
super(ComponentSSD, self).__init__()
self.name = self.COMPONENT_NAME
self.description = self.COMPONENT_DESCRIPTION
self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION
def __install_firmware(self, image_path):
if not self._check_file_validity(image_path):
return False
cmd = self.SSD_FIRMWARE_UPDATE_COMMAND.format(image_path)
try:
print("INFO: Installing {} firmware update".format(self.name))
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError as e:
print("ERROR: Failed to update {} firmware: {}".format(self.name, str(e)))
return False
return True
def get_firmware_version(self):
cmd = self.SSD_INFO_COMMAND
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get {} info: {}".format(self.name, str(e)))
for line in output.splitlines():
if line.startswith(self.FIRMWARE_VERSION_ATTR):
return line.split(':')[1].lstrip(' \t')
raise RuntimeError("Failed to parse {} version".format(self.name))
def get_available_firmware_version(self, image_path):
cmd = self.SSD_FIRMWARE_INFO_COMMAND.format(image_path)
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get {} firmware info: {}".format(self.name, str(e)))
current_firmware_version = None
available_firmware_version = None
upgrade_required = None
for line in output.splitlines():
if line.startswith(self.FIRMWARE_VERSION_ATTR):
current_firmware_version = line.split(':')[1].lstrip(' \t')
if line.startswith(self.AVAILABLE_FIRMWARE_VERSION_ATTR):
available_firmware_version = line.split(':')[1].lstrip(' \t')
if line.startswith(self.UPGRADE_REQUIRED_ATTR):
upgrade_required = line.split(':')[1].lstrip(' \t')
if upgrade_required is None or upgrade_required not in ['yes', 'no']:
raise RuntimeError("Failed to parse {} firmware upgrade status".format(self.name))
if upgrade_required == 'no':
if current_firmware_version is None:
raise RuntimeError("Failed to parse {} current firmware version".format(self.name))
return current_firmware_version
if available_firmware_version is None:
raise RuntimeError("Failed to parse {} available firmware version".format(self.name))
return available_firmware_version
def get_firmware_update_notification(self, image_path):
cmd = self.SSD_FIRMWARE_INFO_COMMAND.format(image_path)
try:
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get {} firmware info: {}".format(self.name, str(e)))
power_cycle_required = None
upgrade_required = None
for line in output.splitlines():
if line.startswith(self.POWER_CYCLE_REQUIRED_ATTR):
power_cycle_required = line.split(':')[1].lstrip(' \t')
if line.startswith(self.UPGRADE_REQUIRED_ATTR):
upgrade_required = line.split(':')[1].lstrip(' \t')
if upgrade_required is None or upgrade_required not in ['yes', 'no']:
raise RuntimeError("Failed to parse {} firmware upgrade status".format(self.name))
if upgrade_required == 'no':
return None
if power_cycle_required is None or power_cycle_required not in ['yes', 'no']:
raise RuntimeError("Failed to parse {} firmware power policy".format(self.name))
notification = None
if power_cycle_required == 'yes':
notification = "Immediate power cycle is required to complete {} firmware update".format(self.name)
return notification
def install_firmware(self, image_path):
return self.__install_firmware(image_path)
def update_firmware(self, image_path):
self.__install_firmware(image_path)
class ComponentBIOS(Component): class ComponentBIOS(Component):
COMPONENT_NAME = 'BIOS' COMPONENT_NAME = 'BIOS'
COMPONENT_DESCRIPTION = 'BIOS - Basic Input/Output System' COMPONENT_DESCRIPTION = 'BIOS - Basic Input/Output System'
COMPONENT_FIRMWARE_EXTENSION = '.rom' COMPONENT_FIRMWARE_EXTENSION = '.rom'
# To update BIOS requires the ONIE with version 5.2.0016 or upper BIOS_VERSION_COMMAND = 'dmidecode --oem-string 1'
ONIE_VERSION_PARSE_PATTERN = '[0-9]{4}\.[0-9]{2}-([0-9]+)\.([0-9]+)\.([0-9]+)'
ONIE_VERSION_MAJOR_OFFSET = 1
ONIE_VERSION_MINOR_OFFSET = 2
ONIE_VERSION_RELEASE_OFFSET = 3
ONIE_REQUIRED_MAJOR = '5'
ONIE_REQUIRED_MINOR = '2'
ONIE_REQUIRED_RELEASE = '0016'
BIOS_VERSION_PARSE_PATTERN = 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
BIOS_PENDING_UPDATE_PATTERN = '([0-9A-Za-z_]*.rom)[\s]*\|[\s]*bios_update'
ONIE_FW_UPDATE_CMD_ADD = '/usr/bin/mlnx-onie-fw-update.sh add {}'
ONIE_FW_UPDATE_CMD_REMOVE = '/usr/bin/mlnx-onie-fw-update.sh remove {}'
ONIE_FW_UPDATE_CMD_UPDATE = '/usr/bin/mlnx-onie-fw-update.sh update'
ONIE_FW_UPDATE_CMD_SHOW = '/usr/bin/mlnx-onie-fw-update.sh show-pending'
BIOS_QUERY_VERSION_COMMAND = 'dmidecode -t 11'
def __init__(self): def __init__(self):
super(ComponentBIOS, self).__init__()
self.name = self.COMPONENT_NAME self.name = self.COMPONENT_NAME
self.description = self.COMPONENT_DESCRIPTION self.description = self.COMPONENT_DESCRIPTION
self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION
self.onie_updater = ONIEUpdater()
def __install_firmware(self, image_path):
def get_firmware_version(self): if not self.onie_updater.is_non_onie_firmware_update_supported():
""" print("ERROR: ONIE {} or later is required".format(self.onie_updater.get_onie_required_version()))
Retrieves the firmware version of the component
Returns:
A string containing the firmware version of the component
BIOS version is retrieved via command 'dmidecode -t 11'
which should return result in the following convention
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.
Handle 0x0022, DMI type 11, 5 bytes
OEM Strings
String 1:*0ABZS017_02.02.002*
String 2: To Be Filled By O.E.M.
By using regular expression 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
we can extrace the version string which is marked with * in the above context
"""
try:
bios_ver_str = self._get_command_result(self.BIOS_QUERY_VERSION_COMMAND)
m = re.search(self.BIOS_VERSION_PARSE_PATTERN, bios_ver_str)
result = m.group(1)
except (AttributeError, RuntimeError) as e:
raise RuntimeError("Failed to parse BIOS version due to {}".format(repr(e)))
return result
def _check_onie_version(self):
# check ONIE version. To update ONIE requires version 5.2.0016 or later.
try:
machine_info = get_machine_info()
onie_version_string = machine_info['onie_version']
m = re.search(self.ONIE_VERSION_PARSE_PATTERN, onie_version_string)
onie_major = m.group(self.ONIE_VERSION_MAJOR_OFFSET)
onie_minor = m.group(self.ONIE_VERSION_MINOR_OFFSET)
onie_release = m.group(self.ONIE_VERSION_RELEASE_OFFSET)
except AttributeError as e:
print("ERROR: Failed to parse ONIE version by {} from {} due to {}".format(
self.ONIE_VERSION_PARSE_PATTERN, machine_conf, repr(e)))
return False return False
if onie_major < self.ONIE_REQUIRED_MAJOR or onie_minor < self.ONIE_REQUIRED_MINOR or onie_release < self.ONIE_REQUIRED_RELEASE:
print("ERROR: ONIE {}.{}.{} or later is required".format(self.ONIE_REQUIRED_MAJOR, self.ONIE_REQUIRED_MINOR, self.ONIE_REQUIRED_RELEASE))
return False
return True
def install_firmware(self, image_path):
"""
Installs firmware to the component
Args:
image_path: A string, path to firmware image
Returns:
A boolean, True if install was successful, False if not
"""
# check ONIE version requirement
if not self._check_onie_version():
return False
# check whether the file exists
if not self._check_file_validity(image_path): if not self._check_file_validity(image_path):
return False return False
# do the real work
try: try:
# check whether there has already been some images pending print("INFO: Staging {} firmware update with ONIE updater".format(self.name))
# if yes, remove them self.onie_updater.update_firmware(image_path)
result = self._get_command_result(self.ONIE_FW_UPDATE_CMD_SHOW)
pending_list = result.split("\n")
for pending in pending_list:
m = re.match(self.BIOS_PENDING_UPDATE_PATTERN, pending)
if m is not None:
pending_image = m.group(1)
self._get_command_result(self.ONIE_FW_UPDATE_CMD_REMOVE.format(pending_image))
print("WARNING: Image {} which is already pending to upgrade has been removed".format(pending_image))
result = subprocess.check_call(self.ONIE_FW_UPDATE_CMD_ADD.format(image_path).split())
if result:
return False
result = subprocess.check_call(self.ONIE_FW_UPDATE_CMD_UPDATE.split())
if result:
return False
except Exception as e: except Exception as e:
print("ERROR: Installing BIOS failed due to {}".format(repr(e))) print("ERROR: Failed to update {} firmware: {}".format(self.name, str(e)))
return False return False
print("INFO: Reboot is required to finish BIOS installation")
return True return True
def get_firmware_version(self):
cmd = self.BIOS_VERSION_COMMAND
try:
version = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).rstrip('\n')
except subprocess.CalledProcessError as e:
raise RuntimeError("Failed to get {} version: {}".format(self.name, str(e)))
return version
def get_available_firmware_version(self, image_path):
raise NotImplementedError("{} component doesn't support firmware version query".format(self.name))
def get_firmware_update_notification(self, image_path):
return "Immediate cold reboot is required to complete {} firmware update".format(self.name)
def install_firmware(self, image_path):
return self.__install_firmware(image_path)
def update_firmware(self, image_path):
self.__install_firmware(image_path)
class ComponentCPLD(Component): class ComponentCPLD(Component):
@ -229,6 +599,9 @@ class ComponentCPLD(Component):
COMPONENT_DESCRIPTION = 'CPLD - Complex Programmable Logic Device' COMPONENT_DESCRIPTION = 'CPLD - Complex Programmable Logic Device'
COMPONENT_FIRMWARE_EXTENSION = '.vme' COMPONENT_FIRMWARE_EXTENSION = '.vme'
MST_DEVICE_PATH = '/dev/mst'
MST_DEVICE_PATTERN = 'mt[0-9]*_pci_cr0'
CPLD_NUMBER_FILE = '/var/run/hw-management/config/cpld_num' CPLD_NUMBER_FILE = '/var/run/hw-management/config/cpld_num'
CPLD_PART_NUMBER_FILE = '/var/run/hw-management/system/cpld{}_pn' CPLD_PART_NUMBER_FILE = '/var/run/hw-management/system/cpld{}_pn'
CPLD_VERSION_FILE = '/var/run/hw-management/system/cpld{}_version' CPLD_VERSION_FILE = '/var/run/hw-management/system/cpld{}_version'
@ -239,28 +612,54 @@ class ComponentCPLD(Component):
CPLD_VERSION_MAX_LENGTH = 2 CPLD_VERSION_MAX_LENGTH = 2
CPLD_VERSION_MINOR_MAX_LENGTH = 2 CPLD_VERSION_MINOR_MAX_LENGTH = 2
CPLD_PART_NUMBER_DEFAULT = ZERO CPLD_PART_NUMBER_DEFAULT = '0'
CPLD_VERSION_MINOR_DEFAULT = ZERO CPLD_VERSION_MINOR_DEFAULT = '0'
CPLD_UPDATE_COMMAND = 'cpldupdate --dev {} --print-progress {}' CPLD_FIRMWARE_UPDATE_COMMAND = 'cpldupdate --dev {} --print-progress {}'
MST_DEVICE_PATTERN = '/dev/mst/mt[0-9]*_pci_cr0'
def __init__(self, idx): def __init__(self, idx):
super(ComponentCPLD, self).__init__()
self.idx = idx self.idx = idx
self.name = self.COMPONENT_NAME.format(self.idx) self.name = self.COMPONENT_NAME.format(self.idx)
self.description = self.COMPONENT_DESCRIPTION self.description = self.COMPONENT_DESCRIPTION
self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION
def __get_mst_device(self):
if not os.path.exists(self.MST_DEVICE_PATH):
print("ERROR: mst driver is not loaded")
return None
pattern = os.path.join(self.MST_DEVICE_PATH, self.MST_DEVICE_PATTERN)
mst_dev_list = glob.glob(pattern)
if not mst_dev_list or len(mst_dev_list) != 1:
devices = str(os.listdir(self.MST_DEVICE_PATH))
print("ERROR: Failed to get mst device: pattern={}, devices={}".format(pattern, devices))
return None
return mst_dev_list[0]
def __install_firmware(self, image_path):
if not self._check_file_validity(image_path):
return False
mst_dev = self.__get_mst_device()
if mst_dev is None:
return False
cmd = self.CPLD_FIRMWARE_UPDATE_COMMAND.format(mst_dev, image_path)
try:
print("INFO: Installing {} firmware update: path={}".format(self.name, image_path))
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError as e:
print("ERROR: Failed to update {} firmware: {}".format(self.name, str(e)))
return False
return True
def get_firmware_version(self): def get_firmware_version(self):
"""
Retrieves the firmware version of the component
Returns:
A string containing the firmware version of the component
"""
part_number_file = self.CPLD_PART_NUMBER_FILE.format(self.idx) part_number_file = self.CPLD_PART_NUMBER_FILE.format(self.idx)
version_file = self.CPLD_VERSION_FILE.format(self.idx) version_file = self.CPLD_VERSION_FILE.format(self.idx)
version_minor_file = self.CPLD_VERSION_MINOR_FILE.format(self.idx) version_minor_file = self.CPLD_VERSION_MINOR_FILE.format(self.idx)
@ -275,75 +674,52 @@ class ComponentCPLD(Component):
if version_minor is None: if version_minor is None:
version_minor = self.CPLD_VERSION_MINOR_DEFAULT version_minor = self.CPLD_VERSION_MINOR_DEFAULT
part_number = part_number.rstrip(NEWLINE).zfill(self.CPLD_PART_NUMBER_MAX_LENGTH) part_number = part_number.rstrip('\n').zfill(self.CPLD_PART_NUMBER_MAX_LENGTH)
version = version.rstrip(NEWLINE).zfill(self.CPLD_VERSION_MAX_LENGTH) version = version.rstrip('\n').zfill(self.CPLD_VERSION_MAX_LENGTH)
version_minor = version_minor.rstrip(NEWLINE).zfill(self.CPLD_VERSION_MINOR_MAX_LENGTH) version_minor = version_minor.rstrip('\n').zfill(self.CPLD_VERSION_MINOR_MAX_LENGTH)
return "CPLD{}_REV{}{}".format(part_number, version, version_minor) return "CPLD{}_REV{}{}".format(part_number, version, version_minor)
def get_available_firmware_version(self, image_path):
with MPFAManager(image_path) as mpfa:
if not mpfa.get_metadata().has_option('version', self.name):
raise RuntimeError("Failed to get {} available firmware version".format(self.name))
def _get_mst_device(self): return mpfa.get_metadata().get('version', self.name)
mst_dev_list = glob(self.MST_DEVICE_PATTERN)
if mst_dev_list is None or len(mst_dev_list) != 1:
return None
return mst_dev_list
def get_firmware_update_notification(self, image_path):
name, ext = os.path.splitext(os.path.basename(image_path))
if ext == self.COMPONENT_FIRMWARE_EXTENSION:
return "Power cycle (with 30 sec delay) or refresh image is required to complete {} firmware update".format(self.name)
return "Immediate power cycle is required to complete {} firmware update".format(self.name)
def install_firmware(self, image_path): def install_firmware(self, image_path):
""" return self.__install_firmware(image_path)
Installs firmware to the component
Args: def update_firmware(self, image_path):
image_path: A string, path to firmware image with MPFAManager(image_path) as mpfa:
if not mpfa.get_metadata().has_option('firmware', 'burn'):
raise RuntimeError("Failed to get {} burn firmware".format(self.name))
if not mpfa.get_metadata().has_option('firmware', 'refresh'):
raise RuntimeError("Failed to get {} refresh firmware".format(self.name))
Returns: burn_firmware = mpfa.get_metadata().get('firmware', 'burn')
A boolean, True if install was successful, False if not refresh_firmware = mpfa.get_metadata().get('firmware', 'refresh')
Details: print("INFO: Processing {} burn file: firmware install".format(self.name))
The command "cpldupdate" is provided to install CPLD. There are two ways to do it: if not self.__install_firmware(os.path.join(mpfa.get_path(), burn_firmware)):
1. To burn CPLD via gpio, which is faster but only supported on new systems, like SN3700, ... return
2. To install CPLD via firmware, which is slower but supported on older systems.
This also requires the mst device designated.
"cpldupdate --dev <devname> <vme_file>" has the logic of testing whether to update via gpio is supported,
and if so then go this way, otherwise tries updating software via fw. So we take advantage of it to update the CPLD.
By doing so we don't have to mind whether to update via gpio supported, which belongs to hardware details.
So the procedure should be:
1. Test whether the file exists
2. Fetch the mst device name
3. Update CPLD via executing "cpldupdate --dev <devname> <vme_file>"
4. Check the result
"""
# check whether the image file exists
if not self._check_file_validity(image_path):
return False
mst_dev_list = self._get_mst_device()
if mst_dev_list is None:
print("ERROR: Failed to get mst device which is required for CPLD updating or multiple device files matched")
return False
cmdline = self.CPLD_UPDATE_COMMAND.format(mst_dev_list[0], image_path)
success_flag = False
try:
subprocess.check_call(cmdline, stderr=subprocess.STDOUT, shell=True)
success_flag = True
except subprocess.CalledProcessError as e:
print("ERROR: Failed to upgrade CPLD: rc={}".format(e.returncode))
if success_flag:
print("INFO: Refresh or power cycle is required to finish CPLD installation")
return success_flag
print("INFO: Processing {} refresh file: firmware update".format(self.name))
self.__install_firmware(os.path.join(mpfa.get_path(), refresh_firmware))
@classmethod @classmethod
def get_component_list(cls): def get_component_list(cls):
component_list = [ ] component_list = [ ]
cpld_number = cls._read_generic_file(cls.CPLD_NUMBER_FILE, cls.CPLD_NUMBER_MAX_LENGTH) cpld_number = cls._read_generic_file(cls.CPLD_NUMBER_FILE, cls.CPLD_NUMBER_MAX_LENGTH)
cpld_number = cpld_number.rstrip(NEWLINE) cpld_number = cpld_number.rstrip('\n')
for cpld_idx in xrange(1, int(cpld_number) + 1): for cpld_idx in xrange(1, int(cpld_number) + 1):
component_list.append(cls(cpld_idx)) component_list.append(cls(cpld_idx))

View File

@ -35,7 +35,7 @@
#= Global variable # #= Global variable #
#= #=
#===== #=====
VERSION="1.3" VERSION="1.5"
#===== #=====
SWITCH_SSD_DEV="/dev/sda" SWITCH_SSD_DEV="/dev/sda"
UTIL_TITLE="This is MLNX SSD firmware update utility to read and write SSD FW. Version ${VERSION}" UTIL_TITLE="This is MLNX SSD firmware update utility to read and write SSD FW. Version ${VERSION}"
@ -79,6 +79,8 @@ function init_script() {
else else
LOGGER_UTIL=$FALSE LOGGER_UTIL=$FALSE
fi fi
export LC_ALL=
export LANG="en_US.UTF-8"
} }
#==============================================================================# #==============================================================================#
@ -415,7 +417,7 @@ function check_package_signing() {
LOG_MSG "checksum_signed_file: ${checksum_signed_file}" ${DEBUG_MSG} LOG_MSG "checksum_signed_file: ${checksum_signed_file}" ${DEBUG_MSG}
LOG_MSG "checksum_unsigned_file: ${checksum_unsigned_file}" ${DEBUG_MSG} LOG_MSG "checksum_unsigned_file: ${checksum_unsigned_file}" ${DEBUG_MSG}
gpg --keyring "$public_cert_file" --verify "$checksum_signed_file" "$checksum_unsigned_file" > /dev/null 2>&1 gpg --ignore-time-conflict --keyring "$public_cert_file" --verify "$checksum_signed_file" "$checksum_unsigned_file" > /dev/null 2>&1
[ $? -ne 0 ] && LOG_MSG_AND_EXIT "Error: fault package signing." [ $? -ne 0 ] && LOG_MSG_AND_EXIT "Error: fault package signing."
LOG_MSG "cd into: ${package_path}" ${DEBUG_MSG} LOG_MSG "cd into: ${package_path}" ${DEBUG_MSG}
@ -559,7 +561,7 @@ function print_ssd_info() {
LOG_MSG "Device Model\t : $SSD_DEVICE_MODEL" LOG_MSG "Device Model\t : $SSD_DEVICE_MODEL"
LOG_MSG "Serial Number\t : $SSD_SERIAL" LOG_MSG "Serial Number\t : $SSD_SERIAL"
LOG_MSG "User Capacity\t : $SSD_SIZE GB" LOG_MSG "User Capacity\t : $SSD_SIZE GB"
LOG_MSG "Firmware Version : $SSD_FW_VER" LOG_MSG "Firmware Version : $SSD_FW_VER"
fi fi
} }
@ -695,7 +697,7 @@ elif [ $ARG_UPDATE_FLAG == $TRUE ]; then
else else
LOG_MSG "SSD FW update completed successfully." LOG_MSG "SSD FW update completed successfully."
if [ $ARG_POWER_CYCLE_FLAG == $TRUE ]; then if [[ "yes" == "$power_policy" || $ARG_POWER_CYCLE_FLAG == $TRUE ]]; then
LOG_MSG "Execute power cycle..." LOG_MSG "Execute power cycle..."
sleep 1 sleep 1
sync sync
@ -715,7 +717,7 @@ elif [ $ARG_UPDATE_FLAG == $TRUE ]; then
fi fi
done done
if [ $UPDATE_DONE == $FALSE ]; then if [ $UPDATE_DONE == $FALSE ]; then
LOG_MSG "SSD FW upgrade not require, based on given package latest version is in used." LOG_MSG "SSD FW upgrade is not required, latest version based on given package is in use."
print_ssd_info "no" print_ssd_info "no"
fi fi