2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-02 15:36:05 +00:00
Commit Graph

3282 Commits

Author SHA1 Message Date
Jarrod Johnson 985aff2c1a Allow ipv4 address extraction on web open with fe80::
If using fe80::, ask for more viable global addresses automatically.
2026-08-18 16:44:47 -04:00
Markus Hilger 7c68758761 Back off a fru read the bmc will not serve in one piece
Completion codes 201 and 202 mean the chunk asked for was too big, and the
check for them sat after a call that raises, so a bmc that cannot serve 224
bytes at once failed the fru read rather than being asked for less.

The retry could not terminate either: chunksize // 2 + 2 is 4 for a chunksize
of 4, so the chunksize == 3 guard was unreachable and a bmc that kept refusing
would have been asked for 4 bytes for ever.
2026-08-15 04:33:28 +02:00
Markus Hilger d5e9be5abb Skip an absent optional sensor again, and read the ipv6 answer
Completion code 203 on a sensor reading means the sensor is not present, which
is expected of an optional device, but the check for it sat after a call that
raises first, so one absent sensor ended the whole sensor sweep.

_supports_standard_ipv6 read rsp['code'] the same way, so it could only ever
answer True; a platform without the standard parameters raised instead.  A
completion code is that platform's answer, while a timeout or a lost session is
not and must not be cached as one.

raw_command's docstring still described itself as the other call it was renamed
from, which is how these checks came to be written against the wrong contract.
2026-08-15 04:33:28 +02:00
Markus Hilger a513bfc04f Read the sdr partial read codes from the exception
raw_command raises on any nonzero completion code, so the 0xCA and 0xC5 checks
in get_sdr could not run: a bmc that will not return a whole record in one go,
or whose reservation went stale, failed the sensor load outright instead of
being retried.  Read the code from the exception, which carries it.

The back off also had a fixed point, size // 2 + 2 being 3 for a size of 3, so
a bmc that kept refusing would have been asked the same question for ever.
Give up when the request cannot get any smaller.  A header read cannot go
under 5 bytes either, since that is where the record length sits, so give up
there rather than parse a reply too short to index.

The stale reservation retry could not end on its own either: it cleared the id
and left taking a new one to the top of the loop, which only reserves for a
partial read.  Take one where the code is handled.
2026-08-15 04:33:28 +02:00
Jarrod Johnson 357d205ee9 Correct the variable name for errmsg 2026-08-14 10:42:18 -04:00
Jarrod Johnson cf87fd2127 Fix network issue handling issue while managing network configuration. 2026-08-14 10:40:08 -04:00
Jarrod Johnson 969bbe009e Merge pull request #273 from Obihoernchen/ipmi-session
Fix ipmi session sharing
2026-08-14 09:41:15 -04:00
Markus Hilger 0b7f6b1395 Tidy three loose ends around sharing a session
Closing a console gives up its claim on the session, and that talks to the
bmc, so let it fail the same way the console's own deactivate is already
allowed to.

kg was left as the caller passed it in both the register key and the reuse
check, so the mismatch fixed for the name and password still applied to it.

The count for a new socket was taken before binding it and before the io
task was known to be up.  Take it last, once nothing is left that can still
fail.
2026-08-14 14:10:38 +02:00
Markus Hilger 8aecc6959a Do not let a logout come back round into its own notification
Telling a keepalive that the session is gone can end up back in logout,
because reporting it is how a console gives up its claim.  The inner pass
finished by clearing the register of keepalives while the outer was still
walking it, so the next entry was looked up on None.  Two entries is what an
XCC has, the console's and the oem handler's.

Take the callbacks and give up the register before notifying anyone.
2026-08-14 14:10:38 +02:00
Markus Hilger 5aec0e69f5 Recognise a session that is already open to the same bmc
The check for sharing a live session compared the credentials a session
keeps encoded against the strings every caller passes, so it never matched
and each caller built another session beside the one it could not see.
Normalise both sides.  Three commands to one bmc went from three sessions on
three sockets to one, and from five sessions open on the bmc to three.

