From 4e166bd246e3dd4955f9cfa66a1a803e1d54ad0a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 28 Aug 2024 19:30:27 -0400 Subject: [PATCH] Refactor redfish settings and leverage in XCC3 XCC3 has some OEM settings managed in a redfish settings registry sort of way. Refactor to pull out generic registry handling and use it with XCC3 settings as well as UEFI. Manifest 'XCC' settings with 'XCC.' prefix, imitating the 'IMM.' of the past. Since OData forbids '.', this means XCC. cannot conflict with hypothetical BIOS names that may conflict. Change-Id: Idb9e5ecffdc0d27f20ae070acdc3cd78658a0955 --- pyghmi/redfish/command.py | 14 ---- pyghmi/redfish/oem/generic.py | 102 ++++++++++++++++++++---------- pyghmi/redfish/oem/lenovo/main.py | 4 ++ pyghmi/redfish/oem/lenovo/xcc3.py | 94 +++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 49 deletions(-) create mode 100644 pyghmi/redfish/oem/lenovo/xcc3.py diff --git a/pyghmi/redfish/command.py b/pyghmi/redfish/command.py index 285e6f5d..7a8d732e 100644 --- a/pyghmi/redfish/command.py +++ b/pyghmi/redfish/command.py @@ -81,20 +81,6 @@ def _mask_to_cidr(mask): return cidr -def _to_boolean(attrval): - attrval = attrval.lower() - if not attrval: - return False - if ('true'.startswith(attrval) or 'yes'.startswith(attrval) - or 'enabled'.startswith(attrval) or attrval == '1'): - return True - if ('false'.startswith(attrval) or 'no'.startswith(attrval) - or 'disabled'.startswith(attrval) or attrval == '0'): - return False - raise Exception( - 'Unrecognized candidate for boolean: {0}'.format(attrval)) - - def _cidr_to_mask(cidr): return socket.inet_ntop( socket.AF_INET, struct.pack( diff --git a/pyghmi/redfish/oem/generic.py b/pyghmi/redfish/oem/generic.py index c2c9add9..a0dc01e9 100644 --- a/pyghmi/redfish/oem/generic.py +++ b/pyghmi/redfish/oem/generic.py @@ -44,11 +44,29 @@ class SensorReading(object): self.units = units self.unavailable = unavailable + +def _to_boolean(attrval): + attrval = attrval.lower() + if not attrval: + return False + if ('true'.startswith(attrval) or 'yes'.startswith(attrval) + or 'enabled'.startswith(attrval) or attrval == '1'): + return True + if ('false'.startswith(attrval) or 'no'.startswith(attrval) + or 'disabled'.startswith(attrval) or attrval == '0'): + return False + raise Exception( + 'Unrecognized candidate for boolean: {0}'.format(attrval)) + + def _normalize_mac(mac): if ':' not in mac: - mac = ':'.join((mac[:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:12])) + mac = ':'.join(( + mac[:2], mac[2:4], mac[4:6], + mac[6:8], mac[8:10], mac[10:12])) return mac.lower() + _healthmap = { 'Critical': const.Health.Critical, 'Unknown': const.Health.Warning, @@ -345,6 +363,38 @@ class OEMHandler(object): def get_system_configuration(self, hideadvanced=True, fishclient=None): return self._getsyscfg(fishclient)[0] + def _get_attrib_registry(self, fishclient, attribreg): + overview = fishclient._do_web_request('/redfish/v1/') + reglist = overview['Registries']['@odata.id'] + reglist = fishclient._do_web_request(reglist) + regurl = None + for cand in reglist.get('Members', []): + cand = cand.get('@odata.id', '') + candname = cand.split('/')[-1] + if candname == '': # implementation uses trailing slash + candname = cand.split('/')[-2] + if candname == attribreg: + regurl = cand + break + if not regurl: + # Workaround a vendor bug where they link to a + # non-existant name + for cand in reglist.get('Members', []): + cand = cand.get('@odata.id', '') + candname = cand.split('/')[-1] + candname = candname.split('.')[0] + if candname == attribreg.split('.')[0]: + regurl = cand + break + if regurl: + reginfo = fishclient._do_web_request(regurl) + for reg in reginfo.get('Location', []): + if reg.get('Language', 'en').startswith('en'): + reguri = reg['Uri'] + reginfo = self._get_biosreg(reguri, fishclient) + return reginfo + extrainfo, valtodisplay, _, self.attrdeps = reginfo + def _getsyscfg(self, fishclient): biosinfo = self._do_web_request(fishclient._biosurl, cache=False) reginfo = ({}, {}, {}, {}) @@ -352,36 +402,9 @@ class OEMHandler(object): valtodisplay = {} self.attrdeps = {'Dependencies': [], 'Attributes': []} if 'AttributeRegistry' in biosinfo: - overview = fishclient._do_web_request('/redfish/v1/') - reglist = overview['Registries']['@odata.id'] - reglist = fishclient._do_web_request(reglist) - regurl = None - for cand in reglist.get('Members', []): - cand = cand.get('@odata.id', '') - candname = cand.split('/')[-1] - if candname == '': # implementation uses trailing slash - candname = cand.split('/')[-2] - if candname == biosinfo['AttributeRegistry']: - regurl = cand - break - if not regurl: - # Workaround a vendor bug where they link to a - # non-existant name - for cand in reglist.get('Members', []): - cand = cand.get('@odata.id', '') - candname = cand.split('/')[-1] - candname = candname.split('.')[0] - if candname == biosinfo[ - 'AttributeRegistry'].split('.')[0]: - regurl = cand - break - if regurl: - reginfo = fishclient._do_web_request(regurl) - for reg in reginfo.get('Location', []): - if reg.get('Language', 'en').startswith('en'): - reguri = reg['Uri'] - reginfo = self._get_biosreg(reguri, fishclient) - extrainfo, valtodisplay, _, self.attrdeps = reginfo + reginfo = self._get_attrib_registry(fishclient, biosinfo['AttributeRegistry']) + if reginfo: + extrainfo, valtodisplay, _, self.attrdeps = reginfo currsettings = {} try: pendingsettings = fishclient._do_web_request( @@ -418,10 +441,19 @@ class OEMHandler(object): rawsettings = fishclient._do_web_request(fishclient._biosurl, cache=False) rawsettings = rawsettings.get('Attributes', {}) - pendingsettings = fishclient._do_web_request(fishclient._setbiosurl) + pendingsettings = fishclient._do_web_request( + fishclient._setbiosurl) + return self._set_redfish_settings( + changeset, fishclient, currsettings, rawsettings, + pendingsettings, self.attrdeps, reginfo, + fishclient._setbiosurl) + + def _set_redfish_settings(self, changeset, fishclient, currsettings, + rawsettings, pendingsettings, attrdeps, reginfo, + seturl): etag = pendingsettings.get('@odata.etag', None) pendingsettings = pendingsettings.get('Attributes', {}) - dephandler = AttrDependencyHandler(self.attrdeps, rawsettings, + dephandler = AttrDependencyHandler(attrdeps, rawsettings, pendingsettings) for change in list(changeset): if change not in currsettings: @@ -440,7 +472,7 @@ class OEMHandler(object): changeval = changeset[change] overrides, blameattrs = dephandler.get_overrides(change) meta = {} - for attr in self.attrdeps['Attributes']: + for attr in attrdeps['Attributes']: if attr['AttributeName'] == change: meta = dict(attr) break @@ -479,7 +511,7 @@ class OEMHandler(object): changeset[change] = _to_boolean(changeset[change]) redfishsettings = {'Attributes': changeset} fishclient._do_web_request( - fishclient._setbiosurl, redfishsettings, 'PATCH', etag=etag) + seturl, redfishsettings, 'PATCH', etag=etag) def attach_remote_media(self, url, username, password, vmurls): return None diff --git a/pyghmi/redfish/oem/lenovo/main.py b/pyghmi/redfish/oem/lenovo/main.py index 1d4c23f0..7e6db6a3 100644 --- a/pyghmi/redfish/oem/lenovo/main.py +++ b/pyghmi/redfish/oem/lenovo/main.py @@ -15,6 +15,7 @@ import pyghmi.redfish.oem.generic as generic from pyghmi.redfish.oem.lenovo import tsma from pyghmi.redfish.oem.lenovo import xcc +from pyghmi.redfish.oem.lenovo import xcc3 def get_handler(sysinfo, sysurl, webclient, cache, cmd): @@ -26,6 +27,9 @@ def get_handler(sysinfo, sysurl, webclient, cache, cmd): if 'FrontPanelUSB' in leninf or 'USBManagementPortAssignment' in leninf or sysinfo.get('SKU', '').startswith('7X58'): return xcc.OEMHandler(sysinfo, sysurl, webclient, cache, gpool=cmd._gpool) + elif 'NextOneTimeBootDevice' in leninf: + return xcc3.OEMHandler(sysinfo, sysurl, webclient, cache, + gpool=cmd._gpool) else: leninv = sysinfo.get('Links', {}).get('OEM', {}).get( 'Lenovo', {}).get('Inventory', {}) diff --git a/pyghmi/redfish/oem/lenovo/xcc3.py b/pyghmi/redfish/oem/lenovo/xcc3.py new file mode 100644 index 00000000..e8457228 --- /dev/null +++ b/pyghmi/redfish/oem/lenovo/xcc3.py @@ -0,0 +1,94 @@ +import pyghmi.redfish.oem.generic as generic + +class OEMHandler(generic.OEMHandler): + + def get_system_configuration(self, hideadvanced=True, fishclient=None): + stgs = self._getsyscfg(fishclient)[0] + outstgs = {} + for stg in stgs: + outstgs[f'UEFI.{stg}'] = stgs[stg] + return outstgs + + def set_system_configuration(self, changeset, fishclient): + bmchangeset = {} + vpdchangeset = {} + for stg in list(changeset): + if stg.startswith('BMC.'): + bmchangeset[stg.replace('BMC.', '')] = changeset[stg] + del changeset[stg] + if stg.startswith('UEFI.'): + changeset[stg.replace('UEFI.' '')] = changeset[stg] + del changeset[stg] + if stg.startswith('VPD.'): + vpdchangeset[stg.replace('VPD.', '')] = changeset[stg] + del changeset[stg] + if changeset: + super().set_system_configuration(changeset, fishclient) + if bmchangeset: + self._set_xcc3_settings(bmchangeset, fishclient) + if vpdchangeset: + self._set_xcc3_vpd(vpdchangeset, fishclient) + + def _set_xcc3_vpd(self, changeset, fishclient): + newvpd = {'Attributes': changeset} + fishclient._do_web_request( + '/redfish/v1/Chassis/1/Oem/Lenovo/SysvpdSettings/Actions/LenovoSysVpdSettings.SetVpdSettings', + newvpd) + + + def _set_xcc3_settings(self, changeset, fishclient): + currsettings, reginfo = self._get_lnv_bmcstgs(fishclient) + rawsettings = fishclient._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings', + cache=False) + rawsettings = rawsettings.get('Attributes', {}) + pendingsettings = {} + ret = self._set_redfish_settings( + changeset, fishclient, currsettings, rawsettings, + pendingsettings, self.lenovobmcattrdeps, reginfo, + '/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings') + fishclient._do_web_request('/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings', cache=False) + return ret + + def get_extended_bmc_configuration(self, fishclient, hideadvanced=True): + cfgin = self._get_lnv_bmcstgs(fishclient)[0] + cfgout = {} + for stgname in cfgin: + cfgout[f'BMC.{stgname}'] = cfgin[stgname] + vpdin = self._get_lnv_vpd(fishclient)[0] + for stgname in vpdin: + cfgout[f'VPD.{stgname}'] = vpdin[stgname] + return cfgout + + def _get_lnv_vpd(self, fishclient): + currsettings, reginfo = self._get_lnv_stgs( + fishclient, '/redfish/v1/Chassis/1/Oem/Lenovo/SysvpdSettings') + self.lenovobmcattrdeps = reginfo[3] + return currsettings, reginfo + + def _get_lnv_bmcstgs(self, fishclient): + currsettings, reginfo = self._get_lnv_stgs( + fishclient, '/redfish/v1/Managers/1/Oem/Lenovo/BMCSettings') + self.lenovobmcattrdeps = reginfo[3] + return currsettings, reginfo + + def _get_lnv_stgs(self, fishclient, url): + bmcstgs = fishclient._do_web_request(url) + bmcreg = bmcstgs.get('AttributeRegistry', None) + extrainfo = {} + valtodisplay = {} + currsettings = {} + reginfo = {}, {}, {}, {} + if bmcreg: + reginfo = self._get_attrib_registry(fishclient, bmcreg) + if reginfo: + extrainfo, valtodisplay, _, _ = reginfo + for setting in bmcstgs.get('Attributes', {}): + val = bmcstgs['Attributes'][setting] + currval = val + val = valtodisplay.get(setting, {}).get(val, val) + val = {'value': val} + val.update(**extrainfo.get(setting, {})) + currsettings[setting] = val + return currsettings, reginfo + +