Hello community,

here is the log from the commit of package python-pyghmi for openSUSE:Factory 
checked in at 2018-10-09 15:53:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyghmi (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyghmi.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyghmi"

Tue Oct  9 15:53:42 2018 rev:11 rq:640679 version:1.2.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyghmi/python-pyghmi.changes      
2018-09-07 15:39:29.502530490 +0200
+++ /work/SRC/openSUSE:Factory/.python-pyghmi.new/python-pyghmi.changes 
2018-10-09 15:53:45.374311969 +0200
@@ -1,0 +2,26 @@
+Tue Sep 11 07:54:24 UTC 2018 - cloud-de...@suse.de
+
+- update to version 1.2.4
+  - Wrap bytes in a StringIO
+  - fix tox python3 overrides
+  - Support default for numeric_data
+  - Fix notification of custom keepalive on broken
+  - Attempt to logout on the way out when broken
+  - Require python-cryptography
+  - Add specific message for missing media license
+  - Try to clear presence in initting sessions on broken
+  - Abandon a broken session
+  - Fix uploads without otherfields
+  - Add IMMv2 remote media upload
+  - Move SOL payload retries to console.py
+  - Fix build id of FPC
+  - Python 3.7 compatibility: async is reserved keyword
+  - Restore IMMv2 mount attempts
+  - Fix console input with unicode
+  - Properly pass formname when using FileUploader
+  - Add progress indication to SMM update
+  - Improve pyghmi performance
+  - Print SOL error on broken ipmi session
+  - Provide a grace period for session validity
+
+-------------------------------------------------------------------

Old:
----
  pyghmi-1.1.0.tar.gz

New:
----
  pyghmi-1.2.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyghmi.spec ++++++
--- /var/tmp/diff_new_pack.1XA3JH/_old  2018-10-09 15:53:46.030311191 +0200
+++ /var/tmp/diff_new_pack.1XA3JH/_new  2018-10-09 15:53:46.034311187 +0200
@@ -17,13 +17,13 @@
 
 
 Name:           python-pyghmi
-Version:        1.1.0
+Version:        1.2.4
 Release:        0
 Summary:        General Hardware Management Initiative (IPMI and others)
 License:        Apache-2.0
 Group:          Development/Languages/Python
 URL:            https://launchpad.net/pyghmi
-Source0:        
https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.1.0.tar.gz
+Source0:        
https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.2.4.tar.gz
 BuildRequires:  openstack-macros
 BuildRequires:  python-devel
 BuildRequires:  python2-cryptography >= 2.1

++++++ pyghmi-1.1.0.tar.gz -> pyghmi-1.2.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/AUTHORS new/pyghmi-1.2.4/AUTHORS
--- old/pyghmi-1.1.0/AUTHORS    2018-05-22 20:18:09.000000000 +0200
+++ new/pyghmi-1.2.4/AUTHORS    2018-08-09 19:23:06.000000000 +0200
@@ -1,6 +1,7 @@
 Akira Yoshiyama <akirayoshiy...@gmail.com>
 Allan Vidal <avi...@lenovo.com>
 Andreas Jaeger <a...@suse.com>
+Daniel Speichert <daniel_speich...@comcast.com>
 Derek Higgins <der...@redhat.com>
 Devananda van der Veen <devananda....@gmail.com>
 Dmitry Tantsur <divius.ins...@gmail.com>
@@ -28,6 +29,7 @@
 Tim Rozet <tro...@redhat.com>
 Tovin Seven <vin...@vn.fujitsu.com>
 Zuul <z...@review.openstack.org>
+huang.zhiping <huang.zhip...@99cloud.net>
 lijingxin <lijing...@sinorail.com>
 linggao <ling...@us.ibm.com>
 luke.li <lilu7...@fiberhome.com>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/ChangeLog new/pyghmi-1.2.4/ChangeLog
--- old/pyghmi-1.1.0/ChangeLog  2018-05-22 20:18:09.000000000 +0200
+++ new/pyghmi-1.2.4/ChangeLog  2018-08-09 19:23:06.000000000 +0200
@@ -1,6 +1,39 @@
 CHANGES
 =======
 
+1.2.4
+-----
+
+* Fix notification of custom keepalive on broken
+
+1.2.2
+-----
+
+* Fix console input with unicode
+
+1.2.0
+-----
+
+* Improve pyghmi performance
+* Abandon a broken session
+* Python 3.7 compatibility: async is reserved keyword
+* Try to clear presence in initting sessions on broken
+* Support default for numeric\_data
+* Print SOL error on broken ipmi session
+* Provide a grace period for session validity
+* Fix build id of FPC
+* Require python-cryptography
+* Fix uploads without otherfields
+* Attempt to logout on the way out when broken
+* Properly pass formname when using FileUploader
+* Add progress indication to SMM update
+* Add IMMv2 remote media upload
+* Wrap bytes in a StringIO
+* Restore IMMv2 mount attempts
+* Move SOL payload retries to console.py
+* fix tox python3 overrides
+* Add specific message for missing media license
+
 1.1.0
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/PKG-INFO new/pyghmi-1.2.4/PKG-INFO
--- old/pyghmi-1.1.0/PKG-INFO   2018-05-22 20:18:10.000000000 +0200
+++ new/pyghmi-1.2.4/PKG-INFO   2018-08-09 19:23:07.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyghmi
-Version: 1.1.0
+Version: 1.2.4
 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.1.0/pyghmi/ipmi/command.py 
new/pyghmi-1.2.4/pyghmi/ipmi/command.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/command.py     2018-05-22 20:15:14.000000000 
+0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/command.py     2018-08-09 19:20:06.000000000 
+0200
@@ -412,7 +412,7 @@
                                             retry=retry, timeout=timeout)
         if 'error' in rsp:
             raise exc.IpmiException(rsp['error'], rsp['code'])
