2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-22 00:49:32 +00:00
Commit Graph

623 Commits

Author SHA1 Message Date
Markus Hilger 9755c8b0a8 Say what is wrong with an unusable parameter file
A parameter file that is not json, or that holds something other than an
object, reached the update as a raw parser message or as a TypeError from the
handler that unpacked it.
2026-08-14 22:31:21 +02:00
Markus Hilger dda9b47a51 Ask a platform which firmware image types it takes
Some will not take an image without being told which kind it is, and the
only way to find out was to attempt an update and read the error, which
writes to the bmc before it gets that far.
2026-08-14 21:26:29 +02:00
Markus Hilger 43531c3a10 Name ipmi user link relations with a string
The uids are dict keys and went out as JSON numbers.
2026-08-14 21:26:29 +02:00
Markus Hilger bd5d7ffb94 Address a redfish account by the id the bmc gave it
Redfish identifies an account by a string, and an implementation is free
to use the account name, which this one does.  The handler converted the
last element of the path to an integer, so every per user read, update and
delete answered "invalid literal for int() with base 10: 'root'" as an
unexpected error, with a traceback to match.  Confluent offered the id
itself, listing the account as "root", and then could not accept it back.

Take the element as given.  Everything below already compares ids as
strings, and the input parsing already keeps a non numeric uid, so only
this conversion stood in the way.  nodebmcpassword goes through exactly
this path, reading users/all for the id and then writing to that account,
so it could not work at all on such a bmc.

ipmi users really are numbered slots, so the conversion is right there and
stays, but say so when it fails rather than letting a ValueError surface
as an unexpected error.
2026-08-14 21:26:29 +02:00
Markus Hilger 564230cf7e Give an unreachable target an error a user can read
A console whose bmc had gone away reported "Unexpected error - None", and
the api answered 504 with an error of None.  The redfish plugin took the
text for an unreachable target from the strerror of the socket error it
caught, guarded by a hasattr that is always true: every OSError has a
strerror attribute, and it is None on most of the ones a bmc going away
produces, TimeoutError and gaierror among them.  Ask for the text the same
way as everywhere else instead, which also keeps the errno on the errors
that do carry one.

The same applies to an unreachable target raised with no message at all,
so use the same helper there, on both transports.

Underneath that, give the node error messages a default to fall back on
rather than carrying whatever they were handed.  Each subclass already had
one, in an __init__ that an explicit None went straight past; making it a
class attribute the base class applies means it holds however the message
was built, and removes five copies of the same constructor.

Also repair an affluent handler that put a closing parenthesis in the
wrong place, passing its error text to Queue.put_nowait as a second
argument.  Any OSError there other than "no route to host" raised
TypeError from inside the except clause instead of reporting anything.
2026-08-14 21:26:28 +02:00
Markus Hilger 9402df2bdd Stop a websocket console spinning once its peer is gone
The receive loop treated only WSMsgType.CLOSE as the end of a session, but
aiohttp reports a peer that has gone away as CLOSED, and it does so
immediately and for every subsequent call.  Everything that was not CLOSE
fell to an else branch that printed a line and went round again, so a
console whose bmc restarted became a full speed loop writing one line per
iteration: measured at 2.7 million iterations a second, and observed
filling 15 GB of log in a quarter of an hour while the daemon stopped
answering requests.

Treat every message that is not data as the end of the session, clear the
connected flag and report the disconnect once.  A session that ended any
other way than a clean close is recorded in the trace log, unbuffered so
that it survives a daemon that does not, rather than printed.

Both websocket console plugins carried the same loop.  While here, give
the openbmc one the parts tsmsol already had: text frames are data rather
than a surprise, and the client session is closed when the upgrade fails
and when the console does, instead of being leaked.
2026-08-14 21:26:28 +02:00
Markus Hilger d9d9fa3df7 Say which resource a transport does not implement
The unhandled tails of handle_request, handle_configuration and handle_alerts
raised a bare Exception('Not implemented'), so asking for a resource the
transport has no code for was reported as an unexpected error and logged with a
traceback. management_controller/location over ipmi is one such resource: R5
implemented it for redfish only.

