[Mellanox] Fix issue: PSU model/serial/revision info should be updated after replacing PSU (#9040)

This commit is contained in:
Junchao-Mellanox 2021-11-01 12:27:09 +08:00 committed by GitHub
parent ada705050d
commit 48d412d49f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 156 additions and 60 deletions

View File

@ -13,6 +13,7 @@ try:
from sonic_platform.fan import Fan from sonic_platform.fan import Fan
from .led import PsuLed, SharedLed, ComponentFaultyIndicator from .led import PsuLed, SharedLed, ComponentFaultyIndicator
from .device_data import DEVICE_DATA from .device_data import DEVICE_DATA
from .vpd_parser import VpdParser
except ImportError as e: except ImportError as e:
raise ImportError (str(e) + "- required module not found") raise ImportError (str(e) + "- required module not found")
@ -31,7 +32,7 @@ SN_VPD_FIELD = "SN_VPD_FIELD"
PN_VPD_FIELD = "PN_VPD_FIELD" PN_VPD_FIELD = "PN_VPD_FIELD"
REV_VPD_FIELD = "REV_VPD_FIELD" REV_VPD_FIELD = "REV_VPD_FIELD"
# in most platforms the file psuX_curr, psuX_volt and psuX_power contain current, voltage and power data respectively. # in most platforms the file psuX_curr, psuX_volt and psuX_power contain current, voltage and power data respectively.
# but there are exceptions which will be handled by the following dictionary # but there are exceptions which will be handled by the following dictionary
platform_dict_psu = {'x86_64-mlnx_msn3420-r0': 1, 'x86_64-mlnx_msn3700-r0': 1, 'x86_64-mlnx_msn3700c-r0': 1, platform_dict_psu = {'x86_64-mlnx_msn3420-r0': 1, 'x86_64-mlnx_msn3700-r0': 1, 'x86_64-mlnx_msn3700c-r0': 1,
@ -88,30 +89,10 @@ class Psu(PsuBase):
self.psu_data = DEVICE_DATA[platform]['psus'] self.psu_data = DEVICE_DATA[platform]['psus']
psu_vpd = filemap[PSU_VPD] psu_vpd = filemap[PSU_VPD]
self.model = "N/A"
self.serial = "N/A"
self.rev = "N/A"
if psu_vpd is not None: if psu_vpd is not None:
self.psu_vpd = os.path.join(self.psu_path, psu_vpd.format(self.index)) self.vpd_parser = VpdParser(os.path.join(self.psu_path, psu_vpd.format(self.index)))
self.vpd_data = self._read_vpd_file(self.psu_vpd) else:
self.vpd_parser = None
if PN_VPD_FIELD in self.vpd_data:
self.model = self.vpd_data[PN_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} model number: No key {} in VPD {}".format(self.index, PN_VPD_FIELD, self.psu_vpd))
if SN_VPD_FIELD in self.vpd_data:
self.serial = self.vpd_data[SN_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} serial number: No key {} in VPD {}".format(self.index, SN_VPD_FIELD, self.psu_vpd))
if REV_VPD_FIELD in self.vpd_data:
self.rev = self.vpd_data[REV_VPD_FIELD]
else:
logger.log_error("Fail to read PSU{} serial number: No key {} in VPD {}".format(self.index, REV_VPD_FIELD, self.psu_vpd))
else:
logger.log_info("Not reading PSU{} VPD data: Platform is fixed".format(self.index)) logger.log_info("Not reading PSU{} VPD data: Platform is fixed".format(self.index))
if not self.psu_data['hot_swappable']: if not self.psu_data['hot_swappable']:
@ -162,24 +143,6 @@ class Psu(PsuBase):
return self._name return self._name
def _read_vpd_file(self, filename):
"""
Read a vpd file parsed from eeprom with keys and values.
Returns a dictionary.
"""
result = {}
try:
if not os.path.exists(filename):
return result
with open(filename, 'r') as fileobj:
for line in fileobj.readlines():
key, val = line.split(":")
result[key.strip()] = val.strip()
except Exception as e:
logger.log_error("Fail to read VPD file {} due to {}".format(filename, repr(e)))
return result
def _read_generic_file(self, filename, len): def _read_generic_file(self, filename, len):
""" """
Read a generic file, returns the contents of the file Read a generic file, returns the contents of the file
@ -202,7 +165,7 @@ class Psu(PsuBase):
Returns: Returns:
string: Model/part number of device string: Model/part number of device
""" """
return self.model return 'N/A' if self.vpd_parser is None else self.vpd_parser.get_model()
def get_serial(self): def get_serial(self):
@ -212,7 +175,7 @@ class Psu(PsuBase):
Returns: Returns:
string: Serial number of device string: Serial number of device
""" """
return self.serial return 'N/A' if self.vpd_parser is None else self.vpd_parser.get_serial()
def get_revision(self): def get_revision(self):
@ -222,7 +185,7 @@ class Psu(PsuBase):
Returns: Returns:
string: Revision value of device string: Revision value of device
""" """
return self.rev return 'N/A' if self.vpd_parser is None else self.vpd_parser.get_revision()
def get_powergood_status(self): def get_powergood_status(self):
@ -256,8 +219,8 @@ class Psu(PsuBase):
Retrieves current PSU voltage output Retrieves current PSU voltage output
Returns: Returns:
A float number, the output voltage in volts, A float number, the output voltage in volts,
e.g. 12.1 e.g. 12.1
""" """
if self.psu_voltage is not None and self.get_powergood_status(): if self.psu_voltage is not None and self.get_powergood_status():
voltage = self._read_generic_file(self.psu_voltage, 0) voltage = self._read_generic_file(self.psu_voltage, 0)
@ -327,7 +290,7 @@ class Psu(PsuBase):
Gets the power available status Gets the power available status
Returns: Returns:
True if power is present and power on. True if power is present and power on.
False and "absence of PSU" if power is not present. False and "absence of PSU" if power is not present.
False and "absence of power" if power is present but not power on. False and "absence of power" if power is present but not power on.
""" """
@ -366,7 +329,7 @@ class Psu(PsuBase):
Returns: Returns:
A float number of current temperature in Celsius up to nearest thousandth A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125 of one degree Celsius, e.g. 30.125
""" """
if self.psu_temp is not None and self.get_powergood_status(): if self.psu_temp is not None and self.get_powergood_status():
try: try:
@ -374,7 +337,7 @@ class Psu(PsuBase):
return float(temp) / 1000 return float(temp) / 1000
except Exception as e: except Exception as e:
logger.log_info("Fail to get temperature for PSU {} due to - {}".format(self._name, repr(e))) logger.log_info("Fail to get temperature for PSU {} due to - {}".format(self._name, repr(e)))
return None return None
def get_temperature_high_threshold(self): def get_temperature_high_threshold(self):
@ -391,7 +354,7 @@ class Psu(PsuBase):
return float(temp_threshold) / 1000 return float(temp_threshold) / 1000
except Exception as e: except Exception as e:
logger.log_info("Fail to get temperature threshold for PSU {} due to - {}".format(self._name, repr(e))) logger.log_info("Fail to get temperature threshold for PSU {} due to - {}".format(self._name, repr(e)))
return None return None
def get_voltage_high_threshold(self): def get_voltage_high_threshold(self):
@ -399,8 +362,8 @@ class Psu(PsuBase):
Retrieves the high threshold PSU voltage output Retrieves the high threshold PSU voltage output
Returns: Returns:
A float number, the high threshold output voltage in volts, A float number, the high threshold output voltage in volts,
e.g. 12.1 e.g. 12.1
""" """
# hw-management doesn't expose those sysfs for now # hw-management doesn't expose those sysfs for now
raise NotImplementedError raise NotImplementedError
@ -410,8 +373,8 @@ class Psu(PsuBase):
Retrieves the low threshold PSU voltage output Retrieves the low threshold PSU voltage output
Returns: Returns:
A float number, the low threshold output voltage in volts, A float number, the low threshold output voltage in volts,
e.g. 12.1 e.g. 12.1
""" """
# hw-management doesn't expose those sysfs for now # hw-management doesn't expose those sysfs for now
raise NotImplementedError raise NotImplementedError

View File

@ -1,10 +1,37 @@
import functools import functools
import subprocess import subprocess
from sonic_py_common.logger import Logger
logger = Logger()
# flags to indicate whether this process is running in docker or host # flags to indicate whether this process is running in docker or host
_is_host = None _is_host = None
def read_from_file(file_path, target_type, default='', raise_exception=False, log_func=logger.log_error):
"""
Read content from file and convert to target type
:param file_path: File path
:param target_type: target type
:param default: Default return value if any exception occur
:param raise_exception: Raise exception to caller if True else just return default value
:param log_func: function to log the error
:return: String content of the file
"""
try:
with open(file_path, 'r') as f:
value = target_type(f.read().strip())
except (ValueError, IOError) as e:
if log_func:
log_func('Failed to read from file {} - {}'.format(file_path, repr(e)))
if not raise_exception:
value = default
else:
raise e
return value
def read_str_from_file(file_path, default='', raise_exception=False): def read_str_from_file(file_path, default='', raise_exception=False):
""" """
Read string content from file Read string content from file
@ -45,6 +72,28 @@ def read_int_from_file(file_path, default=0, raise_exception=False):
return value return value
def _key_value_converter(content):
ret = {}
for line in content.splitlines():
k,v = line.split(':')
ret[k.strip()] = v.strip()
return ret
def read_key_value_file(file_path, default={}, raise_exception=False, log_func=logger.log_error):
"""Read file content and parse the content to a dict. The file content should like:
key1:value1
key2:value2
Args:
file_path (str): file path
default (dict, optional): default return value. Defaults to {}.
raise_exception (bool, optional): If exception should be raised or hiden. Defaults to False.
log_func (optional): logger function.. Defaults to logger.log_error.
"""
return read_from_file(file_path=file_path, target_type=_key_value_converter, default=default, raise_exception=raise_exception, log_func=log_func)
def write_file(file_path, content, raise_exception=False): def write_file(file_path, content, raise_exception=False):
""" """
Write the given value to a file Write the given value to a file
@ -72,13 +121,13 @@ def is_host():
global _is_host global _is_host
if _is_host is not None: if _is_host is not None:
return _is_host return _is_host
_is_host = False _is_host = False
try: try:
proc = subprocess.Popen("docker --version 2>/dev/null", proc = subprocess.Popen("docker --version 2>/dev/null",
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
shell=True, shell=True,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
universal_newlines=True) universal_newlines=True)
stdout = proc.communicate()[0] stdout = proc.communicate()[0]
proc.wait() proc.wait()

View File

@ -0,0 +1,84 @@
#
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES.
# Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
from sonic_py_common.logger import Logger
from . import utils
logger = Logger()
SN_VPD_FIELD = "SN_VPD_FIELD"
PN_VPD_FIELD = "PN_VPD_FIELD"
REV_VPD_FIELD = "REV_VPD_FIELD"
class VpdParser:
def __init__(self, file_path):
self.vpd_data = {}
self.vpd_file = file_path
self.vpd_file_last_mtime = None
def _get_data(self):
if not os.path.exists(self.vpd_file):
self.vpd_data = {}
return False
try:
mtime = os.stat(self.vpd_file).st_mtime
if mtime != self.vpd_file_last_mtime:
self.vpd_file_last_mtime = mtime
self.vpd_data = utils.read_key_value_file(self.vpd_file)
return True
except Exception as e:
self.vpd_data = {}
return False
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
if self._get_data() and PN_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read model number: No key {} in VPD {}".format(PN_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(PN_VPD_FIELD, 'N/A')
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
if self._get_data() and SN_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read serial number: No key {} in VPD {}".format(SN_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(SN_VPD_FIELD, 'N/A')
def get_revision(self):
"""
Retrieves the hardware revision of the device
Returns:
string: Revision value of device
"""
if self._get_data() and REV_VPD_FIELD not in self.vpd_data:
logger.log_error("Fail to read revision: No key {} in VPD {}".format(REV_VPD_FIELD, self.vpd_file))
return 'N/A'
return self.vpd_data.get(REV_VPD_FIELD, 'N/A')