-        rsp['data'] = buffer(bytearray(rsp['data']))
+        rsp['data'] = buffer(rsp['data'])
         return rsp
 
     def raw_command(self, netfn, command, bridge_request=(), data=(),
@@ -434,10 +434,13 @@
         :param timeout: A custom amount of time to wait for initial reply
         :returns: dict -- The response from IPMI device
         """
-        return self.ipmi_session.raw_command(netfn=netfn, command=command,
-                                             bridge_request=bridge_request,
-                                             data=data, delay_xmit=delay_xmit,
-                                             retry=retry, timeout=timeout)
+        rsp = self.ipmi_session.raw_command(netfn=netfn, command=command,
+                                            bridge_request=bridge_request,
+                                            data=data, delay_xmit=delay_xmit,
+                                            retry=retry, timeout=timeout)
+        if 'data' in rsp:
+            rsp['data'] = list(rsp['data'])
+        return rsp
 
     def get_power(self):
         """Get current power state of the managed system
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/console.py 
new/pyghmi-1.2.4/pyghmi/ipmi/console.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/console.py     2018-05-22 20:15:14.000000000 
+0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/console.py     2018-08-09 19:20:06.000000000 
+0200
@@ -22,6 +22,7 @@
 
 from pyghmi.ipmi.private import constants
 from pyghmi.ipmi.private import session
+from pyghmi.ipmi.private.util import _monotonic_time
 
 
 class Console(object):
@@ -257,22 +258,36 @@
         breakbyte = 0
         if sendbreak:
             breakbyte = 0b10000
-        payload = struct.pack("BBBB", self.myseq, 0, 0, breakbyte)
-        payload += output
+        try:
+            payload = bytearray((self.myseq, 0, 0, breakbyte)) + output
+        except TypeError:  # bytearray hits unicode...
+            payload = bytearray((self.myseq, 0, 0, breakbyte
+                                 )) + output.encode('utf8')
         self.lasttextsize = len(output)
         needskeepalive = False
         if self.lasttextsize == 0:
             needskeepalive = True
         self.awaitingack = True
-        payload = struct.unpack("%dB" % len(payload), payload)
         self.lastpayload = payload
-        self.send_payload(payload, needskeepalive=needskeepalive)
+        self.send_payload(payload, retry=False, needskeepalive=needskeepalive)
+        retries = 5
+        while retries and self.awaitingack:
+            expiry = _monotonic_time() + 5.5 - retries
+            while self.awaitingack and _monotonic_time() < expiry:
+                self.wait_for_rsp(0.5)
+            if self.awaitingack:
+                self.send_payload(payload, retry=False,
+                                  needskeepalive=needskeepalive)
+            retries -= 1
+        if not retries:
+            self._print_error('Connection lost')
 
     def send_payload(self, payload, payload_type=1, retry=True,
                      needskeepalive=False):
         while not (self.connected or self.broken):
             session.Session.wait_for_rsp(timeout=10)
         if 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,
                                        payload_type=payload_type,
@@ -331,10 +346,10 @@
                 remdatalen = len(payload[4:])  # store remote len before dupe
                 # retry logic, we must ack *this* many even if it is
                 # a retry packet with new partial data
-                remdata = struct.pack("%dB" % remdatalen, *payload[4:])
+                remdata = bytes(payload[4:])
             if newseq == self.remseq:  # it is a retry, but could have new data
                 if remdatalen > self.lastsize:
-                    remdata = remdata[4 + self.lastsize:]
+                    remdata = bytes(remdata[4 + self.lastsize:])
                 else:  # no new data...
                     remdata = ""
             else:  # TODO(jbjohnso) what if remote sequence number is wrong??
@@ -342,7 +357,7 @@
             self.lastsize = remdatalen
             if remdata:  # Do not subject callers to empty data
                 self._print_data(remdata)
-            ackpayload = (0, self.remseq, remdatalen, 0)
+            ackpayload = bytearray((0, self.remseq, remdatalen, 0))
             # Why not put pending data into the ack? because it's rare
             # and might be hard to decide what to do in the context of
             # retry situation
@@ -363,7 +378,6 @@
                 else:  # retry all or part of packet, but in a new form
                     # also add pending output for efficiency and ease
                     newtext = self.lastpayload[4 + ackcount:]
-                    newtext = struct.pack("B"*len(newtext), *newtext)
                     with self.outputlock:
                         if (self.pendingoutput and
                                 not isinstance(self.pendingoutput[0], dict)):
@@ -382,7 +396,7 @@
             # try to mitigate by avoiding overeager retries
             # occasional retry of a packet
             # sooner than timeout suggests is evidently a big deal
-            self.send_payload(payload=self.lastpayload)
+            self.send_payload(payload=self.lastpayload, retry=False)
 
     def main_loop(self):
         """Process all events until no more sessions exist.
@@ -461,16 +475,16 @@
                 remdatalen = len(payload[4:])  # store remote len before dupe
                 # retry logic, we must ack *this* many even if it is
                 # a retry packet with new partial data
-                remdata = struct.pack("%dB" % remdatalen, *payload[4:])
+                remdata = bytes(payload[4:])
             if newseq == self.remseq:  # it is a retry, but could have new data
                 if remdatalen > self.lastsize:
-                    remdata = remdata[4 + self.lastsize:]
+                    remdata = bytes(remdata[4 + self.lastsize:])
                 else:  # no new data...
                     remdata = ""
             else:  # TODO(jbjohnso) what if remote sequence number is wrong??
                 self.remseq = newseq
             self.lastsize = remdatalen
-            ackpayload = (0, self.remseq, remdatalen, flag)
+            ackpayload = bytearray((0, self.remseq, remdatalen, flag))
             # Why not put pending data into the ack? because it's rare
             # and might be hard to decide what to do in the context of
             # retry situation
@@ -486,7 +500,6 @@
             self.awaitingack = False
             if nacked and not breakdetected:  # the BMC was in some way unhappy
                 newtext = self.lastpayload[4 + ackcount:]
-                newtext = struct.pack("B"*len(newtext), *newtext)
                 with self.outputlock:
                     if (self.pendingoutput and
                             not isinstance(self.pendingoutput[0], dict)):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/config.py 
new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/config.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/config.py   2018-05-22 
20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/config.py   2018-08-09 
19:20:06.000000000 +0200
@@ -299,6 +299,7 @@
                                 protect = True  # not supported yet
                             else:
                                 current = instance.text
+                            default = onedata.get('default', None)
                     if (setting.find('cmd_data') is not None or
                             setting.find('boolean_data') is not None):
                         protect = True  # Hide currently unsupported settings
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/handler.py 
new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/handler.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/handler.py  2018-05-22 
20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/handler.py  2018-08-09 
19:20:06.000000000 +0200
@@ -933,12 +933,12 @@
             self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=(8, 11))
 
     def upload_media(self, filename, progress):
-        if self.has_xcc:
+        if self.has_xcc or self.has_imm:
             return self.immhandler.upload_media(filename, progress)
         return super(OEMHandler, self).upload_media(filename, progress)
 
     def list_media(self):
-        if self.has_xcc:
+        if self.has_xcc or self.has_imm:
             return self.immhandler.list_media()
         return super(OEMHandler, self).list_media()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/imm.py 
new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/imm.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/imm.py      2018-05-22 
20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/imm.py      2018-08-09 
19:20:06.000000000 +0200
@@ -32,7 +32,6 @@
 import re
 import socket
 import struct
-import threading
 import urllib
 import weakref
 
@@ -78,19 +77,6 @@
         ' \xff\x00')
 
 
-class FileUploader(threading.Thread):
-
-    def __init__(self, webclient, url, filename, data):
-        self.wc = webclient
-        self.url = url
-        self.filename = filename
-        self.data = data
-        super(FileUploader, self).__init__()
-
-    def run(self):
-        self.rsp = self.wc.upload(self.url, self.filename, self.data)
-
-
 class IMMClient(object):
     logouturl = '/data/logout'
     bmcname = 'IMM'
@@ -284,6 +270,13 @@
                 if 'token2_name' in rspdata and 'token2_value' in rspdata:
                     wc.set_header(rspdata['token2_name'],
                                   rspdata['token2_value'])
+                if 'token3_name' in rspdata and 'token3_value' in rspdata:
+                    self.uploadtoken = {rspdata['token3_name']:
+                                        rspdata['token3_value']}
+                else:
+                    self.uploadtoken = {}
+                wc.set_header('Referer', self.adp_referer)
+                wc.set_header('Host', self.imm)
                 return wc
 
     @property
@@ -315,31 +308,96 @@
         except KeyError:
             return None
 
+    def upload_media(self, filename, progress=None):
+        xid = random.randint(0, 1000000000)
+        alloc = self.wc.grab_json_response(
+            '/data/set',
+            'RP_VmAllocateLoc({0},{1},1)'.format(self.username, filename))
+        if alloc['return'] != 'Success':
+            raise Exception('Unexpected reply to allocation: ' + repr(alloc))
+        slotid = alloc['slotId']
+        uploadfields = self.uploadtoken
+        uploadfields['filePath'] = alloc['filePath']
+        uploadfields['uploadType'] = 'iframe'
+        uploadfields['available'] = alloc['available']
+        uploadfields['checksum'] = xid
+        ut = webclient.FileUploader(
+            self.wc, '/designs/imm/upload/rp_image_upload.esp', filename,
+            otherfields=uploadfields)
+        ut.start()
+        while ut.isAlive():
+            ut.join(3)
+            if progress:
+                progress({'phase': 'upload',
+                          'progress': 100 * self.wc.get_upload_progress()})
+        status = self.wc.grab_json_response(
+            '/designs/imm/upload/rp_image_upload_status.esp',
+            'filePath={0}'.format(alloc['filePath']))
+        if not status['rpImgUploadResult'].endswith('Success'):
+            raise Exception(
+                'Upload status returned unexpected data: ' + repr(alloc))
+        ups = self.wc.grab_json_response(
+            '/data/set',
+            'RP_VmUpdateSize({1}, {0})'.format(status['originalFileSize'],
+                                               slotid))
+        if ups['return'] != 'Success':
+            raise Exception('Unexpected return to update size: ' + repr(ups))
+        ups = self.wc.grab_json_response('/data/set',
+                                         'RP_VmMount({0})'.format(slotid))
+        if ups['return'] != 'Success':
+            raise Exception('Unexpected return to mount: ' + repr(ups))
+        if progress:
+            progress({'phase': 'complete'})
+
     def attach_remote_media(self, url, user, password):
         url = url.replace(':', '\:')
         params = urllib.urlencode({
             'RP_VmAllocateMountUrl({0},{1},1,,)'.format(
                 self.username, url): ''
         })
-        result = self.wc.grab_json_response('/data?set', params)
+        result = self.wc.grab_json_response('/data?set', params,
+                                            referer=self.adp_referer)
+        if not result:
+            result = self.wc.grab_json_response('/data/set', params,
+                                                referer=self.adp_referer)
         if result['return'] != 'Success':
             raise Exception(result['reason'])
         self.weblogout()
 
+    def list_media(self):
+        rt = self.wc.grab_json_response(
+            '/designs/imm/dataproviders/imm_rp_images.php',
+            referer=self.adp_referer)
+        for item in rt['items']:
+            if 'images' in item:
+                for uload in item['images']:
+                    if uload['status'] != 0:
+                        yield media.Media(uload['filename'])
+
     def detach_remote_media(self):
         mnt = self.wc.grab_json_response(
-            '/designs/imm/dataproviders/imm_rp_images.php')
+            '/designs/imm/dataproviders/imm_rp_images.php',
+            referer=self.adp_referer)
         removeurls = []
         for item in mnt['items']:
             if 'urls' in item:
                 for url in item['urls']:
                     removeurls.append(url['url'])
+            if 'images' in item:
+                for uload in item['images']:
+                    self.wc.grab_json_response(
+                        '/data/set', 'RP_RemoveFile({0}, 0)'.format(
+                            uload['slotId']))
         for url in removeurls:
             url = url.replace(':', '\:')
             params = urllib.urlencode({
                 'RP_VmAllocateUnMountUrl({0},{1},0,)'.format(
                     self.username, url): ''})
-            result = self.wc.grab_json_response('/data?set', params)
+            result = self.wc.grab_json_response('/data?set', params,
+                                                referer=self.adp_referer)
+            if not result:
+                result = self.wc.grab_json_response('/data/set', params,
+                                                    referer=self.adp_referer)
             if result['return'] != 'Success':
                 raise Exception(result['reason'])
         self.weblogout()
@@ -954,6 +1012,9 @@
             if rt['return'] in (657, 659, 656):
                 raise pygexc.InvalidParameterValue(
                     'Given location was unreachable by the XCC')
+            if rt['return'] == 32:
+                raise pygexc.InvalidParameterValue(
+                    'XCC does not have required license for operation')
             raise Exception('Unhandled return: ' + repr(rt))
         rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_mountall',
                                         '{}')
@@ -1105,9 +1166,8 @@
 
     def upload_media(self, filename, progress=None):
         xid = random.randint(0, 1000000000)
-        uploadthread = FileUploader(self.wc,
-                                    '/upload?X-Progress-ID={0}'.format(xid),
-                                    filename, None)
+        uploadthread = webclient.FileUploader(
+            self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, None)
         uploadthread.start()
         while uploadthread.isAlive():
             uploadthread.join(3)
@@ -1192,9 +1252,8 @@
         if rsv['return'] != 0:
             raise Exception('Unexpected return to reservation: ' + repr(rsv))
         xid = random.randint(0, 1000000000)
-        uploadthread = FileUploader(self.wc,
-                                    '/upload?X-Progress-ID={0}'.format(xid),
-                                    filename, data)
+        uploadthread = webclient.FileUploader(
+            self.wc, '/upload?X-Progress-ID={0}'.format(xid), filename, data)
         uploadthread.start()
         uploadstate = None
         while uploadthread.isAlive():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/nextscale.py 
new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/nextscale.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/nextscale.py        2018-05-22 
20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/oem/lenovo/nextscale.py        2018-08-09 
19:20:06.000000000 +0200
@@ -209,7 +209,7 @@
     elif len(builddata) == 8:
         builddata = builddata[1:]  # discard the 'completion code'
         name = 'FPC'
-        buildid = '{0}{1}'.format(builddata[-2], chr(builddata[-1]))
+        buildid = '{0:02X}{1}'.format(builddata[-2], chr(builddata[-1]))
     yield (name, {'version': bmcver, 'build': buildid})
     yield ('PSOC', {'version': '{0}.{1}'.format(builddata[2], builddata[3])})
 
@@ -399,13 +399,19 @@
                     data = z.open(filename)
                     break
         progress({'phase': 'upload', 'progress': 0.0})
-        url = self.wc  # this is just to get self.st1 initted
         self.wc.request('POST', '/data', 'set=fwType:10')  # SMM firmware
         rsp = self.wc.getresponse()
         rsp.read()
         url = '/fwupload/fwupload.esp?ST1={0}'.format(self.st1)
-        self.wc.upload(url, filename, data, formname='fileUpload',
-                       otherfields={'preConfig': 'on'})
+        fu = webclient.FileUploader(
+            self.wc, url, filename, data, formname='fileUpload',
+            otherfields={'preConfig': 'on'})
+        fu.start()
+        while fu.isAlive():
+            fu.join(3)
+            if progress:
+                progress({'phase': 'upload',
+                          'progress': 100 * self.wc.get_upload_progress()})
         progress({'phase': 'validating', 'progress': 0.0})
         url = '/data'
         self.wc.request('POST', url, 'get=fwVersion,spfwInfo')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/ipmi/private/session.py 
new/pyghmi-1.2.4/pyghmi/ipmi/private/session.py
--- old/pyghmi-1.1.0/pyghmi/ipmi/private/session.py     2018-05-22 
20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/pyghmi/ipmi/private/session.py     2018-08-09 
19:20:06.000000000 +0200
@@ -248,18 +248,18 @@
     """ipmi demands a certain pad scheme,
     per table 13-20 AES-CBC encrypted payload fields.
     """
-    newdata = list(data)
     currlen = len(data) + 1  # need to count the pad length field as well
     neededpad = currlen % 16
     if neededpad:  # if it happens to be zero, hurray, but otherwise invert the
         # sense of the padding
         neededpad = 16 - neededpad
     padval = 1
+    pad = bytearray(neededpad)
     while padval <= neededpad:
-        newdata.append(padval)
+        pad[padval - 1] = padval
         padval += 1
-    newdata.append(neededpad)
-    return newdata
+    pad.append(neededpad)
+    return pad
 
 
 def _checksum(*data):  # Two's complement over the data
@@ -406,7 +406,7 @@
         with util.protect(KEEPALIVE_SESSIONS):
             sess = cls.keepalive_sessions.get(session, None)
             if sess is not None and 'timeout' in sess:
-                if sess['timeout'] < _monotonic_time():
+                if sess['timeout'] < _monotonic_time() - 15:
                     # session would have timed out by now, don't use it
                     return False
         return True
@@ -468,7 +468,7 @@
         if hasattr(self, 'initialized'):
             # new found an existing session, do not corrupt it
             if onlogon is None:
-                while self.logging:
+                while self.logging and not self.broken:
                     Session.wait_for_rsp()
             else:
                 if self.logging:
@@ -514,17 +514,17 @@
             self.kg = self.password
         self.port = port
         if onlogon is None:
-            self.async = False
+            self.async_ = False
             self.logonwaiters = [self._sync_login]
         else:
-            self.async = True
+            self.async_ = True
             self.logonwaiters = [onlogon]
         if self.__class__.socketchecking is None:
             self.__class__.socketchecking = threading.Lock()
         with self.socketchecking:
             self.socket = self._assignsocket(forbiddensockets=self.forbidsock)
         self.login()
-        if not self.async:
+        if not self.async_:
             while self.logging:
                 Session.wait_for_rsp()
         if self.broken:
@@ -537,35 +537,16 @@
             Session.keepalive_sessions.pop(self, None)
         with util.protect(WAITING_SESSIONS):
             Session.waiting_sessions.pop(self, None)
+        try:
+            del Session.initting_sessions[(self.bmc, self.userid,
+                                           self.password, self.port,
+                                           self.kgo)]
+        except KeyError:
+            pass
+        self.logout()
         self.logging = False
         self.errormsg = error
-        if self.logged:
-            self.logged = 0  # mark session as busted
-            self.logging = False
-            if self._customkeepalives:
-                for ka in list(self._customkeepalives):
-                    # Be thorough and notify parties through their custom
-                    # keepalives.  In practice, this *should* be the same, but
-                    # if a code somehow makes duplicate SOL handlers,
-                    # this would notify all the handlers rather than just the
-                    # last one to take ownership
-                    self._customkeepalives[ka][1](
-                        {'error': 'Session Disconnected'})
-            self._customkeepalives = None
-            if not self.broken:
-                self.socketpool[self.socket] -= 1
-                self.broken = True
-                # since this session is broken, remove it from the handler list
-                # This allows constructor to create a new, functional object to
-                # replace this one
-                myport = self.socket.getsockname()[1]
-                for sockaddr in self.allsockaddrs:
-                    if (sockaddr in Session.bmc_handlers and
-                            myport in Session.bmc_handlers[sockaddr]):
-                        del Session.bmc_handlers[sockaddr][myport]
-                        if Session.bmc_handlers[sockaddr] == {}:
-                            del Session.bmc_handlers[sockaddr]
-        elif not self.broken:
+        if not self.broken:
             self.broken = True
             self.socketpool[self.socket] -= 1
 
@@ -621,16 +602,16 @@
         """This function generate message for bridge request. It is a
         part of ipmi payload.
         """
-        head = [constants.IPMI_BMC_ADDRESS,
-                constants.netfn_codes['application'] << 2]
+        head = bytearray((constants.IPMI_BMC_ADDRESS,
+                          constants.netfn_codes['application'] << 2))
         check_sum = _checksum(*head)
         # NOTE(fengqian): according IPMI Figure 14-11, rqSWID is set to 81h
-        boday = [0x81, self.seqlun, constants.IPMI_SEND_MESSAGE_CMD,
-                 0x40 | channel]
+        boday = bytearray((0x81, self.seqlun, constants.IPMI_SEND_MESSAGE_CMD,
+                           0x40 | channel))
         # NOTE(fengqian): Track request
         self._add_request_entry((constants.netfn_codes['application'] + 1,
                                  self.seqlun, constants.IPMI_SEND_MESSAGE_CMD))
-        return head + [check_sum] + boday
+        return head + bytearray((check_sum,)) + boday
 
     def _add_request_entry(self, entry=()):
         """This function record the request with netfn, sequence number and
@@ -684,12 +665,12 @@
         # figure 13-4, first two bytes are rsaddr and
         # netfn, for non-bridge request, rsaddr is always 0x20 since we are
         # addressing BMC while rsaddr is specified forbridge request
-        header = [rsaddr, netfn << 2]
+        header = bytearray((rsaddr, netfn << 2))
 
-        reqbody = [rqaddr, self.seqlun, command] + list(data)
-        headsum = _checksum(*header)
-        bodysum = _checksum(*reqbody)
-        payload = header + [headsum] + reqbody + [bodysum]
+        reqbody = bytearray((rqaddr, self.seqlun, command)) + data
+        headsum = bytearray((_checksum(*header),))
+        bodysum = bytearray((_checksum(*reqbody),))
+        payload = header + headsum + reqbody + bodysum
         if bridge_request:
             payload = bridge_msg + payload
             # NOTE(fengqian): For bridge request, another check sum is needed.
@@ -792,11 +773,13 @@
         if retry is None:
             retry = not self.servermode
         if self.servermode:
-            data = [code] + list(data)
+            data = bytearray((code,)) + bytearray(data)
             if netfn is None:
                 netfn = self.clientnetfn
             if command is None:
                 command = self.clientcommand
+        else:
+            data = bytearray(data)
         ipmipayload = self._make_ipmi_payload(netfn, command, bridge_request,
                                               data)
         payload_type = constants.payload_types['ipmi']
@@ -828,10 +811,12 @@
             payload_type = self.last_payload_type
         if not payload:
             payload = self.lastpayload
-        message = [0x6, 0, 0xff, 0x07]  # constant RMCP header for IPMI
+        message = bytearray(b'\x06\x00\xff\x07')  # constant IPMI RMCP header
         if retry:
             self.lastpayload = payload
             self.last_payload_type = payload_type
+        if not isinstance(payload, bytearray):
+            payload = bytearray(payload)
         message.append(self.authtype)
         baretype = payload_type
         if self.integrityalgo:
@@ -846,10 +831,10 @@
             elif baretype not in constants.payload_types.values():
                 raise NotImplementedError(
                     "Unrecognized payload type %d" % baretype)
-            message += struct.unpack("!4B", struct.pack("<I", self.sessionid))
-        message += struct.unpack("!4B", struct.pack("<I", self.sequencenumber))
+            message += struct.pack("<I", self.sessionid)
+        message += struct.pack("<I", self.sequencenumber)
         if self.ipmiversion == 1.5:
-            message += struct.unpack("!4B", struct.pack("<I", self.sessionid))
+            message += struct.pack("<I", self.sessionid)
             if not self.authtype == 0:
                 message += self._ipmi15authcode(payload)
             message.append(len(payload))
@@ -873,23 +858,20 @@
                 message.append(newpsize & 0xff)
                 message.append(newpsize >> 8)
                 iv = os.urandom(16)
-                message += list(struct.unpack("16B", iv))
-                payloadtocrypt = _aespad(payload)
+                message += iv
+                payloadtocrypt = bytes(payload + _aespad(payload))
                 crypter = Cipher(
                     algorithm=algorithms.AES(self.aeskey),
                     mode=modes.CBC(iv),
                     backend=self._crypto_backend
                 )
                 encryptor = crypter.encryptor()
-                plaintext = struct.pack("%dB" % len(payloadtocrypt),
-                                        *payloadtocrypt)
-                crypted = encryptor.update(plaintext) + encryptor.finalize()
-                crypted = list(struct.unpack("%dB" % len(crypted), crypted))
-                message += crypted
+                message += encryptor.update(payloadtocrypt
+                                            ) + encryptor.finalize()
             else:  # no confidetiality algorithm
                 message.append(psize & 0xff)
                 message.append(psize >> 8)
-                message += list(payload)
+                message += payload
             if self.integrityalgo:  # see table 13-8,
                 # RMCP+ packet format
                 # TODO(jbjohnso): SHA256 which is now
@@ -897,18 +879,15 @@
                 neededpad = (len(message) - 2) % 4
                 if neededpad:
                     neededpad = 4 - neededpad
-                message += [0xff] * neededpad
+                message += b'\xff' * neededpad
                 message.append(neededpad)
                 message.append(7)  # reserved, 7 is the required value for the
                 # specification followed
-                integdata = message[4:]
-                authcode = hmac.new(self.k1,
-                                    struct.pack("%dB" % len(integdata),
-                                                *integdata),
+                message += hmac.new(self.k1,
+                                    bytes(message[4:]),
                                     hashlib.sha1).digest()[:12]  # SHA1-96
                 # per RFC2404 truncates to 96 bits
-                message += struct.unpack("12B", authcode)
-        self.netpacket = struct.pack("!%dB" % len(message), *message)
+        self.netpacket = message
         # advance idle timer since we don't need keepalive while sending
         # packets out naturally
         with util.protect(KEEPALIVE_SESSIONS):
@@ -930,19 +909,14 @@
         if padneeded < 0:
             raise exc.IpmiException("Password is too long for ipmi 1.5")
         password += '\x00' * padneeded
-        passdata = struct.unpack("16B", password)
         if checkremotecode:
-            seqbytes = struct.unpack("!4B",
-                                     struct.pack("<I", self.remsequencenumber))
+            seqbytes = struct.pack("<I", self.remsequencenumber)
         else:
-            seqbytes = struct.unpack("!4B",
-                                     struct.pack("<I", self.sequencenumber))
-        sessdata = struct.unpack("!4B", struct.pack("<I", self.sessionid))
-        bodydata = passdata + sessdata + tuple(payload) + seqbytes + passdata
-        dgst = hashlib.md5(
-            struct.pack("%dB" % len(bodydata), *bodydata)).digest()
-        hashdata = struct.unpack("!%dB" % len(dgst), dgst)
-        return hashdata
+            seqbytes = struct.pack("<I", self.sequencenumber)
+        sessdata = struct.pack("<I", self.sessionid)
+        bodydata = password + sessdata + payload + seqbytes + password
+        dgst = hashlib.md5(bodydata).digest()
+        return dgst
 
     def _got_channel_auth_cap(self, response):
         if 'error' in response:
@@ -1283,8 +1257,8 @@
             # things off ignore the second reply since we have one
             # satisfactory answer
         if data[4] in (0, 2):  # This is an ipmi 1.5 paylod
-            remsequencenumber = struct.unpack('<I', bytes(data[5:9]))[0]
-            remsessid = struct.unpack("<I", bytes(data[9:13]))[0]
+            remsequencenumber = struct.unpack('<I', data[5:9])[0]
+            remsessid = struct.unpack("<I", data[9:13])[0]
             if (remsequencenumber == 0 and remsessid == 0 and
                     qent[2] in Session.bmc_handlers):
                 # So a new ipmi client happens to get a previously seen and
@@ -1307,20 +1281,15 @@
 
             if remsessid != self.sessionid:
                 return -1  # does not match our session id, drop it
-            # now we need a mutable representation of the packet, rather than
-            # copying pieces of the packet over and over
-            rsp = list(struct.unpack("!%dB" % len(data), bytes(data)))
             authcode = False
             if data[4] == 2:  # we have authcode in this ipmi 1.5 packet
                 authcode = data[13:29]
-                del rsp[13:29]
+                del data[13:29]
                 # this is why we needed a mutable representation
-            payload = list(rsp[14:14 + rsp[13]])
+            payload = data[14:14 + data[13]]
             if authcode:
                 expectedauthcode = self._ipmi15authcode(payload,
                                                         checkremotecode=True)
-                expectedauthcode = struct.pack("%dB" % len(expectedauthcode),
-                                               *expectedauthcode)
                 if expectedauthcode != authcode:
                     return
             self._parse_ipmi_payload(payload)
@@ -1370,13 +1339,13 @@
             if self.k1 is None:  # we are in no shape to process a packet now
                 return
             expectedauthcode = hmac.new(
-                self.k1, bytes(data[4:-12]), hashlib.sha1).digest()[:12]
+                self.k1, data[4:-12], hashlib.sha1).digest()[:12]
             if authcode != expectedauthcode:
                 return  # BMC failed to assure integrity to us, drop it
-            sid = struct.unpack("<I", bytes(data[6:10]))[0]
+            sid = struct.unpack("<I", data[6:10])[0]
             if sid != self.localsid:  # session id mismatch, drop it
                 return
-            remseqnumber = struct.unpack("<I", bytes(data[10:14]))[0]
+            remseqnumber = struct.unpack("<I", data[10:14])[0]
             if (hasattr(self, 'remseqnumber') and
                 (remseqnumber < self.remseqnumber) and
                     (self.remseqnumber != 0xffffffff)):
@@ -1392,12 +1361,10 @@
                     backend=self._crypto_backend
                 )
                 decryptor = crypter.decryptor()