Raise UnsupportedFunctionality naming the resource instead, which the plugins
already treat as its own case rather than a fault, and give decode_alert over
redfish the same treatment. Any resource added to the tree without an
implementation on one transport now reports that plainly.
2026-08-14 21:26:28 +02:00
Markus Hilger f6ba3802bc Report an error that carries no message of its own
nodereseat printed "Error: " and nothing else against a bmc that refused the
credentials. The redfish plugin reports it properly, but the message it emits
re-raises TargetEndpointBadCredentials with no arguments when a single node is
addressed, and the enclosure plugin renders that with str(e), which is empty.

Both hardwaremanagement plugins already had a helper for exactly this, one copy
each. Keep one in confluent.exceptions instead, teach it to fall back to the
description a confluent exception carries by class before falling back to the
exception name, and use it in the enclosure plugin too.

get_error_body had the mirror image of the same bug, joining the class
description and the message unconditionally and so answering "Bad Credentials -"
with a separator and nothing after it. The apierrorstr property beside it
already gets this right, so use it.
2026-08-14 21:26:28 +02:00
Markus Hilger 58c5827c62 Say when a platform cannot report its ntp state
get_ntp_enabled returns None to mean the platform cannot tell us, and that went
to the caller as the literal text "None", which says nothing at all. Report it as
unsupported instead, which the tooling already renders plainly.
2026-08-14 20:04:19 +02:00
Markus Hilger b30ee29ff6 Implement the redfish resources that called missing methods
Five resources called methods that do not exist on the redfish client, so
each answered with an internal error naming the missing attribute: the leds,
the management controller identifier, the domain name, the remote kvm licence
and the alert destinations. Implement the first four from the manager network
protocol, the graphical console and the chassis indicator.

Alert destinations stay unimplemented on purpose. Redfish describes where to
send events with EventService subscriptions, which is a different model from
the numbered PET destinations this resource was built around, so say so and
drop the code that could never run.

The location resource fetched its data and discarded it, so a read produced
no output whatsoever.
2026-08-14 20:04:19 +02:00
Markus Hilger 8649b22605 Give every unsupported operation a message to report
A bare UnsupportedFunctionality() left the user with an error containing no
text at all, or with no output and a success exit code, so asking a platform
for something it does not implement looked like nothing had happened.

Name what is unsupported at each raise, treat it as its own case in the
plugins so it reads as a limitation rather than an unexpected error and does
not log a traceback, and fall back to naming the exception when an exception
still arrives with nothing to say. The generic redfish
get_extended_bmc_configuration was also declared without async while the
caller awaits it.
2026-08-14 20:04:19 +02:00
Markus Hilger 6e95399528 Repair the asynchronous contracts around ipmi users and extended config
handle_users iterated get_users with async for while the same call is awaited
a few lines below, so listing the users collection, and creating a user,
raised a TypeError about a coroutine having no __aiter__. list_inventory in
the redfish plugin had the mirror of it, awaiting an async generator.

get_extended_bmc_configuration is called with hideadvanced but the ipmi chain
never accepted it, so the extra and extra_advanced resources raised a
TypeError; thread the argument through instead.

A user slot the bmc refuses to describe no longer takes the whole user list
with it: one MegaRAC slot answered Invalid data field for good after an
account was deleted, which broke every user operation.
2026-08-13 18:09:18 +02:00
Markus Hilger c27327dfe0 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.
2026-08-11 05:17:59 +02:00
Markus Hilger 1c675c5f24 Port the Eaton PDU plugin to asyncio
The plugin was written against the http.client based SecureHTTPConnection, and
when that went away the reference was pointed at the aiohttp WebConnection,
which shares the name and nothing else. Nothing in it could run: the transport
called an async request() without awaiting it and then reached for a
getresponse() the new class does not have, and three PDUClient methods that
were never coroutines were awaited by the entry points.