Not from the port to asyncio: upstream compares the same two things the same
way.
2026-08-14 14:10:38 +02:00
Markus Hilger 9a8fe206a4 Let a session serve several callers without one closing it
One session is now routinely handed to a console and a command at once, and
logout closed it for both, leaving whoever was left holding one that
answered as though it had been lost.  Count the holders and give up a claim
instead, unless the session is no longer usable, which logout is told by
sessionok.

A console had no way to give a claim back: close deactivated its sol payload
and left the session alone, which was right when closing meant closing it
for everybody and is a leak now.  Both of its exits release it.
2026-08-14 14:10:38 +02:00
Markus Hilger ed21c7634a Drop a guard that never ran and would not have worked
__init__ opened by checking for an initialized attribute, meaning it had
been handed a session someone else was establishing.  That attribute is only
ever set further down in the same method, so the check cannot be true, and
the port lost the return that made it work upstream.  Waiting for someone
else's login is done in __new__ now, so this is dead code claiming to
protect something.
2026-08-14 14:10:38 +02:00
Markus Hilger 9408c56639 Give back a socket pool count once, not twice
logout decremented the count of sessions on a socket twice over, and
_mark_broken again for the case logout had not, so it went negative and kept
falling. _assignsocket picks the least used socket and refuses one at
MAX_BMCS_PER_SOCKET, and both of those read that number.
2026-08-14 14:10:38 +02:00
Markus Hilger ef608005cf Register an ipmi session before establishing it, not after
initting_sessions exists so a caller can share a session already on its way,
but the entry was added only once the login had finished, leaving the login
itself uncovered.  Two callers asking at once each built a session, both on
the socket the other had not claimed yet, and replies route by bmc address
and local port, so only the last to transmit was ever answered.  That is the
console session failing about one attempt in three.

Register before the login and remove the entry in a finally.  The two old
removals keyed on the encoded credentials while the register is keyed on the
caller's strings, so they never matched and an entry outlived its session.
A session handed over mid login is now waited for rather than returned as
one that answers as though it had been lost.
2026-08-14 14:10:38 +02:00
Markus Hilger c7b6147e74 Report why an ipmi session could not be established
A session that failed raised with no message at all, so a failed console
read "IpmiException: None".  Record the reason wherever a session is marked
broken.
2026-08-14 14:07:49 +02:00
Jarrod Johnson 8fe87bf0eb Fix getfetchable hidden argument 2026-08-13 16:34:22 -04:00
Jarrod Johnson f7815bd7cf Improve tab completion of fetch 2026-08-13 16:23:09 -04:00
Jarrod Johnson 2660842b9a Add signature validation when possible to image fetch 2026-08-13 16:08:14 -04:00
Jarrod Johnson 9be1136ba4 Normalize destination directory in osdeploy fetch 2026-08-13 15:27:11 -04:00
Jarrod Johnson b84cf35d79 Show progress with osdeploy fetch 2026-08-13 14:52:03 -04:00
Jarrod Johnson 3aa5222fef Add packaging and some tab completion for osdeploy fetch 2026-08-13 14:41:06 -04:00
Jarrod Johnson a2a1f76ff2 Attempt a new 'osdeploy fetch' to help with downloading some of the ISOs. 2026-08-13 14:25:04 -04:00
Jarrod Johnson d2d081d94f Fix SSDP ignoring packets unless rapidly following another
The asyncio rework mistakenly follows up a long waiting acquire with blanking and starting over.

Now use 'True' as a sentinel value to trigger a fetch, otherwise, assume srp is ripe for processing.

Immediately discard srp after unpack and replace with sentinal value.
2026-08-13 09:22:51 -04:00
Jarrod Johnson 8d519b57a6 Merge pull request #272 from Obihoernchen/ruff-more-checks
Enable more ruff and pyrefly checks
2026-08-11 07:56:41 -04:00
Markus Hilger cb5a6fe964 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.
2026-08-11 05:38:15 +02:00
Markus Hilger 1faa79134f 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.
2026-08-11 05:38:00 +02:00
Markus Hilger a22dd62f18 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.
2026-08-11 05:38:00 +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 016e08fa63 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.
2026-08-11 05:17:05 +02:00
Markus Hilger a325f65076 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.
2026-08-11 05:17:05 +02:00
Markus Hilger acd6bb228c Clear the last findings in four ruff groups
Each is the only thing keeping its rule group from being selectable whole.
userutil.py imported ctypes with a star; the names it uses are POINTER,
byref, c_char_p, c_int, c_int32, c_uint and cdll. The oem lookup loop had an
else with no break, so the else always ran. The alert parameter table wrapped
int in a lambda that only forwards to it. And the watchdog interval passed 0
where os.environ.get documents a string, which worked because int(0) is 0.
2026-08-11 04:16:29 +02:00
Jarrod Johnson 1a4475f64e Merge pull request #270 from Obihoernchen/fix/fpc-sensor-enumeration
Fix FPC/SMM sensor enumeration
2026-08-10 18:31:20 -04:00
Jarrod Johnson 8cbfaa9662 Merge pull request #269 from Obihoernchen/fix/dangling-asyncio-tasks
Keep spawned asyncio tasks referenced
2026-08-10 18:30:47 -04:00
Jarrod Johnson 05e42a3b09 Merge pull request #268 from Obihoernchen/fix/osdeploy-local-trust-awaits
Await the coroutines in osdeploy local node trust setup
2026-08-10 18:28:38 -04:00
Markus Hilger f5ee86f97e Make the FPC sensor generators coroutines
get_sensor_names and get_sensor_descriptions reach get_psu_count for any
sensor whose table entry carries elementsfun, and get_psu_count is a
coroutine. As plain generators they could not await it, so range() was handed
the coroutine object and enumeration died with "'coroutine' object cannot be
interpreted as an integer".

Every DW612S has such entries, so nodesensors returned nothing for the
enclosure. get_sensor_descriptions was doubly broken: the Lenovo handler
already iterated it with async for, which a plain generator cannot satisfy.

Verified against a DW612S SMM (FPC variant 38). Before, descriptions raised at
the async for and readings raised partway through enumeration; after, both
return all 34 sensors, 19 of which are the PSU entries that never enumerated.
2026-08-11 00:20:49 +02:00
Markus Hilger 9e41fdc598 Hold the async HTTP handler task until it finishes
run_handler scheduled the coroutine that serves an async HTTP request and
dropped the returned task. The event loop only keeps a weak reference, so the
task could be collected while still pending, leaving the request unanswered
and "Task was destroyed but it is pending!" in the log.

The session already outlives the request in _asyncsessions, so it holds the
task in a set and discards it from a done callback.
2026-08-10 23:18:24 +02:00
Markus Hilger 151fb1efc8 Await the coroutines in osdeploy local node trust setup
local_node_trust_setup() called get_cluster_list() and sign_host_key() without
awaiting them, so "osdeploy initialize -l" aborted with "TypeError: cannot
unpack non-iterable coroutine object" before doing any work.

Both awaits have to land together: sign_host_key() is called in a loop that
unlinks the existing ssh_host_*_key-cert.pub before writing the new one, so
fixing only the unpack would delete every host certificate and then fail.
2026-08-10 23:05:44 +02:00
Jarrod Johnson 120050ae78 Perodically reopen the multicast sockets
It has been observed there are times where an ethernet switch is partially working with MLD snoop/IGMP snoop.  A workaround for the unreliable behavior seems to be to reassert multicast joins ever so often.

Give it a try to restart the SSDP sockets every minute.
2026-08-10 11:24:02 -04: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 a53351a730 Give the virsh console loop a reason to wake
virEventRunDefaultImpl waits for an event that an idle domain need not
produce, so the thread could outlive a deactivation that reported success, and
every later activation was refused while it did. Registering a timeout is what
makes it return: measured, a thread with nothing registered was still running
four seconds after being asked to stop, and with a half second timer it came
out at once.
2026-08-10 14:36:37 +02:00
Markus Hilger 11dc8196b0 Keep hold of a virsh console thread that will not stop
Deactivation dropped its reference once the wait expired, whether or not the
thread had stopped, so the next activation started a second one and revived
the first by setting run_console again. The reference is cleared only when the
thread is really gone, and activation refuses with 0x80 while one is alive.
That makes the wait a courtesy rather than a correctness measure, so it drops
to a second.
2026-08-10 14:36:37 +02:00
Markus Hilger bda403766e Start and stop the virsh console thread with the payload
Activation started an event thread whichever way the base handler had just
answered, so a refusal started one anyway and an already active console got a
second. activated alone cannot tell the two refusals apart, being true
already on the already active path, so the value from before the call decides.

Deactivation joined that thread on the event loop, where it could stall every
other session and its own response. The wait moves off the loop and is
bounded, and the thread is a daemon.
2026-08-10 14:36:37 +02:00
Markus Hilger 42ae44d5bf Finish the server side SOL and cleanup paths
The console awaits its output handler, but both sample BMCs supplied a plain
function, and both dropped the send_data coroutine. virshbmc additionally
receives its stream callback on a libvirt thread, so the send goes through
run_coroutine_threadsafe against the loop captured at activation, called
asyncloop because the class already has a loop method it uses as a thread
target.

ServerConsole asked the session layer to retry, which a ServerSession cannot
do: it never runs Session.__init__, so it has no timeout, and its _timedout
does nothing. IpmiServer.logout was synchronous and an argument short while
_cleanup awaits logout(False). The boot options handler answered, then read an
unbound name and answered again with 0xff.
2026-08-10 14:36:37 +02:00
Markus Hilger 8521c6ad4b Port the IPMI server side to asyncio
bmc.py, serversession.py, fakebmc.py and virshbmc.py were byte identical to
upstream pyghmi: the async port went through the session layer beneath them
and left the server side alone. So every response created a coroutine and
dropped it, and the overrides the now async parent awaits returned None.
Running fakebmc bound no socket, spun a core, and answered nothing.

Everything that sends a response is a coroutine now, and so is the dispatch
that reaches it; the hooks a subclass implements stay ordinary functions, so
an out of tree Bmc is unaffected unless it overrides the payload handlers.
Two things had to leave their constructors, both being coroutines: assigning
the server socket, into bind(), which is why listen() is no longer a
classmethod, and answering the open session request, into
send_open_session_response.

Verified against upstream with ipmitool over nine commands, with identical
output. SOL is not covered: fakebmc reports the payload disabled on both.
2026-08-10 14:36:37 +02:00
Markus Hilger 7ecc2b2818 Replace the housekeeping thread with a loop owned task
Housekeeper dates from when this work was a blocking select loop. Under
asyncio the event loop is already that place, and a thread around it takes a
captured loop reference, a scheduling step before the thread starts, and a
daemon flag, only to sit waiting on a coroutine that never returns with no way
to stop it. A task runs on the right loop by construction and cancels. The
loop is looked up before the coroutine is built, so calling this without one
raises rather than stranding it.

Nothing in this tree used the class. Out of tree callers need
start_housekeeping() instead, and gain the ability to stop it.
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 c9d7343e02 Feed console input through a queue
A task per read swallowed failures, could deliver keystrokes out of order, and
at end of input returned with the reader still registered, so a level
triggered selector called it again for the same EOF. The reader queues now,
one consumer sends in order, and it is gathered with the main loop so a
failure reaches the caller.
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 e1071317ed Create shell sessions through the async factory
ConsoleSession grew an async create and lost its constructor, and ShellSession
inherits that. sockapi was updated for the console branch but not the shell
branch immediately below it, so opening a shell session raised TypeError. It
is the only place in the tree that builds one.
2026-08-10 14:36:37 +02:00
Markus Hilger dfde5736e9 Stop the aiohmi event loop from spinning when it has nothing to do
Command.eventloop called wait_for_rsp with no timeout. With nothing waiting or
being kept alive there is no deadline to derive one from, so it returns
without suspending and the loop runs flat out, measured at over 100000
iterations in two tenths of a second.

MAX_IDLE gives it something to wait on, as a ceiling rather than a fixed
delay: real deadlines still shorten it and an arriving packet still ends it
early.
2026-08-10 14:36:37 +02:00