-                ciphertext = struct.pack("%dB" % len(payload[16:]),
-                                         *payload[16:])
-                decrypted = decryptor.update(ciphertext) + decryptor.finalize()
-                payload = struct.unpack("%dB" % len(decrypted), decrypted)
+                payload = bytearray(decryptor.update(bytes(payload[16:])
+                                                     ) + decryptor.finalize())
                 padsize = payload[-1] + 1
-                payload = list(payload[:-padsize])
+                payload = payload[:-padsize]
             if ptype == 0:
                 self._parse_ipmi_payload(payload)
             elif ptype == 1:  # There should be no other option
@@ -1647,8 +1614,7 @@
         # doing anything, though it shouldn't matter
         self.lastpayload = None
         self.last_payload_type = None
-        response = {}
-        response['netfn'] = payload[1] >> 2
+        response = {'netfn': payload[1] >> 2}
         # ^^ remove header of rsaddr/netfn/lun/checksum/rq/seq/lun
         del payload[0:5]
         # remove the trailing checksum
@@ -1777,7 +1743,29 @@
             Session.keepalive_sessions.pop(self, None)
         self.logged = 0
         self.logging = False
+        if self._customkeepalives:
+            for ka in list(self._customkeepalives):
+                # Be thorough and notify parties through their custom
+                # keepalives.  In practice, this *should* be the same, but
+                # if a code somehow makes duplicate SOL handlers,
+                # this would notify all the handlers rather than just the
+                # last one to take ownership
+                self._customkeepalives[ka][1](
+                    {'error': 'Session Disconnected'})
         self._customkeepalives = None