Two transports now, both local to this plugin. https is aiohttp and stays on
the event loop, since the cert verifier records new fingerprints through
tasks.spawn. http is http.client in a thread, with its own socket so it can
still ask for a smaller segment size before connect: aiohttp only takes a
socket factory from 3.12 on, newer than el9, el10, ubuntu 24.04 or Leap 16
ship. That side has no cert to verify and its credentials arrive already read,
so the thread touches nothing.

connect() establishes and authenticates, wc is just the accessor now, and
logout() no longer sends a session id it never obtained. update() reports an
unsupported element instead of raising NameError.

On the https side cookies follow aiohttp's domain rules and the one POST with
a body goes out as text/plain, where http.client replayed every cookie and
sent no content type. The http side is as before, and neither can be settled
without an Eaton PDU on the bench. Both transports were exercised against a
stand-in: login, outlet read and set, sensors, logout, and a clamped segment
size on the plaintext path.
2026-08-10 14:36:37 +02:00
Markus Hilger e828ed4ff4 Close the TSM console web session
TsmConsole created an aiohttp ClientSession and never closed it, and leaked it
again when ws_connect failed. Neither was reachable before the connection path
was repaired. It is closed on both paths, and starts as None so that closing
before a connect does not trip over a missing attribute.
2026-08-10 14:36:37 +02:00
Markus Hilger 91960527aa Repair the TSM console connection
Three faults in the same few lines. The redfish Command lost its constructor
for an async create, so building one raised TypeError, which the except below
reported as TargetEndpointUnreachable. await_redirect is defined nowhere in
this repository's history, so that call raised too; create performs the
session setup it was meant to trigger. And oem is a coroutine method rather
than an attribute, with its web connection coming from get_wc, which is what
performs the login that sets csrftok.
2026-08-10 14:36:37 +02:00
Markus Hilger 92c9abc74a Await the remaining reachable coroutines
sockapi sent its collective refusal without awaiting tlvdata.send. redfish
handle_sensors returned a coroutine from most branches and None from the short
ones, so the caller's await raised TypeError; it is a coroutine throughout
now. console send_payload waited for a response without awaiting the wait.

Two suppressions are pyrefly limitations rather than bugs: Session defines an
async __new__, and the keepalive registry holds coroutine functions in an
untyped dict.
2026-08-10 14:36:37 +02:00
Markus Hilger 2ea7aed2cc Port the SMM handler, Delta PDU logout and XCC config to async
Taken from fix/asyncio-port-critical, limited to what pyrefly reports.

The SMM handler still used the httplib style connect/request/getresponse that
the async webclient does not have, so nothing was ever sent. It goes through
grab_response_with_status now, with allow_redirects=False to preserve httplib
behaviour, hence the new webclient parameter. That also fixes a login passing
its headers as urlencode's second positional argument.

Delta PDU's logout was a plain function both callers awaited, so the power
paths raised TypeError. XCC's set_system_configuration was the last
synchronous implementation of a method every caller awaits.
2026-08-10 14:36:37 +02:00
Markus Hilger 9148a9e1cf Run the module self tests through asyncio
These __main__ blocks called coroutines as if they were functions, so they did
nothing at all. Single calls go through asyncio.run; sshutil, proxmox and
vcenter needed an _selftest coroutine. Two were invisible to pyrefly because
repr() and list() count as using the result: vcenter's get_vm_serial needs an
await, and proxmox's get_vm_inventory is an async generator.

