Hello community, here is the log from the commit of package python-pyghmi for openSUSE:Factory checked in at 2018-09-07 15:39:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyghmi (Old) and /work/SRC/openSUSE:Factory/.python-pyghmi.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyghmi" Fri Sep 7 15:39:28 2018 rev:10 rq:633117 version:1.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyghmi/python-pyghmi.changes 2018-04-30 22:53:20.264723250 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyghmi.new/python-pyghmi.changes 2018-09-07 15:39:29.502530490 +0200 @@ -1,0 +2,24 @@ +Tue Sep 4 08:05:43 UTC 2018 - cloud-de...@suse.de + +- update to version 1.1.0 + - Support '=' as a list delimiter + - Do not run functional (API) tests in the CI + - Switch the dsvm job to the "ipmi" hardware type + - Prefer cyrptodomex if present + - add lower-constraints + - Add hostname for FPC and XCC + - Normalize spaces in values and candidates + - Handle missing properties + - Fix SMM updates on newer SMM firmware versions + - Improve performance of get_health for XCC + - Error on invalid signature + - More informative message on password expiry + - Update to newer hacking + - Get additional enclosure data + - Revise the FPC fix + - Have logged set to 0 early + - Check the status for no power permission + - Fix duplicate adapter name handling + - Migrate from PyCrypto to Cryptography + +------------------------------------------------------------------- Old: ---- pyghmi-1.0.44.tar.gz New: ---- pyghmi-1.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyghmi.spec ++++++ --- /var/tmp/diff_new_pack.Oznfhq/_old 2018-09-07 15:39:29.946530014 +0200 +++ /var/tmp/diff_new_pack.Oznfhq/_new 2018-09-07 15:39:29.950530009 +0200 @@ -17,21 +17,23 @@ Name: python-pyghmi -Version: 1.0.44 +Version: 1.1.0 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.0.44.tar.gz +Source0: https://files.pythonhosted.org/packages/source/p/pyghmi/pyghmi-1.1.0.tar.gz BuildRequires: openstack-macros BuildRequires: python-devel -BuildRequires: python2-oslotest >= 3.2.0 -BuildRequires: python2-pycrypto >= 2.6 +BuildRequires: python2-cryptography >= 2.1 +BuildRequires: python2-oslotest +BuildRequires: python2-testrepository +BuildRequires: python3-cryptography >= 2.1 BuildRequires: python3-devel -BuildRequires: python3-oslotest >= 3.2.0 -BuildRequires: python3-pycrypto >= 2.6 -Requires: python-pycrypto >= 2.6 +BuildRequires: python3-oslotest +BuildRequires: python3-testrepository +Requires: python-cryptography >= 2.1 BuildArch: noarch %if 0%{?suse_version} Requires(post): update-alternatives @@ -53,7 +55,6 @@ Summary: General Hardware Management Initiative (IPMI and others) -- Documentation Group: Documentation/HTML BuildRequires: python-Sphinx -BuildRequires: python-openstackdocstheme >= 1.18.1 %description -n python-pyghmi-doc This is a pure python implementation of IPMI protocol. ++++++ _service ++++++ --- /var/tmp/diff_new_pack.Oznfhq/_old 2018-09-07 15:39:29.970529988 +0200 +++ /var/tmp/diff_new_pack.Oznfhq/_new 2018-09-07 15:39:29.974529984 +0200 @@ -1,8 +1,8 @@ <services> <service mode="disabled" name="renderspec"> - <param name="input-template">https://raw.githubusercontent.com/openstack/rpm-packaging/master/openstack/pyghmi/pyghmi.spec.j2</param> + <param name="input-template">https://raw.githubusercontent.com/openstack/rpm-packaging/stable/rocky/openstack/pyghmi/pyghmi.spec.j2</param> <param name="output-name">python-pyghmi.spec</param> - <param name="requirements">https://raw.githubusercontent.com/openstack/rpm-packaging/master/requirements.txt</param> + <param name="requirements">https://raw.githubusercontent.com/openstack/pyghmi/master/requirements.txt</param> <param name="changelog-email">cloud-de...@suse.de</param> <param name="changelog-provider">gh,openstack,pyghmi</param> </service> ++++++ pyghmi-1.0.44.tar.gz -> pyghmi-1.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/.zuul.yaml new/pyghmi-1.1.0/.zuul.yaml --- old/pyghmi-1.0.44/.zuul.yaml 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/.zuul.yaml 2018-05-22 20:15:14.000000000 +0200 @@ -1,30 +0,0 @@ -- project: - check: - jobs: - - pyghmi-tempest-devstack-ironic-pxe_ipmitool-src: - irrelevant-files: - - ^(test-|)requirements.txt$ - - ^setup.cfg$ - gate: - jobs: - - pyghmi-tempest-devstack-ironic-pxe_ipmitool-src: - irrelevant-files: - - ^(test-|)requirements.txt$ - - ^setup.cfg$ - -- job: - name: pyghmi-tempest-devstack-ironic-pxe_ipmitool-src - parent: legacy-dsvm-base - run: playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml - post-run: playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/post.yaml - timeout: 10800 - required-projects: - - openstack-infra/devstack-gate - - openstack/ironic - - openstack/ironic-lib - - openstack/ironic-python-agent - - openstack/ironic-tempest-plugin - - openstack/pyghmi - - openstack/python-ironicclient - - openstack/tempest - - openstack/virtualbmc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/AUTHORS new/pyghmi-1.1.0/AUTHORS --- old/pyghmi-1.0.44/AUTHORS 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/AUTHORS 2018-05-22 20:18:09.000000000 +0200 @@ -3,8 +3,10 @@ Andreas Jaeger <a...@suse.com> Derek Higgins <der...@redhat.com> Devananda van der Veen <devananda....@gmail.com> +Dmitry Tantsur <divius.ins...@gmail.com> Fabio Dassan <fdas...@lenovo.com> Fengqian Gao <fengqian....@intel.com> +Ilya Etingof <etin...@gmail.com> James E. Blair <jebl...@redhat.com> Jarrod Johnon <jbjoh...@us.ibm.com> Jarrod Johnon <jjohns...@lenovo.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/ChangeLog new/pyghmi-1.1.0/ChangeLog --- old/pyghmi-1.0.44/ChangeLog 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/ChangeLog 2018-05-22 20:18:09.000000000 +0200 @@ -1,6 +1,29 @@ CHANGES ======= +1.1.0 +----- + +* Migrate from PyCrypto to Cryptography +* Error on invalid signature +* Fix SMM updates on newer SMM firmware versions +* More informative message on password expiry +* Fix duplicate adapter name handling +* add lower-constraints +* Handle missing properties +* Update to newer hacking +* Do not run functional (API) tests in the CI +* Support '=' as a list delimiter +* Normalize spaces in values and candidates +* Revise the FPC fix +* Check the status for no power permission +* Prefer cyrptodomex if present +* Add hostname for FPC and XCC +* Improve performance of get\_health for XCC +* Have logged set to 0 early +* Switch the dsvm job to the "ipmi" hardware type +* Get additional enclosure data + 1.0.44 ------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/PKG-INFO new/pyghmi-1.1.0/PKG-INFO --- old/pyghmi-1.0.44/PKG-INFO 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/PKG-INFO 2018-05-22 20:18:10.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.0.44 +Version: 1.1.0 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.0.44/bin/fakebmc new/pyghmi-1.1.0/bin/fakebmc --- old/pyghmi-1.0.44/bin/fakebmc 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/bin/fakebmc 2018-05-22 20:15:14.000000000 +0200 @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -__author__ = 'jjohns...@lenovo.com' +# __author__ = 'jjohns...@lenovo.com' # this is a quick sample of how to write something that acts like a bmc # to play: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/bin/pyghmicons new/pyghmi-1.1.0/bin/pyghmicons --- old/pyghmi-1.0.44/bin/pyghmicons 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/bin/pyghmicons 2018-05-22 20:15:14.000000000 +0200 @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -@author: Jarrod Johnson <jbjoh...@us.ibm.com> -""" - -"""A simple little script to exemplify/test ipmi.console module -""" +# """ +# @author: Jarrod Johnson <jbjoh...@us.ibm.com> +# """ +# +# """A simple little script to exemplify/test ipmi.console module +# """ import fcntl import os import select diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/bin/pyghmiutil new/pyghmi-1.1.0/bin/pyghmiutil --- old/pyghmi-1.0.44/bin/pyghmiutil 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/bin/pyghmiutil 2018-05-22 20:15:14.000000000 +0200 @@ -12,14 +12,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -@author: Jarrod Johnson <jbjoh...@us.ibm.com> -""" +# """ +# @author: Jarrod Johnson <jbjoh...@us.ibm.com> +# """ -"""This is an example of using the library in a synchronous fashion. For now, -it isn't conceived as a general utility to actually use, just help developers -understand how the ipmi_command class workes. -""" +# """This is an example of using the library in a synchronous fashion. For now, +# it isn't conceived as a general utility to actually use, just help developers +# understand how the ipmi_command class workes. +# """ import os import string import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/bin/virshbmc new/pyghmi-1.1.0/bin/virshbmc --- old/pyghmi-1.0.44/bin/virshbmc 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/bin/virshbmc 2018-05-22 20:15:14.000000000 +0200 @@ -12,7 +12,7 @@ # limitations under the License. # Written by pmartini2, but mostly a clone of fakebmc, written by jjohnson2 -__author__ = 'pmarti...@bloomberg.net' +# __author__ = 'pmarti...@bloomberg.net' # This is a simple, but working proof of concept of using pyghmi.ipmi.bmc to # control a VM diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/lower-constraints.txt new/pyghmi-1.1.0/lower-constraints.txt --- old/pyghmi-1.0.44/lower-constraints.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/pyghmi-1.1.0/lower-constraints.txt 2018-05-22 20:15:14.000000000 +0200 @@ -0,0 +1,10 @@ +coverage===4.0 +cryptography===2.1 +fixtures===3.0.0 +oslotest===3.2.0 +os-testr===1.0.0 +python-subunit===1.0.0 +Sphinx===1.6.5 +testrepository===0.0.18 +testscenarios===0.4 +testtools===2.2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml new/pyghmi-1.1.0/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml --- old/pyghmi-1.0.44/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/playbooks/legacy/tempest-devstack-ironic-pxe_ipmitool-pyghmi-src/run.yaml 2018-05-22 20:15:14.000000000 +0200 @@ -53,7 +53,7 @@ - shell: cmd: | cat << 'EOF' >> ironic-extra-vars - export DEVSTACK_GATE_TEMPEST_REGEX="ironic" + export DEVSTACK_GATE_TEMPEST_REGEX="ironic_tempest_plugin.tests.scenario" EOF chdir: '{{ ansible_user_dir }}/workspace' @@ -96,7 +96,7 @@ export DEVSTACK_GATE_NEUTRON=1 export DEVSTACK_GATE_VIRT_DRIVER=ironic export DEVSTACK_GATE_CONFIGDRIVE=1 - export DEVSTACK_GATE_IRONIC_DRIVER=pxe_ipmitool + export DEVSTACK_GATE_IRONIC_DRIVER=ipmi export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE @@ -106,17 +106,6 @@ export DEVSTACK_GATE_TLSPROXY=1 fi - if [ "pxe_ipmitool" == "pxe_snmp" ] ; then - # explicitly enable pxe_snmp driver - export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_DRIVERS=fake,pxe_snmp" - fi - - if [ "pxe_ipmitool" == "redfish" ] ; then - # When deploying with redfish we need to enable the "redfish" - # hardware type - export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_HARDWARE_TYPES=redfish" - fi - if [ "partition" == "wholedisk" ] ; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/exceptions.py new/pyghmi-1.1.0/pyghmi/exceptions.py --- old/pyghmi-1.0.44/pyghmi/exceptions.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/exceptions.py 2018-05-22 20:15:14.000000000 +0200 @@ -54,3 +54,9 @@ # Indicates when functionality is requested that is not supported by # current endpoint pass + + +class BypassGenericBehavior(PyghmiException): + # Indicates that an OEM handler wants to abort any standards based + # follow up + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/command.py new/pyghmi-1.1.0/pyghmi/ipmi/command.py --- old/pyghmi-1.0.44/pyghmi/ipmi/command.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/command.py 2018-05-22 20:15:14.000000000 +0200 @@ -657,10 +657,15 @@ warning, critical, or failed assessments. """ summary = {'badreadings': [], 'health': const.Health.Ok} - for reading in self.get_sensor_data(): - if reading.health != const.Health.Ok: - summary['health'] |= reading.health - summary['badreadings'].append(reading) + try: + self.oem_init() + self._oem.get_health(summary) + for reading in self.get_sensor_data(): + if reading.health != const.Health.Ok: + summary['health'] |= reading.health + summary['badreadings'].append(reading) + except exc.BypassGenericBehavior: + pass return summary def get_sensor_reading(self, sensorname): @@ -1137,6 +1142,23 @@ if not ip == '0.0.0.0': self._assure_alert_policy(channel, destination) + def get_hostname(self): + """Get the hostname used by the BMC in various contexts + + This can vary somewhat in interpretation, but generally speaking + this should be the name that shows up on UIs and in DHCP requests and + DNS registration requests, as applicable. + + :return: current hostname + """ + self.oem_init() + try: + return self._oem.get_hostname() + except exc.UnsupportedFunctionality: + # Use the DCMI MCI field as a fallback, since it's the closest + # thing in the IPMI Spec for this + return self.get_mci() + def get_mci(self): """Get the Management Controller Identifier, per DCMI specification @@ -1144,6 +1166,20 @@ """ return self._chunkwise_dcmi_fetch(9) + def set_hostname(self, hostname): + """Set the hostname to be used by the BMC in various contexts. + + See get_hostname for details + + :param hostname: The hostname to set + :return: Nothing + """ + self.oem_init() + try: + return self._oem.set_hostname(hostname) + except exc.UnsupportedFunctionality: + return self.set_mci(hostname) + def set_mci(self, mci): """Set the management controller identifier, per DCMI specification diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/oem/generic.py new/pyghmi-1.1.0/pyghmi/ipmi/oem/generic.py --- old/pyghmi-1.0.44/pyghmi/ipmi/oem/generic.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/oem/generic.py 2018-05-22 20:15:14.000000000 +0200 @@ -265,6 +265,31 @@ """ raise exc.UnsupportedFunctionality() + def get_health(self, summary): + """Provide an alternative or augmented health assessment + + An OEM handler can preprocess the summary and extend it with OEM + specific data, and then return to let generic processing occur. + It can also raise the pyghmi exception BypassGenericBehavior to + suppress the standards based routine, for enhanced performance. + + :param summary: The health summary as prepared by the generic function + :return: Nothing, modifies the summary object + """ + return + + def set_hostname(self, hostname): + """OEM specific hook to specify name information + + """ + raise exc.UnsupportedFunctionality() + + def get_hostname(self): + """OEM specific hook to specify name information + + """ + raise exc.UnsupportedFunctionality() + def set_alert_ipv6_destination(self, ip, destination, channel): """Set an IPv6 alert destination diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/handler.py new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/handler.py --- old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/handler.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/handler.py 2018-05-22 20:15:14.000000000 +0200 @@ -652,6 +652,8 @@ data=(4, i)) name += rsp['data'][:] return name.rstrip('\x00') + elif self.is_fpc: + return self.smmhandler.get_domain() def set_oem_domain_name(self, name): if self.has_tsm: @@ -669,6 +671,22 @@ self._restart_dns() return + elif self.is_fpc: + self.smmhandler.set_domain(name) + + def set_hostname(self, hostname): + if self.is_fpc: + return self.smmhandler.set_hostname(hostname) + elif self.has_xcc: + return self.immhandler.set_hostname(hostname) + return super(OEMHandler, self).set_hostname(hostname) + + def get_hostname(self): + if self.is_fpc: + return self.smmhandler.get_hostname() + elif self.has_xcc: + return self.immhandler.get_hostname() + return super(OEMHandler, self).get_hostname() """ Gets a remote console launcher for a Lenovo ThinkServer. @@ -923,3 +941,8 @@ if self.has_xcc: return self.immhandler.list_media() return super(OEMHandler, self).list_media() + + def get_health(self, summary): + if self.has_xcc: + return self.immhandler.get_health(summary) + return super(OEMHandler, self).get_health(summary) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/imm.py new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/imm.py --- old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/imm.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/imm.py 2018-05-22 20:15:14.000000000 +0200 @@ -29,6 +29,7 @@ import pyghmi.storage as storage import pyghmi.util.webclient as webclient import random +import re import socket import struct import threading @@ -36,6 +37,32 @@ import weakref +numregex = re.compile('([0-9]+)') + + +def naturalize_string(key): + """Analyzes string in a human way to enable natural sort + + :param nodename: The node name to analyze + :returns: A structure that can be consumed by 'sorted' + """ + return [int(text) if text.isdigit() else text.lower() + for text in re.split(numregex, key)] + + +def natural_sort(iterable): + """Return a sort using natural sort if possible + + :param iterable: + :return: + """ + try: + return sorted(iterable, key=naturalize_string) + except TypeError: + # The natural sort attempt failed, fallback to ascii sort + return sorted(iterable) + + def fixup_uuid(uuidprop): baduuid = ''.join(uuidprop.split()) uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16]) @@ -44,6 +71,13 @@ return '-'.join(uuid).upper() +def fixup_str(propstr): + if propstr is None: + return '' + return ''.join([chr(int(c, 16)) for c in propstr.split()]).strip( + ' \xff\x00') + + class FileUploader(threading.Thread): def __init__(self, webclient, url, filename, data): @@ -168,19 +202,26 @@ changeset[key] = {'value': changeset[key]} newvalue = changeset[key]['value'] if self.fwo[key]['is_list'] and not isinstance(newvalue, list): - newvalues = newvalue.split(',') + if '=' in newvalue: + # ASU set a precedent of = delimited settings + # for now, honor that delimiter as well + newvalues = newvalue.split('=') + else: + newvalues = newvalue.split(',') else: newvalues = [newvalue] newnewvalues = [] for newvalue in newvalues: + newv = re.sub('\s+', ' ', newvalue) if (self.fwo[key]['possible'] and newvalue not in self.fwo[key]['possible']): candlist = [] for candidate in self.fwo[key]['possible']: - if newvalue.lower().startswith(candidate.lower()): + candid = re.sub('\s+', ' ', candidate) + if newv.lower().startswith(candid.lower()): newvalue = candidate break - if candidate.lower().startswith(newvalue.lower()): + if candid.lower().startswith(newv.lower()): candlist.append(candidate) else: if len(candlist) == 1: @@ -316,8 +357,14 @@ self.datacache['lenovo_cached_adapters'] = ( adapterdata, util._monotonic_time()) if adapterdata and 'items' in adapterdata: + anames = {} for adata in adapterdata['items']: aname = adata[self.ADP_NAME] + if aname in anames: + anames[aname] += 1 + aname = '{0} {1}'.format(aname, anames[aname]) + else: + anames[aname] = 1 donenames = set([]) for fundata in adata[self.ADP_FUN]: fdata = fundata.get('firmwares', ()) @@ -370,12 +417,12 @@ def get_hw_inventory(self): hwmap = self.hardware_inventory_map() - for key in hwmap: + for key in natural_sort(hwmap): yield (key, hwmap[key]) def get_hw_descriptions(self): hwmap = self.hardware_inventory_map() - for key in hwmap: + for key in natural_sort(hwmap): yield key def get_component_inventory(self, compname): @@ -425,9 +472,13 @@ enclosureuuid = self.get_property('/v2/ibmc/smm/chassis/uuid') if enclosureuuid: bay = self.get_property('/v2/cmm/sp/7') + serial = self.get_property('/v2/ibmc/smm/chassis/sn') + model = self.get_property('/v2/ibmc/smm/chassis/mtm') hwmap['Enclosure'] = { 'UUID': fixup_uuid(enclosureuuid), 'Bay': bay, + 'Model': fixup_str(model), + 'Serial': fixup_str(serial), } adapterdata = self.get_cached_data('lenovo_cached_adapters') if not adapterdata: @@ -441,11 +492,17 @@ self.datacache['lenovo_cached_adapters'] = ( adapterdata, util._monotonic_time()) if adapterdata and 'items' in adapterdata: + anames = {} for adata in adapterdata['items']: skipadapter = False if not adata[self.ADP_OOB]: continue aname = adata[self.ADP_NAME] + if aname in anames: + anames[aname] += 1 + aname = '{0} {1}'.format(aname, anames[aname]) + else: + anames[aname] = 1 clabel = adata[self.ADP_LABEL] if clabel == 'Unknown': continue @@ -560,7 +617,7 @@ super(XCCClient, self).__init__(ipmicmd) self.adp_referer = None - def get_webclient(self): + def get_webclient(self, login=True): cv = self.ipmicmd.certverify wc = webclient.SecureHTTPConnection(self.imm, 443, verifycallback=cv) try: @@ -569,6 +626,8 @@ if se.errno != errno.ECONNREFUSED: raise return None + if not login: + return wc adata = json.dumps({'username': self.username, 'password': self.password }) @@ -865,7 +924,7 @@ standalonedisks.append( storage.Disk( name=disk['name'], description=disk['type'], - id=(cid, disk['id']), status=disk['RAIDState'], + id=(cid, disk['id']), status=disk['RAIDState'], serial=disk['serialNo'], fru=disk['fruPartNo'])) return storage.ConfigSpec(disks=standalonedisks, arrays=pools) @@ -1112,6 +1171,16 @@ if '_csrf_token' in wc.cookies: wc.set_header('X-XSRF-TOKEN', self.wc.cookies['_csrf_token']) + def set_hostname(self, hostname): + self.wc.grab_json_response('/api/dataset', {'IMM_HostName': hostname}) + self.wc.grab_json_response('/api/dataset', {'IMM_DescName': hostname}) + self.weblogout() + + def get_hostname(self): + rsp = self.wc.grab_json_response('/api/dataset/sys_info') + self.weblogout() + return rsp['items'][0]['system_name'] + def update_firmware_backend(self, filename, data=None, progress=None, bank=None): self.weblogout() @@ -1242,3 +1311,11 @@ if bank == 'backup': return 'complete' return 'pending' + + def get_health(self, summary): + wc = self.get_webclient(False) + rsp = wc.grab_json_response('/api/providers/imm_active_events') + if 'items' in rsp and len(rsp['items']) == 0: + # The XCC reports healthy, no need to interrogate + raise pygexc.BypassGenericBehavior() + # Will use the generic handling for unhealthy systems diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/nextscale.py new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/nextscale.py --- old/pyghmi-1.0.44/pyghmi/ipmi/oem/lenovo/nextscale.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/oem/lenovo/nextscale.py 2018-05-22 20:15:14.000000000 +0200 @@ -94,21 +94,29 @@ if ie.ipmicode == 0xd5: # no node present return (pygconst.Health.Ok, ['Absent']) raise - perminfo = ord(rsp['data'][1]) health = pygconst.Health.Ok states = [] if len(rsp['data']) == 4: # different gens handled rc differently rsp['data'] = b'\x00' + bytes(rsp['data']) + elif len(rsp['data']) == 6: # New FPC format + rsp['data'] = rsp['data'][:2] + rsp['data'][3:] + perminfo = ord(rsp['data'][1]) if sz == 6: # FPC permfail = ('\x02', '\x03') elif sz == 2: # SMM permfail = ('\x02',) - if rsp['data'][4] in permfail: - states.append('Insufficient Power') - health = pygconst.Health.Failed + if perminfo & 0x20: + if rsp['data'][4] in permfail: + states.append('Insufficient Power') + health = pygconst.Health.Failed + elif rsp['data'][3:5] != '\x00\x00': + states.append('No Power Permission') + health = pygconst.Health.Failed if perminfo & 0x40: states.append('Node Fault') health = pygconst.Health.Failed + if rsp['data'][3:5] == '\x00\x00': + states.append('Absent') return (health, states) @@ -233,6 +241,7 @@ class SMMClient(object): + def __init__(self, ipmicmd): self.ipmicmd = weakref.proxy(ipmicmd) self.smm = ipmicmd.bmc @@ -269,15 +278,74 @@ for data in authdata.findall('authResult'): if int(data.text) != 0: raise Exception("Firmware update already in progress") + for data in authdata.findall('forwardUrl'): + if 'renew' in data.text: + raise Exception("Account password has expired on remote " + "device") self.st1 = None self.st2 = None for data in authdata.findall('st1'): self.st1 = data.text for data in authdata.findall('st2'): self.st2 = data.text + if not self.st2: + # This firmware puts tokens in the html file, parse that + wc.request('GET', '/index.html') + rsp = wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + indexhtml = rsp.read() + for line in indexhtml.split('\n'): + if '"ST1"' in line: + self.st1 = line.split()[-1].replace( + '"', '').replace(',', '') + if '"ST2"' in line: + self.st2 = line.split()[-1].replace( + '"', '').replace(',', '') wc.set_header('ST2', self.st2) return wc + def set_hostname(self, hostname): + self.wc.request('POST', '/data', 'set=hostname:' + hostname) + rsp = self.wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + rsp.read() + self.logout() + + def get_hostname(self): + currinfo = self.get_netinfo() + self.logout() + for data in currinfo.find('netConfig').findall('hostname'): + return data.text + + def get_netinfo(self): + self.wc.request('POST', '/data', 'get=hostname') + rsp = self.wc.getresponse() + data = rsp.read() + if rsp.status == 400: + self.wc.request('POST', '/data?get=hostname', '') + rsp = self.wc.getresponse() + data = rsp.read() + if rsp.status != 200: + raise Exception(data) + currinfo = fromstring(data) + return currinfo + + def set_domain(self, domain): + self.wc.request('POST', '/data', 'set=dnsDomain:' + domain) + rsp = self.wc.getresponse() + if rsp.status != 200: + raise Exception(rsp.read()) + rsp.read() + self.logout() + + def get_domain(self): + currinfo = self.get_netinfo() + self.logout() + for data in currinfo.find('netConfig').findall('dnsDomain'): + return data.text + def get_ntp_enabled(self, variant): self.wc.request('POST', '/data', 'get=ntpOpMode') rsp = self.wc.getresponse() @@ -332,6 +400,9 @@ 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'}) @@ -355,6 +426,8 @@ if rsp.status != 200: raise Exception('Error applying firmware') progdata = fromstring(progdata) + if progdata.findall('fwUpdate')[0].text == 'invalid signature': + raise Exception('Firmware signature invalid') percent = float(progdata.findall('fwProgress')[0].text) progress({'phase': 'apply', @@ -370,6 +443,6 @@ @property def wc(self): - if not self._wc: + if not self._wc or self._wc.broken: self._wc = self.get_webclient() return self._wc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/private/constants.py new/pyghmi-1.1.0/pyghmi/ipmi/private/constants.py --- old/pyghmi-1.0.44/pyghmi/ipmi/private/constants.py 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/private/constants.py 2018-05-22 20:15:14.000000000 +0200 @@ -1285,7 +1285,7 @@ 'deassertion_severity': const.Health.Ok, }, }, - 0x21: { # slot/connector + 0x21: { # slot/connector 0x0: { 'desc': 'Fault', 'severity': const.Health.Critical, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/ipmi/private/session.py new/pyghmi-1.1.0/pyghmi/ipmi/private/session.py --- old/pyghmi-1.0.44/pyghmi/ipmi/private/session.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/ipmi/private/session.py 2018-05-22 20:15:14.000000000 +0200 @@ -27,7 +27,9 @@ import struct import threading -from Crypto.Cipher import AES + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes import pyghmi.exceptions as exc from pyghmi.ipmi.private import constants @@ -302,6 +304,10 @@ # can do something like reassign our threading and select modules socketchecking = None + # Maintain single Cryptography backend for all IPMI sessions (seems to be + # thread-safe) + _crypto_backend = default_backend() + @classmethod def _cleanup(cls): for sesskey in list(cls.bmc_handlers): @@ -471,6 +477,7 @@ self.iterwaiters.append(onlogon) return self.broken = False + self.logged = 0 self.privlevel = 4 self.maxtimeout = 3 # be aggressive about giving up on initial packet self.incommand = False @@ -868,10 +875,15 @@ iv = os.urandom(16) message += list(struct.unpack("16B", iv)) payloadtocrypt = _aespad(payload) - crypter = AES.new(self.aeskey, AES.MODE_CBC, iv) - crypted = crypter.encrypt(struct.pack("%dB" % - len(payloadtocrypt), - *payloadtocrypt)) + 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 else: # no confidetiality algorithm @@ -1374,10 +1386,15 @@ payload = data[16:16 + psize] if encrypted: iv = data[16:32] - decrypter = AES.new(self.aeskey, AES.MODE_CBC, bytes(iv)) - decrypted = decrypter.decrypt( - struct.pack("%dB" % len(payload[16:]), - *payload[16:])) + crypter = Cipher( + algorithm=algorithms.AES(self.aeskey), + mode=modes.CBC(bytes(iv)), + 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) padsize = payload[-1] + 1 payload = list(payload[:-padsize]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi/util/webclient.py new/pyghmi-1.1.0/pyghmi/util/webclient.py --- old/pyghmi-1.0.44/pyghmi/util/webclient.py 2018-04-10 16:27:58.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi/util/webclient.py 2018-05-22 20:15:14.000000000 +0200 @@ -69,6 +69,7 @@ **kwargs): if 'timeout' not in kwargs: kwargs['timeout'] = 60 + self.broken = False self.thehost = host self.theport = port httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs) @@ -107,12 +108,16 @@ bincert) def getresponse(self): - rsp = super(SecureHTTPConnection, self).getresponse() - for hdr in rsp.msg.headers: - if hdr.startswith('Set-Cookie:'): - c = Cookie.BaseCookie(hdr[11:]) - for k in c: - self.cookies[k] = c[k].value + try: + rsp = super(SecureHTTPConnection, self).getresponse() + for hdr in rsp.msg.headers: + if hdr.startswith('Set-Cookie:'): + c = Cookie.BaseCookie(hdr[11:]) + for k in c: + self.cookies[k] = c[k].value + except httplib.BadStatusLine: + self.broken = True + raise return rsp def grab_json_response(self, url, data=None, referer=None): @@ -164,6 +169,8 @@ headers = self.stdheaders.copy() if method == 'GET' and 'Content-Type' in headers: del headers['Content-Type'] + if method == 'POST' and body and 'Content-Type' not in headers: + headers['Content-Type'] = 'application/x-www-form-urlencoded' if self.cookies: cookies = [] for ckey in self.cookies: @@ -175,5 +182,9 @@ headers['Cookie'] += '; ' + '; '.join(cookies) if referer: headers['Referer'] = referer - return super(SecureHTTPConnection, self).request(method, url, body, - headers) + try: + return super(SecureHTTPConnection, self).request(method, url, body, + headers) + except httplib.CannotSendRequest: + self.broken = True + raise diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi.egg-info/PKG-INFO new/pyghmi-1.1.0/pyghmi.egg-info/PKG-INFO --- old/pyghmi-1.0.44/pyghmi.egg-info/PKG-INFO 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi.egg-info/PKG-INFO 2018-05-22 20:18:09.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyghmi -Version: 1.0.44 +Version: 1.1.0 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.0.44/pyghmi.egg-info/SOURCES.txt new/pyghmi-1.1.0/pyghmi.egg-info/SOURCES.txt --- old/pyghmi-1.0.44/pyghmi.egg-info/SOURCES.txt 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi.egg-info/SOURCES.txt 2018-05-22 20:18:10.000000000 +0200 @@ -7,6 +7,7 @@ README README.md buildrpm +lower-constraints.txt python-pyghmi.spec requirements.txt setup.cfg @@ -73,4 +74,5 @@ pyghmi/tests/unit/ipmi/__init__.py pyghmi/tests/unit/ipmi/test_sdr.py pyghmi/util/__init__.py -pyghmi/util/webclient.py \ No newline at end of file +pyghmi/util/webclient.py +zuul.d/project.yaml \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi.egg-info/pbr.json new/pyghmi-1.1.0/pyghmi.egg-info/pbr.json --- old/pyghmi-1.0.44/pyghmi.egg-info/pbr.json 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi.egg-info/pbr.json 2018-05-22 20:18:09.000000000 +0200 @@ -1 +1 @@ -{"git_version": "ffd9c55", "is_release": true} \ No newline at end of file +{"git_version": "2df9280", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/pyghmi.egg-info/requires.txt new/pyghmi-1.1.0/pyghmi.egg-info/requires.txt --- old/pyghmi-1.0.44/pyghmi.egg-info/requires.txt 2018-04-10 16:30:46.000000000 +0200 +++ new/pyghmi-1.1.0/pyghmi.egg-info/requires.txt 2018-05-22 20:18:09.000000000 +0200 @@ -1 +1 @@ -pycrypto>=2.6 +cryptography!=2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/requirements.txt new/pyghmi-1.1.0/requirements.txt --- old/pyghmi-1.0.44/requirements.txt 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/requirements.txt 2018-05-22 20:15:14.000000000 +0200 @@ -1 +1 @@ -pycrypto>=2.6 +cryptography!=2.0 # BSD/Apache-2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/test-requirements.txt new/pyghmi-1.1.0/test-requirements.txt --- old/pyghmi-1.0.44/test-requirements.txt 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/test-requirements.txt 2018-05-22 20:15:14.000000000 +0200 @@ -1,12 +1,10 @@ hacking>=0.5.6 - -coverage>=3.6 -discover -fixtures>=0.3.14 -python-subunit -sphinx>=1.1.2 -testrepository>=0.0.17 +coverage>=4.0 +fixtures>=3.0.0 +python-subunit>=1.0.0 +sphinx>=1.6.5 +testrepository>=0.0.18 testscenarios>=0.4 -testtools>=0.9.32 -os-testr>=0.8.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 +testtools>=2.2.0 +os-testr>=1.0.0 # Apache-2.0 +oslotest>=3.2.0 # Apache-2.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/tox.ini new/pyghmi-1.1.0/tox.ini --- old/pyghmi-1.0.44/tox.ini 2018-04-10 16:27:36.000000000 +0200 +++ new/pyghmi-1.1.0/tox.ini 2018-05-22 20:15:14.000000000 +0200 @@ -16,7 +16,7 @@ [testenv:pep8] whitelist_externals = bash -commands = bash -c 'pep8 pyghmi bin/*' +commands = bash -c 'pycodestyle pyghmi bin/*' [testenv:cover] setenv = VIRTUAL_ENV={envdir} @@ -29,3 +29,13 @@ [flake8] exclude = .venv,.tox,dist,doc,*.egg,build show-source = true + +[pep8] +ignore = E731,E226,E123 + +[testenv:lower-constraints] +basepython = python3 +deps = + -c{toxinidir}/lower-constraints.txt + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyghmi-1.0.44/zuul.d/project.yaml new/pyghmi-1.1.0/zuul.d/project.yaml --- old/pyghmi-1.0.44/zuul.d/project.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/pyghmi-1.1.0/zuul.d/project.yaml 2018-05-22 20:15:14.000000000 +0200 @@ -0,0 +1,7 @@ +- project: + check: + jobs: + - openstack-tox-lower-constraints + gate: + jobs: + - openstack-tox-lower-constraints