+        if not self.broken:
+            self.socketpool[self.socket] -= 1
+            self.broken = True
+            # since this session is broken, remove it from the handler list
+            # This allows constructor to create a new, functional object to
+            # replace this one
+            myport = self.socket.getsockname()[1]
+            for sockaddr in self.allsockaddrs:
+                if (sockaddr in Session.bmc_handlers and
+                        myport in Session.bmc_handlers[sockaddr]):
+                    del Session.bmc_handlers[sockaddr][myport]
+                    if Session.bmc_handlers[sockaddr] == {}:
+                        del Session.bmc_handlers[sockaddr]
         self.nowait = False
         self.socketpool[self.socket] -= 1
         return {'success': True}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi/util/webclient.py 
new/pyghmi-1.2.4/pyghmi/util/webclient.py
--- old/pyghmi-1.1.0/pyghmi/util/webclient.py   2018-05-22 20:15:14.000000000 
+0200
+++ new/pyghmi-1.2.4/pyghmi/util/webclient.py   2018-08-09 19:20:06.000000000 
+0200
@@ -20,13 +20,16 @@
 import pyghmi.exceptions as pygexc
 import socket
 import ssl
+import threading
 
 try:
     import Cookie
     import httplib
+    import StringIO
 except ImportError:
     import http.client as httplib
     import http.cookies as Cookie