lldp called _extract_neighbor_data twice, once correctly, so the bare call is
dropped. xcc3.remote_nodecfg is not a self test: every other handler defines
it as a coroutine and selfservice.py awaits it.
2026-08-10 14:36:37 +02:00
Markus Hilger 644843b892 Remove unused imports and pointless f-string prefixes (F401, F541, E713)
Entirely mechanical, produced by `ruff check --fix --select F401,F541,E713`
and reviewed rather than taken on faith: deleting an import is only safe if
nothing imports it for its side effects or re-exports it.  None of the 19
removed names is referenced anywhere in its file, none appears in any string
literal, and none of the touched files uses eval, exec, globals() or
__import__, so there is no dynamic lookup that could reach them.
2026-08-10 05:32:00 +02:00
Markus Hilger b119de345b Remove shadowed duplicate definitions (F811)
Three names were defined twice in the same scope, so the first definition
was unreachable:

- lenovo OEM handler: two set_user_access methods, the second silently
  replacing the first.  That made the SMM privilege update dead code.  The
  conditions are mutually exclusive (is_fpc returns None once has_xcc is
  true), so merge both into the surviving method.
- redfish plugin handle_cert_authorities and prepfish
  disable_host_interface: byte identical copies, drop the redundant one.
2026-08-10 05:32:00 +02:00
Markus Hilger 8a3fce85c0 Fix undefined names (F821)
Every one of these raises NameError if its code path is reached:

- nodeapply: run_automation accumulated into an exitcode that only existed
  in run(), so any automation error crashed instead of being reported.  It
  now keeps and returns its own, tracked separately from the exit code of
  the ssh commands: the early exit after the spawn loop tests that one,
  and folding automation failures into it would exit with children already
  running and their pipes abandoned.  Both are reported at the real exits.
- nodeconsole: redraw() reads firstnodename, which was local to
  do_screenshot(); promote it to a module global like the other drawing
  state.
- nodedeploy: the redeploy path appended to a lockednodes list that did not
  exist yet.  The block that follows re-reads the same lock state and acts
  on it, so drop the dead duplicate.
- samples/nodeattrib_from_switch.py, misc/filterpasswd: missing import sys.
- xcc3: fixuuid was never imported.  xcc imports xcc3, so take a local copy
  the way the smm handler does instead of creating an import cycle.
- httpapi: the async session call still passed the WSGI-era env and an
  extra argument to handle_async(), which has taken only querydict since
  the aiohttp port.  Calling it correctly exposed that handle_async()
  registers an AsyncSession before raising on the discontinued long poll
  path, so every request to it would leak a session that is never reaped.
  It now only creates one when there is a websocket handler to yield it to.
- messages: the InputFirmwareUpdate.filename property checked
  self.filebynode[node] with no node in scope.  __init__ already validates
  every expanded path and nodefile() rechecks per node, so drop the checks.
- pam: drop the python2 branches referencing unicode and raw_input.  The
  server has been python3 only since the asyncio port.
- cooltera: the sensor-name listing referenced a nonexistent sensors dict.
  The available sensors depend on the model, which is only known after
  reading the device, so list them from the same status data the readings
  use.
- deltapdu, eatonpdu, geist: the not-implemented response in update() used
  node outside the loop, unlike retrieve() in the same files and unlike
  raritan/enlogic.
- confluentdbgcli: stray self. on a module-level socket connect.
2026-08-10 05:32:00 +02:00
Markus Hilger fa605160e0 Merge branch 'master' into ci 2026-07-14 17:04:16 +02:00
Jarrod Johnson 0263ad0914 Merge pull request #241 from Obihoernchen/ipv6fix
Preserve scoped IPv6 console addresses
2026-07-14 07:55:03 -04:00
Jarrod Johnson 2b1facb2c5 Merge pull request #240 from Obihoernchen/enos
Reuse ENOS health data
2026-07-14 07:50:56 -04:00
Markus Hilger ab6eeb3ced Merge branch 'master' into ruff 2026-07-14 05:28:53 +02:00
Markus Hilger 2af402b13c ruff auto fixes
Apply ruff's safe autofixes.
The changes are mechanical and behaviour-preserving. Issues fixed:

- F401: remove unused imports.
- F841: drop unused local variables and assignments, including discarded
  await/return values, unused "except ... as e" bindings, and unused
  "with ... as name" targets.
