[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,20 +1,18 @@
#!/bin/sh
#!/bin/bash
# Copyright (C) 2019 Mellanox Technologies Ltd.
# Copyright (C) 2019 Michael Shych <michaelsh@mellanox.com>
#
# 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_lib=/lib/onie
os_boot=/host
onie_partition=
export ONIE_FWPKG_PROGRAM_NAME=$(basename $(realpath $0))
usage()
{
print_help() {
cat <<EOF
update
The 'update' command will reboot system to ONIE update mode
@ -24,97 +22,184 @@ cat <<EOF
EOF
}
enable_onie_access()
{
onie_partition=$(fdisk -l | grep "ONIE boot" | awk '{print $1}')
if [ ! -d $onie_mount ]; then
mkdir /mnt/onie-boot
enable_onie_access() {
if [[ ! -d "${onie_mount}" ]]; then
mkdir ${onie_mount}
fi
mount $onie_partition /mnt/onie-boot
if [ ! -e /lib/onie ]; then
ln -s /mnt/onie-boot/onie/tools/lib/onie /lib/onie
if ! mountpoint -q "${onie_mount}"; then
mount LABEL="ONIE-BOOT" ${onie_mount}
fi
if [[ ! -e "${onie_lib}" ]]; then
ln -s ${onie_mount}/onie/tools/lib/onie ${onie_lib}
fi
PATH=/sbin:/usr/sbin:/bin:/usr/bin:$onie_mount/onie/tools/bin/
export PATH
}
clean_onie_access()
{
rm -f /lib/onie
umount $onie_partition
disable_onie_access() {
if [[ -e "${onie_lib}" ]]; then
unlink ${onie_lib}
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()
{
grub-editenv $os_boot/grub/grubenv set onie_entry=ONIE
grub-editenv $onie_mount/grub/grubenv set onie_mode=update
enable_onie_fw_update_mode() {
if [[ ! -f ${os_boot}/grub/grubenv || ! -f ${onie_mount}/grub/grubenv ]]; then
return 1
fi
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()
{
curr_dir=$(pwd)
cd $onie_mount/onie/update/pending || return 0
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."
disable_onie_fw_update_mode() {
if [[ ! -f ${os_boot}/grub/grubenv || ! -f ${onie_mount}/grub/grubenv ]]; then
return 1
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
}
# Process command arguments
cmd=$1
# optional argument
name="$2"
show_pending() {
if [[ ! -d ${onie_mount}/onie/update/pending ]]; then
return 0
fi
if [ -z "$cmd" ] ; then
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
cmd="${1}"
# Optional argument
arg="${2}"
if [[ -z "${cmd}" ]]; then
# Default to 'show' if no command is specified.
cmd="show"
fi
case "$cmd" in
add | remove)
[ -z "$name" ] && {
echo "ERROR: This command requires a firmware update file name."
echo "Run '$this_script help' for complete details."
case "${cmd}" in
add|remove)
if [[ -z "${arg}" ]]; then
echo "ERROR: This command requires a firmware update file name"
echo "Run: '${this_script} help' for complete details"
exit 1
}
fi
;;
update)
enable_onie_access
show_pending
rc=$?
if [ $rc -ne 0 ]; then
change_grub_boot_order
if [[ ${rc} -ne 0 ]]; then
enable_onie_fw_update_mode
rc=$?
clean_onie_access
exit $rc
disable_onie_access
if [[ ${rc} -eq 0 ]]; then
system_reboot
else
echo "ERROR: NO FW images for update."
echo "Run: $this_script add <image> before update."
clean_onie_access
echo "ERROR: failed to enable ONIE firmware update mode"
exit ${rc}
fi
else
echo "ERROR: No firmware images for update"
echo "Run: '${this_script} add <image>' before update"
disable_onie_access
exit 1
fi
;;
purge | show | show-results | show-log | show-pending | help)
purge|show-pending|show-results|show|show-log|help)
;;
*)
echo "Unknown command: $cmd"
echo "ERROR: Unknown command: ${cmd}"
exit 1
;;
esac
enable_onie_access
$onie_mount/onie/tools/bin/onie-fwpkg "$@"
${onie_mount}/onie/tools/bin/onie-fwpkg "$@"
rc=$?
if [ $cmd = "help" ]; then
usage
if [[ "${cmd}" = "help" ]]; then
print_help
fi
clean_onie_access
disable_onie_access
exit $rc
exit ${rc}

View File

@ -149,7 +149,9 @@ class Chassis(ChassisBase):
def initialize_components(self):
# 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.extend(ComponentCPLD.get_component_list())

View File

@ -6,19 +6,308 @@
# implementation of new platform api
#############################################################################
from __future__ import print_function
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 io
import re
import glob
import tempfile
import subprocess
import ConfigParser
from sonic_platform_base.component_base import ComponentBase
except ImportError as e:
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):
def __init__(self):
@ -26,27 +315,12 @@ class Component(ComponentBase):
self.description = None
self.image_ext_name = None
def get_name(self):
"""
Retrieves the name of the component
Returns:
A string containing the name of the component
"""
return self.name
def get_description(self):
"""
Retrieves the description of the component
Returns:
A string containing the description of the component
"""
return self.description
@staticmethod
def _read_generic_file(filename, len, ignore_errors=False):
"""
@ -63,7 +337,6 @@ class Component(ComponentBase):
return result
@staticmethod
def _get_command_result(cmdline):
try:
@ -79,149 +352,246 @@ class Component(ComponentBase):
return result
def _check_file_validity(self, image_path):
# check whether the image file exists
if not os.path.isfile(image_path):
print("ERROR: File {} doesn't exist or is not a file".format(image_path))
return False
if self.image_ext_name is not None:
name_list = os.path.splitext(image_path)
if self.image_ext_name is not None:
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))
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
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):
COMPONENT_NAME = 'BIOS'
COMPONENT_DESCRIPTION = 'BIOS - Basic Input/Output System'
COMPONENT_FIRMWARE_EXTENSION = '.rom'
# To update BIOS requires the ONIE with version 5.2.0016 or upper
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'
BIOS_VERSION_COMMAND = 'dmidecode --oem-string 1'
def __init__(self):
super(ComponentBIOS, self).__init__()
self.name = self.COMPONENT_NAME
self.description = self.COMPONENT_DESCRIPTION
self.image_ext_name = self.COMPONENT_FIRMWARE_EXTENSION
self.onie_updater = ONIEUpdater()
def get_firmware_version(self):
"""
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)))
def __install_firmware(self, image_path):
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()))
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):
return False
# do the real work
try:
# check whether there has already been some images pending
# if yes, remove them
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
print("INFO: Staging {} firmware update with ONIE updater".format(self.name))
self.onie_updater.update_firmware(image_path)
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
print("INFO: Reboot is required to finish BIOS installation")
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):
@ -229,6 +599,9 @@ class ComponentCPLD(Component):
COMPONENT_DESCRIPTION = 'CPLD - Complex Programmable Logic Device'
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_PART_NUMBER_FILE = '/var/run/hw-management/system/cpld{}_pn'
CPLD_VERSION_FILE = '/var/run/hw-management/system/cpld{}_version'
@ -239,28 +612,54 @@ class ComponentCPLD(Component):
CPLD_VERSION_MAX_LENGTH = 2
CPLD_VERSION_MINOR_MAX_LENGTH = 2
CPLD_PART_NUMBER_DEFAULT = ZERO
CPLD_VERSION_MINOR_DEFAULT = ZERO
CPLD_PART_NUMBER_DEFAULT = '0'
CPLD_VERSION_MINOR_DEFAULT = '0'
CPLD_UPDATE_COMMAND = 'cpldupdate --dev {} --print-progress {}'
MST_DEVICE_PATTERN = '/dev/mst/mt[0-9]*_pci_cr0'
CPLD_FIRMWARE_UPDATE_COMMAND = 'cpldupdate --dev {} --print-progress {}'
def __init__(self, idx):
super(ComponentCPLD, self).__init__()
self.idx = idx
self.name = self.COMPONENT_NAME.format(self.idx)
self.description = self.COMPONENT_DESCRIPTION
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):
"""
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)
version_file = self.CPLD_VERSION_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:
version_minor = self.CPLD_VERSION_MINOR_DEFAULT
part_number = part_number.rstrip(NEWLINE).zfill(self.CPLD_PART_NUMBER_MAX_LENGTH)
version = version.rstrip(NEWLINE).zfill(self.CPLD_VERSION_MAX_LENGTH)
version_minor = version_minor.rstrip(NEWLINE).zfill(self.CPLD_VERSION_MINOR_MAX_LENGTH)
part_number = part_number.rstrip('\n').zfill(self.CPLD_PART_NUMBER_MAX_LENGTH)
version = version.rstrip('\n').zfill(self.CPLD_VERSION_MAX_LENGTH)
version_minor = version_minor.rstrip('\n').zfill(self.CPLD_VERSION_MINOR_MAX_LENGTH)
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):
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
return mpfa.get_metadata().get('version', self.name)
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):
"""
Installs firmware to the component
return self.__install_firmware(image_path)
Args:
image_path: A string, path to firmware image
def update_firmware(self, image_path):
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:
A boolean, True if install was successful, False if not
burn_firmware = mpfa.get_metadata().get('firmware', 'burn')
refresh_firmware = mpfa.get_metadata().get('firmware', 'refresh')
Details:
The command "cpldupdate" is provided to install CPLD. There are two ways to do it:
1. To burn CPLD via gpio, which is faster but only supported on new systems, like SN3700, ...
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 {} burn file: firmware install".format(self.name))
if not self.__install_firmware(os.path.join(mpfa.get_path(), burn_firmware)):
return
print("INFO: Processing {} refresh file: firmware update".format(self.name))
self.__install_firmware(os.path.join(mpfa.get_path(), refresh_firmware))
@classmethod
def get_component_list(cls):
component_list = [ ]
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):
component_list.append(cls(cpld_idx))

View File

@ -35,7 +35,7 @@
#= Global variable #
#=
#=====
VERSION="1.3"
VERSION="1.5"
#=====
SWITCH_SSD_DEV="/dev/sda"
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
LOGGER_UTIL=$FALSE
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_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."
LOG_MSG "cd into: ${package_path}" ${DEBUG_MSG}
@ -695,7 +697,7 @@ elif [ $ARG_UPDATE_FLAG == $TRUE ]; then
else
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..."
sleep 1
sync
@ -715,7 +717,7 @@ elif [ $ARG_UPDATE_FLAG == $TRUE ]; then
fi
done
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"
fi