+    import io as StringIO
 
 __author__ = 'jjohnson2'
 
@@ -39,6 +42,23 @@
 uploadforms = {}
 
 
+class FileUploader(threading.Thread):
+
+    def __init__(self, webclient, url, filename, data=None, formname=None,
+                 otherfields=()):
+        self.wc = webclient
+        self.url = url
+        self.filename = filename
+        self.data = data
+        self.otherfields = otherfields
+        self.formname = formname
+        super(FileUploader, self).__init__()
+
+    def run(self):
+        self.rsp = self.wc.upload(self.url, self.filename, self.data,
+                                  self.formname, otherfields=self.otherfields)
+
+
 def get_upload_form(filename, data, formname, otherfields):
     if not formname:
         formname = filename
@@ -146,11 +166,15 @@
         """
         if data is None:
             data = open(filename, 'rb')
-        form = get_upload_form(filename, data, formname, otherfields)
+        self._upbuffer = StringIO.StringIO(get_upload_form(filename, data,
+                                                           formname,
+                                                           otherfields))
         ulheaders = self.stdheaders.copy()
         ulheaders['Content-Type'] = 'multipart/form-data; boundary=' + BND
+        ulheaders['Content-Length'] = len(uploadforms[filename])
+        self.ulsize = len(uploadforms[filename])
         webclient = self.dupe()
-        webclient.request('POST', url, form, ulheaders)
+        webclient.request('POST', url, self._upbuffer, ulheaders)
         rsp = webclient.getresponse()
         # peer updates in progress should already have pointers,
         # subsequent transactions will cause memory to needlessly double,
@@ -164,6 +188,9 @@
                             rsp.read())
         return rsp.read()
 
+    def get_upload_progress(self):
+        return float(self._upbuffer.tell()) / float(self.ulsize)
+
     def request(self, method, url, body=None, headers=None, referer=None):
         if headers is None:
             headers = self.stdheaders.copy()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/pyghmi.egg-info/PKG-INFO 
new/pyghmi-1.2.4/pyghmi.egg-info/PKG-INFO
--- old/pyghmi-1.1.0/pyghmi.egg-info/PKG-INFO   2018-05-22 20:18:09.000000000 
+0200
+++ new/pyghmi-1.2.4/pyghmi.egg-info/PKG-INFO   2018-08-09 19:23:06.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyghmi
-Version: 1.1.0
+Version: 1.2.4
 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.1.0/pyghmi.egg-info/pbr.json 
new/pyghmi-1.2.4/pyghmi.egg-info/pbr.json
--- old/pyghmi-1.1.0/pyghmi.egg-info/pbr.json   2018-05-22 20:18:09.000000000 
+0200
+++ new/pyghmi-1.2.4/pyghmi.egg-info/pbr.json   2018-08-09 19:23:06.000000000 
+0200
@@ -1 +1 @@
-{"git_version": "2df9280", "is_release": true}
\ No newline at end of file
+{"git_version": "9e443c8", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/python-pyghmi.spec 
new/pyghmi-1.2.4/python-pyghmi.spec
--- old/pyghmi-1.1.0/python-pyghmi.spec 2018-05-22 20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/python-pyghmi.spec 2018-08-09 19:20:06.000000000 +0200
@@ -10,6 +10,7 @@
 BuildArch: noarch
 Vendor: Jarrod Johnson <jjohns...@lenovo.com>
 Url: https://git.openstack.org/cgit/openstack/pyghmi
+Requires: python-cryptography
 
 
 %description
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.1.0/tox.ini new/pyghmi-1.2.4/tox.ini
--- old/pyghmi-1.1.0/tox.ini    2018-05-22 20:15:14.000000000 +0200
+++ new/pyghmi-1.2.4/tox.ini    2018-08-09 19:20:06.000000000 +0200
@@ -15,15 +15,18 @@
 sitepackages = True
 
 [testenv:pep8]
+basepython = python3
 whitelist_externals = bash
 commands = bash -c 'pycodestyle pyghmi bin/*'
 
 [testenv:cover]
+basepython = python3
 setenv = VIRTUAL_ENV={envdir}
 commands =
   python setup.py testr --coverage
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [flake8]


Reply via email to