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]