From de8292f6dd45611a7b0fed4a713516be3c06eb85 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 6 Sep 2019 13:31:14 -0400 Subject: [PATCH 01/56] Ignore current channel if current channel is disabled A fluke can cause current channel to be 1 when we are wanting 8. --- confluent_server/confluent/discovery/handlers/tsm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/confluent_server/confluent/discovery/handlers/tsm.py b/confluent_server/confluent/discovery/handlers/tsm.py index 82365a7e..610a2e3b 100644 --- a/confluent_server/confluent/discovery/handlers/tsm.py +++ b/confluent_server/confluent/discovery/handlers/tsm.py @@ -149,6 +149,9 @@ class NodeHandler(generic.NodeHandler): raise exc.NotImplementedException('IPv6 remote config TODO') currnet = wc.grab_json_response('/api/settings/network') for net in currnet: + if net['channel_number'] == self.channel and net['lan_enable'] == 0: + # ignore false indication and switch to 8 (dedicated) + self.channel = 8 if net['channel_number'] == self.channel: # we have found the interface to potentially manipulate if net['ipv4_address'] != newip: From ed320f4a1736ba83e0a51338ca76fb58fe194c26 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 17 Sep 2019 09:48:02 -0400 Subject: [PATCH 02/56] Add 'check' to permit comparison against current value For implementing some security policies, it is useful to check new value against current value. --- confluent_server/confluent/core.py | 1 + .../plugins/configuration/attributes.py | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 7398464e..2244f0a4 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -398,6 +398,7 @@ def _init_core(): nodegroupresources = { 'attributes': { + 'check': PluginRoute({'handler': 'attributes'}), 'rename': PluginRoute({'handler': 'attributes'}), 'all': PluginRoute({'handler': 'attributes'}), 'current': PluginRoute({'handler': 'attributes'}), diff --git a/confluent_server/confluent/plugins/configuration/attributes.py b/confluent_server/confluent/plugins/configuration/attributes.py index c1eb94bc..04da045f 100644 --- a/confluent_server/confluent/plugins/configuration/attributes.py +++ b/confluent_server/confluent/plugins/configuration/attributes.py @@ -164,6 +164,20 @@ def update(nodes, element, configmanager, inputdata): def update_nodegroup(group, element, configmanager, inputdata): + if element == 'check': + check = inputdata.attribs + decrypt = configmanager.decrypt + configmanager.decrypt = True + currinfo = configmanager.get_nodegroup_attributes(group, list(check)) + configmanager.decrypt = decrypt + for inf in check: + checkvalue = check[inf] + if isinstance(checkvalue, dict): + checkvalue = checkvalue.get('value', None) + currvalue = currinfo.get(inf, {}).get('value') + if checkvalue == currvalue: + raise exc.InvalidArgumentException('Checked value matches existing value') + return retrieve_nodegroup(group, element, configmanager, inputdata) if 'rename' in element: namemap = {} namemap[group] = inputdata.attribs['rename'] @@ -221,6 +235,18 @@ def update_nodes(nodes, element, configmanager, inputdata): raise exc.InvalidArgumentException( 'No action to take, noderange is empty (if trying to define ' 'group attributes, use nodegroupattrib)') + if element[-1] == 'check': + for node in nodes: + check = inputdata.get_attributes(node, allattributes.node) + currinfo = configmanager.get_node_attributes(node, list(check), decrypt=True) + for inf in check: + checkvalue = check[inf] + if isinstance(checkvalue, dict): + checkvalue = checkvalue.get('value', None) + currvalue = currinfo.get(node, {}).get(inf, {}).get('value') + if checkvalue == currvalue: + raise exc.InvalidArgumentException('Checked value matches existing value') + return retrieve(nodes, element, configmanager, inputdata) if 'rename' in element: namemap = {} for node in nodes: From 5008128d57ae9629d01c37a65e9fd4a3f24d0c44 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 19 Sep 2019 10:02:40 -0400 Subject: [PATCH 03/56] Add IPv6 support to TSM TSM firmware fixes enable IPv6, enable our support of it. --- confluent_server/confluent/discovery/handlers/tsm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/confluent_server/confluent/discovery/handlers/tsm.py b/confluent_server/confluent/discovery/handlers/tsm.py index 610a2e3b..3d5ef74c 100644 --- a/confluent_server/confluent/discovery/handlers/tsm.py +++ b/confluent_server/confluent/discovery/handlers/tsm.py @@ -24,6 +24,7 @@ getaddrinfo = eventlet.support.greendns.getaddrinfo webclient = eventlet.import_patched('pyghmi.util.webclient') class NodeHandler(generic.NodeHandler): + devname = 'TSM' DEFAULT_USER = 'USERID' DEFAULT_PASS = 'PASSW0RD' @@ -165,6 +166,12 @@ class NodeHandler(generic.NodeHandler): rsp, status = wc.grab_json_response_with_status( '/api/settings/network/{0}'.format(net['id']), net, method='PUT') break + elif self.ipaddr.startswith('fe80::'): + self.configmanager.set_node_attributes( + {nodename: {'hardwaremanagement.manager': self.ipaddr}}) + else: + raise exc.TargetEndpointUnreachable( + 'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)') rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE') From 56fa13279e926e7b7662293346e076984e3e5c57 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 23 Sep 2019 10:59:00 -0400 Subject: [PATCH 04/56] Explicitly indicate use of python2 RHEL8 will no longer tolerate implicit use of python. For now relent to being python2, though ideally one day it could be either. Unfortunately, this means once code is ready for python3, we have to probably implement build time changes for python3 enabled distros to have different shebangs than python2 distros. --- confluent_client/bin/nodeeventlog | 2 +- confluent_client/bin/nodefirmware | 4 ++-- confluent_client/bin/nodeinventory | 2 +- confluent_client/bin/nodelicense | 2 +- confluent_client/bin/nodemedia | 4 ++-- confluent_client/bin/nodestorage | 2 +- confluent_client/bin/nodesupport | 4 ++-- misc/logreader.py | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/confluent_client/bin/nodeeventlog b/confluent_client/bin/nodeeventlog index 291f6ac4..46d08604 100755 --- a/confluent_client/bin/nodeeventlog +++ b/confluent_client/bin/nodeeventlog @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2019 Lenovo diff --git a/confluent_client/bin/nodefirmware b/confluent_client/bin/nodefirmware index fa6a3cca..f251f9cf 100755 --- a/confluent_client/bin/nodefirmware +++ b/confluent_client/bin/nodefirmware @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2016-2017 Lenovo @@ -163,4 +163,4 @@ try: update_firmware(session, upfile) except KeyboardInterrupt: print('') -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodeinventory b/confluent_client/bin/nodeinventory index 2be1fe27..48259f05 100755 --- a/confluent_client/bin/nodeinventory +++ b/confluent_client/bin/nodeinventory @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2016-2017 Lenovo diff --git a/confluent_client/bin/nodelicense b/confluent_client/bin/nodelicense index ecc5d6fb..b602e733 100755 --- a/confluent_client/bin/nodelicense +++ b/confluent_client/bin/nodelicense @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2019 Lenovo diff --git a/confluent_client/bin/nodemedia b/confluent_client/bin/nodemedia index b4c95723..bb71a8f4 100644 --- a/confluent_client/bin/nodemedia +++ b/confluent_client/bin/nodemedia @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2018 Lenovo @@ -188,4 +188,4 @@ def main(): sys.exit(1) handler(noderange, media) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/confluent_client/bin/nodestorage b/confluent_client/bin/nodestorage index 0cdd30be..e1170d15 100644 --- a/confluent_client/bin/nodestorage +++ b/confluent_client/bin/nodestorage @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2018 Lenovo diff --git a/confluent_client/bin/nodesupport b/confluent_client/bin/nodesupport index c9c1787f..135a16df 100644 --- a/confluent_client/bin/nodesupport +++ b/confluent_client/bin/nodesupport @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2018 Lenovo @@ -144,4 +144,4 @@ def main(): sys.exit(1) handler(noderange, media) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/misc/logreader.py b/misc/logreader.py index 696693c0..8b08d28d 100644 --- a/misc/logreader.py +++ b/misc/logreader.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 import collections import os import struct From 44d6bde3ff172f80f5300fc71675aacfc0627dc0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 23 Sep 2019 11:04:52 -0400 Subject: [PATCH 05/56] Make /usr/bin/env python point to python2 Same as before, more RHEL8 compatibility changes --- confluent_client/bin/collate | 2 +- confluent_client/bin/confetty | 2 +- confluent_client/bin/confluent2ansible | 2 +- confluent_client/bin/confluent2lxca | 2 +- confluent_client/bin/confluent2xcat | 2 +- confluent_client/bin/nodeattrib | 4 ++-- confluent_client/bin/nodebmcreset | 2 +- confluent_client/bin/nodeboot | 2 +- confluent_client/bin/nodeconfig | 2 +- confluent_client/bin/nodeconsole | 2 +- confluent_client/bin/nodedefine | 4 ++-- confluent_client/bin/nodediscover | 2 +- confluent_client/bin/nodegroupattrib | 4 ++-- confluent_client/bin/nodegroupdefine | 4 ++-- confluent_client/bin/nodegrouplist | 2 +- confluent_client/bin/nodegroupremove | 4 ++-- confluent_client/bin/nodegrouprename | 2 +- confluent_client/bin/nodehealth | 2 +- confluent_client/bin/nodeidentify | 2 +- confluent_client/bin/nodelist | 2 +- confluent_client/bin/nodepower | 4 ++-- confluent_client/bin/noderemove | 4 ++-- confluent_client/bin/noderename | 2 +- confluent_client/bin/nodereseat | 2 +- confluent_client/bin/nodersync | 2 +- confluent_client/bin/noderun | 2 +- confluent_client/bin/nodesensors | 2 +- confluent_client/bin/nodesetboot | 2 +- confluent_client/bin/nodeshell | 2 +- confluent_client/bin/stats | 2 +- confluent_client/samples/nodeattrib_from_switch.py | 2 +- confluent_server/bin/collective | 2 +- confluent_server/bin/confluent | 2 +- confluent_server/bin/confluentdbutil | 2 +- confluent_server/bin/confluentsrv.py | 2 +- confluent_server/confluentdbgcli.py | 2 +- confluent_server/dbgtools/confluentdbgcli.py | 2 +- confluent_server/dbgtools/processhangtraces.py | 2 +- misc/disablepasscomplexity.py | 2 +- misc/fixexpiry.py | 2 +- misc/fixsmmexpiry.py | 2 +- 41 files changed, 48 insertions(+), 48 deletions(-) diff --git a/confluent_client/bin/collate b/confluent_client/bin/collate index 0efa9ffd..45cc5f69 100755 --- a/confluent_client/bin/collate +++ b/confluent_client/bin/collate @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 277384d2..522fbb22 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2014 IBM Corporation diff --git a/confluent_client/bin/confluent2ansible b/confluent_client/bin/confluent2ansible index d0c8597f..e6da2050 100644 --- a/confluent_client/bin/confluent2ansible +++ b/confluent_client/bin/confluent2ansible @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import optparse import signal import sys diff --git a/confluent_client/bin/confluent2lxca b/confluent_client/bin/confluent2lxca index 0f8a58a5..905efb0e 100644 --- a/confluent_client/bin/confluent2lxca +++ b/confluent_client/bin/confluent2lxca @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import csv import optparse import signal diff --git a/confluent_client/bin/confluent2xcat b/confluent_client/bin/confluent2xcat index 9ee8caaa..91e42028 100644 --- a/confluent_client/bin/confluent2xcat +++ b/confluent_client/bin/confluent2xcat @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import csv import optparse import signal diff --git a/confluent_client/bin/nodeattrib b/confluent_client/bin/nodeattrib index 61e1a25e..3489bb8b 100755 --- a/confluent_client/bin/nodeattrib +++ b/confluent_client/bin/nodeattrib @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -128,4 +128,4 @@ else: else: print(res['item']['href'].replace('/', '')) -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodebmcreset b/confluent_client/bin/nodebmcreset index e662ec64..eb7ef477 100755 --- a/confluent_client/bin/nodebmcreset +++ b/confluent_client/bin/nodebmcreset @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodeboot b/confluent_client/bin/nodeboot index a39e4c42..73c44b7b 100755 --- a/confluent_client/bin/nodeboot +++ b/confluent_client/bin/nodeboot @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015 Lenovo diff --git a/confluent_client/bin/nodeconfig b/confluent_client/bin/nodeconfig index 5b752826..89e65702 100755 --- a/confluent_client/bin/nodeconfig +++ b/confluent_client/bin/nodeconfig @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo diff --git a/confluent_client/bin/nodeconsole b/confluent_client/bin/nodeconsole index 31e45ad0..4f7d7b76 100755 --- a/confluent_client/bin/nodeconsole +++ b/confluent_client/bin/nodeconsole @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015 Lenovo diff --git a/confluent_client/bin/nodedefine b/confluent_client/bin/nodedefine index 3ea1fbcc..6e56722a 100755 --- a/confluent_client/bin/nodedefine +++ b/confluent_client/bin/nodedefine @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -55,4 +55,4 @@ for r in session.create('/noderange/', attribs): exitcode |= 1 if 'created' in r: print('{0}: created'.format(r['created'])) -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodediscover b/confluent_client/bin/nodediscover index c045f20c..29982411 100755 --- a/confluent_client/bin/nodediscover +++ b/confluent_client/bin/nodediscover @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo diff --git a/confluent_client/bin/nodegroupattrib b/confluent_client/bin/nodegroupattrib index 3daf0fe6..b913a9c8 100755 --- a/confluent_client/bin/nodegroupattrib +++ b/confluent_client/bin/nodegroupattrib @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -125,4 +125,4 @@ else: else: print res['item']['href'].replace('/', '') -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodegroupdefine b/confluent_client/bin/nodegroupdefine index 068f86ad..6fd1f8a5 100755 --- a/confluent_client/bin/nodegroupdefine +++ b/confluent_client/bin/nodegroupdefine @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -55,4 +55,4 @@ for r in session.create('/nodegroups/', attribs): exitcode |= 1 if 'created' in r: print('{0}: created'.format(r['created'])) -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodegrouplist b/confluent_client/bin/nodegrouplist index baeb17a6..e24a45c5 100644 --- a/confluent_client/bin/nodegrouplist +++ b/confluent_client/bin/nodegrouplist @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodegroupremove b/confluent_client/bin/nodegroupremove index 1c7c8d3f..e06d09e0 100755 --- a/confluent_client/bin/nodegroupremove +++ b/confluent_client/bin/nodegroupremove @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -49,4 +49,4 @@ for r in session.delete('/nodegroups/{0}'.format(noderange)): exitcode |= 1 if 'deleted' in r: print('{0}: deleted'.format(r['deleted'])) -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/nodegrouprename b/confluent_client/bin/nodegrouprename index 55429046..33f25803 100644 --- a/confluent_client/bin/nodegrouprename +++ b/confluent_client/bin/nodegrouprename @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2019 Lenovo diff --git a/confluent_client/bin/nodehealth b/confluent_client/bin/nodehealth index 1896c18e..2dc844da 100755 --- a/confluent_client/bin/nodehealth +++ b/confluent_client/bin/nodehealth @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodeidentify b/confluent_client/bin/nodeidentify index 32f7f82f..73e4fa86 100755 --- a/confluent_client/bin/nodeidentify +++ b/confluent_client/bin/nodeidentify @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodelist b/confluent_client/bin/nodelist index 39c07c93..b6892e53 100755 --- a/confluent_client/bin/nodelist +++ b/confluent_client/bin/nodelist @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodepower b/confluent_client/bin/nodepower index 10378a1b..13b5e5d5 100755 --- a/confluent_client/bin/nodepower +++ b/confluent_client/bin/nodepower @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo @@ -72,4 +72,4 @@ if options.previous: # add dictionary to session session.add_precede_dict(prev) -sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate)) \ No newline at end of file +sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate)) diff --git a/confluent_client/bin/noderemove b/confluent_client/bin/noderemove index c4615a36..7cde4247 100755 --- a/confluent_client/bin/noderemove +++ b/confluent_client/bin/noderemove @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo @@ -49,4 +49,4 @@ for r in session.delete('/noderange/{0}'.format(noderange)): exitcode |= 1 if 'deleted' in r: print('{0}: deleted'.format(r['deleted'])) -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) diff --git a/confluent_client/bin/noderename b/confluent_client/bin/noderename index 3308a293..f0f26770 100644 --- a/confluent_client/bin/noderename +++ b/confluent_client/bin/noderename @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2019 Lenovo diff --git a/confluent_client/bin/nodereseat b/confluent_client/bin/nodereseat index e838e42d..7bfc221f 100755 --- a/confluent_client/bin/nodereseat +++ b/confluent_client/bin/nodereseat @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodersync b/confluent_client/bin/nodersync index 2d1802af..2d6e91cf 100755 --- a/confluent_client/bin/nodersync +++ b/confluent_client/bin/nodersync @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2016-2017 Lenovo diff --git a/confluent_client/bin/noderun b/confluent_client/bin/noderun index c931a548..b199135b 100755 --- a/confluent_client/bin/noderun +++ b/confluent_client/bin/noderun @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2016-2017 Lenovo diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index 6c13cc74..5e5b1746 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015-2017 Lenovo diff --git a/confluent_client/bin/nodesetboot b/confluent_client/bin/nodesetboot index 1ec19099..9963208e 100755 --- a/confluent_client/bin/nodesetboot +++ b/confluent_client/bin/nodesetboot @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2015 Lenovo diff --git a/confluent_client/bin/nodeshell b/confluent_client/bin/nodeshell index 779bec7d..27ca5ad8 100755 --- a/confluent_client/bin/nodeshell +++ b/confluent_client/bin/nodeshell @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2016-2017 Lenovo diff --git a/confluent_client/bin/stats b/confluent_client/bin/stats index 39463f90..c9231a43 100755 --- a/confluent_client/bin/stats +++ b/confluent_client/bin/stats @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2019 Lenovo diff --git a/confluent_client/samples/nodeattrib_from_switch.py b/confluent_client/samples/nodeattrib_from_switch.py index b8e88ec3..eff3e394 100644 --- a/confluent_client/samples/nodeattrib_from_switch.py +++ b/confluent_client/samples/nodeattrib_from_switch.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # This is a sample python script for going through all observed mac addresses # and assuming they are BMC related and printing nodeattrib commands diff --git a/confluent_server/bin/collective b/confluent_server/bin/collective index 27b12566..aba64680 100644 --- a/confluent_server/bin/collective +++ b/confluent_server/bin/collective @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import argparse import errno diff --git a/confluent_server/bin/confluent b/confluent_server/bin/confluent index 4f1d73f4..62d6ab3d 100755 --- a/confluent_server/bin/confluent +++ b/confluent_server/bin/confluent @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2014 IBM Corporation diff --git a/confluent_server/bin/confluentdbutil b/confluent_server/bin/confluentdbutil index d14de99c..0122b782 100755 --- a/confluent_server/bin/confluentdbutil +++ b/confluent_server/bin/confluentdbutil @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo diff --git a/confluent_server/bin/confluentsrv.py b/confluent_server/bin/confluentsrv.py index 4f1d73f4..62d6ab3d 100644 --- a/confluent_server/bin/confluentsrv.py +++ b/confluent_server/bin/confluentsrv.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2014 IBM Corporation diff --git a/confluent_server/confluentdbgcli.py b/confluent_server/confluentdbgcli.py index 4ed804b5..6c804cc3 100644 --- a/confluent_server/confluentdbgcli.py +++ b/confluent_server/confluentdbgcli.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2014 IBM Corporation diff --git a/confluent_server/dbgtools/confluentdbgcli.py b/confluent_server/dbgtools/confluentdbgcli.py index 59f897dc..ad9314e8 100644 --- a/confluent_server/dbgtools/confluentdbgcli.py +++ b/confluent_server/dbgtools/confluentdbgcli.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2017 Lenovo diff --git a/confluent_server/dbgtools/processhangtraces.py b/confluent_server/dbgtools/processhangtraces.py index 329efe29..98698ad4 100644 --- a/confluent_server/dbgtools/processhangtraces.py +++ b/confluent_server/dbgtools/processhangtraces.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 # Copyright 2017 Lenovo # diff --git a/misc/disablepasscomplexity.py b/misc/disablepasscomplexity.py index ac2a62c8..01628e38 100644 --- a/misc/disablepasscomplexity.py +++ b/misc/disablepasscomplexity.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import pyghmi.util.webclient as webclient import json import os diff --git a/misc/fixexpiry.py b/misc/fixexpiry.py index 3359c2fa..f2d6fa45 100644 --- a/misc/fixexpiry.py +++ b/misc/fixexpiry.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import pyghmi.util.webclient as webclient import json import os diff --git a/misc/fixsmmexpiry.py b/misc/fixsmmexpiry.py index 6b4f8314..649b3c73 100644 --- a/misc/fixsmmexpiry.py +++ b/misc/fixsmmexpiry.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python2 import pyghmi.util.webclient as webclient from xml.etree.ElementTree import fromstring import os From 8909fb16d6dfcd9ab0401167968a72d581937f89 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 23 Sep 2019 11:11:16 -0400 Subject: [PATCH 06/56] Change rpm spec to build using python2 This will fix shebang mangling. --- confluent_client/confluent_client.spec.tmpl | 4 ++-- confluent_common/confluent_common.spec.tmpl | 4 ++-- confluent_server/confluent_server.spec.tmpl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/confluent_client/confluent_client.spec.tmpl b/confluent_client/confluent_client.spec.tmpl index 5317b0c6..68e045a4 100644 --- a/confluent_client/confluent_client.spec.tmpl +++ b/confluent_client/confluent_client.spec.tmpl @@ -24,10 +24,10 @@ a confluent server. %setup -n %{name}-%{version} -n %{name}-%{version} %build -python setup.py build +python2 setup.py build %install -python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python +python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python %clean diff --git a/confluent_common/confluent_common.spec.tmpl b/confluent_common/confluent_common.spec.tmpl index 18461bba..94a1ea73 100644 --- a/confluent_common/confluent_common.spec.tmpl +++ b/confluent_common/confluent_common.spec.tmpl @@ -22,10 +22,10 @@ This provides the modules common for both client and server %setup -n %{name}-%{version} -n %{name}-%{version} %build -python setup.py build +python2 setup.py build %install -python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin +python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin %clean rm -rf $RPM_BUILD_ROOT diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 53fa067c..d5e4111c 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -23,10 +23,10 @@ Server for console management and systems management aggregation %setup -n %{name}-%{version} -n %{name}-%{version} %build -python setup.py build +python2 setup.py build %install -python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin +python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do rm $RPM_BUILD_ROOT/$file done From c532cf9ecf031b90028f029a6334ecd45e1383d5 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 25 Sep 2019 15:38:36 -0400 Subject: [PATCH 07/56] Add EL8 requires adapting to confluent --- confluent_server/confluent_server.spec.tmpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index d5e4111c..478f6c8a 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -12,7 +12,11 @@ Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch +%if "%{dist}" == ".el8" +Requires: python2-pyghmi >= 1.0.34, python2-eventlet, python2-greenlet, python2-pycryptodomex >= 3.4.7, confluent_client, python2-pyparsing, python2-paramiko, python2-dns, python2-netifaces, python2-pyasn1 >= 0.2.3, python2-pysnmp >= 4.3.4, python2-pyte, python2-lxml, python2-eficompressor, python2-setuptools, python2-dateutil +%else Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil +%endif Vendor: Jarrod Johnson Url: http://xcat.sf.net/ From 6a6fd3184ec84f2fffb600dc534ded352c64e5d8 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 30 Sep 2019 10:51:26 -0400 Subject: [PATCH 08/56] Add missing dependencies for EL8 --- confluent_server/confluent_server.spec.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 478f6c8a..85a78c7f 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -13,7 +13,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch %if "%{dist}" == ".el8" -Requires: python2-pyghmi >= 1.0.34, python2-eventlet, python2-greenlet, python2-pycryptodomex >= 3.4.7, confluent_client, python2-pyparsing, python2-paramiko, python2-dns, python2-netifaces, python2-pyasn1 >= 0.2.3, python2-pysnmp >= 4.3.4, python2-pyte, python2-lxml, python2-eficompressor, python2-setuptools, python2-dateutil +Requires: python2-pyghmi >= 1.0.34, python2-eventlet, python2-greenlet, python2-pycryptodomex >= 3.4.7, confluent_client, python2-pyparsing, python2-paramiko, python2-dns, python2-netifaces, python2-pyasn1 >= 0.2.3, python2-pysnmp >= 4.3.4, python2-pyte, python2-lxml, python2-eficompressor, python2-setuptools, python2-dateutil, python2-enum34, python2-asn1crypto, python2-ipaddress, python2-cffi, python2-pyOpenSSL %else Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil %endif From 147d59cba7986ecb4b5e96b53801f8fa11a2923a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 1 Oct 2019 11:28:43 -0400 Subject: [PATCH 09/56] Migrate from PyPAM PyPAM is no longer part of the distributions. Closest match is also not in the distributions and also contains a security problem without an external patch, so it is pulled in and pull request with copyright and license intact. --- confluent_server/confluent/auth.py | 39 +---- confluent_server/confluent/pam.py | 235 +++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+), 31 deletions(-) create mode 100644 confluent_server/confluent/pam.py diff --git a/confluent_server/confluent/auth.py b/confluent_server/confluent/auth.py index ed73ab7e..4bdfa885 100644 --- a/confluent_server/confluent/auth.py +++ b/confluent_server/confluent/auth.py @@ -28,8 +28,9 @@ import hashlib import hmac import multiprocessing import confluent.userutil as userutil +pam = None try: - import PAM + import confluent.pam as pam except ImportError: pass import time @@ -94,23 +95,6 @@ _deniedbyrole = { ] } } -class Credentials(object): - def __init__(self, username, passphrase): - self.username = username - self.passphrase = passphrase - self.haspam = False - - def pam_conv(self, auth, query_list): - # use stored credentials in a pam conversation - self.haspam = True - resp = [] - for query_entry in query_list: - query, pamtype = query_entry - if query.startswith('Password'): - resp.append((self.passphrase, 0)) - else: - return None - return resp def _prune_passcache(): @@ -227,7 +211,6 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant # would normally make an event and wait # but here there's no need for that eventlet.sleep(0.5) - credobj = Credentials(user, passphrase) cfm = configmanager.ConfigManager(tenant, username=user) ucfg = cfm.get_user(user) if ucfg is None: @@ -278,22 +261,16 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant if crypt == crypted: _passcache[(user, tenant)] = hashlib.sha256(passphrase).digest() return authorize(user, element, tenant, operation) - try: - pammy = PAM.pam() - pammy.start(_pamservice, user, credobj.pam_conv) - pammy.authenticate() - pammy.acct_mgmt() + if pam: + pammy = pam.pam() + usergood = pammy.authenticate(user, passphrase) del pammy - _passcache[(user, tenant)] = hashlib.sha256(passphrase).digest() - return authorize(user, element, tenant, operation, skipuserobj=False) - except NameError: - pass - except PAM.error: - pass + if usergood: + _passcache[(user, tenant)] = hashlib.sha256(passphrase).digest() + return authorize(user, element, tenant, operation, skipuserobj=False) eventlet.sleep(0.05) # stall even on test for existence of a username return None - def _apply_pbkdf(passphrase, salt): return KDF.PBKDF2(passphrase, salt, 32, 10000, lambda p, s: hmac.new(p, s, hashlib.sha256).digest()) diff --git a/confluent_server/confluent/pam.py b/confluent_server/confluent/pam.py new file mode 100644 index 00000000..9bf28eaf --- /dev/null +++ b/confluent_server/confluent/pam.py @@ -0,0 +1,235 @@ +# Pulled from: +# https://raw.githubusercontent.com/FirefighterBlu3/python-pam/fe44b334970f421635d9e373b563c9e6566613bd/pam.py +# and https://github.com/FirefighterBlu3/python-pam/pull/16/files +# (c) 2007 Chris AtLee +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# Original author: Chris AtLee +# +# Modified by David Ford, 2011-12-6 +# added py3 support and encoding +# added pam_end +# added pam_setcred to reset credentials after seeing Leon Walker's remarks +# added byref as well +# use readline to prestuff the getuser input + +''' +PAM module for python + +Provides an authenticate function that will allow the caller to authenticate +a user against the Pluggable Authentication Modules (PAM) on the system. + +Implemented using ctypes, so no compilation is necessary. +''' + +__all__ = ['pam'] +__version__ = '1.8.4' +__author__ = 'David Ford ' +__released__ = '2018 June 15' + +import sys + +from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof +from ctypes import c_void_p, c_size_t, c_char_p, c_char, c_int +from ctypes import memmove +from ctypes.util import find_library + +class PamHandle(Structure): + """wrapper class for pam_handle_t pointer""" + _fields_ = [ ("handle", c_void_p) ] + + def __init__(self): + Structure.__init__(self) + self.handle = 0 + +class PamMessage(Structure): + """wrapper class for pam_message structure""" + _fields_ = [ ("msg_style", c_int), ("msg", c_char_p) ] + + def __repr__(self): + return "" % (self.msg_style, self.msg) + +class PamResponse(Structure): + """wrapper class for pam_response structure""" + _fields_ = [ ("resp", c_char_p), ("resp_retcode", c_int) ] + + def __repr__(self): + return "" % (self.resp_retcode, self.resp) + +conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p) + +class PamConv(Structure): + """wrapper class for pam_conv structure""" + _fields_ = [ ("conv", conv_func), ("appdata_ptr", c_void_p) ] + +# Various constants +PAM_PROMPT_ECHO_OFF = 1 +PAM_PROMPT_ECHO_ON = 2 +PAM_ERROR_MSG = 3 +PAM_TEXT_INFO = 4 +PAM_REINITIALIZE_CRED = 8 + +libc = CDLL(find_library("c")) +libpam = CDLL(find_library("pam")) + +calloc = libc.calloc +calloc.restype = c_void_p +calloc.argtypes = [c_size_t, c_size_t] + +# bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this function +if hasattr(libpam, 'pam_end'): + pam_end = libpam.pam_end + pam_end.restype = c_int + pam_end.argtypes = [PamHandle, c_int] + +pam_start = libpam.pam_start +pam_start.restype = c_int +pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] + +pam_acct_mgmt = libpam.pam_acct_mgmt +pam_acct_mgmt.restype = c_int +pam_acct_mgmt.argtypes = [PamHandle, c_int] + +pam_setcred = libpam.pam_setcred +pam_setcred.restype = c_int +pam_setcred.argtypes = [PamHandle, c_int] + +pam_strerror = libpam.pam_strerror +pam_strerror.restype = c_char_p +pam_strerror.argtypes = [PamHandle, c_int] + +pam_authenticate = libpam.pam_authenticate +pam_authenticate.restype = c_int +pam_authenticate.argtypes = [PamHandle, c_int] + +class pam(): + code = 0 + reason = None + + def __init__(self): + pass + + def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): + """username and password authentication for the given service. + + Returns True for success, or False for failure. + + self.code (integer) and self.reason (string) are always stored and may + be referenced for the reason why authentication failed. 0/'Success' will + be stored for success. + + Python3 expects bytes() for ctypes inputs. This function will make + necessary conversions using the supplied encoding. + + Inputs: + username: username to authenticate + password: password in plain text + service: PAM service to authenticate against, defaults to 'login' + + Returns: + success: True + failure: False + """ + + @conv_func + def my_conv(n_messages, messages, p_response, app_data): + """Simple conversation function that responds to any + prompt where the echo is off with the supplied password""" + # Create an array of n_messages response objects + addr = calloc(n_messages, sizeof(PamResponse)) + response = cast(addr, POINTER(PamResponse)) + p_response[0] = response + for i in range(n_messages): + if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: + dst = calloc(len(password)+1, sizeof(c_char)) + memmove(dst, cpassword, len(password)) + response[i].resp = dst + response[i].resp_retcode = 0 + return 0 + + # python3 ctypes prefers bytes + if sys.version_info >= (3,): + if isinstance(username, str): username = username.encode(encoding) + if isinstance(password, str): password = password.encode(encoding) + if isinstance(service, str): service = service.encode(encoding) + else: + if isinstance(username, unicode): + username = username.encode(encoding) + if isinstance(password, unicode): + password = password.encode(encoding) + if isinstance(service, unicode): + service = service.encode(encoding) + + if b'\x00' in username or b'\x00' in password or b'\x00' in service: + self.code = 4 # PAM_SYSTEM_ERR in Linux-PAM + self.reason = 'strings may not contain NUL' + return False + + # do this up front so we can safely throw an exception if there's + # anything wrong with it + cpassword = c_char_p(password) + + handle = PamHandle() + conv = PamConv(my_conv, 0) + retval = pam_start(service, username, byref(conv), byref(handle)) + + if retval != 0: + # This is not an authentication error, something has gone wrong starting up PAM + self.code = retval + self.reason = "pam_start() failed" + return False + + retval = pam_authenticate(handle, 0) + auth_success = retval == 0 + + if auth_success: + retval = pam_acct_mgmt(handle, 0) + auth_success = retval == 0 + + if auth_success and resetcreds: + retval = pam_setcred(handle, PAM_REINITIALIZE_CRED) + + # store information to inform the caller why we failed + self.code = retval + self.reason = pam_strerror(handle, retval) + if sys.version_info >= (3,): + self.reason = self.reason.decode(encoding) + + if hasattr(libpam, 'pam_end'): + pam_end(handle, retval) + + return auth_success + + +def authenticate(*vargs, **dargs): + """ + Compatibility function for older versions of python-pam. + """ + return pam().authenticate(*vargs, **dargs) + + +if __name__ == "__main__": + import readline, getpass + + def input_with_prefill(prompt, text): + def hook(): + readline.insert_text(text) + readline.redisplay() + readline.set_pre_input_hook(hook) + + if sys.version_info >= (3,): + result = input(prompt) + else: + result = raw_input(prompt) + + readline.set_pre_input_hook() + return result + + pam = pam() + + username = input_with_prefill('Username: ', getpass.getuser()) + + # enter a valid username and an invalid/valid password, to verify both failure and success + pam.authenticate(username, getpass.getpass()) + print('{} {}'.format(pam.code, pam.reason)) \ No newline at end of file From 90e546bcac76c65722f7794a6350f4e1e0c0f7a3 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 2 Oct 2019 08:58:39 -0400 Subject: [PATCH 10/56] Implement a number of py3 compatible adjustments --- confluent_server/bin/confluentdbutil | 2 +- confluent_server/confluent/config/conf.py | 5 ++- .../confluent/config/configmanager.py | 45 ++++++++++++++----- confluent_server/confluent/core.py | 2 + .../confluent/discovery/protocols/pxe.py | 2 +- .../confluent/discovery/protocols/slp.py | 12 ++--- confluent_server/confluent/httpapi.py | 10 ++++- confluent_server/confluent/log.py | 6 +++ confluent_server/confluent/main.py | 4 +- confluent_server/confluent/neighutil.py | 8 ++-- confluent_server/confluent/shellmodule.py | 4 +- confluent_server/confluent/sockapi.py | 2 +- 12 files changed, 71 insertions(+), 31 deletions(-) diff --git a/confluent_server/bin/confluentdbutil b/confluent_server/bin/confluentdbutil index 0122b782..f8495741 100755 --- a/confluent_server/bin/confluentdbutil +++ b/confluent_server/bin/confluentdbutil @@ -82,7 +82,7 @@ elif args[0] == 'dump': "or -s to do encrypted backup that requires keys.json from " "another backup to restore.") sys.exit(1) - os.umask(077) + os.umask(0o77) main._initsecurity(conf.get_config()) if not os.path.exists(dumpdir): os.makedirs(dumpdir) diff --git a/confluent_server/confluent/config/conf.py b/confluent_server/confluent/config/conf.py index aeca1cd6..620e12f8 100644 --- a/confluent_server/confluent/config/conf.py +++ b/confluent_server/confluent/config/conf.py @@ -16,7 +16,10 @@ # This defines config variable to store the global configuration for confluent -import ConfigParser +try: + import ConfigParser +except ModuleNotFoundError: + import configparser as ConfigParser import os _config = None diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 4a8fc3c0..859598a8 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -46,7 +46,10 @@ import Cryptodome.Protocol.KDF as KDF from Cryptodome.Cipher import AES from Cryptodome.Hash import HMAC from Cryptodome.Hash import SHA256 -import anydbm as dbm +try: + import anydbm as dbm +except ModuleNotFoundError: + import dbm import ast import base64 import confluent.config.attributes as allattributes @@ -57,7 +60,10 @@ import confluent.util import confluent.netutil as netutil import confluent.exceptions as exc import copy -import cPickle +try: + import cPickle +except ModuleNotFoundError: + import pickle as cPickle import errno import eventlet import eventlet.event as event @@ -74,6 +80,10 @@ import struct import sys import threading import traceback +try: + unicode +except NameError: + unicode = str _masterkey = None @@ -478,7 +488,7 @@ def _load_dict_from_dbm(dpath, tdb): currdict[elem] = {} currdict = currdict[elem] try: - for tk in dbe: + for tk in dbe.keys(): currdict[tk] = cPickle.loads(dbe[tk]) except AttributeError: tk = dbe.firstkey() @@ -1145,9 +1155,9 @@ class ConfigManager(object): Returns an identifier that can be used to unsubscribe from these notifications using remove_watcher """ - notifierid = random.randint(0, sys.maxint) + notifierid = random.randint(0, sys.maxsize) while notifierid in self._notifierids: - notifierid = random.randint(0, sys.maxint) + notifierid = random.randint(0, sys.maxsize) self._notifierids[notifierid] = {'attriblist': []} if self.tenant not in self._attribwatchers: self._attribwatchers[self.tenant] = {} @@ -1186,9 +1196,9 @@ class ConfigManager(object): # use in case of cancellation. # I anticipate no more than a handful of watchers of this sort, so # this loop should not have to iterate too many times - notifierid = random.randint(0, sys.maxint) + notifierid = random.randint(0, sys.maxsize) while notifierid in self._notifierids: - notifierid = random.randint(0, sys.maxint) + notifierid = random.randint(0, sys.maxsize) # going to track that this is a nodecollection type watcher, # but there is no additional data associated. self._notifierids[notifierid] = set(['nodecollection']) @@ -1665,6 +1675,8 @@ class ConfigManager(object): node, group)) for group in attribmap: group = group.encode('utf-8') + if not isinstance(group, str): + group = group.decode('utf-8') if group not in self._cfgstore['nodegroups']: self._cfgstore['nodegroups'][group] = {'nodes': set()} cfgobj = self._cfgstore['nodegroups'][group] @@ -1836,6 +1848,8 @@ class ConfigManager(object): # framework to trigger on changeset[node] = {'_nodedeleted': 1} node = node.encode('utf-8') + if not isinstance(node, str): + node = node.decode('utf-8') if node in self._cfgstore['nodes']: self._sync_groups_to_node(node=node, groups=[], changeset=changeset) @@ -2012,6 +2026,8 @@ class ConfigManager(object): # this mitigates risk of arguments being partially applied for node in attribmap: node = node.encode('utf-8') + if not isinstance(group, str): + node = node.decode('utf-8') if node == '': raise ValueError('"{0}" is not a valid node name'.format(node)) if autocreate: @@ -2068,6 +2084,8 @@ class ConfigManager(object): attribmap[node][attrname] = attrval for node in attribmap: node = node.encode('utf-8') + if not isinstance(node, str): + node = node.decode('utf-8') exprmgr = None if node not in self._cfgstore['nodes']: newnodes.append(node) @@ -2248,7 +2266,7 @@ class ConfigManager(object): _cfgstore = {} rootpath = cls._cfgdir try: - with open(os.path.join(rootpath, 'transactioncount'), 'r') as f: + with open(os.path.join(rootpath, 'transactioncount'), 'rb') as f: txbytes = f.read() if len(txbytes) == 8: _txcount = struct.unpack('!Q', txbytes)[0] @@ -2306,7 +2324,7 @@ class ConfigManager(object): if statelessmode: return _mkpath(cls._cfgdir) - with open(os.path.join(cls._cfgdir, 'transactioncount'), 'w') as f: + with open(os.path.join(cls._cfgdir, 'transactioncount'), 'wb') as f: f.write(struct.pack('!Q', _txcount)) if (fullsync or 'dirtyglobals' in _cfgstore and 'globals' in _cfgstore): @@ -2417,7 +2435,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True): else: keydata = json.loads(jsond) cryptkey = _parse_key(keydata['cryptkey'], password) - integritykey = _parse_key(keydata['integritykey'], password) + integritykey = None + if 'integritykey' in keydata: + integritykey = _parse_key(keydata['integritykey'], password) conf.init_config() cfg = conf.get_config() if cfg.has_option('security', 'externalcfgkey'): @@ -2426,8 +2446,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True): newpassword = keyfile.read() set_global('master_privacy_key', _format_key(cryptkey, password=newpassword), sync) - set_global('master_integrity_key', _format_key(integritykey, - password=newpassword), sync) + if integritykey: + set_global('master_integrity_key', _format_key(integritykey, + password=newpassword), sync) _masterkey = cryptkey _masterintegritykey = integritykey if sync: diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 2244f0a4..b5a4e19c 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -106,6 +106,8 @@ def load_plugins(): for plugin in os.listdir(plugindir): if plugin.startswith('.'): continue + if '__pycache__' in plugin: + continue (plugin, plugtype) = os.path.splitext(plugin) if plugtype == '.sh': pluginmap[plugin] = shellmodule.Plugin( diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 66369bf5..ad52bf08 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -98,7 +98,7 @@ def snoop(handler, protocol=None): netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr]) optidx = 0 try: - optidx = rq.index('\x63\x82\x53\x63') + 4 + optidx = rq.index(b'\x63\x82\x53\x63') + 4 except ValueError: continue uuid, arch = find_info_in_options(rq, optidx) diff --git a/confluent_server/confluent/discovery/protocols/slp.py b/confluent_server/confluent/discovery/protocols/slp.py index 3c4adfc2..f3eec2e7 100644 --- a/confluent_server/confluent/discovery/protocols/slp.py +++ b/confluent_server/confluent/discovery/protocols/slp.py @@ -249,18 +249,18 @@ def _parse_attrlist(attrstr): attribs = {} while attrstr: if attrstr[0] == '(': - if ')' not in attrstr: + if b')' not in attrstr: attribs['INCOMPLETE'] = True return attribs - currattr = attrstr[1:attrstr.index(')')] - if '=' not in currattr: # Not allegedly kosher, but still.. + currattr = attrstr[1:attrstr.index(b')')] + if b'=' not in currattr: # Not allegedly kosher, but still.. currattr = currattr.decode('utf-8') attribs[currattr] = None else: attrname, attrval = currattr.split('=', 1) attrname = attrname.decode('utf-8') attribs[attrname] = [] - for val in attrval.split(','): + for val in attrval.split(b','): try: val = val.decode('utf-8') except UnicodeDecodeError: @@ -284,9 +284,9 @@ def _parse_attrlist(attrstr): ).lower() attribs[attrname].append(val) attrstr = attrstr[attrstr.index(')'):] - elif attrstr[0] == ',': + elif attrstr[0] == b','[0]: attrstr = attrstr[1:] - elif ',' in attrstr: + elif b',' in attrstr: currattr = attrstr[:attrstr.index(',')] attribs[currattr] = None attrstr = attrstr[attrstr.index(','):] diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index bf7176e5..bcb81a1d 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -17,7 +17,10 @@ # This SCGI server provides a http wrap to confluent api # It additionally manages httprequest console sessions import base64 -import Cookie +try: + import Cookie +except ModuleNotFoundError: + import http.cookies as Cookie import confluent.auth as auth import confluent.config.attributes as attribs import confluent.consoleserver as consoleserver @@ -39,7 +42,10 @@ import socket import sys import traceback import time -import urlparse +try: + import urlparse +except ModuleNotFoundError: + import urllib.parse as urlparse import eventlet.wsgi #scgi = eventlet.import_patched('flup.server.scgi') tlvdata = confluent.tlvdata diff --git a/confluent_server/confluent/log.py b/confluent_server/confluent/log.py index 161dffa4..19e744f5 100644 --- a/confluent_server/confluent/log.py +++ b/confluent_server/confluent/log.py @@ -76,6 +76,10 @@ import stat import struct import time import traceback +try: + unicode +except NameError: + unicode = str daemonized = False logfull = False @@ -176,6 +180,8 @@ class BaseRotatingHandler(object): self.textfile = open(self.textpath, mode='ab') if self.binfile is None: self.binfile = open(self.binpath, mode='ab') + if not isinstance(textrecord, bytes): + textrecord = textrecord.encode('utf-8') self.textfile.write(textrecord) self.binfile.write(binrecord) self.textfile.flush() diff --git a/confluent_server/confluent/main.py b/confluent_server/confluent/main.py index 67d0db2f..50978a95 100644 --- a/confluent_server/confluent/main.py +++ b/confluent_server/confluent/main.py @@ -43,9 +43,11 @@ except ImportError: import confluent.discovery.core as disco import eventlet dbgif = False -if map(int, (eventlet.__version__.split('.'))) > [0, 18]: +try: import eventlet.backdoor as backdoor dbgif = True +except Exception: + pass havefcntl = True try: import fcntl diff --git a/confluent_server/confluent/neighutil.py b/confluent_server/confluent/neighutil.py index 9da1d195..5b5d2546 100644 --- a/confluent_server/confluent/neighutil.py +++ b/confluent_server/confluent/neighutil.py @@ -26,7 +26,7 @@ neightime = 0 import re -_validmac = re.compile('..:..:..:..:..:..') +_validmac = re.compile(b'..:..:..:..:..:..') def update_neigh(): @@ -39,11 +39,11 @@ def update_neigh(): stdout=subprocess.PIPE, stderr=subprocess.PIPE) (neighdata, err) = ipn.communicate() - for entry in neighdata.split('\n'): - entry = entry.split(' ') + for entry in neighdata.split(b'\n'): + entry = entry.split(b' ') if len(entry) < 5 or not entry[4]: continue - if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'): + if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'192.168.70.125'): # Note that these addresses are common static ip addresses # that are hopelessly ambiguous if there are many # so ignore such entries and move on diff --git a/confluent_server/confluent/shellmodule.py b/confluent_server/confluent/shellmodule.py index 5b7e44e3..d8cc224f 100644 --- a/confluent_server/confluent/shellmodule.py +++ b/confluent_server/confluent/shellmodule.py @@ -89,7 +89,7 @@ class ExecConsole(conapi.Console): stdin=slave, stdout=slave, stderr=subprocess.PIPE, close_fds=True) except OSError: - print "Unable to execute " + self.executable + " (permissions?)" + print("Unable to execute " + self.executable + " (permissions?)") self.close() return os.close(slave) @@ -104,7 +104,7 @@ class ExecConsole(conapi.Console): try: os.close(self._master) except OSError: - print "Error closing master of child process, ignoring" + print("Error closing master of child process, ignoring") if self.subproc is None or self.subproc.poll() is not None: return self.subproc.terminate() diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index d4382814..76ec6131 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -412,7 +412,7 @@ def _unixdomainhandler(): except OSError: # if file does not exist, no big deal pass if not os.path.isdir("/var/run/confluent"): - os.makedirs('/var/run/confluent', 0755) + os.makedirs('/var/run/confluent', 0o755) unixsocket.bind("/var/run/confluent/api.sock") os.chmod("/var/run/confluent/api.sock", stat.S_IWOTH | stat.S_IROTH | stat.S_IWGRP | From 6fb82bbbad79fef1cb3af7113d687b4f7ad5d08f Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 2 Oct 2019 11:29:13 -0400 Subject: [PATCH 11/56] Further Python3 compatibility changes --- confluent_client/confluent/tlvdata.py | 11 +++++-- .../confluent/config/configmanager.py | 14 +++++--- confluent_server/confluent/consoleserver.py | 32 ++++++++++++------- confluent_server/confluent/core.py | 5 ++- .../confluent/discovery/protocols/slp.py | 13 +++++--- confluent_server/confluent/messages.py | 25 +++++++++------ confluent_server/confluent/noderange.py | 13 +++++--- .../plugins/hardwaremanagement/ipmi.py | 6 ++-- 8 files changed, 76 insertions(+), 43 deletions(-) diff --git a/confluent_client/confluent/tlvdata.py b/confluent_client/confluent/tlvdata.py index 18a3328b..f7d567fe 100644 --- a/confluent_client/confluent/tlvdata.py +++ b/confluent_client/confluent/tlvdata.py @@ -25,6 +25,11 @@ try: except NameError: unicode = str +try: + range = xrange +except NameError: + pass + def decodestr(value): ret = None try: @@ -40,7 +45,7 @@ def decodestr(value): def unicode_dictvalues(dictdata): for key in dictdata: - if isinstance(dictdata[key], str): + if isinstance(dictdata[key], bytes): dictdata[key] = decodestr(dictdata[key]) elif isinstance(dictdata[key], datetime): dictdata[key] = dictdata[key].strftime('%Y-%m-%dT%H:%M:%S') @@ -51,7 +56,7 @@ def unicode_dictvalues(dictdata): def _unicode_list(currlist): - for i in xrange(len(currlist)): + for i in range(len(currlist)): if isinstance(currlist[i], str): currlist[i] = decodestr(currlist[i]) elif isinstance(currlist[i], dict): @@ -66,7 +71,7 @@ def send(handle, data): data = data.encode('utf-8') except AttributeError: pass - if isinstance(data, str) or isinstance(data, unicode): + if isinstance(data, bytes) or isinstance(data, unicode): # plain text, e.g. console data tl = len(data) if tl == 0: diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 859598a8..503fb3ec 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -1826,7 +1826,8 @@ class ConfigManager(object): 'nodeattrs': {node: [attrname]}, 'callback': attribwatcher[watchkey][notifierid] } - for watcher in notifdata.itervalues(): + for watcher in notifdata: + watcher = notifdata[watcher] callback = watcher['callback'] eventlet.spawn_n(_do_notifier, self, watcher, callback) @@ -1840,7 +1841,8 @@ class ConfigManager(object): def _true_del_nodes(self, nodes): if self.tenant in self._nodecollwatchers: - for watcher in self._nodecollwatchers[self.tenant].itervalues(): + for watcher in self._nodecollwatchers[self.tenant]: + watcher = self._nodecollwatchers[self.tenant][watcher] watcher(added=(), deleting=nodes, renamed=(), configmanager=self) changeset = {} for node in nodes: @@ -1968,7 +1970,8 @@ class ConfigManager(object): self._recalculate_expressions(cfgobj, formatter=exprmgr, node=renamemap[name], changeset=changeset) if self.tenant in self._nodecollwatchers: nodecollwatchers = self._nodecollwatchers[self.tenant] - for watcher in nodecollwatchers.itervalues(): + for watcher in nodecollwatchers: + watcher = nodecollwatchers[watcher] eventlet.spawn_n(_do_add_watcher, watcher, (), self, renamemap) self._bg_sync_to_file() @@ -2026,7 +2029,7 @@ class ConfigManager(object): # this mitigates risk of arguments being partially applied for node in attribmap: node = node.encode('utf-8') - if not isinstance(group, str): + if not isinstance(node, str): node = node.decode('utf-8') if node == '': raise ValueError('"{0}" is not a valid node name'.format(node)) @@ -2128,7 +2131,8 @@ class ConfigManager(object): if newnodes: if self.tenant in self._nodecollwatchers: nodecollwatchers = self._nodecollwatchers[self.tenant] - for watcher in nodecollwatchers.itervalues(): + for watcher in nodecollwatchers: + watcher = nodecollwatchers[watcher] eventlet.spawn_n(_do_add_watcher, watcher, newnodes, self) self._bg_sync_to_file() #TODO: wait for synchronization to suceed/fail??) diff --git a/confluent_server/confluent/consoleserver.py b/confluent_server/confluent/consoleserver.py index 0413d1a6..c65a397d 100644 --- a/confluent_server/confluent/consoleserver.py +++ b/confluent_server/confluent/consoleserver.py @@ -94,7 +94,7 @@ def _utf8_normalize(data, shiftin, decoder): def pytechars2line(chars, maxlen=None): - line = '\x1b[m' # start at default params + line = b'\x1b[m' # start at default params lb = False # last bold li = False # last italic lu = False # last underline @@ -130,9 +130,12 @@ def pytechars2line(chars, maxlen=None): csi.append(7 if lr else 27) if csi: line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm' - if not hasdata and char.data.encode('utf-8').rstrip(): + if not hasdata and char.data.rstrip(): hasdata = True - line += char.data.encode('utf-8') + chardata = char.data + if not isinstance(chardata, bytes): + chardata = chardata.encode('utf-8') + line += chardata if maxlen and len >= maxlen: break len += 1 @@ -185,7 +188,7 @@ class ConsoleHandler(object): if termstate & 1: self.appmodedetected = True if termstate & 2: - self.shiftin = '0' + self.shiftin = b'0' self.users = {} self._attribwatcher = None self._console = None @@ -210,6 +213,8 @@ class ConsoleHandler(object): return retrytime + (retrytime * random.random()) def feedbuffer(self, data): + if not isinstance(data, bytes): + data = data.encode('utf-8') try: self.termstream.feed(data) except StopIteration: # corrupt parser state, start over @@ -535,7 +540,7 @@ class ConsoleHandler(object): self.appmodedetected = True if '\x1b)0' in data: # console indicates it wants access to special drawing characters - self.shiftin = '0' + self.shiftin = b'0' eventdata = 0 if self.appmodedetected: eventdata |= 1 @@ -588,20 +593,23 @@ class ConsoleHandler(object): if pendingbl: retdata += pendingbl pendingbl = b'' - retdata += nline + '\r\n' + retdata += nline + b'\r\n' else: - pendingbl += nline + '\r\n' + pendingbl += nline + b'\r\n' if len(retdata) > 6: retdata = retdata[:-2] # remove the last \r\n - retdata += b'\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1, - self.buffer.cursor.x + 1) + cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1, + self.buffer.cursor.x + 1) + if not isinstance(cursordata, bytes): + cursordata = cursordata.encode('utf-8') + retdata += cursordata if self.shiftin is not None: # detected that terminal requested a # shiftin character set, relay that to the terminal that cannected - retdata += '\x1b)' + self.shiftin + retdata += b'\x1b)' + self.shiftin if self.appmodedetected: - retdata += '\x1b[?1h' + retdata += b'\x1b[?1h' else: - retdata += '\x1b[?1l' + retdata += b'\x1b[?1l' return retdata, connstate def write(self, data): diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index b5a4e19c..a92e9439 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -86,7 +86,10 @@ def seek_element(currplace, currkey): def nested_lookup(nestdict, key): try: - return reduce(seek_element, key, nestdict) + currloc = nestdict + for currk in key: + currloc = seek_element(currloc, currk) + return currloc except TypeError: raise exc.NotFoundException("Invalid element requested") diff --git a/confluent_server/confluent/discovery/protocols/slp.py b/confluent_server/confluent/discovery/protocols/slp.py index f3eec2e7..1a4fedd0 100644 --- a/confluent_server/confluent/discovery/protocols/slp.py +++ b/confluent_server/confluent/discovery/protocols/slp.py @@ -49,7 +49,6 @@ except AttributeError: IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it - def _parse_slp_header(packet): packet = bytearray(packet) if len(packet) < 16 or packet[0] != 2: @@ -247,7 +246,11 @@ def _grab_rsps(socks, rsps, interval, xidmap): def _parse_attrlist(attrstr): attribs = {} + previousattrlen = None while attrstr: + if len(attrstr) == previousattrlen: + raise Exception('Looping in attrstr parsing') + previousattrlen = len(attrstr) if attrstr[0] == '(': if b')' not in attrstr: attribs['INCOMPLETE'] = True @@ -274,9 +277,9 @@ def _parse_attrlist(attrstr): val = finalval if 'uuid' in attrname and len(val) == 16: lebytes = struct.unpack_from( - 'HHI', buffer(val[8:])) + '>HHI', memoryview(val[8:])) val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \ '{4:04X}{5:08X}'.format( lebytes[0], lebytes[1], lebytes[2], bebytes[0], @@ -287,9 +290,9 @@ def _parse_attrlist(attrstr): elif attrstr[0] == b','[0]: attrstr = attrstr[1:] elif b',' in attrstr: - currattr = attrstr[:attrstr.index(',')] + currattr = attrstr[:attrstr.index(b',')] attribs[currattr] = None - attrstr = attrstr[attrstr.index(','):] + attrstr = attrstr[attrstr.index(b','):] else: currattr = attrstr attribs[currattr] = None diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index d328d26c..ae8bc12f 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -25,6 +25,11 @@ from copy import deepcopy from datetime import datetime import json +try: + unicode +except NameError: + unicode = str + valid_health_values = set([ 'ok', 'warning', @@ -54,7 +59,7 @@ def _htmlify_structure(indict): if isinstance(indict, dict): for key in sorted(indict): ret += "
  • {0}: ".format(key) - if type(indict[key]) in (str, unicode, float, int): + if type(indict[key]) in (bytes, unicode, float, int): ret += str(indict[key]) elif isinstance(indict[key], datetime): ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S') @@ -62,7 +67,7 @@ def _htmlify_structure(indict): ret += _htmlify_structure(indict[key]) elif isinstance(indict, list): if len(indict) > 0: - if type(indict[0]) in (str, unicode, None): + if type(indict[0]) in (bytes, unicode, None): nd = [] for datum in indict: if datum is None: @@ -156,7 +161,7 @@ class ConfluentMessage(object): '\r').format(valtype, key, self.desc) return snippet - if (isinstance(val, bool) or isinstance(val, str) or + if (isinstance(val, bool) or isinstance(val, bytes) or isinstance(val, unicode)): value = str(val) elif val is not None and 'value' in val: @@ -587,7 +592,7 @@ class InputConfigChangeSet(InputExpression): endattrs = {} for attr in attrs: origval = attrs[attr] - if isinstance(origval, str) or isinstance(origval, unicode): + if isinstance(origval, bytes) or isinstance(origval, unicode): origval = {'expression': origval} if 'expression' not in origval: endattrs[attr] = attrs[attr] @@ -614,7 +619,7 @@ class InputAttributes(ConfluentMessage): if nodes is None: self.attribs = inputdata for attrib in self.attribs: - if type(self.attribs[attrib]) in (str, unicode): + if type(self.attribs[attrib]) in (bytes, unicode): try: # ok, try to use format against the string # store back result to the attribute to @@ -640,7 +645,7 @@ class InputAttributes(ConfluentMessage): return {} nodeattr = deepcopy(self.nodeattribs[node]) for attr in nodeattr: - if type(nodeattr[attr]) in (str, unicode): + if type(nodeattr[attr]) in (bytes, unicode): try: # as above, use format() to see if string follows # expression, store value back in case of escapes @@ -743,7 +748,7 @@ class InputCredential(ConfluentMessage): if len(path) == 4: inputdata['uid'] = path[-1] # if the operation is 'create' check if all fields are present - if (isinstance(inputdata['uid'], str) and + if (type(inputdata['uid']) in (bytes, unicode) and not inputdata['uid'].isdigit()): inputdata['uid'] = inputdata['uid'] else: @@ -769,7 +774,7 @@ class InputCredential(ConfluentMessage): return {} credential = deepcopy(self.credentials[node]) for attr in credential: - if type(credential[attr]) in (str, unicode): + if type(credential[attr]) in (bytes, unicode): try: # as above, use format() to see if string follows # expression, store value back in case of escapes @@ -1359,7 +1364,7 @@ class AlertDestination(ConfluentMessage): class InputAlertDestination(ConfluentMessage): valid_alert_params = { - 'acknowledge': lambda x: False if type(x) in (unicode,str) and x.lower() == 'false' else bool(x), + 'acknowledge': lambda x: False if type(x) in (unicode, bytes) and x.lower() == 'false' else bool(x), 'acknowledge_timeout': lambda x: int(x) if x and x.isdigit() else None, 'ip': lambda x: x, 'retries': lambda x: int(x) @@ -1573,7 +1578,7 @@ class Attributes(ConfluentMessage): nkv = {} self.notnode = name is None for key in kv: - if type(kv[key]) in (str, unicode): + if type(kv[key]) in (bytes, unicode): nkv[key] = {'value': kv[key]} else: nkv[key] = kv[key] diff --git a/confluent_server/confluent/noderange.py b/confluent_server/confluent/noderange.py index 245227f2..9b4b9b46 100644 --- a/confluent_server/confluent/noderange.py +++ b/confluent_server/confluent/noderange.py @@ -25,6 +25,11 @@ import itertools import pyparsing as pp import re +try: + range = xrange +except NameError: + pass + # construct custom grammar with pyparsing _nodeword = pp.Word(pp.alphanums + '~^$/=-_:.*+!') _nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']', @@ -166,7 +171,7 @@ class NodeRange(object): return self.failorreturn(seqrange) finalfmt = '' iterators = [] - for idx in xrange(len(leftbits)): + for idx in range(len(leftbits)): if leftbits[idx] == rightbits[idx]: finalfmt += leftbits[idx] elif leftbits[idx][0] in pp.alphas: @@ -181,7 +186,7 @@ class NodeRange(object): if leftnum > rightnum: width = len(rightbits[idx]) minnum = rightnum - maxnum = leftnum + 1 # xrange goes to n-1... + maxnum = leftnum + 1 # range goes to n-1... elif rightnum > leftnum: width = len(leftbits[idx]) minnum = leftnum @@ -189,7 +194,7 @@ class NodeRange(object): else: # differently padded, but same number... return self.failorreturn(seqrange) numformat = '{0:0%d}' % width - for num in xrange(minnum, maxnum): + for num in range(minnum, maxnum): curseq.append(numformat.format(num)) results = set([]) for combo in itertools.product(*iterators): @@ -222,7 +227,7 @@ class NodeRange(object): if self.cfm is None: raise Exception('Verification configmanager required') return set(self.cfm.filter_node_attributes(element, filternodes)) - for idx in xrange(len(element)): + for idx in range(len(element)): if element[idx][0] == '[': nodes = set([]) for numeric in NodeRange(element[idx][1:-1]).nodes: diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index e5fb8b35..e6c90faf 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -383,7 +383,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop): raise datum if (hasattr(datum, 'kvpairs') and datum.kvpairs and len(datum.kvpairs) == 1): - bundle.append((datum.kvpairs.keys()[0], datum)) + bundle.append((list(datum.kvpairs)[0], datum)) numnodes -= 1 else: yield datum @@ -491,8 +491,8 @@ class IpmiHandler(object): # raise exc.TargetEndpointUnreachable( # "Login process to " + connparams['bmc'] + " died") except socket.gaierror as ge: - if ge[0] == -2: - raise exc.TargetEndpointUnreachable(ge[1]) + if ge.errno == -2: + raise exc.TargetEndpointUnreachable(ge.strerror) raise self.ipmicmd = persistent_ipmicmds[(node, tenant)] From 521be5d44de958b7471d2fb1c1a4aed074129013 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 3 Oct 2019 15:57:56 -0400 Subject: [PATCH 12/56] Further Python3 compatibility changes With this as well as eficompressor and pyghmi updates, things seem to be in roughly working order --- .../confluent/config/configmanager.py | 57 +++++++------------ confluent_server/confluent/consoleserver.py | 6 +- confluent_server/confluent/log.py | 2 +- confluent_server/confluent/messages.py | 2 +- .../plugins/configuration/attributes.py | 4 +- .../plugins/hardwaremanagement/redfish.py | 6 +- confluent_server/confluent/util.py | 8 +++ 7 files changed, 38 insertions(+), 47 deletions(-) diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 503fb3ec..7d558404 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -472,10 +472,8 @@ def crypt_value(value, key = _masterkey iv = os.urandom(12) crypter = AES.new(key, AES.MODE_GCM, nonce=iv) - try: - cryptval, hmac = crypter.encrypt_and_digest(value) - except TypeError: - cryptval, hmac = crypter.encrypt_and_digest(value.encode('utf-8')) + value = confluent.util.stringify(value).encode('utf-8') + cryptval, hmac = crypter.encrypt_and_digest(value) return iv, cryptval, hmac, '\x02' @@ -484,16 +482,19 @@ def _load_dict_from_dbm(dpath, tdb): dbe = dbm.open(tdb, 'r') currdict = _cfgstore for elem in dpath: + elem = confluent.util.stringify(elem) if elem not in currdict: currdict[elem] = {} currdict = currdict[elem] try: for tk in dbe.keys(): - currdict[tk] = cPickle.loads(dbe[tk]) + tks = confluent.util.stringify(tk) + currdict[tks] = cPickle.loads(dbe[tk]) except AttributeError: tk = dbe.firstkey() while tk != None: - currdict[tk] = cPickle.loads(dbe[tk]) + tks = confluent.util.stringify(tk) + currdict[tks] = cPickle.loads(dbe[tk]) tk = dbe.nextkey(tk) except dbm.error: return @@ -532,13 +533,7 @@ def set_global(globalname, value, sync=True): """ if _cfgstore is None: init(not sync) - try: - globalname = globalname.encode('utf-8') - except AttributeError: - # We have to remove the unicode-ness of the string, - # but if it is already bytes in python 3, then we will - # get an attributeerror, so pass - pass + globalname = confluent.util.stringify(globalname) with _dirtylock: if 'dirtyglobals' not in _cfgstore: _cfgstore['dirtyglobals'] = set() @@ -795,10 +790,7 @@ def apply_pending_collective_updates(): def _true_add_collective_member(name, address, fingerprint, sync=True): - try: - name = name.encode('utf-8') - except AttributeError: - pass + name = confluent.util.stringify(name) if _cfgstore is None: init(not sync) # use not sync to avoid read from disk if 'collective' not in _cfgstore: @@ -833,8 +825,7 @@ def get_collective_member_by_address(address): def _mark_dirtykey(category, key, tenant=None): - if type(key) in (str, unicode): - key = key.encode('utf-8') + key = confluent.util.stringify(key) with _dirtylock: if 'dirtykeys' not in _cfgstore: _cfgstore['dirtykeys'] = {} @@ -1314,7 +1305,7 @@ class ConfigManager(object): def _true_create_usergroup(self, groupname, role="Administrator"): if 'usergroups' not in self._cfgstore: self._cfgstore['usergroups'] = {} - groupname = groupname.encode('utf-8') + groupname = confluent.util.stringify(groupname) if groupname in self._cfgstore['usergroups']: raise Exception("Duplicate groupname requested") self._cfgstore['usergroups'][groupname] = {'role': role} @@ -1416,7 +1407,7 @@ class ConfigManager(object): raise Exception("Duplicate id requested") if 'users' not in self._cfgstore: self._cfgstore['users'] = {} - name = name.encode('utf-8') + name = confluent.util.stringify(name) if name in self._cfgstore['users']: raise Exception("Duplicate username requested") self._cfgstore['users'][name] = {'id': uid} @@ -1674,9 +1665,7 @@ class ConfigManager(object): "{0} node does not exist to add to {1}".format( node, group)) for group in attribmap: - group = group.encode('utf-8') - if not isinstance(group, str): - group = group.decode('utf-8') + group = confluent.util.stringify(group) if group not in self._cfgstore['nodegroups']: self._cfgstore['nodegroups'][group] = {'nodes': set()} cfgobj = self._cfgstore['nodegroups'][group] @@ -1734,8 +1723,8 @@ class ConfigManager(object): attributes = realattributes if type(groups) in (str, unicode): groups = (groups,) - for group in groups: - group = group.encode('utf-8') + for group in groups: + group = confluent.util.stringify(group) try: groupentry = self._cfgstore['nodegroups'][group] except KeyError: @@ -1849,9 +1838,7 @@ class ConfigManager(object): # set a reserved attribute for the sake of the change notification # framework to trigger on changeset[node] = {'_nodedeleted': 1} - node = node.encode('utf-8') - if not isinstance(node, str): - node = node.decode('utf-8') + node = confluent.util.stringify(node) if node in self._cfgstore['nodes']: self._sync_groups_to_node(node=node, groups=[], changeset=changeset) @@ -1899,7 +1886,7 @@ class ConfigManager(object): realattributes.append(attrname) attributes = realattributes for node in nodes: - node = node.encode('utf-8') + node = confluent.util.stringify(node) try: nodek = self._cfgstore['nodes'][node] except KeyError: @@ -2028,9 +2015,7 @@ class ConfigManager(object): # first do a sanity check of the input upfront # this mitigates risk of arguments being partially applied for node in attribmap: - node = node.encode('utf-8') - if not isinstance(node, str): - node = node.decode('utf-8') + node = confluent.util.stringify(node) if node == '': raise ValueError('"{0}" is not a valid node name'.format(node)) if autocreate: @@ -2085,10 +2070,8 @@ class ConfigManager(object): attrname, node) raise ValueError(errstr) attribmap[node][attrname] = attrval - for node in attribmap: - node = node.encode('utf-8') - if not isinstance(node, str): - node = node.decode('utf-8') + for node in attribmap: + node = confluent.util.stringify(node) exprmgr = None if node not in self._cfgstore['nodes']: newnodes.append(node) diff --git a/confluent_server/confluent/consoleserver.py b/confluent_server/confluent/consoleserver.py index c65a397d..fececf78 100644 --- a/confluent_server/confluent/consoleserver.py +++ b/confluent_server/confluent/consoleserver.py @@ -533,12 +533,12 @@ class ConsoleHandler(object): elif data == '': # ignore empty strings from a cconsole provider return - if '\x1b[?1l' in data: # request for ansi mode cursor keys + if b'\x1b[?1l' in data: # request for ansi mode cursor keys self.appmodedetected = False - if '\x1b[?1h' in data: # remember the session wants the client to use + if b'\x1b[?1h' in data: # remember the session wants the client to use # 'application mode' Thus far only observed on esxi self.appmodedetected = True - if '\x1b)0' in data: + if b'\x1b)0' in data: # console indicates it wants access to special drawing characters self.shiftin = b'0' eventdata = 0 diff --git a/confluent_server/confluent/log.py b/confluent_server/confluent/log.py index 19e744f5..8a978490 100644 --- a/confluent_server/confluent/log.py +++ b/confluent_server/confluent/log.py @@ -748,7 +748,7 @@ class Logger(object): pass def log(self, logdata=None, ltype=None, event=0, eventdata=None): - if type(logdata) not in (str, unicode, dict): + if type(logdata) not in (bytes, unicode, dict): raise Exception("Unsupported logdata") if ltype is None: if type(logdata) == dict: diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index ae8bc12f..07718a3d 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -1689,7 +1689,7 @@ class CryptedAttributes(Attributes): # for now, just keep the dictionary keys and discard crypt value self.desc = desc nkv = {} - for key in kv.iterkeys(): + for key in kv: nkv[key] = {'isset': False} try: if kv[key] is not None and kv[key]['cryptvalue'] != '': diff --git a/confluent_server/confluent/plugins/configuration/attributes.py b/confluent_server/confluent/plugins/configuration/attributes.py index 04da045f..04f62f09 100644 --- a/confluent_server/confluent/plugins/configuration/attributes.py +++ b/confluent_server/confluent/plugins/configuration/attributes.py @@ -134,7 +134,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata): attribute, {}).get('description', '')) elif element[-1] == 'current': for node in util.natural_sort(list(attributes)): - for attribute in sorted(attributes[node].iterkeys()): + for attribute in sorted(attributes[node]): currattr = attributes[node][attribute] try: desc = allattributes.node[attribute]['description'] @@ -185,7 +185,7 @@ def update_nodegroup(group, element, configmanager, inputdata): return yield_rename_resources(namemap, isnode=False) try: clearattribs = [] - for attrib in inputdata.attribs.iterkeys(): + for attrib in inputdata.attribs: if inputdata.attribs[attrib] is None: clearattribs.append(attrib) for attrib in clearattribs: diff --git a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py index 93cc31af..f9b609ec 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py @@ -274,7 +274,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop): raise datum if (hasattr(datum, 'kvpairs') and datum.kvpairs and len(datum.kvpairs) == 1): - bundle.append((datum.kvpairs.keys()[0], datum)) + bundle.append((list(datum.kvpairs)[0], datum)) numnodes -= 1 else: yield datum @@ -376,8 +376,8 @@ class IpmiHandler(object): self.loggedin = True self.ipmicmd = persistent_ipmicmds[(node, tenant)] except socket.gaierror as ge: - if ge[0] == -2: - raise exc.TargetEndpointUnreachable(ge[1]) + if ge.errno == -2: + raise exc.TargetEndpointUnreachable(ge.strerror) raise self.ipmicmd = persistent_ipmicmds[(node, tenant)] diff --git a/confluent_server/confluent/util.py b/confluent_server/confluent/util.py index 07bd25ed..9f03cc32 100644 --- a/confluent_server/confluent/util.py +++ b/confluent_server/confluent/util.py @@ -27,6 +27,14 @@ import socket import ssl import struct +def stringify(instr): + # Normalize unicode and bytes to 'str', correcting for + # current python version + if isinstance(instr, bytes) and not isinstance(instr, str): + return instr.decode('utf-8') + elif not isinstance(instr, bytes) and not isinstance(instr, str): + return instr.encode('utf-8') + return instr def list_interface_indexes(): # Getting the interface indexes in a portable manner From 59789bae7df5de6d7568a1b372b95a891fd5c3a2 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 3 Oct 2019 16:06:15 -0400 Subject: [PATCH 13/56] Fix python3 ctypes str usage In python3, the string is likely to be unicode and incompatible with the libc function. If it isn't bytes, force it to be bytes. --- confluent_server/confluent/userutil.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/confluent_server/confluent/userutil.py b/confluent_server/confluent/userutil.py index 04f8e976..a171758d 100644 --- a/confluent_server/confluent/userutil.py +++ b/confluent_server/confluent/userutil.py @@ -18,6 +18,8 @@ def getgrouplist(name, gid, ng=32): _getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)] glist = (c_uint * ng)() nglist = c_int(ng) + if not isinstance(name, bytes): + name = name.encode('utf-8') count = _getgrouplist(name, gid, byref(glist), byref(nglist)) if count < 0: raise TooSmallException(nglist.value) From d9be6ae2e900dd46f88dac1761b88b617797534f Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 4 Oct 2019 09:57:54 -0400 Subject: [PATCH 14/56] Close console on disconnect Fixes leftover console on user initiated disconnect. --- confluent_server/confluent/consoleserver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/confluent_server/confluent/consoleserver.py b/confluent_server/confluent/consoleserver.py index fececf78..bb5ba89d 100644 --- a/confluent_server/confluent/consoleserver.py +++ b/confluent_server/confluent/consoleserver.py @@ -453,6 +453,7 @@ class ConsoleHandler(object): def _got_disconnected(self): if self.connectstate != 'unconnected': + self._console.close() self.connectstate = 'unconnected' self.log( logdata='console disconnected', ltype=log.DataTypes.event, From 74f18d55714b93b4dd8fdd0f180ee7a92ce89105 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 4 Oct 2019 10:37:48 -0400 Subject: [PATCH 15/56] Client side py3 changes --- confluent_client/bin/confetty | 24 ++++++++++++++---------- confluent_client/bin/nodersync | 10 ++++++---- confluent_client/bin/noderun | 4 +++- confluent_client/bin/nodesensors | 13 ++++++++++--- confluent_client/bin/nodeshell | 4 +++- confluent_client/confluent/client.py | 9 +++++++++ 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 522fbb22..d8f88d7a 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -89,6 +89,10 @@ except NameError: netserver = None laststate = {} +try: + input = raw_input +except NameError: + pass class BailOut(Exception): def __init__(self, errorcode=0): @@ -195,9 +199,9 @@ def prompt(): if os.environ.get('TERM', '') not in ('linux'): sys.stdout.write('\x1b]0;confetty: %s\x07' % target) try: - return raw_input(target + ' -> ') + return input(target + ' -> ') except KeyboardInterrupt: - print "" + print("") return "" except EOFError: # ctrl-d print("exit") @@ -296,7 +300,7 @@ currchildren = None def print_result(res): if 'errorcode' in res or 'error' in res: - print res['error'] + print(res['error']) return if 'databynode' in res: print_result(res['databynode']) @@ -309,9 +313,9 @@ def print_result(res): attrstr = '%s=%s' % (key, recurse_format(res[key])) elif not isinstance(res[key], dict): try: - print '{0}: {1}'.format(key, res[key]) + print('{0}: {1}'.format(key, res[key])) except UnicodeEncodeError: - print '{0}: {1}'.format(key, repr(res[key])) + print('{0}: {1}'.format(key, repr(res[key]))) continue elif 'value' in res[key] and res[key]['value'] is not None: attrstr = '%s="%s"' % (key, res[key]['value']) @@ -324,7 +328,7 @@ def print_result(res): else: sys.stdout.write('{0}: '.format(key)) if isinstance(res[key], str) or isinstance(res[key], unicode): - print res[key] + print(res[key]) else: print_result(res[key]) continue @@ -423,10 +427,10 @@ def do_command(command, server): for res in session.read(targpath): if 'item' in res: # a link relation if type(res['item']) == dict: - print res['item']["href"] + print(res['item']["href"]) else: for item in res['item']: - print item["href"] + print(item["href"]) else: # generic attributes to list if 'error' in res: sys.stderr.write(res['error'] + '\n') @@ -851,7 +855,7 @@ def server_connect(): passphrase = os.environ['CONFLUENT_PASSPHRASE'] session.authenticate(username, passphrase) while not session.authenticated: - username = raw_input("Name: ") + username = input("Name: ") passphrase = getpass.getpass("Passphrase: ") session.authenticate(username, passphrase) @@ -871,7 +875,7 @@ def main(): global inconsole try: server_connect() - except EOFError, KeyboardInterrupt: + except (EOFError, KeyboardInterrupt) as _: raise BailOut(0) except socket.gaierror: sys.stderr.write('Could not connect to confluent\n') diff --git a/confluent_client/bin/nodersync b/confluent_client/bin/nodersync index 2d6e91cf..42179cf8 100755 --- a/confluent_client/bin/nodersync +++ b/confluent_client/bin/nodersync @@ -55,7 +55,7 @@ def run(): noderange, targpath = args[-1].split(':', 1) client.check_globbing(noderange) c = client.Command() - cmdstr = " ".join(args[:-1]) + cmdstr = ' '.join(args[:-1]) cmdstr = 'rsync -av --info=progress2 ' + cmdstr cmdstr += ' {node}:' + targpath @@ -66,13 +66,15 @@ def run(): exitcode = 0 for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange), - {'expression': cmdstr}): + {'expression': cmdstr}): if 'error' in exp: sys.stderr.write(exp['error'] + '\n') exitcode |= exp.get('errorcode', 1) ex = exp.get('databynode', ()) for node in ex: - cmd = ex[node]['value'].encode('utf-8') + cmd = ex[node]['value'] + if not isinstance(cmd, bytes) and not isinstance(cmd, str): + cmd = cmd.encode('utf-8') cmdv = shlex.split(cmd) if currprocs < concurrentprocs: currprocs += 1 @@ -117,7 +119,7 @@ def run(): output.set_output(node, 'error!') if node not in nodeerrs: nodeerrs[node] = '' - nodeerrs[node] += data + nodeerrs[node] += client.stringify(data) else: pop = desc['popen'] ret = pop.poll() diff --git a/confluent_client/bin/noderun b/confluent_client/bin/noderun index b199135b..10ee3975 100755 --- a/confluent_client/bin/noderun +++ b/confluent_client/bin/noderun @@ -71,7 +71,9 @@ def run(): exitcode |= exp.get('errorcode', 1) ex = exp.get('databynode', ()) for node in ex: - cmd = ex[node]['value'].encode('utf-8') + cmd = ex[node]['value'] + if not isinstance(cmd, bytes) and not isinstance(cmd, str): + cmd = cmd.encode('utf-8') cmdv = shlex.split(cmd) if currprocs < concurrentprocs: currprocs += 1 diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index 5e5b1746..f9c544c6 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -149,8 +149,11 @@ def sensorpass(showout=True, appendtime=False): if appendtime: showval += ' @' + time.strftime( '%Y-%m-%dT%H:%M:%S') - print(u'{0}: {1}:{2}'.format( - node, sensedata['name'], showval).encode('utf8')) + printval = u'{0}: {1}:{2}'.format( + node, sensedata['name'], showval) + if not isinstance(printval, str): + printval = printval.encode('utf-8') + print(printval) sys.stdout.flush() return resultdata @@ -199,7 +202,11 @@ def main(): orderedsensors.append(name) orderedsensors.sort() for name in orderedsensors: - headernames.append(sensorheaders[name].encode('utf-8')) + headername = sensorheaders[name] + if (not isinstance(headername, str) and + not isinstance(headername, bytes)): + headername = headername.encode('utf-8') + headernames.append(headername) if options.csv: linebyline = False csvwriter = csv.writer(sys.stdout) diff --git a/confluent_client/bin/nodeshell b/confluent_client/bin/nodeshell index 27ca5ad8..d0619f70 100755 --- a/confluent_client/bin/nodeshell +++ b/confluent_client/bin/nodeshell @@ -72,7 +72,9 @@ def run(): exitcode |= exp.get('errorcode', 1) ex = exp.get('databynode', ()) for node in ex: - cmd = ex[node]['value'].encode('utf-8') + cmd = ex[node]['value'] + if not isinstance(str) and not isinstance(bytes): + cmd = cmd.encode('utf-8') cmdv = ['ssh', node, cmd] if currprocs < concurrentprocs: currprocs += 1 diff --git a/confluent_client/confluent/client.py b/confluent_client/confluent/client.py index 15524b4a..c70662ec 100644 --- a/confluent_client/confluent/client.py +++ b/confluent_client/confluent/client.py @@ -40,6 +40,15 @@ _attraliases = { } +def stringify(instr): + # Normalize unicode and bytes to 'str', correcting for + # current python version + if isinstance(instr, bytes) and not isinstance(instr, str): + return instr.decode('utf-8') + elif not isinstance(instr, bytes) and not isinstance(instr, str): + return instr.encode('utf-8') + return instr + class Tabulator(object): def __init__(self, headers): self.headers = headers From 5c288a27ddd78181e9de52f46b348d79460335e3 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 4 Oct 2019 10:54:20 -0400 Subject: [PATCH 16/56] Have EL8 use python3 for confluent --- confluent_client/confluent_client.spec.tmpl | 8 ++++++++ confluent_server/confluent_server.spec.tmpl | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/confluent_client/confluent_client.spec.tmpl b/confluent_client/confluent_client.spec.tmpl index 68e045a4..d6a2ebf9 100644 --- a/confluent_client/confluent_client.spec.tmpl +++ b/confluent_client/confluent_client.spec.tmpl @@ -24,10 +24,18 @@ a confluent server. %setup -n %{name}-%{version} -n %{name}-%{version} %build +%if "%{dist}" == ".el8" +python3 setup.py build +%else python2 setup.py build +%endif %install +%if "%{dist}" == ".el8" +python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python +%else python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python +%endif %clean diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 85a78c7f..5efc0d3d 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -13,7 +13,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch %if "%{dist}" == ".el8" -Requires: python2-pyghmi >= 1.0.34, python2-eventlet, python2-greenlet, python2-pycryptodomex >= 3.4.7, confluent_client, python2-pyparsing, python2-paramiko, python2-dns, python2-netifaces, python2-pyasn1 >= 0.2.3, python2-pysnmp >= 4.3.4, python2-pyte, python2-lxml, python2-eficompressor, python2-setuptools, python2-dateutil, python2-enum34, python2-asn1crypto, python2-ipaddress, python2-cffi, python2-pyOpenSSL +Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL %else Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil %endif @@ -27,10 +27,18 @@ Server for console management and systems management aggregation %setup -n %{name}-%{version} -n %{name}-%{version} %build +%if "%{dist}" == ".el8" +python3 setup.py build +%else python2 setup.py build +%endif %install +%if "%{dist}" == ".el8" +python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin +%else python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin +%endif for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do rm $RPM_BUILD_ROOT/$file done From 6ea6ebd80eec1c0e5347284e795e73bcab22b935 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 4 Oct 2019 16:49:51 -0400 Subject: [PATCH 17/56] Remove superfluous dependency --- confluent_server/confluent_server.spec.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 5efc0d3d..f965bbf3 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -13,7 +13,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch %if "%{dist}" == ".el8" -Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL +Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL %else Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil %endif From 8fc3b7c9c0bcadfb980cef1b0ae8d18ea863cad4 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 7 Oct 2019 15:41:38 -0400 Subject: [PATCH 18/56] Implement cross-python collective compat This enables cross-version compatibility for a collective. --- .../confluent/collective/manager.py | 23 ++++++++--- .../confluent/config/configmanager.py | 40 +++++++++++++------ confluent_server/confluent/core.py | 25 ++++++++---- confluent_server/confluent/sockapi.py | 3 +- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/confluent_server/confluent/collective/manager.py b/confluent_server/confluent/collective/manager.py index 5e49a2cf..8f3b9f15 100644 --- a/confluent_server/confluent/collective/manager.py +++ b/confluent_server/confluent/collective/manager.py @@ -27,6 +27,7 @@ import eventlet.green.ssl as ssl import eventlet.green.threading as threading import greenlet import random +import sys try: import OpenSSL.crypto as crypto except ImportError: @@ -70,11 +71,22 @@ def connect_to_leader(cert=None, name=None, leader=None): return False with connecting: with cfm._initlock: - tlvdata.recv(remote) # the banner + banner = tlvdata.recv(remote) # the banner + vers = banner.split()[2] + pvers = 0 + reqver = 4 + if vers == 'v0': + pvers = 2 + elif vers == 'v1': + pvers = 4 + if sys.version_info[0] < 3: + pvers = 2 + reqver = 2 tlvdata.recv(remote) # authpassed... 0.. if name is None: name = get_myname() tlvdata.send(remote, {'collective': {'operation': 'connect', + 'protover': reqver, 'name': name, 'txcount': cfm._txcount}}) keydata = tlvdata.recv(remote) @@ -148,15 +160,15 @@ def connect_to_leader(cert=None, name=None, leader=None): raise currentleader = leader #spawn this as a thread... - follower = eventlet.spawn(follow_leader, remote) + follower = eventlet.spawn(follow_leader, remote, pvers) return True -def follow_leader(remote): +def follow_leader(remote, proto): global currentleader cleanexit = False try: - cfm.follow_channel(remote) + cfm.follow_channel(remote, proto) except greenlet.GreenletExit: cleanexit = True finally: @@ -402,6 +414,7 @@ def handle_connection(connection, cert, request, local=False): tlvdata.send(connection, collinfo) if 'connect' == operation: drone = request['name'] + folver = request.get('protover', 2) droneinfo = cfm.get_collective_member(drone) if not (droneinfo and util.cert_matches(droneinfo['fingerprint'], cert)): @@ -450,7 +463,7 @@ def handle_connection(connection, cert, request, local=False): connection.sendall(cfgdata) #tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now, # so far unused anyway - if not cfm.relay_slaved_requests(drone, connection): + if not cfm.relay_slaved_requests(drone, connection, folver): if not retrythread: # start a recovery if everyone else seems # to have disappeared retrythread = eventlet.spawn_after(30 + random.random(), diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 7d558404..f727a68f 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -101,6 +101,10 @@ _cfgstore = None _pendingchangesets = {} _txcount = 0 _hasquorum = True +if sys.version_info[0] >= 3: + lowestver = 4 +else: + lowestver = 2 _attraliases = { 'bmc': 'hardwaremanagement.manager', @@ -314,7 +318,7 @@ def exec_on_leader(function, *args): xid = os.urandom(8) _pendingchangesets[xid] = event.Event() rpcpayload = cPickle.dumps({'function': function, 'args': args, - 'xid': xid}) + 'xid': xid}, protocol=cfgproto) rpclen = len(rpcpayload) cfgleader.sendall(struct.pack('!Q', rpclen)) cfgleader.sendall(rpcpayload) @@ -331,7 +335,7 @@ def exec_on_followers(fnname, *args): pushes = eventlet.GreenPool() _txcount += 1 payload = cPickle.dumps({'function': fnname, 'args': args, - 'txcount': _txcount}) + 'txcount': _txcount}, protocol=lowestver) for res in pushes.starmap( _push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]): pass @@ -546,9 +550,14 @@ def set_global(globalname, value, sync=True): ConfigManager._bg_sync_to_file() cfgstreams = {} -def relay_slaved_requests(name, listener): +def relay_slaved_requests(name, listener, vers): global cfgleader global _hasquorum + global lowestver + if vers > 2 and sys.version_info[0] < 3: + vers = 2 + if vers < lowestver: + lowestver = vers pushes = eventlet.GreenPool() if name not in _followerlocks: _followerlocks[name] = gthread.RLock() @@ -565,7 +574,7 @@ def relay_slaved_requests(name, listener): lh = StreamHandler(listener) _hasquorum = len(cfgstreams) >= ( len(_cfgstore['collective']) // 2) - payload = cPickle.dumps({'quorum': _hasquorum}) + payload = cPickle.dumps({'quorum': _hasquorum}, protocol=vers) for _ in pushes.starmap( _push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]): @@ -592,7 +601,7 @@ def relay_slaved_requests(name, listener): exc = e if 'xid' in rpc: _push_rpc(listener, cPickle.dumps({'xid': rpc['xid'], - 'exc': exc})) + 'exc': exc}, protocol=vers)) try: msg = lh.get_next_msg() except Exception: @@ -609,7 +618,7 @@ def relay_slaved_requests(name, listener): if cfgstreams: _hasquorum = len(cfgstreams) >= ( len(_cfgstore['collective']) // 2) - payload = cPickle.dumps({'quorum': _hasquorum}) + payload = cPickle.dumps({'quorum': _hasquorum}, protocol=vers) for _ in pushes.starmap( _push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]): @@ -649,15 +658,19 @@ class StreamHandler(object): self.sock = None -def stop_following(replacement=None): +def stop_following(replacement=None, proto=2): with _leaderlock: global cfgleader + global cfgproto if cfgleader and not isinstance(cfgleader, bool): try: cfgleader.close() except Exception: pass cfgleader = replacement + if proto > 2 and sys.version_info[0] < 3: + proto = 2 + cfgproto = proto def stop_leading(): for stream in list(cfgstreams): @@ -715,14 +728,15 @@ def commit_clear(): ConfigManager._bg_sync_to_file() cfgleader = None +cfgproto = 2 -def follow_channel(channel): +def follow_channel(channel, proto=2): global _txcount global _hasquorum try: stop_leading() - stop_following(channel) + stop_following(channel, proto) lh = StreamHandler(channel) msg = lh.get_next_msg() while msg: @@ -2326,7 +2340,7 @@ class ConfigManager(object): for globalkey in dirtyglobals: if globalkey in _cfgstore['globals']: globalf[globalkey] = \ - cPickle.dumps(_cfgstore['globals'][globalkey]) + cPickle.dumps(_cfgstore['globals'][globalkey], protocol=cPickle.HIGHEST_PROTOCOL) else: if globalkey in globalf: del globalf[globalkey] @@ -2345,7 +2359,7 @@ class ConfigManager(object): for coll in colls: if coll in _cfgstore['collective']: collectivef[coll] = cPickle.dumps( - _cfgstore['collective'][coll]) + _cfgstore['collective'][coll], protocol=cPickle.HIGHEST_PROTOCOL) else: if coll in collectivef: del globalf[coll] @@ -2359,7 +2373,7 @@ class ConfigManager(object): dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600 try: for ck in currdict[category]: - dbf[ck] = cPickle.dumps(currdict[category][ck]) + dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL) finally: dbf.close() elif 'dirtykeys' in _cfgstore: @@ -2383,7 +2397,7 @@ class ConfigManager(object): if ck in dbf: del dbf[ck] else: - dbf[ck] = cPickle.dumps(currdict[category][ck]) + dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL) finally: dbf.close() willrun = False diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index a92e9439..d43897ef 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -687,6 +687,9 @@ def handle_dispatch(connection, cert, dispatch, peername): cfm.get_collective_member(peername)['fingerprint'], cert): connection.close() return + pversion = 0 + if bytearray(dispatch)[0] == 0x80: + pversion = bytearray(dispatch)[1] dispatch = pickle.loads(dispatch) configmanager = cfm.ConfigManager(dispatch['tenant']) nodes = dispatch['nodes'] @@ -723,18 +726,18 @@ def handle_dispatch(connection, cert, dispatch, peername): configmanager=configmanager, inputdata=inputdata)) for res in itertools.chain(*passvalues): - _forward_rsp(connection, res) + _forward_rsp(connection, res, pversion) except Exception as res: - _forward_rsp(connection, res) + _forward_rsp(connection, res, pversion) connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00') -def _forward_rsp(connection, res): +def _forward_rsp(connection, res, pversion): try: - r = pickle.dumps(res) + r = pickle.dumps(res, protocol=pversion) except TypeError: r = pickle.dumps(Exception( - 'Cannot serialize error, check collective.manager error logs for details' + str(res))) + 'Cannot serialize error, check collective.manager error logs for details' + str(res)), protocol=pversion) rlen = len(r) if not rlen: return @@ -963,12 +966,20 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata, if not util.cert_matches(a['fingerprint'], remote.getpeercert( binary_form=True)): raise Exception("Invalid certificate on peer") - tlvdata.recv(remote) + banner = tlvdata.recv(remote) + vers = banner.split()[2] + if vers == 'v0': + pvers = 2 + elif vers == 'v1': + pvers = 4 + if sys.version_info[0] < 3: + pvers = 2 tlvdata.recv(remote) myname = collective.get_myname() dreq = pickle.dumps({'name': myname, 'nodes': list(nodes), 'path': element,'tenant': configmanager.tenant, - 'operation': operation, 'inputdata': inputdata}) + 'operation': operation, 'inputdata': inputdata}, + version=pvers) tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}}) remote.sendall(dreq) while True: diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index 76ec6131..154adfc2 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -123,7 +123,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None): if authdata: cfm = authdata[1] authenticated = True - send_data(connection, "Confluent -- v0 --") + # version 0 == original, version 1 == pickle3 allowed + send_data(connection, "Confluent -- v{0} --".format(sys.version_info[0] - 2)) while not authenticated: # prompt for name and passphrase send_data(connection, {'authpassed': 0}) response = tlvdata.recv(connection) From 0975881d3bed204a0a8f41ef71034b9d1d8fa25a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 7 Oct 2019 17:06:45 -0400 Subject: [PATCH 19/56] Add monotonic dependency --- confluent_server/confluent_server.spec.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index f965bbf3..3f5ff42c 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -13,7 +13,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch %if "%{dist}" == ".el8" -Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL +Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic %else Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil %endif From 578ba06aa322627e066c3e1369564578d9dc2640 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 09:06:15 -0400 Subject: [PATCH 20/56] Fix python3 problem with octal --- confluent_server/bin/collective | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/bin/collective b/confluent_server/bin/collective index aba64680..5a658895 100644 --- a/confluent_server/bin/collective +++ b/confluent_server/bin/collective @@ -22,7 +22,7 @@ except NameError: pass def make_certificate(): - umask = os.umask(0077) + umask = os.umask(0o77) try: os.makedirs('/etc/confluent/cfg') except OSError as e: From c1953bdad36c301c967a4f72621cbdfafe4a6460 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 10:45:43 -0400 Subject: [PATCH 21/56] Another set of python 3 compatibility Numerous issues arose, particularly when participating in a mixed collective. --- confluent_client/bin/confetty | 1 + confluent_server/bin/collective | 2 +- confluent_server/confluent/collective/manager.py | 9 +++++---- confluent_server/confluent/config/configmanager.py | 6 +++--- confluent_server/confluent/core.py | 12 +++++++----- confluent_server/confluent/noderange.py | 2 +- confluent_server/confluent/util.py | 2 +- 7 files changed, 19 insertions(+), 15 deletions(-) diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index d8f88d7a..3844f6b8 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -933,6 +933,7 @@ def main(): updatestatus(data) continue if data is not None: + data = client.stringify(data) if clearpowermessage: sys.stdout.write("\x1b[2J\x1b[;H") clearpowermessage = False diff --git a/confluent_server/bin/collective b/confluent_server/bin/collective index 5a658895..297401a0 100644 --- a/confluent_server/bin/collective +++ b/confluent_server/bin/collective @@ -61,7 +61,7 @@ def join_collective(server, invitation): make_certificate() s = client.Command().connection while not invitation: - invitation = raw_input('Paste the invitation here: ') + invitation = input('Paste the invitation here: ') tlvdata.send(s, {'collective': {'operation': 'join', 'invitation': invitation, 'server': server}}) diff --git a/confluent_server/confluent/collective/manager.py b/confluent_server/confluent/collective/manager.py index 8f3b9f15..789858e4 100644 --- a/confluent_server/confluent/collective/manager.py +++ b/confluent_server/confluent/collective/manager.py @@ -75,9 +75,9 @@ def connect_to_leader(cert=None, name=None, leader=None): vers = banner.split()[2] pvers = 0 reqver = 4 - if vers == 'v0': + if vers == b'v0': pvers = 2 - elif vers == 'v1': + elif vers == b'v1': pvers = 4 if sys.version_info[0] < 3: pvers = 2 @@ -131,7 +131,7 @@ def connect_to_leader(cert=None, name=None, leader=None): globaldata = tlvdata.recv(remote) dbi = tlvdata.recv(remote) dbsize = dbi['dbsize'] - dbjson = '' + dbjson = b'' while (len(dbjson) < dbsize): ndata = remote.recv(dbsize - len(dbjson)) if not ndata: @@ -279,7 +279,8 @@ def handle_connection(connection, cert, request, local=False): invitation = request['invitation'] try: invitation = base64.b64decode(invitation) - name, invitation = invitation.split('@', 1) + name, invitation = invitation.split(b'@', 1) + name = util.stringify(name) except Exception: tlvdata.send( connection, diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index f727a68f..3e7628a9 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -313,9 +313,9 @@ def check_quorum(): def exec_on_leader(function, *args): if isinstance(cfgleader, bool): raise exc.DegradedCollective() - xid = os.urandom(8) + xid = confluent.util.stringify(base64.b64encode(os.urandom(8))) while xid in _pendingchangesets: - xid = os.urandom(8) + xid = confluent.util.stringify(base64.b64encode(os.urandom(8)) _pendingchangesets[xid] = event.Event() rpcpayload = cPickle.dumps({'function': function, 'args': args, 'xid': xid}, protocol=cfgproto) @@ -742,7 +742,7 @@ def follow_channel(channel, proto=2): while msg: sz = struct.unpack('!Q', msg)[0] if sz != 0: - rpc = '' + rpc = b'' while len(rpc) < sz: nrpc = channel.recv(sz - len(rpc)) if not nrpc: diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index d43897ef..ff816422 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -63,8 +63,10 @@ import itertools import os try: import cPickle as pickle + pargs = {} except ImportError: import pickle + pargs = {'encoding': 'utf-8'} import socket import struct import sys @@ -690,7 +692,7 @@ def handle_dispatch(connection, cert, dispatch, peername): pversion = 0 if bytearray(dispatch)[0] == 0x80: pversion = bytearray(dispatch)[1] - dispatch = pickle.loads(dispatch) + dispatch = pickle.loads(dispatch, **pargs) configmanager = cfm.ConfigManager(dispatch['tenant']) nodes = dispatch['nodes'] inputdata = dispatch['inputdata'] @@ -968,9 +970,9 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata, raise Exception("Invalid certificate on peer") banner = tlvdata.recv(remote) vers = banner.split()[2] - if vers == 'v0': + if vers == b'v0': pvers = 2 - elif vers == 'v1': + elif vers == b'v1': pvers = 4 if sys.version_info[0] < 3: pvers = 2 @@ -979,7 +981,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata, dreq = pickle.dumps({'name': myname, 'nodes': list(nodes), 'path': element,'tenant': configmanager.tenant, 'operation': operation, 'inputdata': inputdata}, - version=pvers) + protocol=pvers) tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}}) remote.sendall(dreq) while True: @@ -1026,7 +1028,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata, a['name'])) return rsp += nrsp - rsp = pickle.loads(rsp) + rsp = pickle.loads(rsp, **pargs) if isinstance(rsp, Exception): raise rsp yield rsp diff --git a/confluent_server/confluent/noderange.py b/confluent_server/confluent/noderange.py index 9b4b9b46..be35829e 100644 --- a/confluent_server/confluent/noderange.py +++ b/confluent_server/confluent/noderange.py @@ -162,7 +162,7 @@ class NodeRange(object): pieces = seqrange.split(delimiter) if len(pieces) % 2 != 0: return self.failorreturn(seqrange) - halflen = len(pieces) / 2 + halflen = len(pieces) // 2 left = delimiter.join(pieces[:halflen]) right = delimiter.join(pieces[halflen:]) leftbits = _numextractor.parseString(left).asList() diff --git a/confluent_server/confluent/util.py b/confluent_server/confluent/util.py index 9f03cc32..b347c788 100644 --- a/confluent_server/confluent/util.py +++ b/confluent_server/confluent/util.py @@ -109,7 +109,7 @@ def monotonic_time(): def get_certificate_from_file(certfile): - cert = open(certfile, 'rb').read() + cert = open(certfile, 'r').read() inpemcert = False prunedcert = '' for line in cert.split('\n'): From 0633b2ca675a5dd3868370c647141196363466c4 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 10:59:44 -0400 Subject: [PATCH 22/56] Fix syntax error in code --- confluent_server/confluent/config/configmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 3e7628a9..271a0042 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -315,7 +315,7 @@ def exec_on_leader(function, *args): raise exc.DegradedCollective() xid = confluent.util.stringify(base64.b64encode(os.urandom(8))) while xid in _pendingchangesets: - xid = confluent.util.stringify(base64.b64encode(os.urandom(8)) + xid = confluent.util.stringify(base64.b64encode(os.urandom(8))) _pendingchangesets[xid] = event.Event() rpcpayload = cPickle.dumps({'function': function, 'args': args, 'xid': xid}, protocol=cfgproto) From 8e87f5b9e539de0098609c1ff0371611d57038e7 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 11:20:27 -0400 Subject: [PATCH 23/56] Fix python3 issue with base64 and str --- confluent_server/confluent/config/configmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 271a0042..56928a55 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -2247,7 +2247,7 @@ class ConfigManager(object): target = dumpdata[confarea][element][attribute]['cryptvalue'] cryptval = [] for value in target: - cryptval.append(base64.b64encode(value)) + cryptval.append(confluent.util.stringify(base64.b64encode(value))) if attribute == 'cryptpass': dumpdata[confarea][element][attribute] = '!'.join(cryptval) else: From 0edd1efe0d29d91ebc92a6b96bb1fa4a72777386 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 13:21:34 -0400 Subject: [PATCH 24/56] Fix python3 dump_keys When doing format on base64 output, it must be explicitly coerced into native string format. --- .../confluent/config/configmanager.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 56928a55..978a4c01 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -2461,20 +2461,21 @@ def _dump_keys(password, dojson=True): init_masterkey() cryptkey = _format_key(_masterkey, password=password) if 'passphraseprotected' in cryptkey: - cryptkey = '!'.join(map(base64.b64encode, - cryptkey['passphraseprotected'])) + cryptkey = '!'.join( + [confluent.util.stringify(base64.b64encode(x)) + for x in cryptkey['passphraseprotected']]) else: - cryptkey = '*unencrypted:{0}'.format(base64.b64encode( - cryptkey['unencryptedvalue'])) + cryptkey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode( + cryptkey['unencryptedvalue']))) keydata = {'cryptkey': cryptkey} if _masterintegritykey is not None: integritykey = _format_key(_masterintegritykey, password=password) if 'passphraseprotected' in integritykey: - integritykey = '!'.join(map(base64.b64encode, - integritykey['passphraseprotected'])) + integritykey = '!'.join([confluent.util.stringify(base64.b64encode(x)) for x in + integritykey['passphraseprotected']]) else: - integritykey = '*unencrypted:{0}'.format(base64.b64encode( - integritykey['unencryptedvalue'])) + integritykey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode( + integritykey['unencryptedvalue']))) keydata['integritykey'] = integritykey if dojson: return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': ')) From dbc6747c38f748915c06a8463c33770076504dba Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 13:43:53 -0400 Subject: [PATCH 25/56] Fix nodebmcreset for python3 --- confluent_client/bin/nodebmcreset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_client/bin/nodebmcreset b/confluent_client/bin/nodebmcreset index eb7ef477..7a2bd018 100755 --- a/confluent_client/bin/nodebmcreset +++ b/confluent_client/bin/nodebmcreset @@ -56,7 +56,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)): goodNodes = allNodes - errorNodes for node in goodNodes: - print node + ": BMC Reset Successful" + print(node + ": BMC Reset Successful") sys.exit(success) From 5353b479d940e2e8531f64071133751e4ad1bbfa Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 13:46:44 -0400 Subject: [PATCH 26/56] More python3 fixes --- confluent_client/bin/nodegroupattrib | 4 ++-- confluent_client/bin/nodereseat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/confluent_client/bin/nodegroupattrib b/confluent_client/bin/nodegroupattrib index b913a9c8..42847ee4 100755 --- a/confluent_client/bin/nodegroupattrib +++ b/confluent_client/bin/nodegroupattrib @@ -97,7 +97,7 @@ if len(args) > 1: requestargs=args[1:] except Exception as e: - print str(e) + print(str(e)) if exitcode != 0: sys.exit(exitcode) @@ -123,6 +123,6 @@ else: sys.stderr.write(res['error'] + '\n') exitcode = 1 else: - print res['item']['href'].replace('/', '') + print(res['item']['href'].replace('/', '')) sys.exit(exitcode) diff --git a/confluent_client/bin/nodereseat b/confluent_client/bin/nodereseat index 7bfc221f..68dcc25f 100755 --- a/confluent_client/bin/nodereseat +++ b/confluent_client/bin/nodereseat @@ -56,7 +56,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)): goodNodes = allNodes - errorNodes for node in goodNodes: - print node + ": Reseat successful" + print(node + ": Reseat successful") sys.exit(success) From a9f0e345db640bc833798e30e5ff5f261cd8040f Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 8 Oct 2019 17:10:56 -0400 Subject: [PATCH 27/56] Another set of python3 fixes --- confluent_server/confluent/auth.py | 6 +++--- confluent_server/confluent/httpapi.py | 27 +++++++++++++------------- confluent_server/confluent/messages.py | 8 ++++---- confluent_server/confluent/userutil.py | 2 ++ confluent_server/confluent/util.py | 4 ++-- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/confluent_server/confluent/auth.py b/confluent_server/confluent/auth.py index 4bdfa885..eb11efc8 100644 --- a/confluent_server/confluent/auth.py +++ b/confluent_server/confluent/auth.py @@ -122,8 +122,8 @@ def _get_usertenant(name, tenant=False): if not isinstance(tenant, bool): # if not boolean, it must be explicit tenant user = name - elif '/' in name: # tenant scoped name - tenant, user = name.split('/', 1) + elif b'/' in name: # tenant scoped name + tenant, user = name.split(b'/', 1) elif configmanager.is_tenant(name): # the account is the implicit tenant owner account user = name @@ -288,4 +288,4 @@ def _do_pbkdf(passphrase, salt): # compute. However, we do want to wait for result, so we have # one of the exceedingly rare sort of circumstances where 'apply' # actually makes sense - return authworkers.apply(_apply_pbkdf, [passphrase, salt]) \ No newline at end of file + return authworkers.apply(_apply_pbkdf, [passphrase, salt]) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index bcb81a1d..a03ea80f 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -80,7 +80,7 @@ def group_creation_resources(): yield confluent.messages.ListAttributes(kv={'nodes': []}, desc='Nodes to add to the group' ).html() + '
    \n' - for attr in sorted(attribs.node.iterkeys()): + for attr in sorted(attribs.node): if attr == 'groups': continue if attr.startswith("secret."): @@ -101,7 +101,7 @@ def group_creation_resources(): def node_creation_resources(): yield confluent.messages.Attributes( kv={'name': None}, desc="Name of the node").html() + '
    ' - for attr in sorted(attribs.node.iterkeys()): + for attr in sorted(attribs.node): if attr.startswith("secret."): yield confluent.messages.CryptedAttributes( kv={attr: None}, @@ -132,7 +132,7 @@ def user_creation_resources(): 'description': (''), }, } - for attr in sorted(credential.iterkeys()): + for attr in sorted(credential): if attr == "password": yield confluent.messages.CryptedAttributes( kv={attr: None}, @@ -182,7 +182,7 @@ def _get_query_dict(env, reqbody, reqtype): if reqbody is not None: if "application/x-www-form-urlencoded" in reqtype: pbody = urlparse.parse_qs(reqbody, True) - for ky in pbody.iterkeys(): + for ky in pbody: if len(pbody[ky]) > 1: # e.g. REST explorer na = [i for i in pbody[ky] if i != ''] qdict[ky] = na @@ -190,7 +190,7 @@ def _get_query_dict(env, reqbody, reqtype): qdict[ky] = pbody[ky][0] elif 'application/json' in reqtype: pbody = json.loads(reqbody) - for key in pbody.iterkeys(): + for key in pbody: qdict[key] = pbody[key] if 'restexplorerhonorkey' in qdict: nqdict = {} @@ -311,7 +311,7 @@ def _authorize_request(env, operation): return {'code': 401} return ('logout',) name, passphrase = base64.b64decode( - env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1) + env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1) authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element) if authdata is False: return {'code': 403} @@ -325,14 +325,14 @@ def _authorize_request(env, operation): 'inflight': set([])} if 'HTTP_CONFLUENTAUTHTOKEN' in env: httpsessions[sessid]['csrftoken'] = util.randomstring(32) - cookie['confluentsessionid'] = sessid + cookie['confluentsessionid'] = util.stringify(sessid) cookie['confluentsessionid']['secure'] = 1 cookie['confluentsessionid']['httponly'] = 1 cookie['confluentsessionid']['path'] = '/' skiplog = _should_skip_authlog(env) if authdata: auditmsg = { - 'user': name, + 'user': util.stringify(name), 'operation': operation, 'target': env['PATH_INFO'], } @@ -344,7 +344,7 @@ def _authorize_request(env, operation): if authdata[3] is not None: auditmsg['tenant'] = authdata[3] authinfo['tenant'] = authdata[3] - auditmsg['user'] = authdata[2] + auditmsg['user'] = util.stringify(authdata[2]) if sessid is not None: authinfo['sessionid'] = sessid if not skiplog: @@ -632,6 +632,7 @@ def resourcehandler_backend(env, start_response): sessinfo = {'username': authorized['username']} if 'authtoken' in authorized: sessinfo['authtoken'] = authorized['authtoken'] + tlvdata.unicode_dictvalues(sessinfo) yield json.dumps(sessinfo) return resource = '.' + url[url.rindex('/'):] @@ -737,7 +738,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None): for rsp in responses: if isinstance(rsp, confluent.messages.LinkRelation): haldata = rsp.raw() - for hk in haldata.iterkeys(): + for hk in haldata: if 'href' in haldata[hk]: if isinstance(haldata[hk]['href'], int): haldata[hk]['href'] = str(haldata[hk]['href']) @@ -753,7 +754,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None): links[hk] = haldata[hk] else: rsp = rsp.raw() - for dk in rsp.iterkeys(): + for dk in rsp: if dk in rspdata: if isinstance(rspdata[dk], list): if isinstance(rsp[dk], list): @@ -772,8 +773,8 @@ def _assemble_json(responses, resource=None, url=None, extension=None): rspdata[dk] = rsp[dk] rspdata["_links"] = links tlvdata.unicode_dictvalues(rspdata) - yield json.dumps( - rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8') + yield util.stringify(json.dumps( + rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')) def serve(bind_host, bind_port): diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 07718a3d..32eee244 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -126,14 +126,14 @@ class ConfluentMessage(object): return self._generic_html_value(self.kvpairs) if not self.stripped: htmlout = '' - for node in self.kvpairs.iterkeys(): + for node in self.kvpairs: htmlout += '{0}:{1}\n'.format( node, self._generic_html_value(self.kvpairs[node])) return htmlout def _generic_html_value(self, pairs): snippet = "" - for key in pairs.iterkeys(): + for key in pairs: val = pairs[key] value = self.defaultvalue if isinstance(val, dict) and 'type' in val: @@ -326,14 +326,14 @@ class ConfluentChoiceMessage(ConfluentMessage): return self._create_option(self.kvpairs) else: htmlout = '' - for node in self.kvpairs.iterkeys(): + for node in self.kvpairs: htmlout += '{0}:{1}\n'.format( node, self._create_option(self.kvpairs[node])) return htmlout def _create_option(self, pairdata): snippet = '' - for key in pairdata.iterkeys(): + for key in pairdata: val = pairdata[key] snippet += key + ':