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:

Reply via email to