- F541: remove the f prefix from f-strings that contain no placeholders.
- E711: compare against None with "is"/"is not" instead of "=="/"!=".
- E712: test truthiness directly instead of comparing to True.
- E713: use "x not in y" instead of "not x in y".
- E714: use "is not" instead of "not ... is".
- E731: convert lambdas bound to a name into def statements.
- W291/W293: trim trailing whitespace on touched lines.
2026-07-14 05:03:58 +02:00
Markus Hilger b8739b1feb Preserve scoped IPv6 console addresses
Before the async port the bmc var was used for a "host=bmc" parameter
which does not exist anymore.

Now add [] around IPv6 addresses with missing brackets but keep the scope zone like %eth0
as this is needed in current code.
2026-07-14 04:00:45 +02:00
Markus Hilger 686fef6730 Reuse ENOS health data 2026-07-14 03:49:55 +02:00
Markus Hilger 69a6714108 Use raw string notation to fix python compile warnings
E.g.: SyntaxError: "\d" is an invalid escape sequence. Did you mean "\\d"? A raw string is also an option.
2026-07-14 01:26:19 +02:00
Markus Hilger 670a11666d Fix remaining hardware async responses 2026-07-13 02:50:12 +02:00
Markus Hilger 9c4f9e1935 Fix hardware management async dispatch 2026-07-13 02:50:11 +02:00
Markus Hilger 89d0fa81b9 Fix asynchronous discovery call contracts 2026-07-13 02:50:11 +02:00
Markus Hilger 5fc036a2b7 Await asynchronous configuration mutations 2026-07-13 02:50:11 +02:00
Jarrod Johnson c3b75f0ca1 Remove stale logging output from enlogic 2026-07-02 16:23:32 -04:00
Markus Hilger 7727cd86fc Fix typos in help text, errors, and log messages 2026-07-02 22:07:27 +02:00
Jarrod Johnson 3a6887b4b4 Provide nicer message when requested VM does not exist 2026-07-01 09:55:15 -04:00
Jarrod Johnson d761c7e6da Slow down reconnect attempts to powered down Proxmox VMs and better handle closed websockets. 2026-07-01 09:30:28 -04:00
Jarrod Johnson 45b392932d Handle unreachable proxmox host more friendly 2026-07-01 09:14:07 -04:00
Jarrod Johnson 33c67db3c4 Further mitigate potential XML misbehavior
Since it turns out we already incurred lxml dependency, use lxml etree instead of xml and mitigate risky xml features beyond blocking the word '!entity'
2026-07-01 08:28:25 -04:00
Jarrod Johnson fbec09c073 Fix behavior with IPMI bad user/password 2026-06-30 15:17:45 -04:00
Jarrod Johnson 5abd080ba2 Restore some sanity to redfish error handling 2026-06-30 13:56:34 -04:00
gosforthcross 6505810833 Improve handling of IPs with colon notation with seperate IPv4 and IPv6 paths, as well as whitespace stripping 2026-06-30 13:05:22 +02:00
gosforthcross b4882692ea Add port discovery via colon notation 2026-06-30 11:22:30 +02:00
Jarrod Johnson 9462de42ac Fix setboot when network not in bootorder 2026-06-25 15:56:11 -04:00
Jarrod Johnson 1449eee4f4 Fix to more reliably default to 47 2026-06-23 09:55:29 -04:00
Jarrod Johnson cf7f2f434d Add function for nodes to request a TLS certificate from confluent
Also, make certificate lifetime default configurable as attribute, with 47 as explicit default.
2026-06-23 09:39:16 -04:00
Jarrod Johnson 525186ac7f Add empty sensors to VM health 2026-06-09 16:52:59 -04:00
Jarrod Johnson 5bfd44528d Provide unknown health for vcenter and proxmox 2026-06-09 16:35:13 -04:00
Jarrod Johnson 44385388b6 Provide more verbose feedback 2026-06-09 16:17:40 -04:00