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 2022-06-20 15:39:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyghmi (Old) and /work/SRC/openSUSE:Factory/.python-pyghmi.new.1548 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyghmi" Mon Jun 20 15:39:01 2022 rev:18 rq:983966 version:1.5.29 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyghmi/python-pyghmi.changes 2021-05-10 15:39:56.865424845 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyghmi.new.1548/python-pyghmi.changes 2022-06-20 15:39:36.335063956 +0200 @@ -1,0 +2,43 @@ +Mon Dec 6 09:28:49 UTC 2021 - Dirk M??ller <dmuel...@suse.com> + +- update to 1.5.29: + * Configure default initialization when creating a volume + * Fix redfish firmware update without progress + * Provide access to read redfish location info + * Adapt the generic redfish virtual media call + * Remove nulls and FFs if present + * Add location data to redfish module + * Tolerate more standard variations + * Fix PSU fan count for 9-PSU chasssis + * Some firmware presents GiB instead of GB + * Fix SMM build id + * Correct mispelling in error message + * Update to new form of get gpio command + * Improve generic non-support message + * Fix TSMA error on media upload attempt + * Apply new PSU configuration to non-FPC variants + * Improve dense PSU support + * Fix SOL behavior after print\_error + * Do not error on None callback + * Fix user enablement on SMM on python3 + * Remove null bytes within a string + * Support SMMv2 variant of VPD + * Add Drip Sensor to water cooled SMMv2 + * Use most recent python tests + * Reduce average memory of XCC Uefi configuration + * Remove 2.7 from test + * Tolerate spec deviations + * Fix relog attempt + * Fix redfish FFDC name save with autosuffix + * Fix SMMv2 ffdc download + * Accept . for \_ in redfish names + * Fix DHCP config logic on static input + * Show LXPM bundle information for TSM + * Fix TSM LXPM handling + * Fix plain rom update of nextscale + * Add reseat to redfish command for XCC + * Implement support for remote reseat + * Support passing file obj for media + * Fix data parameter for apply\_license + +------------------------------------------------------------------- Old: ---- pyghmi-1.5.23.tar.gz New: ---- pyghmi-1.5.29.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyghmi.spec ++++++ --- /var/tmp/diff_new_pack.TU5Z0E/_old 2022-06-20 15:39:37.043064992 +0200 +++ /var/tmp/diff_new_pack.TU5Z0E/_new 2022-06-20 15:39:37.047064998 +0200 @@ -17,13 +17,13 @@ Name: python-pyghmi -Version: 1.5.23 +Version: 1.5.29 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.23.tar.gz +Source0: https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.5.29.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.23 %sphinx_build -b html doc/source doc/build/html +PYTHONPATH=. PBR_VERSION=1.5.29 %sphinx_build -b html doc/source doc/build/html rm -rf doc/build/html/.{doctrees,buildinfo} %install ++++++ _service ++++++ --- /var/tmp/diff_new_pack.TU5Z0E/_old 2022-06-20 15:39:37.075065039 +0200 +++ /var/tmp/diff_new_pack.TU5Z0E/_new 2022-06-20 15:39:37.079065044 +0200 @@ -1,8 +1,8 @@ <services> <service mode="disabled" name="renderspec"> - <param name="input-template">https://opendev.org/openstack/rpm-packaging/raw/branch/stable/wallaby/openstack/pyghmi/pyghmi.spec.j2</param> + <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/branch/master/requirements.txt</param> + <param name="requirements">https://opendev.org/x/pyghmi/raw/master/requirements.txt</param> <param name="changelog-email">cloud-de...@suse.de</param> <param name="changelog-provider">gh,openstack,pyghmi</param> </service> ++++++ pyghmi-1.5.23.tar.gz -> pyghmi-1.5.29.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/ChangeLog new/pyghmi-1.5.29/ChangeLog --- old/pyghmi-1.5.23/ChangeLog 2021-03-02 17:17:34.000000000 +0100 +++ new/pyghmi-1.5.29/ChangeLog 2021-08-04 13:47:00.000000000 +0200 @@ -1,6 +1,68 @@ CHANGES ======= +1.5.29 +------ + +* Configure default initialization when creating a volume +* Fix redfish firmware update without progress +* Provide access to read redfish location info +* Adapt the generic redfish virtual media call +* Remove nulls and FFs if present +* Add location data to redfish module +* Tolerate more standard variations + +1.5.28 +------ + +* Fix PSU fan count for 9-PSU chasssis +* Some firmware presents GiB instead of GB + +1.5.27 +------ + +* Fix SMM build id +* Correct mispelling in error message +* Update to new form of get gpio command +* Improve generic non-support message + +1.5.26 +------ + +* Fix TSMA error on media upload attempt +* Apply new PSU configuration to non-FPC variants +* Improve dense PSU support +* Fix SOL behavior after print\_error +* Do not error on None callback +* Fix user enablement on SMM on python3 +* Remove null bytes within a string +* Support SMMv2 variant of VPD +* Add Drip Sensor to water cooled SMMv2 +* Use most recent python tests +* Reduce average memory of XCC Uefi configuration +* Remove 2.7 from test +* Tolerate spec deviations +* Fix relog attempt +* Fix redfish FFDC name save with autosuffix + +1.5.25 +------ + +* Fix SMMv2 ffdc download +* Accept . for \_ in redfish names +* Fix DHCP config logic on static input + +1.5.24 +------ + +* Show LXPM bundle information for TSM +* Fix TSM LXPM handling +* Fix plain rom update of nextscale +* Add reseat to redfish command for XCC +* Implement support for remote reseat +* Support passing file obj for media +* Fix data parameter for apply\_license + 1.5.23 ------ @@ -10,6 +72,7 @@ * Support updating from a file-like object * Fix error on long apply phase * Fix SMM ffdc behavior +* Do not use openstackdocstheme * Add remote presence assertion to bmc config * Port fast media list from XCC IPMI plugin * Move inventory to oem to allow override diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/PKG-INFO new/pyghmi-1.5.29/PKG-INFO --- old/pyghmi-1.5.23/PKG-INFO 2021-03-02 17:17:35.173040900 +0100 +++ new/pyghmi-1.5.29/PKG-INFO 2021-08-04 13:47:00.565451100 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.5.23 +Version: 1.5.29 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.23/doc/requirements.txt new/pyghmi-1.5.29/doc/requirements.txt --- old/pyghmi-1.5.23/doc/requirements.txt 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/doc/requirements.txt 2021-08-04 13:46:26.000000000 +0200 @@ -1,5 +1,4 @@ sphinx>=2.0.0,!=2.1.0 # BSD -openstackdocstheme>=2.2.1 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/doc/source/conf.py new/pyghmi-1.5.29/doc/source/conf.py --- old/pyghmi-1.5.23/doc/source/conf.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/doc/source/conf.py 2021-08-04 13:46:26.000000000 +0200 @@ -29,7 +29,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'openstackdocstheme'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -86,7 +86,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'openstackdocs' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/command.py new/pyghmi-1.5.29/pyghmi/ipmi/command.py --- old/pyghmi-1.5.23/pyghmi/ipmi/command.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/command.py 2021-08-04 13:46:26.000000000 +0200 @@ -771,7 +771,12 @@ one byte, return the int value """ fetchcmd = bytearray((channel, param, 0, 0)) - fetched = self.xraw_command(0xc, 2, data=fetchcmd) + try: + fetched = self.xraw_command(0xc, 2, data=fetchcmd) + except exc.IpmiException as ie: + if ie.ipmicode == 0x80: + return None + raise fetchdata = fetched['data'] if bytearray(fetchdata)[0] != 17: return None @@ -2028,7 +2033,7 @@ self.oem_init() return self._oem.detach_remote_media() - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): """Upload a file to be hosted on the target BMC This will upload the specified data to @@ -2040,7 +2045,7 @@ :param progress: Optional callback for progress updates """ self.oem_init() - return self._oem.upload_media(filename, progress) + return self._oem.upload_media(filename, progress, data) def list_media(self): """List attached remote media @@ -2068,4 +2073,4 @@ def apply_license(self, filename, progress=None, data=None): self.oem_init() - return self._oem.apply_license(filename, progress, data=None) + return self._oem.apply_license(filename, progress, data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/console.py new/pyghmi-1.5.29/pyghmi/ipmi/console.py --- old/pyghmi-1.5.23/pyghmi/ipmi/console.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/console.py 2021-08-04 13:46:26.000000000 +0200 @@ -199,7 +199,7 @@ if self.ipmi_session: self.ipmi_session.unregister_keepalive(self.keepaliveid) - if self.activated: + if self.activated and self.ipmi_session is not None: try: self.ipmi_session.raw_command(netfn=6, command=0x49, data=(1, 1, 0, 0, 0, 0)) @@ -295,7 +295,7 @@ needskeepalive=False): while not (self.connected or self.broken): session.Session.wait_for_rsp(timeout=10) - if not self.ipmi_session.logged: + if self.ipmi_session is None or not self.ipmi_session.logged: self._print_error('Session no longer connected') raise exc.IpmiException('Session no longer connected') self.ipmi_session.send_payload(payload, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/fru.py new/pyghmi-1.5.29/pyghmi/ipmi/fru.py --- old/pyghmi-1.5.23/pyghmi/ipmi/fru.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/fru.py 2021-08-04 13:46:26.000000000 +0200 @@ -133,7 +133,7 @@ try: self.fetch_fru(fruid) except iexc.IpmiException as ie: - if ie.ipmicode in (203, 129): + if ie.ipmicode in (195, 201, 203, 129): return raise self.parsedata() @@ -229,6 +229,7 @@ # Additionally 0xfe has been observed, which should be a thorn, but # again assuming termination of string is more likely than thorn. retinfo = retinfo.rstrip(b'\xfe\xff\x10\x03\x00 ') + retinfo = retinfo.replace(b'\x00', b'') if lang in (0, 25): try: retinfo = retinfo.decode('iso-8859-1') @@ -262,7 +263,7 @@ if offset == 0: return if self.databytes[offset] & 0b1111 != 1: - raise iexc.BmcErrorException("Invallid/Unsupported chassis area") + raise iexc.BmcErrorException("Invalid/Unsupported chassis area") inf = self.info # ignore length field, just process the data inf['Chassis type'] = enclosure_types[self.databytes[offset + 2]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/oem/generic.py new/pyghmi-1.5.29/pyghmi/ipmi/oem/generic.py --- old/pyghmi-1.5.23/pyghmi/ipmi/oem/generic.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/oem/generic.py 2021-08-04 13:46:26.000000000 +0200 @@ -277,8 +277,9 @@ def attach_remote_media(self, imagename, username, password): raise exc.UnsupportedFunctionality() - def upload_media(self, filename, progress): - raise exc.UnsupportedFunctionality() + def upload_media(self, filename, progress, data): + raise exc.UnsupportedFunctionality( + 'Remote media upload not supported on this system') def list_media(self): raise exc.UnsupportedFunctionality() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/handler.py new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/handler.py --- old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/handler.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/handler.py 2021-08-04 13:46:26.000000000 +0200 @@ -155,7 +155,7 @@ elif self.has_imm: self.immhandler = imm.IMMClient(ipmicmd) elif self.is_fpc: - self.smmhandler = nextscale.SMMClient(ipmicmd) + self.smmhandler = nextscale.SMMClient(ipmicmd, self.is_fpc) elif self.has_tsma: conn = wc.SecureHTTPConnection( ipmicmd.bmc, 443, @@ -284,6 +284,8 @@ def reseat_bay(self, bay): if self.is_fpc: return self.smmhandler.reseat_bay(bay) + elif self.has_xcc and bay == -1: + return self.immhandler.reseat() return super(OEMHandler, self).reseat_bay(bay) def get_ntp_enabled(self): @@ -411,6 +413,8 @@ return iter(self.oem_inventory_info) elif self.has_imm: return self.immhandler.get_hw_descriptions() + elif self.is_fpc: + return self.smmhandler.get_inventory_descriptions(self.is_fpc) return () def get_oem_inventory(self): @@ -421,6 +425,11 @@ elif self.has_imm: for inv in self.immhandler.get_hw_inventory(): yield inv + elif self.is_fpc: + for compname in self.smmhandler.get_inventory_descriptions( + self.ipmicmd, self.is_fpc): + yield (compname, self.smmhandler.get_inventory_of_component( + self.ipmicmd, compname)) def get_sensor_data(self): if self.has_imm: @@ -428,7 +437,8 @@ yield self.immhandler.get_oem_sensor_reading(name, self.ipmicmd) elif self.is_fpc: - for name in nextscale.get_sensor_names(self._fpc_variant): + for name in nextscale.get_sensor_names(self.ipmicmd, + self._fpc_variant): yield nextscale.get_sensor_reading(name, self.ipmicmd, self._fpc_variant) @@ -436,7 +446,8 @@ if self.has_imm: return self.immhandler.get_oem_sensor_descriptions(self.ipmicmd) elif self.is_fpc: - return nextscale.get_sensor_descriptions(self._fpc_variant) + return nextscale.get_sensor_descriptions( + self.ipmicmd, self._fpc_variant) return () def get_sensor_reading(self, sensorname): @@ -454,6 +465,8 @@ return self.oem_inventory_info.get(component, None) if self.has_imm: return self.immhandler.get_component_inventory(component) + if self.is_fpc: + return self.smmhandler.get_inventory_of_component(component) def _collect_tsm_inventory(self): self.oem_inventory_info = {} @@ -650,8 +663,8 @@ return nextscale.get_fpc_firmware(bmcver, self.ipmicmd, self._fpc_variant) elif self.has_tsma: - return self.tsmahandler.get_firmware_inventory(components, - raisebypass=False) + return self.tsmahandler.get_firmware_inventory( + components, raisebypass=False, ipmicmd=self.ipmicmd) return super(OEMHandler, self).get_oem_firmware(bmcver, components) def get_diagnostic_data(self, savefile, progress, autosuffix=False): @@ -660,7 +673,8 @@ autosuffix) if self.is_fpc: return self.smmhandler.get_diagnostic_data(savefile, progress, - autosuffix) + autosuffix, + self._fpc_variant) if self.has_tsma: return self.tsmahandler.get_diagnostic_data(savefile, progress, autosuffix) @@ -1048,10 +1062,10 @@ netfn=0x32, command=0x9f, data=(8, 10, 0, 0)) self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=(8, 11)) - def upload_media(self, filename, progress): + def upload_media(self, filename, progress, data): if self.has_xcc or self.has_imm: - return self.immhandler.upload_media(filename, progress) - return super(OEMHandler, self).upload_media(filename, progress) + return self.immhandler.upload_media(filename, progress, data) + return super(OEMHandler, self).upload_media(filename, progress, data) def list_media(self): if self.has_xcc or self.has_imm: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/imm.py new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/imm.py --- old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/imm.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/imm.py 2021-08-04 13:46:26.000000000 +0200 @@ -134,7 +134,6 @@ self._wc = None # The webclient shall be initiated on demand self._energymanager = None self.datacache = {} - self.webkeepalive = None self._keepalivesession = None self.fwc = None self.fwo = None @@ -425,7 +424,7 @@ except KeyError: return None - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): xid = random.randint(0, 1000000000) alloc = self.wc.grab_json_response( '/data/set', @@ -439,7 +438,7 @@ uploadfields['available'] = alloc['available'] uploadfields['checksum'] = xid ut = webclient.FileUploader( - self.wc, '/designs/imm/upload/rp_image_upload.esp', filename, + self.wc, '/designs/imm/upload/rp_image_upload.esp', filename, data, otherfields=uploadfields) ut.start() while ut.isAlive(): @@ -858,8 +857,16 @@ def __init__(self, ipmicmd): super(XCCClient, self).__init__(ipmicmd) + self.ipmicmd.ipmi_session.register_keepalive(self.keepalive, None) self.adp_referer = None + def reseat(self): + rsp = self.wc.grab_json_response_with_status( + '/api/providers/virt_reseat', '{}') + if rsp[1] != 200 or rsp[0].get('return', 1) != 0: + raise pygexc.UnsupportedFunctionality( + 'This platform does not support AC reseat.') + def get_description(self): dsc = self.wc.grab_json_response('/DeviceDescription.json') dsc = dsc[0] @@ -1337,6 +1344,10 @@ write_policy = vol.write_policy else: write_policy = props["cpwb"] + if vol.default_init is not None: + default_init = vol.default_init + else: + default_init = props["initstate"] strsize = 'remainder' if vol.size is None else str(vol.size) if strsize in ('all', '100%'): volsize = params['capacity'] @@ -1357,7 +1368,7 @@ 'Requested sizes exceed available capacity') vols.append('{0};{1};{2};{3};{4};{5};{6};{7};{8};|'.format( name, volsize, stripsize, write_policy, read_policy, - props['cpio'], props['ap'], props['dcp'], props['initstate'])) + props['cpio'], props['ap'], props['dcp'], default_init)) url = '/api/function' cid = params['controller'].split(',') cnum = cid[0] @@ -1466,9 +1477,11 @@ spares.append(diskinfo) else: disks.append(diskinfo) - totalsize = pool['totalCapacityStr'].replace('GB', '') + totalsize = pool['totalCapacityStr'].replace( + 'GB', '').replace('GiB', '') totalsize = int(float(totalsize) * 1024) - freesize = pool['freeCapacityStr'].replace('GB', '') + freesize = pool['freeCapacityStr'].replace( + 'GB', '').replace('GiB', '') freesize = int(float(freesize) * 1024) pools.append(storage.Array( disks=disks, raid=pool['rdlvlstr'], volumes=volumes, @@ -1522,14 +1535,15 @@ raise pygexc.InvalidParameterValue( 'Given location was unreachable by the XCC') raise Exception('Unhandled return: ' + repr(rt)) - if not self.webkeepalive: + if not self._keepalivesession: self._keepalivesession = self._wc - self.webkeepalive = self.ipmicmd.ipmi_session.register_keepalive( - self.keepalive, None) self._wc = None def keepalive(self): - self._refresh_token_wc(self._keepalivesession) + if self.fwo and util._monotonic_time() - self.fwovintage > 15: + self.fwo = None + if self._keepalivesession: + self._refresh_token_wc(self._keepalivesession) def fetch_psu_firmware(self): psudata = self.get_cached_data('lenovo_cached_psu') @@ -1643,9 +1657,7 @@ yield firm def detach_remote_media(self): - if self.webkeepalive: - self.ipmicmd.ipmi_session.unregister_keepalive(self.webkeepalive) - self._keepalivesession = None + self._keepalivesession = None rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_getdisk') if 'items' in rt: slots = [] @@ -1684,11 +1696,11 @@ yield media.Media(mt['filename']) self.weblogout() - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): xid = random.randint(0, 1000000000) self._refresh_token() uploadthread = webclient.FileUploader( - self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, None) + self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, data) uploadthread.start() while uploadthread.isAlive(): uploadthread.join(3) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/nextscale.py new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/nextscale.py --- old/pyghmi-1.5.23/pyghmi/ipmi/oem/lenovo/nextscale.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/oem/lenovo/nextscale.py 2021-08-04 13:46:26.000000000 +0200 @@ -37,6 +37,24 @@ pass +psutypes = { + 0x63: 'CFF v3 550W PT', + 0x64: 'CFF v3 750W PT', + 0x65: 'CFF v3 750W TT', + 0x66: 'CFF v3 1100W PT', + 0x67: 'CFF v3 1600W PT', + 0x68: 'CFF v3 2000W PT', + 0x6B: 'CFF v4 500W PT', + 0x6C: 'CFF v4 750W PT', + 0x6D: 'CFF v4 750W TT', + 0x6E: 'CFF v4 1100W PT', + 0x6F: 'CFF v4 1100W -48Vdc', + 0x70: 'CFF v4 1100W 200-277Vac/240-380Vdc', + 0x71: 'CFF v4 1800W PT', + 0x72: 'CFF v4 2400W PT', +} + + def fromstring(inputdata): if b'!entity' in inputdata.lower(): raise Exception('!ENTITY not supported in this interface') @@ -158,6 +176,33 @@ return struct.unpack_from('<H', rsp['data'][3:])[0] +def get_psu_count(ipmicmd, variant): + if variant == 0x26: + mymsg = ipmicmd.xraw_command(netfn=0x32, command=0xa8) + builddata = bytearray(mymsg['data']) + if builddata[13] == 3: + return 9 + else: + return 6 + else: + return variant & 0xf + + +def fpc_get_dripstatus(ipmicmd, number, sz): + health = pygconst.Health.Ok + states = [] + rsp = ipmicmd.xraw_command(0x34, 5) + rdata = bytearray(rsp['data']) + number = number - 1 + if rdata[0] & (1 << number) == 0: + states.append('Absent') + health = pygconst.Health.Critical + if rdata[1] & (1 << number) != 0: + states.append('Leak detected') + health = pygconst.Health.Critical + return (health, states) + + fpc_sensors = { 'AC Power': { 'type': 'Power', @@ -183,7 +228,7 @@ 'type': 'Fan', 'units': 'RPM', 'provider': fpc_read_psu_fan, - 'elements': 1, + 'elementsfun': get_psu_count, }, 'Total Power Capacity': { 'type': 'Power', @@ -201,13 +246,22 @@ 'type': 'Power Supply', 'returns': 'tuple', 'units': None, + 'elements': True, 'provider': fpc_get_psustatus, - 'elements': 1, + 'elementsfun': get_psu_count, + }, + 'Drip Sensor': { + 'type': 'Management Subsystem Health', + 'elements': True, + 'abselements': 2, + 'returns': 'tuple', + 'units': None, + 'provider': fpc_get_dripstatus, } } -def get_sensor_names(size): +def get_sensor_names(ipmicmd, size): global fpc_sensors for name in fpc_sensors: if size != 6 and name in ('Fan Power', 'Total Power Capacity', @@ -215,8 +269,19 @@ continue if size == 6 and name == 'PSU Power Loss': continue + if size != 0x26 and name == 'Drip Sensor': + continue sensor = fpc_sensors[name] - if 'elements' in sensor: + + if 'abselements' in sensor: + for elemidx in range(sensor['abselements']): + elemidx += 1 + yield '{0} {1}'.format(name, elemidx) + elif 'elementsfun' in sensor: + for elemidx in range(sensor['elementsfun'](ipmicmd, size)): + elemidx += 1 + yield '{0} {1}'.format(name, elemidx) + elif 'elements' in sensor: for elemidx in range(sensor['elements'] * (size & 0b11111)): elemidx += 1 yield '{0} {1}'.format(name, elemidx) @@ -224,7 +289,7 @@ yield name -def get_sensor_descriptions(size): +def get_sensor_descriptions(ipmicmd, size): global fpc_sensors for name in fpc_sensors: if size != 6 and name in ('Fan Power', 'Total Power Capacity', @@ -232,8 +297,20 @@ continue if size == 6 and name == 'PSU Power Loss': continue + if size != 0x26 and name == 'Drip Sensor': + continue sensor = fpc_sensors[name] - if 'elements' in sensor: + if 'abselements' in sensor: + for elemidx in range(sensor['abselements']): + elemidx += 1 + yield {'name': '{0} {1}'.format(name, elemidx), + 'type': sensor['type']} + elif 'elementsfun' in sensor: + for elemidx in range(sensor['elementsfun'](ipmicmd, size)): + elemidx += 1 + yield {'name': '{0} {1}'.format(name, elemidx), + 'type': sensor['type']} + elif 'elements' in sensor: for elemidx in range(sensor['elements'] * (size & 0b11111)): elemidx += 1 yield {'name': '{0} {1}'.format(name, elemidx), @@ -252,7 +329,7 @@ else: name = 'SMM' buildid = '{0}{1}{2}{3}{4}{5}{6}'.format( - *[chr(x) for x in builddata[-7:]]) + *[chr(x) for x in builddata[6:13]]) elif len(builddata) == 8: builddata = builddata[1:] # discard the 'completion code' name = 'FPC' @@ -279,12 +356,20 @@ else: bnam, _, idx = name.rpartition(' ') idx = int(idx) - if bnam in fpc_sensors and idx <= fpc_sensors[bnam]['elements'] * sz: - sensor = fpc_sensors[bnam] - if 'returns' in sensor: - health, states = sensor['provider'](ipmicmd, idx, sz) - else: - value = sensor['provider'](ipmicmd, idx, sz) + if bnam in fpc_sensors: + max = -1 + if 'abselements' in fpc_sensors[bnam]: + max = fpc_sensors[bnam]['abselements'] + elif 'elementsfun' in fpc_sensors[bnam]: + max = 99 + elif 'elements' in fpc_sensors[bnam]: + max = fpc_sensors[bnam]['elements'] * sz + if idx <= max: + sensor = fpc_sensors[bnam] + if 'returns' in sensor: + health, states = sensor['provider'](ipmicmd, idx, sz) + else: + value = sensor['provider'](ipmicmd, idx, sz) if sensor is not None: return sdr.SensorReading({'name': name, 'imprecision': None, 'value': value, 'states': states, @@ -296,7 +381,8 @@ class SMMClient(object): - def __init__(self, ipmicmd): + def __init__(self, ipmicmd, variant): + self.smm_variant = variant self.ipmicmd = weakref.proxy(ipmicmd) self.smm = ipmicmd.bmc self.username = ipmicmd.ipmi_session.userid @@ -341,7 +427,7 @@ settings[rule] = {'value': int(ruleinfo.text)} dwc = self.ipmicmd.xraw_command(0x32, 0x94) dwc = bytearray(dwc['data']) - if len(dwc) != 3 or dwc[0] == 1: + if len(dwc) not in (3, 4) or dwc[0] == 1: rsp = self.ipmicmd.xraw_command(0x34, 3) fanmode = self.fanmodes[bytearray(rsp['data'])[0]] settings['fanspeed'] = { @@ -473,7 +559,7 @@ self.wc.request('POST', '/data', rules) self.wc.getresponse().read() if powercfg != [None, None]: - if variant == 2: + if variant != 6: if None in powercfg: currcfg = self.ipmicmd.xraw_command(0x32, 0xa2) currcfg = bytearray(currcfg['data']) @@ -498,8 +584,10 @@ if priv.lower() == 'administrator': rsp = self.ipmicmd.xraw_command(netfn=6, command=0x46, data=(uid,)) username = bytes(rsp['data']).rstrip(b'\x00') + if not isinstance(username, str): + username = username.decode('utf8') self.wc.request( - 'POST', '/data', b'set=user({0},1,{1},511,,4,15,0)'.format( + 'POST', '/data', 'set=user({0},1,{1},511,,4,15,0)'.format( uid, username)) rsp = self.wc.getresponse() rsp.read() @@ -508,7 +596,8 @@ self.ipmicmd.xraw_command(netfn=0x32, command=0xa4, data=[int(bay), 2]) - def get_diagnostic_data(self, savefile, progress=None, autosuffix=False): + def get_diagnostic_data(self, savefile, progress=None, autosuffix=False, + variant=None): rsp = self.ipmicmd.xraw_command(netfn=0x32, command=0xb1, data=[0]) if bytearray(rsp['data'])[0] != 0: raise Exception("Service data generation already in progress") @@ -529,7 +618,10 @@ progress({'phase': 'initializing', 'progress': initpct}) if self.wc is None: raise Exception("Failed to connect to web api") - url = '/preview/smm-ffdc.tgz?ST1={0}'.format(self.st1) + if variant and variant >> 5: + url = '/preview/smm2-ffdc.tgz?ST1={0}'.format(self.st1) + else: + url = '/preview/smm-ffdc.tgz?ST1={0}'.format(self.st1) if autosuffix and not savefile.endswith('.tgz'): savefile += '-smm-ffdc.tgz' fd = webclient.FileDownloader(self.wc, url, savefile) @@ -544,13 +636,17 @@ return savefile def process_fru(self, fru): + smmv1 = self.smm_variant & 0xf0 == 0 # TODO(jjohnson2): can also get EIOM, SMM, and riser data if warranted - fru['Serial Number'] = bytes(self.ipmicmd.xraw_command( - netfn=0x32, command=0xb0, data=(5, 1))['data'][:]).strip( - b' \x00\xff').replace(b'\xff', b'') - fru['Model'] = bytes(self.ipmicmd.xraw_command( - netfn=0x32, command=0xb0, data=(5, 0))['data'][:]).strip( - b' \x00\xff').replace(b'\xff', b'') + snum = bytes(self.ipmicmd.xraw_command( + netfn=0x32, command=0xb0, data=(5, 1))['data'][:]) + mnum = bytes(self.ipmicmd.xraw_command( + netfn=0x32, command=0xb0, data=(5, 0))['data'][:]) + if not smmv1: + snum = snum[2:] + mnum = mnum[2:] + fru['Serial Number'] = snum.strip(b' \x00\xff').replace(b'\xff', b'') + fru['Model'] = mnum.strip(b' \x00\xff').replace(b'\xff', b'') return fru def get_webclient(self): @@ -712,6 +808,8 @@ if data and hasattr(data, 'read'): if zipfile.is_zipfile(data): z = zipfile.ZipFile(data) + else: + data.seek(0) elif data is None and zipfile.is_zipfile(filename): z = zipfile.ZipFile(filename) if z: @@ -765,6 +863,48 @@ complete = percent >= 100.0 return 'complete' + def get_inventory_descriptions(self, ipmicmd, variant): + psucount = get_psu_count(ipmicmd, variant) + for idx in range(psucount): + yield 'PSU {}'.format(idx + 1) + + def get_inventory_of_component(self, ipmicmd, component): + psuidx = int(component.replace('PSU ', '')) + return self.get_psu_info(ipmicmd, psuidx) + + def get_psu_info(self, ipmicmd, psunum): + psuinfo = ipmicmd.xraw_command(0x34, 0x6, data=(psunum,)) + psuinfo = bytearray(psuinfo['data']) + psutype = struct.unpack('<H', psuinfo[23:25])[0] + psui = {} + if psuinfo[0] != 1: + return {'Model': 'Unavailable'} + psui['Revision'] = psuinfo[34] + psui['Description'] = psutypes.get( + psutype, 'Unknown ({})'.format(psutype)) + psui['Part Number'] = str(psuinfo[35:47].strip( + b' \x00\xff').decode('utf8')) + psui['FRU Number'] = str(psuinfo[47:59].strip( + b' \x00\xff').decode('utf8')) + psui['Serial Number'] = str(psuinfo[59:71].strip( + b' \x00\xff').decode('utf8')) + psui['Header Code'] = str(psuinfo[71:75].strip( + b' \x00\xff').decode('utf8')) + psui['Vendor'] = str(psuinfo[25:29].strip( + b' \x00\xff').decode('utf8')) + psui['Manufacturing Date'] = '20{}-W{}'.format( + psuinfo[77:79].decode('utf8'), psuinfo[75:77].decode('utf8')) + psui['Primary Firmware Version'] = '{:x}.{:x}'.format( + psuinfo[80], psuinfo[79]) + psui['Secondary Firmware Version'] = '{:x}.{:x}'.format( + psuinfo[82], psuinfo[81]) + psui['Model'] = str(psuinfo[5:23].strip(b' \x00\xff').decode('utf8')) + psui['Manufacturer Location'] = str( + psuinfo[83:85].strip(b' \x00\xff').decode('utf8')) + psui['Barcode'] = str(psuinfo[85:108].strip( + b' \x00\xff').decode('utf8')) + return psui + def logout(self): self.wc.request('POST', '/data/logout', None) rsp = self.wc.getresponse() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/ipmi/private/session.py new/pyghmi-1.5.29/pyghmi/ipmi/private/session.py --- old/pyghmi-1.5.23/pyghmi/ipmi/private/session.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/ipmi/private/session.py 2021-08-04 13:46:26.000000000 +0200 @@ -1580,6 +1580,7 @@ # RAKP4 got dropped, BMC can consider it done and we must # restart self._relog() + return # ignore 15 value if we are retrying. # xCAT did but I can't recall why exactly if data[1] == 15 and self.logontries: @@ -1820,6 +1821,8 @@ # if a code somehow makes duplicate SOL handlers, # this would notify all the handlers rather than just the # last one to take ownership + if self._customkeepalives[ka][1] is None: + continue self._customkeepalives[ka][1]( {'error': 'Session Disconnected'}) self._customkeepalives = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/redfish/command.py new/pyghmi-1.5.29/pyghmi/redfish/command.py --- old/pyghmi-1.5.23/pyghmi/redfish/command.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/redfish/command.py 2021-08-04 13:46:26.000000000 +0200 @@ -1,5 +1,5 @@ # coding: utf8 -# Copyright 2019 Lenovo +# Copyright 2021 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -527,6 +527,17 @@ currinfo = self._do_web_request(self.sysurl, cache=False) return {'powerstate': str(currinfo['PowerState'].lower())} + def reseat_bay(self, bay): + """Request the reseat of a bay + + Request the enclosure manager to reseat the system in a particular + bay. + + :param bay: The bay identifier to reseat + :return: + """ + self.oem.reseat_bay(bay) + def set_power(self, powerstate, wait=False): if powerstate == 'boot': oldpowerstate = self.get_power()['powerstate'] @@ -1099,6 +1110,10 @@ if fnmatch(attr.lower(), change.lower()): found = True changeset[attr] = changeset[change] + if fnmatch(attr.lower(), + change.replace('.', '_').lower()): + found = True + changeset[attr] = changeset[change] if found: del changeset[change] for change in changeset: @@ -1157,11 +1172,13 @@ netmask = _cidr_to_mask(int(cidr)) patch['IPv4StaticAddresses'] = [ipinfo] ipinfo['Address'] = ipv4_address + ipv4_configuration = 'static' if netmask: ipinfo['SubnetMask'] = netmask if ipv4_gateway: patch['IPv4StaticAddresses'] = [ipinfo] ipinfo['Gateway'] = ipv4_gateway + ipv4_configuration = 'static' if ipv4_configuration.lower() == 'dhcp': dodhcp = True patch['DHCPv4'] = {'DHCPEnabled': True} @@ -1262,6 +1279,63 @@ def get_inventory(self, withids=False): return self.oem.get_inventory(withids) + def get_location_information(self): + locationinfo = {} + for chassis in self.sysinfo.get('Links', {}).get('Chassis', []): + chassisurl = chassis['@odata.id'] + data = self._do_web_request(chassisurl) + locdata = data.get('Location', {}) + postaladdress = locdata.get('PostalAddress', {}) + placement = locdata.get('Placement', {}) + contactinfo = locdata.get('Contacts', []) + currval = postaladdress.get('Room', '') + if currval: + locationinfo['room'] = currval + currval = postaladdress.get('Location', '') + if currval: + locationinfo['location'] = currval + currval = postaladdress.get('Building', '') + if currval: + locationinfo['building'] = currval + currval = placement.get('Rack', '') + if currval: + locationinfo['rack'] = currval + for contact in contactinfo: + contact = contact.get('ContactName', '') + if not contact: + continue + if 'contactnames' not in locationinfo: + locationinfo['contactnames'] = [contact] + else: + locationinfo['contactnames'].append(contact) + return locationinfo + + def set_location_information(self, room=None, contactnames=None, + location=None, building=None, rack=None): + locationinfo = {} + postaladdress = {} + placement = {} + if contactnames is not None: + locationinfo['Contacts'] = [ + {'ContactName': x} for x in contactnames] + if room is not None: + postaladdress['Room'] = room + if location is not None: + postaladdress['Location'] = location + if building is not None: + postaladdress['Building'] = building + if rack is not None: + placement['Rack'] = rack + if postaladdress: + locationinfo['PostalAddress'] = postaladdress + if placement: + locationinfo['Placement'] = placement + if locationinfo: + for chassis in self.sysinfo.get('Links', {}).get('Chassis', []): + chassisurl = chassis['@odata.id'] + self._do_web_request(chassisurl, {'Location': locationinfo}, + method='PATCH') + @property def oem(self): if not self._oem: @@ -1439,10 +1513,16 @@ return for vmurl in vmurls: vminfo = self._do_web_request(vmurl, cache=False) - if vminfo['ConnectedVia'] != 'NotConnected': + if vminfo.get('ConnectedVia', None) != 'NotConnected': continue - self._do_web_request(vmurl, {'Image': url, 'Inserted': True}, - 'PATCH') + try: + self._do_web_request(vmurl, {'Image': url, 'Inserted': True}, + 'PATCH') + except exc.RedfishError as re: + if re.msgid.endswith(u'PropertyUnknown'): + self._do_web_request(vmurl, {'Image': url}, 'PATCH') + else: + raise break def detach_remote_media(self): @@ -1458,12 +1538,19 @@ for vminfo in self._do_bulk_requests(vmurls): vminfo, currl = vminfo if vminfo['Image']: - self._do_web_request(currl, - {'Image': None, - 'Inserted': False}, - method='PATCH') + try: + self._do_web_request(currl, + {'Image': None, + 'Inserted': False}, + method='PATCH') + except exc.RedfishError as re: + if re.msgid.endswith(u'PropertyUnknown'): + self._do_web_request(currl, {'Image': None}, + method='PATCH') + else: + raise - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): """Upload a file to be hosted on the target BMC This will upload the specified data to @@ -1474,7 +1561,7 @@ will be given to the bmc. :param progress: Optional callback for progress updates """ - return self.oem.upload_media(filename, progress) + return self.oem.upload_media(filename, progress, data) def update_firmware(self, file, data=None, progress=None, bank=None): """Send file to BMC to perform firmware update @@ -1486,6 +1573,8 @@ update process. Provide if :param bank: Indicate a target 'bank' of firmware if supported """ + if progress is None: + progress = lambda x: True return self.oem.update_firmware(file, data, progress, bank) def get_diagnostic_data(self, savefile, progress=None, autosuffix=False): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/redfish/oem/generic.py new/pyghmi-1.5.29/pyghmi/redfish/oem/generic.py --- old/pyghmi-1.5.23/pyghmi/redfish/oem/generic.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/redfish/oem/generic.py 2021-08-04 13:46:26.000000000 +0200 @@ -73,11 +73,12 @@ vmurls = [x['@odata.id'] for x in vmlist.get('Members', [])] for vminfo in fishclient._do_bulk_requests(vmurls): vminfo = vminfo[0] - if vminfo['Image']: + if vminfo.get('Image', None): imageurl = vminfo['Image'].replace( '/' + vminfo['ImageName'], '') yield media.Media(vminfo['ImageName'], imageurl) - elif vminfo['Inserted'] and vminfo['ImageName']: + elif vminfo.get('Inserted', None) and vminfo.get( + 'ImageName', None): yield media.Media(vminfo['ImageName']) def get_inventory_descriptions(self, withids=False): @@ -117,19 +118,32 @@ } yield ('System', sysinfo) self._hwnamemap = {} + cpumemurls = [] memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None) + if memurl: + cpumemurls.append(memurl) cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None) - list(self._do_bulk_requests([memurl, cpurl])) + if cpurl: + cpumemurls.append(cpurl) + list(self._do_bulk_requests(cpumemurls)) adpurls = self._get_adp_urls() - cpurls = self._get_cpu_urls() - memurls = self._get_mem_urls() + if cpurl: + cpurls = self._get_cpu_urls() + else: + cpurls = [] + if memurl: + memurls = self._get_mem_urls() + else: + memurls = [] diskurls = self._get_disk_urls() allurls = adpurls + cpurls + memurls + diskurls list(self._do_bulk_requests(allurls)) - for cpu in self._get_cpu_inventory(withids=withids, urls=cpurls): - yield cpu - for mem in self._get_mem_inventory(withids=withids, urls=memurls): - yield mem + if cpurl: + for cpu in self._get_cpu_inventory(withids=withids, urls=cpurls): + yield cpu + if memurl: + for mem in self._get_mem_inventory(withids=withids, urls=memurls): + yield mem for adp in self._get_adp_inventory(withids=withids, urls=adpurls): yield adp for disk in self._get_disk_inventory(withids=withids, urls=diskurls): @@ -324,7 +338,7 @@ raise exc.UnsupportedFunctionality( 'Remote storage configuration not supported on this platform') - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): raise exc.UnsupportedFunctionality( 'Remote media upload not supported on this platform') @@ -398,3 +412,7 @@ def get_user_expiration(self, uid): return None + + def reseat_bay(self, bay): + raise exc.UnsupportedFunctionality( + 'Bay reseat not supported on this platform') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/redfish/oem/lenovo/tsma.py new/pyghmi-1.5.29/pyghmi/redfish/oem/lenovo/tsma.py --- old/pyghmi-1.5.23/pyghmi/redfish/oem/lenovo/tsma.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/redfish/oem/lenovo/tsma.py 2021-08-04 13:46:27.000000000 +0200 @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import struct import time try: @@ -34,6 +35,17 @@ 'hash_size', 'combo_image'] +def cstr_to_str(cstr): + try: + endidx = cstr.index(b'\x00') + cstr = cstr[:endidx] + except Exception: + pass + if not isinstance(cstr, str): + cstr = cstr.decode('utf8') + return cstr + + def read_hpm(filename, data): hpminfo = [] if data: @@ -309,7 +321,8 @@ if status != 200: raise Exception(repr(rsp)) - def get_firmware_inventory(self, components, raisebypass=True): + def get_firmware_inventory(self, components, raisebypass=True, + ipmicmd=None): wc = self.wc fwinf, status = wc.grab_json_response_with_status( '/api/DeviceVersion') @@ -343,6 +356,13 @@ lxpmres['version'] = '{0}.{1:02x}'.format( lxpminf['main'], subver) yield ('LXPM', lxpmres) + if ipmicmd: + rsp = ipmicmd.xraw_command(0x3c, 0x40, data=(7, 2)) + buildid = cstr_to_str(bytes(rsp['data'])) + yield ('LXPM Windows Driver Bundle', {'build': buildid}) + rsp = ipmicmd.xraw_command(0x3c, 0x40, data=(7, 3)) + buildid = cstr_to_str(bytes(rsp['data'])) + yield ('LXPM Linux Driver Bundle', {'build': buildid}) name = 'TSM' fwinf, status = wc.grab_json_response_with_status('/api/get-sysfwinfo') if status != 200: @@ -401,14 +421,16 @@ def update_firmware(self, filename, data=None, progress=None, bank=None): wc = self.wc wc.set_header('Content-Type', 'application/json') + basefilename = os.path.basename(filename) if filename.endswith('.hpm'): return self.update_hpm_firmware(filename, progress, wc, data) - elif 'uefi' in filename and filename.endswith('.rom'): + elif 'uefi' in basefilename and filename.endswith('.rom'): return self.update_sys_firmware(filename, progress, wc, data=data) - elif 'amd-sas' in filename and filename.endswith('.bin'): + elif 'amd-sas' in basefilename and filename.endswith('.bin'): return self.update_sys_firmware(filename, progress, wc, data=data, type='bp') - elif 'lxpm' in filename and filename.endswith('.img'): + elif (('lxpm' in basefilename or 'fw_drv' in basefilename) + and filename.endswith('.img')): return self.update_lxpm_firmware(filename, progress, wc, data) else: raise Exception('Unsupported filename {0}'.format(filename)) @@ -752,7 +774,7 @@ {'image_name': name, 'image_type': img['image_type'], 'image_index': img['image_index']}) - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): raise exc.UnsupportedFunctionality( 'Remote media upload not supported on this system') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/redfish/oem/lenovo/xcc.py new/pyghmi-1.5.29/pyghmi/redfish/oem/lenovo/xcc.py --- old/pyghmi-1.5.23/pyghmi/redfish/oem/lenovo/xcc.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/redfish/oem/lenovo/xcc.py 2021-08-04 13:46:27.000000000 +0200 @@ -95,6 +95,16 @@ self.updating = False self.datacache = {} + def reseat_bay(self, bay): + if bay != -1: + raise pygexc.UnsupportedFunctionality( + 'This is not an enclosure manager') + rsp = self.wc.grab_json_response_with_status( + '/api/providers/virt_reseat', '{}') + if rsp[1] != 200 or rsp[0].get('return', 1) != 0: + raise pygexc.UnsupportedFunctionality( + 'This platform does not support AC reseat.') + def get_cached_data(self, attribute, age=30): try: kv = self.datacache[attribute] @@ -298,9 +308,11 @@ spares.append(diskinfo) else: disks.append(diskinfo) - totalsize = pool['totalCapacityStr'].replace('GB', '') + totalsize = pool['totalCapacityStr'].replace( + 'GB', '').replace('GiB', '') totalsize = int(float(totalsize) * 1024) - freesize = pool['freeCapacityStr'].replace('GB', '') + freesize = pool['freeCapacityStr'].replace( + 'GB', '').replace('GiB', '') freesize = int(float(freesize) * 1024) pools.append(storage.Array( disks=disks, raid=pool['rdlvlstr'], volumes=volumes, @@ -503,6 +515,10 @@ write_policy = vol.write_policy else: write_policy = props["cpwb"] + if vol.default_init is not None: + default_init = vol.default_init + else: + default_init = props["initstate"] strsize = 'remainder' if vol.size is None else str(vol.size) if strsize in ('all', '100%'): volsize = params['capacity'] @@ -524,7 +540,7 @@ 'Requested sizes exceed available capacity') vols.append('{0};{1};{2};{3};{4};{5};{6};{7};{8};|'.format( name, volsize, stripsize, write_policy, read_policy, - props['cpio'], props['ap'], props['dcp'], props['initstate'])) + props['cpio'], props['ap'], props['dcp'], default_init)) url = '/api/function' cid = params['controller'].split(',') cnum = cid[0] @@ -656,11 +672,11 @@ raise pygexc.InvalidParameterValue( 'XCC does not have required license for operation') - def upload_media(self, filename, progress=None): + def upload_media(self, filename, progress=None, data=None): xid = random.randint(0, 1000000000) self._refresh_token() uploadthread = webclient.FileUploader( - self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, None) + self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, data) uploadthread.start() while uploadthread.isAlive(): uploadthread.join(3) @@ -918,7 +934,7 @@ {'Generate_FFDC_status': 1}) url = '/ffdc/{0}'.format(result['FileName']) if autosuffix and not savefile.endswith('.tzz'): - savefile += '.tzz' + savefile += '-{0}'.format(result['FileName']) fd = webclient.FileDownloader(self.wc, url, savefile) fd.start() while fd.isAlive(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/storage.py new/pyghmi-1.5.29/pyghmi/storage.py --- old/pyghmi-1.5.23/pyghmi/storage.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/storage.py 2021-08-04 13:46:27.000000000 +0200 @@ -64,7 +64,8 @@ class Volume(object): def __init__(self, name=None, size=None, status=None, id=None, - stripsize=None, read_policy=None, write_policy=None): + stripsize=None, read_policy=None, write_policy=None, + default_init=None): """Define a Volume as an object :param name: Name of the volume @@ -74,6 +75,7 @@ :param stripsize: The stripsize of the volume in kibibytes :param read_policy: The read policy of the volume :param write_policy: The write policy of the volume + :param default_init: The default initialization of the volume """ self.name = name if isinstance(size, int): @@ -93,6 +95,7 @@ self.stripsize = stripsize self.read_policy = read_policy self.write_policy = write_policy + self.default_init = default_init class ConfigSpec(object): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi/util/webclient.py new/pyghmi-1.5.29/pyghmi/util/webclient.py --- old/pyghmi-1.5.23/pyghmi/util/webclient.py 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi/util/webclient.py 2021-08-04 13:46:27.000000000 +0200 @@ -239,7 +239,12 @@ raise body = rsp.read() if rsp.getheader('Content-Encoding', None) == 'gzip': - body = gzip.GzipFile(fileobj=io.BytesIO(body)).read() + try: + body = gzip.GzipFile(fileobj=io.BytesIO(body)).read() + except IOError: + # some implementations will send non-gzipped and claim it as + # gzip + pass if rsp.status >= 200 and rsp.status < 300: if body and not isinstance(body, str): body = body.decode('utf8') @@ -307,7 +312,12 @@ % rsp.read()) body = rsp.read() if rsp.getheader('Content-Encoding', None) == 'gzip': - body = gzip.GzipFile(fileobj=io.BytesIO(body)).read() + try: + body = gzip.GzipFile(fileobj=io.BytesIO(body)).read() + except IOError: + # In case the implementation lied, let the body return + # unprocessed + pass return body def get_upload_progress(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/pyghmi.egg-info/PKG-INFO new/pyghmi-1.5.29/pyghmi.egg-info/PKG-INFO --- old/pyghmi-1.5.23/pyghmi.egg-info/PKG-INFO 2021-03-02 17:17:35.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi.egg-info/PKG-INFO 2021-08-04 13:47:00.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.5.23 +Version: 1.5.29 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.23/pyghmi.egg-info/pbr.json new/pyghmi-1.5.29/pyghmi.egg-info/pbr.json --- old/pyghmi-1.5.23/pyghmi.egg-info/pbr.json 2021-03-02 17:17:35.000000000 +0100 +++ new/pyghmi-1.5.29/pyghmi.egg-info/pbr.json 2021-08-04 13:47:00.000000000 +0200 @@ -1 +1 @@ -{"git_version": "32f81a8", "is_release": true} \ No newline at end of file +{"git_version": "1cd47f7", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/tox.ini new/pyghmi-1.5.29/tox.ini --- old/pyghmi-1.5.23/tox.ini 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/tox.ini 2021-08-04 13:46:27.000000000 +0200 @@ -1,6 +1,6 @@ [tox] minversion = 3.9.0 -envlist = py27,py3,pep8 +envlist = py3,pep8 skipsdist = True ignore_basepython_conflict=true @@ -27,12 +27,6 @@ whitelist_externals = bash commands = flake8 -[testenv:py27] -deps = - -c{toxinidir}/py27-constraints.txt - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - [testenv:cover] setenv = {[testenv]setenv} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.5.23/zuul.d/project.yaml new/pyghmi-1.5.29/zuul.d/project.yaml --- old/pyghmi-1.5.23/zuul.d/project.yaml 2021-03-02 17:16:57.000000000 +0100 +++ new/pyghmi-1.5.29/zuul.d/project.yaml 2021-08-04 13:46:27.000000000 +0200 @@ -7,8 +7,7 @@ - project: templates: - check-requirements - - openstack-python-jobs - - openstack-python3-wallaby-jobs + - openstack-python3-xena-jobs - build-openstack-docs-pti check: jobs: