From a325f65076b0ccc3c16c5494653734273b236a2c Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 10 Aug 2026 23:50:24 +0200 Subject: [PATCH 1/6] Pass the address family and type to getaddrinfo by keyword The loop resolver takes only host and port positionally, so these three calls raised "BaseEventLoop.getaddrinfo() takes 3 positional arguments but 5 were given" every time they ran. get_ipaddr and _find_service have no handler above them, so link local XCC discovery and a targeted SSDP search both died outright. The snoop copy sits under an except Exception, which swallowed it and left the MGTIFACE reply unanswered instead. --- confluent_server/confluent/discovery/handlers/xcc.py | 2 +- confluent_server/confluent/discovery/protocols/ssdp.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/confluent_server/confluent/discovery/handlers/xcc.py b/confluent_server/confluent/discovery/handlers/xcc.py index 4f357337..4b5f42fa 100644 --- a/confluent_server/confluent/discovery/handlers/xcc.py +++ b/confluent_server/confluent/discovery/handlers/xcc.py @@ -64,7 +64,7 @@ class NodeHandler(immhandler.NodeHandler): if lla: for idx in util.list_interface_indexes(): tmplla = '{0}%{1}'.format(lla, idx) - addr = (await cloop.getaddrinfo(tmplla, 443, 0, socket.SOCK_STREAM))[0][4] + addr = (await cloop.getaddrinfo(tmplla, 443, type=socket.SOCK_STREAM))[0][4] try: tsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) tsock.setblocking(0) diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index eaeea4e2..cb35dc7e 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -329,7 +329,8 @@ async def snoop(handler, byehandler=None, protocol=None, uuidlookup=None): if await netutil.ip_on_same_subnet(theip, 'fe80::', 64): if '%' in peer[0]: ifidx = peer[0].split('%', 1)[1] - iface = (await cloop.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM))[0][-1][-1] + iface = (await cloop.getaddrinfo(peer[0], 0, family=socket.AF_INET6, + type=socket.SOCK_DGRAM))[0][-1][-1] else: ifidx = '{}'.format(peer[-1]) iface = peer[-1] @@ -390,7 +391,7 @@ async def _find_service(service, target): cloop.add_reader(net4, _relay_pkt, net4, pktq) cloop.add_reader(net6, _relay_pkt, net6, pktq) if target: - addrs = await cloop.getaddrinfo(target, 1900, 0, socket.SOCK_DGRAM) + addrs = await cloop.getaddrinfo(target, 1900, type=socket.SOCK_DGRAM) for addr in addrs: host = addr[4][0] if addr[0] == socket.AF_INET: From 016e08fa63a30f2bf53fcb938ccda23add8dec87 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 10 Aug 2026 23:50:39 +0200 Subject: [PATCH 2/6] Catch socket errors, not the socket class, while firmware applies The retry around the firmware progress poll named socket.socket, which is not an exception class, so the moment the request it guards actually failed Python raised "catching classes that do not inherit from BaseException is not allowed" in place of the error. socket.error is OSError, which is what a failed poll raises and what the retry below was written for. --- confluent_server/aiohmi/redfish/oem/lenovo/xcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py index 33eeaab3..166aad02 100644 --- a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py +++ b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py @@ -1371,7 +1371,7 @@ class OEMHandler(generic.OEMHandler): while not complete and retry > 0: try: pgress = await self._do_web_request(monitorurl, cache=False) - except socket.socket: + except socket.error: pgress = None if not pgress: retry -= 1 From c27327dfe01ba545f442788fb462ba0a98db0f45 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Tue, 11 Aug 2026 05:17:59 +0200 Subject: [PATCH 3/6] Name the node when an inventory component is missing ConfluentTargetNotFound takes the node as its first argument, and every other caller passes it. The two inventory plugins constructed it with no arguments at all, so asking for a component that is not in the inventory map raised TypeError instead of returning the 404 the path was written to return. Both now follow the pattern used a few lines further down for volumes and name the component that was not found. --- confluent_server/confluent/plugins/hardwaremanagement/ipmi.py | 4 +++- .../confluent/plugins/hardwaremanagement/redfish.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index 85e29e4c..1e28a187 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -1053,7 +1053,9 @@ class IpmiHandler: await self.make_inventory_map() compname = self.invmap.get(component, None) if compname is None: - await self.output.put(msg.ConfluentTargetNotFound()) + await self.output.put(msg.ConfluentTargetNotFound( + self.node, + "No component named '{0}' found".format(component))) return invdata = await self.ipmicmd.get_inventory_of_component(compname) if invdata is None: diff --git a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py index 86800d21..bcdf8bee 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py @@ -975,7 +975,9 @@ class IpmiHandler: await self.make_inventory_map() compname = self.invmap.get(component, None) if compname is None: - await self.output.put(msg.ConfluentTargetNotFound()) + await self.output.put(msg.ConfluentTargetNotFound( + self.node, + "No component named '{0}' found".format(component))) return invdata = await self.ipmicmd.get_inventory_of_component(compname) if invdata is None: From a22dd62f188e412e599012f405ec2d35b5cee80e Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Tue, 11 Aug 2026 05:26:25 +0200 Subject: [PATCH 4/6] Keep OEM handler signatures in step with their base class Four calls reached a base method with an argument list it does not accept, so they raised TypeError about the argument count. Three of them would have failed either way, since the base only raises UnsupportedFunctionality. What changes there is that the failure becomes the intended, catchable one rather than an argument count error the caller cannot interpret. get_diagnostic_data grew an autosuffix argument everywhere except the ipmi generic handler, which is the handler used for unrecognized hardware. The redfish generic handler already had it. The two storage super() calls dropped the cfgspec they were given. The fourth is a real fallback rather than a message: the XCC user_delete dropped the fishclient it receives from redfish/command.py, so deleting a uid the XCC does not list raised TypeError instead of attempting the generic Redfish delete. --- confluent_server/aiohmi/ipmi/oem/generic.py | 4 +++- confluent_server/aiohmi/ipmi/oem/lenovo/handler.py | 6 ++++-- confluent_server/aiohmi/redfish/oem/lenovo/xcc.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/confluent_server/aiohmi/ipmi/oem/generic.py b/confluent_server/aiohmi/ipmi/oem/generic.py index 6ff81339..2b5019e9 100644 --- a/confluent_server/aiohmi/ipmi/oem/generic.py +++ b/confluent_server/aiohmi/ipmi/oem/generic.py @@ -189,13 +189,15 @@ class OEMHandler(object): if False: yield None - async def get_diagnostic_data(self, savefile, progress=None): + async def get_diagnostic_data(self, savefile, progress=None, + autosuffix=False): """Download diagnostic data about target to a file This should be a payload that the vendor's support team can use to do diagnostics. :param savefile: File object or filename to save to :param progress: Callback to be informed about progress + :param autosuffix: Whether to append a vendor suffix to savefile :return: """ raise exc.UnsupportedFunctionality( diff --git a/confluent_server/aiohmi/ipmi/oem/lenovo/handler.py b/confluent_server/aiohmi/ipmi/oem/lenovo/handler.py index b95c2763..e566ee45 100755 --- a/confluent_server/aiohmi/ipmi/oem/lenovo/handler.py +++ b/confluent_server/aiohmi/ipmi/oem/lenovo/handler.py @@ -222,7 +222,8 @@ class OEMHandler(generic.OEMHandler): async def remove_storage_configuration(self, cfgspec): if await self.has_xcc(): return await self.immhandler.remove_storage_configuration(cfgspec) - return await super(OEMHandler, self).remove_storage_configuration() + return await super(OEMHandler, self).remove_storage_configuration( + cfgspec) async def get_ikvm_methods(self): if await self.has_xcc(): @@ -241,7 +242,8 @@ class OEMHandler(generic.OEMHandler): async def apply_storage_configuration(self, cfgspec): if await self.has_xcc(): return await self.immhandler.apply_storage_configuration(cfgspec) - return await super(OEMHandler, self).apply_storage_configuration() + return await super(OEMHandler, self).apply_storage_configuration( + cfgspec) async def check_storage_configuration(self, cfgspec): if await self.has_xcc(): diff --git a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py index 166aad02..d9a9d173 100644 --- a/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py +++ b/confluent_server/aiohmi/redfish/oem/lenovo/xcc.py @@ -1739,7 +1739,7 @@ class OEMHandler(generic.OEMHandler): deltarget = '{0},{1}'.format(uid, uidtonamemap[uid]) await wc.grab_json_response('/api/function', {"USER_UserDelete": deltarget}) return True - return await super(OEMHandler, self).user_delete(uid) + return await super(OEMHandler, self).user_delete(uid, fishclient) async def get_user_expiration(self, uid): wc = await self.wc() From 1faa79134f58eb4b98d7ce176a9f624f34cba827 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Tue, 11 Aug 2026 05:26:25 +0200 Subject: [PATCH 5/6] Give the relay web connection a port WebConnection requires a port, and every other caller passes one, twice in this very file. Following a relay URL during discovery raised TypeError instead. --- confluent_server/confluent/discovery/handlers/generic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/discovery/handlers/generic.py b/confluent_server/confluent/discovery/handlers/generic.py index f1960b88..522e4cc9 100644 --- a/confluent_server/confluent/discovery/handlers/generic.py +++ b/confluent_server/confluent/discovery/handlers/generic.py @@ -186,7 +186,8 @@ class NodeHandler(object): if self.relay_url: kv = util.TLSCertVerifier(self.configmanager, self.relay_server, 'pubkeys.tls_hardwaremanager').verify_cert - w = webclient.WebConnection(self.relay_server, verifycallback=kv) + w = webclient.WebConnection(self.relay_server, verifycallback=kv, + port=443) relaycreds = self.configmanager.get_node_attributes(self.relay_server, 'secret.*', decrypt=True) relaycreds = relaycreds.get(self.relay_server, {}) relayuser = relaycreds.get('secret.hardwaremanagementuser', {}).get('value', None) From cb5a6fe964add85487ae5500f2ef2c2956645e08 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Tue, 11 Aug 2026 05:38:15 +0200 Subject: [PATCH 6/6] Remove the Windows service entry point bin/confluentsrv.py is a python2 script that setup.py never lists in scripts, so no package has ever installed it, and both the systemd unit and the sysvinit script start bin/confluent instead. It had also drifted out of step with what it calls: main.run takes the argument vector and this passed none. confluentsrv.spec goes with it. It is a PyInstaller spec whose only input is c:/Python27/Scripts/confluentsrv.py, a path that has never existed in this tree, left over from the Windows compatibility work. --- confluent_server/bin/confluentsrv.py | 40 ---------------------------- confluent_server/confluentsrv.spec | 30 --------------------- 2 files changed, 70 deletions(-) delete mode 100644 confluent_server/bin/confluentsrv.py delete mode 100644 confluent_server/confluentsrv.spec diff --git a/confluent_server/bin/confluentsrv.py b/confluent_server/bin/confluentsrv.py deleted file mode 100644 index 62d6ab3d..00000000 --- a/confluent_server/bin/confluentsrv.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/python2 -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2014 IBM Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import os -path = os.path.dirname(os.path.realpath(__file__)) -path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) -if path.startswith('/opt'): - # if installed into system path, do not muck with things - sys.path.append(path) -import confluent.main - -#import cProfile -#import time -#p = cProfile.Profile(time.clock) -#p.enable() -#try: -import multiprocessing -if __name__ == '__main__': - multiprocessing.freeze_support() - confluent.main.run() -#except: -# pass -#p.disable() -#p.print_stats(sort='cumulative') -#p.print_stats(sort='time') diff --git a/confluent_server/confluentsrv.spec b/confluent_server/confluentsrv.spec deleted file mode 100644 index a0797042..00000000 --- a/confluent_server/confluentsrv.spec +++ /dev/null @@ -1,30 +0,0 @@ -# -*- mode: python -*- - -block_cipher = None - - -a = Analysis(['c:/Python27/Scripts/confluentsrv.py'], - pathex=[], - hiddenimports=[], # ['pyghmi.constants', 'pyghmi.exceptions', 'pyghmi.ipmi.console', 'pyghmi.ipmi.private.constants', 'pyghmi.ipmi.private', 'pyghmi.ipmi.private.session', 'pyghmi.ipmi.command', 'pyghmi.ipmi.events', 'pyghmi.ipmi.fru', 'pyghmi.ipmi.private.spd', 'pyghmi.ipmi.oem.lookup', 'pyghmi.ipmi.oem.generic', 'pyghmi.ipmi.oem.lenovo', 'pyghmi.ipmi.private.util', 'pyghmi.ipmi.sdr'], - hookspath=None, - runtime_hooks=None, - excludes=None, - cipher=block_cipher) -pyz = PYZ(a.pure, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - exclude_binaries=True, - name='confluentsrv.exe', - debug=False, - strip=None, - upx=True, - console=True ) -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - Tree('confluent/plugins', prefix='confluent/plugins'), - strip=None, - upx=True, - name='confluentsrv')