Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyghmi for openSUSE:Factory checked in at 2024-01-05 21:41:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyghmi (Old) and /work/SRC/openSUSE:Factory/.python-pyghmi.new.28375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyghmi" Fri Jan 5 21:41:36 2024 rev:20 rq:1136723 version:1.5.63 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyghmi/python-pyghmi.changes 2023-06-22 23:25:12.177627882 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyghmi.new.28375/python-pyghmi.changes 2024-01-05 21:42:44.937210285 +0100 @@ -1,0 +2,24 @@ +Thu Jan 4 09:06:54 UTC 2024 - Dirk Müller <[email protected]> + +- update to 1.5.63: + * Provide generic redfish push update support + * Return empty tuple rather than None + * Expect timeout on reseat + * Reset seek on unwrapped zip + * Prefer first PCI device id + * Provide error checking using XCC hints + * Handle unseekable data + * Fix capitilazation consistency + * Trigger unwrapping only with singular update + * Apply uxz payload rather than whole zip to XCC + * Implement XCC override for health in redfish + * Avoid error on unexpected shutdown + * Add missing class to generic redfish support + * Correct missing lookup in generic OEM redfish + * Prepare redfish for OEM health + * Add deduplicated events and let XCC events control entirely + * Fix compatibility with python2 + * Clear any logonwaiters on broken + * Handle non-numeric with 0 number format + +------------------------------------------------------------------- Old: ---- pyghmi-1.5.61.tar.gz New: ---- pyghmi-1.5.63.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyghmi.spec ++++++ --- /var/tmp/diff_new_pack.pJY1GX/_old 2024-01-05 21:42:45.529231932 +0100 +++ /var/tmp/diff_new_pack.pJY1GX/_new 2024-01-05 21:42:45.529231932 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pyghmi # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: python-pyghmi -Version: 1.5.61 +Version: 1.5.63 Release: 0 Summary: General Hardware Management Initiative (IPMI and others) License: Apache-2.0 Group: Development/Languages/Python URL: https://docs.openstack.org/pyghmi -Source0: https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.5.61.tar.gz +Source0: https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.5.63.tar.gz BuildRequires: openstack-macros BuildRequires: python3-cryptography >= 2.1 BuildRequires: python3-devel @@ -73,7 +73,7 @@ %build %{py3_build} -PYTHONPATH=. PBR_VERSION=1.5.61 %sphinx_build -b html doc/source doc/build/html +PYTHONPATH=. PBR_VERSION=1.5.63 %sphinx_build -b html doc/source doc/build/html rm -rf doc/build/html/.{doctrees,buildinfo} %install ++++++ _service ++++++ --- /var/tmp/diff_new_pack.pJY1GX/_old 2024-01-05 21:42:45.553232809 +0100 +++ /var/tmp/diff_new_pack.pJY1GX/_new 2024-01-05 21:42:45.557232956 +0100 @@ -1,12 +1,12 @@ <services> - <service mode="disabled" name="renderspec"> + <service mode="manual" name="renderspec"> <param name="input-template">https://opendev.org/openstack/rpm-packaging/raw/master/openstack/pyghmi/pyghmi.spec.j2</param> <param name="output-name">python-pyghmi.spec</param> <param name="requirements">https://opendev.org/x/pyghmi/raw/master/requirements.txt</param> <param name="changelog-email">[email protected]</param> </service> - <service mode="disabled" name="download_files"> + <service mode="manual" name="download_files"> </service> - <service name="format_spec_file" mode="disabled"/> + <service name="format_spec_file" mode="manual"/> </services> ++++++ pyghmi-1.5.61.tar.gz -> pyghmi-1.5.63.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/ChangeLog new/pyghmi-1.5.63/ChangeLog --- old/pyghmi-1.5.61/ChangeLog 2023-04-13 14:58:47.000000000 +0200 +++ new/pyghmi-1.5.63/ChangeLog 2023-08-28 16:40:08.000000000 +0200 @@ -1,6 +1,33 @@ CHANGES ======= +1.5.63 +------ + +* Provide generic redfish push update support +* Return empty tuple rather than None + +1.5.62 +------ + +* Expect timeout on reseat +* Reset seek on unwrapped zip +* Prefer first PCI device id +* Provide error checking using XCC hints +* Handle unseekable data +* Fix capitilazation consistency +* Trigger unwrapping only with singular update +* Apply uxz payload rather than whole zip to XCC +* Implement XCC override for health in redfish +* Avoid error on unexpected shutdown +* Add missing class to generic redfish support +* Correct missing lookup in generic OEM redfish +* Prepare redfish for OEM health +* Add deduplicated events and let XCC events control entirely +* Fix compatibility with python2 +* Clear any logonwaiters on broken +* Handle non-numeric with 0 number format + 1.5.61 ------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/PKG-INFO new/pyghmi-1.5.63/PKG-INFO --- old/pyghmi-1.5.61/PKG-INFO 2023-04-13 14:58:48.335039900 +0200 +++ new/pyghmi-1.5.63/PKG-INFO 2023-08-28 16:40:08.964266500 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.5.61 +Version: 1.5.63 Summary: Python General Hardware Management Initiative (IPMI and others) Home-page: http://github.com/openstack/pyghmi/ Author: Jarrod Johnson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/command.py new/pyghmi-1.5.63/pyghmi/ipmi/command.py --- old/pyghmi-1.5.61/pyghmi/ipmi/command.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/command.py 2023-08-28 16:39:39.000000000 +0200 @@ -907,7 +907,7 @@ ip6gd = bytearray(ip6g['data']) if ip6gd[0] != 0x11: raise Exception('Unsupported reply') - gwa = socket.inet_ntop(socket.AF_INET6, ip6gd[1:17]) + gwa = socket.inet_ntop(socket.AF_INET6, bytes(ip6gd[1:17])) retdata['static_gateway'] = gwa return retdata diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/config.py new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/config.py --- old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/config.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/config.py 2023-08-28 16:39:39.000000000 +0200 @@ -353,6 +353,7 @@ reset = False name = setting.find("mriName").text help = setting.find("desc").text + validexpression = None onedata = setting.find('text_data') if onedata is not None: if onedata.get('password') == 'true': @@ -366,6 +367,7 @@ if onedata is not None: if onedata.get('maxinstance') is not None: forceinstance = True + validexpression = onedata.get('pattern', None) instances = list(onedata.iter('instance')) if not instances: protect = True # not supported yet @@ -451,6 +453,7 @@ readonly_expression=readonly, hide_expression=hide, sortid=sortid, + validexpression=validexpression, alias=alias) sortid += 1 instidx += 1 @@ -479,6 +482,7 @@ readonly_expression=readonly, hide_expression=hide, sortid=sortid, + validexpression=validexpression, alias=alias) sortid += 1 continue @@ -503,6 +507,7 @@ readonly_expression=readonly, hide_expression=hide, sortid=sortid, + validexpression=validexpression, alias=alias) sortid = sortid + 1 for opt in options: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/handler.py new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/handler.py --- old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/handler.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/handler.py 2023-08-28 16:39:39.000000000 +0200 @@ -346,7 +346,7 @@ return self.smmhandler.get_ntp_servers() if self.has_tsma: return self.tsmahandler.get_ntp_servers() - return None + return () def set_ntp_enabled(self, enabled): if self.has_tsm or self.has_ami or self.has_asrock: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/imm.py new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/imm.py --- old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/imm.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/imm.py 2023-08-28 16:39:39.000000000 +0200 @@ -1,5 +1,5 @@ # coding=utf8 -# Copyright 2016-2019 Lenovo +# Copyright 2016-2023 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import weakref import six +import zipfile import pyghmi.constants as pygconst import pyghmi.exceptions as pygexc @@ -314,6 +315,11 @@ '({2})'.format( newvalue, key, ','.join(self.fwo[key]['possible']))) + elif self.fwo[key]['validexpression']: + if not re.match(self.fwo[key]['validexpression'], newvalue): + raise pygexc.InvalidParameterValue( + '"{0}" does not match expression "{1}"'.format( + newvalue, self.fwo[key]['validexpression'])) newnewvalues.append(newvalue) if len(newnewvalues) == 1: self.fwo[key]['new_value'] = newnewvalues[0] @@ -790,7 +796,7 @@ if venid is not None: bdata['PCI Vendor ID'] = '{0:04x}'.format(venid) devid = fundata.get(self.ADP_DEVID, None) - if devid is not None: + if devid is not None and 'PCI Device ID' not in bdata: bdata['PCI Device ID'] = '{0:04x}'.format(devid) venid = fundata.get(self.ADP_SUBVENID, None) if venid is not None: @@ -933,8 +939,11 @@ {'RoleId': role}, method='PATCH') def reseat(self): - rsp = self.wc.grab_json_response_with_status( + wc = self.wc.dupe(timeout=5) + rsp = wc.grab_json_response_with_status( '/api/providers/virt_reseat', '{}') + if rsp[1] == 500 and rsp[0] == 'Target Unavailable': + return if rsp[1] != 200 or rsp[0].get('return', 1) != 0: raise pygexc.UnsupportedFunctionality( 'This platform does not support AC reseat.') @@ -1985,6 +1994,29 @@ if usd['HttpPushUriTargetsBusy']: raise pygexc.TemporaryError('Cannot run multiple updates to same ' 'target concurrently') + z = None + wrappedfilename = None + uxzcount = 0 + needseek = False + if data and hasattr(data, 'read'): + if zipfile.is_zipfile(data): + needseek = True + z = zipfile.ZipFile(data) + else: + data.seek(0) + elif data is None and zipfile.is_zipfile(filename): + z = zipfile.ZipFile(filename) + if z: + for tmpname in z.namelist(): + if tmpname.startswith('payloads/'): + uxzcount += 1 + if tmpname.endswith('.uxz'): + wrappedfilename = tmpname + if uxzcount == 1 and wrappedfilename: + filename = os.path.basename(wrappedfilename) + data = z.open(wrappedfilename) + elif needseek: + data.seek(0) upurl = usd['HttpPushUri'] self.grab_redfish_response_with_status( '/redfish/v1/UpdateService', @@ -2339,6 +2371,7 @@ 'W': pygconst.Health.Warning, } infoevents = False + existingevts = set([]) for item in rsp.get('items', ()): # while usually the ipmi interrogation shall explain things, # just in case there is a gap, make sure at least the @@ -2353,14 +2386,14 @@ if item['cmnid'] == 'FQXSPPW0104J': # This event does not get modeled by the sensors # add a made up sensor to explain - summary['badreadings'].append( + fallbackdata.append( sdr.SensorReading({'name': item['source'], 'states': ['Not Redundant'], 'state_ids': [3], 'health': pygconst.Health.Warning, 'type': 'Power'}, '')) elif item['cmnid'] == 'FQXSFMA0041K': - summary['badreadings'].append( + fallbackdata.append( sdr.SensorReading({ 'name': 'Optane DCPDIMM', 'health': pygconst.Health.Warning, @@ -2369,6 +2402,10 @@ '') ) else: + currevt = '{}:{}'.format(item['source'], item['message']) + if currevt in existingevts: + continue + existingevts.add(currevt) fallbackdata.append(sdr.SensorReading({ 'name': item['source'], 'states': [item['message']], @@ -2387,6 +2424,9 @@ 'health': pygconst.Health.Warning, 'type': 'LED', }, '')) + summary['badreadings'] = fallbackdata + if fallbackdata: + raise pygexc.BypassGenericBehavior() return fallbackdata # Will use the generic handling for unhealthy systems diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/nextscale.py new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/nextscale.py --- old/pyghmi-1.5.61/pyghmi/ipmi/oem/lenovo/nextscale.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/oem/lenovo/nextscale.py 2023-08-28 16:39:39.000000000 +0200 @@ -1028,11 +1028,18 @@ rsp = wc.getresponse() rsp.read() complete = False + tries = 0 while not complete: ipmisession.Session.pause(3) wc.request('POST', '/data', 'get=fwProgress,fwUpdate') - rsp = wc.getresponse() - progdata = rsp.read() + try: + rsp = wc.getresponse() + progdata = rsp.read() + except Exception: + if tries > 2: + break + tries += 1 + continue if rsp.status != 200: raise Exception('Error applying firmware') progdata = fromstring(progdata) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/private/session.py new/pyghmi-1.5.63/pyghmi/ipmi/private/session.py --- old/pyghmi-1.5.61/pyghmi/ipmi/private/session.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/private/session.py 2023-08-28 16:39:39.000000000 +0200 @@ -583,6 +583,12 @@ self.broken = True if self.socket: self.socketpool[self.socket] -= 1 + while self.logonwaiters: + waiter = self.logonwaiters.pop() + try: + waiter({'error': 'Session failed to initalize'}) + except Exception: + pass def onlogon(self, parameter): if 'error' in parameter: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/ipmi/sdr.py new/pyghmi-1.5.63/pyghmi/ipmi/sdr.py --- old/pyghmi-1.5.61/pyghmi/ipmi/sdr.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/ipmi/sdr.py 2023-08-28 16:39:39.000000000 +0200 @@ -372,6 +372,7 @@ # event only, compact and full are very similar # this function handles the common aspects of compact and full # offsets from spec, minus 6 + self.has_thresholds = False self.sensor_owner = entry[0] self.sensor_lun = entry[1] & 0x03 self.sensor_number = entry[2] @@ -383,6 +384,8 @@ else: self.sensor_type_number = entry[7] self.reading_type = entry[8] # table 42-1 + if self.rectype == 1 and entry[6] & 0b00001100: + self.has_thresholds = True try: self.sensor_type = self.event_consts.sensor_type_codes[ self.sensor_type_number] @@ -487,7 +490,7 @@ numeric = twos_complement(reading[0], 8) elif self.numeric_format == 1: numeric = ones_complement(reading[0], 8) - elif self.numeric_format == 0: + elif self.numeric_format == 0 and (self.has_thresholds or self.reading_type == 1): numeric = reading[0] discrete = True if numeric is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/redfish/command.py new/pyghmi-1.5.63/pyghmi/redfish/command.py --- old/pyghmi-1.5.61/pyghmi/redfish/command.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/redfish/command.py 2023-08-28 16:39:39.000000000 +0200 @@ -765,72 +765,7 @@ return {'identifystate': self._idstatemap[ledstate]} def get_health(self, verbose=True): - health = self.sysinfo.get('Status', {}) - health = health.get('HealthRollup', health.get('Health', 'Unknown')) - warnunknown = health == 'Unknown' - health = _healthmap[health] - summary = {'badreadings': [], 'health': health} - if health > 0 and verbose: - # now have to manually peruse all psus, fans, processors, ram, - # storage - procsumstatus = self.sysinfo.get('ProcessorSummary', {}).get( - 'Status', {}) - procsumstatus = procsumstatus.get('HealthRollup', - procsumstatus.get('Health', - None)) - if procsumstatus != 'OK': - procfound = False - procurl = self.sysinfo.get('Processors', {}).get('@odata.id', - None) - if procurl: - for cpu in self._do_web_request(procurl).get( - 'Members', []): - cinfo = self._do_web_request(cpu['@odata.id']) - if cinfo.get('Status', {}).get( - 'State', None) == 'Absent': - continue - if cinfo.get('Status', {}).get( - 'Health', None) not in ('OK', None): - procfound = True - summary['badreadings'].append(SensorReading(cinfo)) - if not procfound: - procinfo = self.sysinfo['ProcessorSummary'] - procinfo['Name'] = 'Processors' - summary['badreadings'].append(SensorReading(procinfo)) - memsumstatus = self.sysinfo.get( - 'MemorySummary', {}).get('Status', {}) - memsumstatus = memsumstatus.get('HealthRollup', - memsumstatus.get('Health', None)) - if memsumstatus != 'OK': - dimmfound = False - for mem in self._do_web_request( - self.sysinfo['Memory']['@odata.id'])['Members']: - dimminfo = self._do_web_request(mem['@odata.id']) - if dimminfo.get('Status', {}).get( - 'State', None) == 'Absent': - continue - if dimminfo.get('Status', {}).get( - 'Health', None) not in ('OK', None): - summary['badreadings'].append(SensorReading(dimminfo)) - dimmfound = True - if not dimmfound: - meminfo = self.sysinfo['MemorySummary'] - meminfo['Name'] = 'Memory' - summary['badreadings'].append(SensorReading(meminfo)) - for adapter in self.sysinfo['PCIeDevices']: - adpinfo = self._do_web_request(adapter['@odata.id']) - if adpinfo['Status']['Health'] not in ('OK', None): - summary['badreadings'].append(SensorReading(adpinfo)) - for fun in self.sysinfo['PCIeFunctions']: - funinfo = self._do_web_request(fun['@odata.id']) - if funinfo['Status']['Health'] not in ('OK', None): - summary['badreadings'].append(SensorReading(funinfo)) - if warnunknown and not summary['badreadings']: - unkinf = SensorReading({'Name': 'BMC', - 'Status': {'Health': 'Unknown'}}) - unkinf.states = ['System does not provide health information'] - summary['badreadings'].append(unkinf) - return summary + return self.oem.get_health(self, verbose) def get_bmc_configuration(self): """Get miscellaneous BMC configuration diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/redfish/oem/generic.py new/pyghmi-1.5.63/pyghmi/redfish/oem/generic.py --- old/pyghmi-1.5.61/pyghmi/redfish/oem/generic.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/redfish/oem/generic.py 2023-08-28 16:39:39.000000000 +0200 @@ -16,9 +16,39 @@ import json import os import re +import time +import pyghmi.constants as const import pyghmi.exceptions as exc import pyghmi.media as media +import pyghmi.util.webclient as webclient + + +class SensorReading(object): + def __init__(self, healthinfo, sensor=None, value=None, units=None, + unavailable=False): + if sensor: + self.name = sensor['name'] + else: + self.name = healthinfo['Name'] + self.health = _healthmap.get(healthinfo.get( + 'Status', {}).get('Health', None), const.Health.Warning) + self.states = [healthinfo.get('Status', {}).get('Health', + 'Unknown')] + self.health = _healthmap[healthinfo['Status']['Health']] + self.states = [healthinfo['Status']['Health']] + self.value = value + self.state_ids = None + self.imprecision = None + self.units = units + self.unavailable = unavailable + +_healthmap = { + 'Critical': const.Health.Critical, + 'Unknown': const.Health.Warning, + 'Warning': const.Health.Warning, + 'OK': const.Health.Ok, +} boot_devices_write = { 'net': 'Pxe', @@ -133,6 +163,74 @@ self._urlcache = cache self.webclient = webclient + def get_health(self, fishclient, verbose=True): + health = fishclient.sysinfo.get('Status', {}) + health = health.get('HealthRollup', health.get('Health', 'Unknown')) + warnunknown = health == 'Unknown' + health = _healthmap[health] + summary = {'badreadings': [], 'health': health} + if health > 0 and verbose: + # now have to manually peruse all psus, fans, processors, ram, + # storage + procsumstatus = fishclient.sysinfo.get('ProcessorSummary', {}).get( + 'Status', {}) + procsumstatus = procsumstatus.get('HealthRollup', + procsumstatus.get('Health', + None)) + if procsumstatus != 'OK': + procfound = False + procurl = fishclient.sysinfo.get('Processors', {}).get('@odata.id', + None) + if procurl: + for cpu in fishclient._do_web_request(procurl).get( + 'Members', []): + cinfo = fishclient._do_web_request(cpu['@odata.id']) + if cinfo.get('Status', {}).get( + 'State', None) == 'Absent': + continue + if cinfo.get('Status', {}).get( + 'Health', None) not in ('OK', None): + procfound = True + summary['badreadings'].append(SensorReading(cinfo)) + if not procfound: + procinfo = fishclient.sysinfo['ProcessorSummary'] + procinfo['Name'] = 'Processors' + summary['badreadings'].append(SensorReading(procinfo)) + memsumstatus = fishclient.sysinfo.get( + 'MemorySummary', {}).get('Status', {}) + memsumstatus = memsumstatus.get('HealthRollup', + memsumstatus.get('Health', None)) + if memsumstatus != 'OK': + dimmfound = False + for mem in fishclient._do_web_request( + fishclient.sysinfo['Memory']['@odata.id'])['Members']: + dimminfo = fishclient._do_web_request(mem['@odata.id']) + if dimminfo.get('Status', {}).get( + 'State', None) == 'Absent': + continue + if dimminfo.get('Status', {}).get( + 'Health', None) not in ('OK', None): + summary['badreadings'].append(SensorReading(dimminfo)) + dimmfound = True + if not dimmfound: + meminfo = fishclient.sysinfo['MemorySummary'] + meminfo['Name'] = 'Memory' + summary['badreadings'].append(SensorReading(meminfo)) + for adapter in fishclient.sysinfo['PCIeDevices']: + adpinfo = fishclient._do_web_request(adapter['@odata.id']) + if adpinfo['Status']['Health'] not in ('OK', None): + summary['badreadings'].append(SensorReading(adpinfo)) + for fun in fishclient.sysinfo['PCIeFunctions']: + funinfo = fishclient._do_web_request(fun['@odata.id']) + if funinfo['Status']['Health'] not in ('OK', None): + summary['badreadings'].append(SensorReading(funinfo)) + if warnunknown and not summary['badreadings']: + unkinf = SensorReading({'Name': 'BMC', + 'Status': {'Health': 'Unknown'}}) + unkinf.states = ['System does not provide health information'] + summary['badreadings'].append(unkinf) + return summary + def user_delete(self, uid): # Redfish doesn't do so well with Deleting users either... # Blanking the username seems to be the convention @@ -431,7 +529,7 @@ 'UUID': self._varsysinfo.get('UUID', ''), 'Serial Number': self._varsysinfo.get('SerialNumber', ''), 'Manufacturer': self._varsysinfo.get('Manufacturer', ''), - 'Product Name': self._varsysinfo.get('Model', ''), + 'Product name': self._varsysinfo.get('Model', ''), 'Model': self._varsysinfo.get( 'SKU', self._varsysinfo.get('PartNumber', '')), } @@ -446,7 +544,7 @@ 'UUID': self._varsysinfo.get('UUID', ''), 'Serial Number': self._varsysinfo.get('SerialNumber', ''), 'Manufacturer': self._varsysinfo.get('Manufacturer', ''), - 'Product Name': self._varsysinfo.get('Model', ''), + 'Product name': self._varsysinfo.get('Model', ''), 'Model': self._varsysinfo.get( 'SKU', self._varsysinfo.get('PartNumber', '')), } @@ -681,8 +779,78 @@ 'Remote media upload not supported on this platform') def update_firmware(self, filename, data=None, progress=None, bank=None): - raise exc.UnsupportedFunctionality( - 'Firmware update not supported on this platform') + usd = self._do_web_request('/redfish/v1/UpdateService') + if usd.get('HttpPushUriTargetsBusy', False): + raise pygexc.TemporaryError('Cannot run multtiple updates to ' + 'same target concurrently') + try: + upurl = usd['HttpPushUri'] + except KeyError: + raise pygexc.UnsupportedFunctionality('Redfish firmware update only supported for implementations with push update support') + if 'HttpPushUriTargetsBusy' in usd: + self._do_web_request( + '/redfish/v1/UpdateService', + {'HttpPushUriTargetsBusy': True}, method='PATCH') + try: + uploadthread = webclient.FileUploader( + self.webclient, upurl, filename, data, formwrap=False, + excepterror=False) + uploadthread.start() + wc = self.webclient + while uploadthread.isAlive(): + uploadthread.join(3) + if progress: + progress( + {'phase': 'upload', + 'progress': 100 * wc.get_upload_progress()}) + if (uploadthread.rspstatus >= 300 + or uploadthread.rspstatus < 200): + rsp = uploadthread.rsp + errmsg = '' + try: + rsp = json.loads(rsp) + errmsg = ( + rsp['error'][ + '@Message.ExtendedInfo'][0]['Message']) + except Exception: + raise Exception(uploadthread.rsp) + raise Exception(errmsg) + rsp = json.loads(uploadthread.rsp) + monitorurl = rsp['@odata.id'] + complete = False + phase = "apply" + statetype = 'TaskState' + while not complete: + pgress = self._do_web_request(monitorurl, cache=False) + if not pgress: + break + for msg in pgress.get('Messages', []): + if 'Verify failed' in msg.get('Message', ''): + raise Exception(msg['Message']) + state = pgress[statetype] + if state in ('Cancelled', 'Exception', 'Interrupted', + 'Suspended'): + raise Exception( + json.dumps(json.dumps(pgress['Messages']))) + pct = float(pgress['PercentComplete']) + complete = state == 'Completed' + progress({'phase': phase, 'progress': pct}) + if complete: + if 'OperationTransitionedToJob' in pgress['Messages'][0]['MessageId']: + monitorurl = pgress['Messages'][0]['MessageArgs'][0] + phase = 'validating' + statetype = 'JobState' + complete = False + time.sleep(3) + else: + time.sleep(3) + return 'pending' + finally: + if 'HttpPushUriTargetsBusy' in usd: + self._do_web_request( + '/redfish/v1/UpdateService', + {'HttpPushUriTargetsBusy': False}, method='PATCH') + def _do_bulk_requests(self, urls, cache=True): if self._gpool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/redfish/oem/lenovo/xcc.py new/pyghmi-1.5.63/pyghmi/redfish/oem/lenovo/xcc.py --- old/pyghmi-1.5.61/pyghmi/redfish/oem/lenovo/xcc.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/redfish/oem/lenovo/xcc.py 2023-08-28 16:39:39.000000000 +0200 @@ -24,7 +24,9 @@ import time import six +import zipfile +import pyghmi.constants as pygconst import pyghmi.exceptions as pygexc import pyghmi.ipmi.private.util as util import pyghmi.ipmi.oem.lenovo.config as config @@ -34,6 +36,21 @@ from pyghmi.util.parse import parse_time import pyghmi.util.webclient as webclient +class SensorReading(object): + def __init__(self, healthinfo, sensor=None, value=None, units=None, + unavailable=False): + if sensor: + self.name = sensor['name'] + else: + self.name = healthinfo['name'] + self.health = healthinfo['health'] + self.states = healthinfo['states'] + self.state_ids = healthinfo.get('state_ids', None) + self.value = value + self.imprecision = None + self.units = units + self.unavailable = unavailable + numregex = re.compile('([0-9]+)') funtypes = { 0: 'RAID Controller', @@ -236,6 +253,11 @@ '({2})'.format( newvalue, key, ','.join(self.fwo[key]['possible']))) + elif self.fwo[key]['validexpression']: + if not re.match(self.fwo[key]['validexpression'], newvalue): + raise pygexc.InvalidParameterValue( + '"{0}" does not match expression "{1}"'.format( + newvalue, self.fwo[key]['validexpression'])) newnewvalues.append(newvalue) if len(newnewvalues) == 1: self.fwo[key]['new_value'] = newnewvalues[0] @@ -246,8 +268,11 @@ if bay != -1: raise pygexc.UnsupportedFunctionality( 'This is not an enclosure manager') - rsp = self.wc.grab_json_response_with_status( + wc = self.wc.dupe(timeout=5) + rsp = wc.grab_json_response_with_status( '/api/providers/virt_reseat', '{}') + if rsp[1] == 500 and rsp[0] == 'Target Unavailable': + return if rsp[1] != 200 or rsp[0].get('return', 1) != 0: raise pygexc.UnsupportedFunctionality( 'This platform does not support AC reseat.') @@ -477,6 +502,60 @@ self.wc.grab_json_response( '/api/function', {'USB_AddMapping': newmapping}) + def get_health(self, fishclient, verbose=True): + rsp = self._do_web_request('/api/providers/imm_active_events') + summary = {'badreadings': [], 'health': pygconst.Health.Ok} + fallbackdata = [] + hmap = { + 'I': pygconst.Health.Ok, + 'E': pygconst.Health.Critical, + 'W': pygconst.Health.Warning, + } + infoevents = False + existingevts = set([]) + for item in rsp.get('items', ()): + # while usually the ipmi interrogation shall explain things, + # just in case there is a gap, make sure at least the + # health field is accurately updated + itemseverity = hmap.get(item.get('severity', 'E'), + pygconst.Health.Critical) + if itemseverity == pygconst.Health.Ok: + infoevents = True + continue + if (summary['health'] < itemseverity): + summary['health'] = itemseverity + if item['cmnid'] == 'FQXSPPW0104J': + # This event does not get modeled by the sensors + # add a made up sensor to explain + fallbackdata.append( + SensorReading({'name': item['source'], + 'states': ['Not Redundant'], + 'state_ids': [3], + 'health': pygconst.Health.Warning, + 'type': 'Power'}, '')) + elif item['cmnid'] == 'FQXSFMA0041K': + fallbackdata.append( + SensorReading({ + 'name': 'Optane DCPDIMM', + 'health': pygconst.Health.Warning, + 'type': 'Memory', + 'states': [item['message']]}, + '') + ) + else: + currevt = '{}:{}'.format(item['source'], item['message']) + if currevt in existingevts: + continue + existingevts.add(currevt) + fallbackdata.append(SensorReading({ + 'name': item['source'], + 'states': [item['message']], + 'health': itemseverity, + 'type': item['source'], + }, '')) + summary['badreadings'] = fallbackdata + return summary + def get_description(self): description = self._do_web_request('/DeviceDescription.json') if description: @@ -1121,6 +1200,29 @@ if usd['HttpPushUriTargetsBusy']: raise pygexc.TemporaryError('Cannot run multtiple updates to ' 'same target concurrently') + z = None + wrappedfilename = None + uxzcount = 0 + needseek = False + if data and hasattr(data, 'read'): + if zipfile.is_zipfile(data): + needseek = True + z = zipfile.ZipFile(data) + else: + data.seek(0) + elif data is None and zipfile.is_zipfile(filename): + z = zipfile.ZipFile(filename) + if z: + for tmpname in z.namelist(): + if tmpname.startswith('payloads/'): + uxzcount += 1 + if tmpname.endswith('.uxz'): + wrappedfilename = tmpname + if uxzcount == 1 and wrappedfilename: + filename = os.path.basename(wrappedfilename) + data = z.open(wrappedfilename) + elif needseek: + data.seek(0) upurl = usd['HttpPushUri'] self._do_web_request( '/redfish/v1/UpdateService', @@ -1529,7 +1631,7 @@ 'UUID': self._varsysinfo.get('UUID', ''), 'Serial Number': self._varsysinfo.get('SerialNumber', ''), 'Manufacturer': self._varsysinfo.get('Manufacturer', ''), - 'Product Name': self._varsysinfo.get('Model', ''), + 'Product name': self._varsysinfo.get('Model', ''), 'Model': self._varsysinfo.get( 'SKU', self._varsysinfo.get('PartNumber', '')), } @@ -1546,7 +1648,7 @@ 'UUID': self._varsysinfo.get('UUID', ''), 'Serial Number': self._varsysinfo.get('SerialNumber', ''), 'Manufacturer': self._varsysinfo.get('Manufacturer', ''), - 'Product Name': self._varsysinfo.get('Model', ''), + 'Product name': self._varsysinfo.get('Model', ''), 'Model': self._varsysinfo.get( 'SKU', self._varsysinfo.get('PartNumber', '')), } @@ -1614,7 +1716,7 @@ if venid is not None: bdata['PCI Vendor ID'] = '{0:04x}'.format(venid) devid = fundata.get(self.ADP_DEVID, None) - if devid is not None: + if devid is not None and 'PCIE Device ID' not in bdata: bdata['PCI Device ID'] = '{0:04x}'.format(devid) venid = fundata.get(self.ADP_SUBVENID, None) if venid is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi/util/webclient.py new/pyghmi-1.5.63/pyghmi/util/webclient.py --- old/pyghmi-1.5.61/pyghmi/util/webclient.py 2023-04-13 14:58:19.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi/util/webclient.py 2023-08-28 16:39:39.000000000 +0200 @@ -160,9 +160,11 @@ self.sock.close() self.sock = None - def dupe(self): + def dupe(self, timeout=None): + if timeout is None: + timeout = self.mytimeout return SecureHTTPConnection(self.thehost, self.theport, clone=self, - timeout=self.mytimeout) + timeout=timeout) def set_header(self, key, value): self.stdheaders[key] = value @@ -317,11 +319,19 @@ ulhdrs['Content-Length'] = len(uploadforms[filename]) self.ulsize = len(uploadforms[filename]) else: - curroff = data.tell() - data.seek(0, 2) - self.ulsize = data.tell() - data.seek(curroff, 0) - self._upbuffer = data + canseek = True + try: + curroff = data.tell() + except io.UnsupportedOperation: + canseek = False + databytes = data.read() + self.ulsize = len(databytes) + self._upbuffer = io.BytesIO(databytes) + if canseek: + data.seek(0, 2) + self.ulsize = data.tell() + data.seek(curroff, 0) + self._upbuffer = data ulhdrs['Content-Type'] = b'application/octet-stream' ulhdrs['Content-Length'] = self.ulsize webclient = self.dupe() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi.egg-info/PKG-INFO new/pyghmi-1.5.63/pyghmi.egg-info/PKG-INFO --- old/pyghmi-1.5.61/pyghmi.egg-info/PKG-INFO 2023-04-13 14:58:48.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi.egg-info/PKG-INFO 2023-08-28 16:40:08.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.5.61 +Version: 1.5.63 Summary: Python General Hardware Management Initiative (IPMI and others) Home-page: http://github.com/openstack/pyghmi/ Author: Jarrod Johnson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.61/pyghmi.egg-info/pbr.json new/pyghmi-1.5.63/pyghmi.egg-info/pbr.json --- old/pyghmi-1.5.61/pyghmi.egg-info/pbr.json 2023-04-13 14:58:48.000000000 +0200 +++ new/pyghmi-1.5.63/pyghmi.egg-info/pbr.json 2023-08-28 16:40:08.000000000 +0200 @@ -1 +1 @@ -{"git_version": "537fccb", "is_release": true} \ No newline at end of file +{"git_version": "b2f46df", "is_release": true} \ No newline at end of file
