mirror of
https://github.com/xcat2/confluent.git
synced 2026-08-28 01:26:44 +00:00
Merge pull request #267 from Obihoernchen/pyrefly
Add pyrefly for async correctness
This commit is contained in:
@@ -52,6 +52,13 @@ jobs:
|
||||
# workspace can be handed over as-is.
|
||||
args: check --output-format=github
|
||||
|
||||
pyrefly:
|
||||
name: Pyrefly (async correctness)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: facebook/pyrefly@main
|
||||
|
||||
python-compileall:
|
||||
name: Python compileall
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -159,7 +159,7 @@ class NetplanManager(object):
|
||||
currinfo[devname]['routes'] = routeinfo
|
||||
currcfg[devname] = currinfo[devname]
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
def apply_configuration(self, cfg, lastchance=False):
|
||||
devnames = cfg['interfaces']
|
||||
if len(devnames) > 1:
|
||||
teammode = cfg['settings'].get('team_mode', None)
|
||||
@@ -342,7 +342,7 @@ class WickedManager(object):
|
||||
v = v.strip()
|
||||
currcfg[k] = v
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
def apply_configuration(self, cfg, lastchance=False):
|
||||
stgs = cfg['settings']
|
||||
ipcfg = 'STARTMODE=auto\n'
|
||||
routecfg = ''
|
||||
|
||||
@@ -151,7 +151,7 @@ class NetplanManager(object):
|
||||
nicinfo[devname]['routes'] = routeinfo
|
||||
self.cfgbydev[devname] = nicinfo[devname]
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
def apply_configuration(self, cfg, lastchance=False):
|
||||
devnames = cfg['interfaces']
|
||||
if len(devnames) != 1:
|
||||
raise Exception('Multi-nic team/bonds not yet supported')
|
||||
@@ -266,7 +266,7 @@ class WickedManager(object):
|
||||
v = v.strip()
|
||||
currcfg[k] = v
|
||||
|
||||
def apply_configuration(self, cfg):
|
||||
def apply_configuration(self, cfg, lastchance=False):
|
||||
stgs = cfg['settings']
|
||||
ipcfg = 'STARTMODE=auto\n'
|
||||
routecfg = ''
|
||||
|
||||
@@ -27,6 +27,7 @@ Sent cold reset command to MC
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import aiohmi.ipmi.bmc as bmc
|
||||
@@ -72,10 +73,10 @@ class FakeBmc(bmc.Bmc):
|
||||
def is_active(self):
|
||||
return self.powerstate == 'on'
|
||||
|
||||
def iohandler(self, data):
|
||||
async def iohandler(self, data):
|
||||
print(data)
|
||||
if self.sol:
|
||||
self.sol.send_data(data)
|
||||
await self.sol.send_data(data)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -90,7 +91,7 @@ def main():
|
||||
help='Port to listen on; defaults to 623')
|
||||
args = parser.parse_args()
|
||||
mybmc = FakeBmc({'admin': 'password'}, port=args.port)
|
||||
mybmc.listen()
|
||||
asyncio.run(mybmc.listen())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -14,32 +14,43 @@
|
||||
|
||||
""" A simple little script to exemplify/test ipmi.console module """
|
||||
|
||||
import asyncio
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
import termios
|
||||
import threading
|
||||
import tty
|
||||
|
||||
|
||||
from aiohmi.ipmi import console
|
||||
|
||||
|
||||
def _doinput(sol):
|
||||
async def _feed_input(sol, inqueue):
|
||||
"""Sends what the reader below collected, one chunk at a time"""
|
||||
|
||||
while True:
|
||||
select.select((sys.stdin,), (), (), 600)
|
||||
try:
|
||||
data = sys.stdin.read()
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == 11:
|
||||
continue
|
||||
raise
|
||||
|
||||
sol.send_data(data)
|
||||
await sol.send_data(await inqueue.get())
|
||||
|
||||
|
||||
def _print(data):
|
||||
def _got_input(inqueue):
|
||||
"""Called by the event loop whenever stdin has something to read"""
|
||||
|
||||
try:
|
||||
data = sys.stdin.read()
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == errno.EAGAIN:
|
||||
return
|
||||
raise
|
||||
if not data:
|
||||
# End of input. The reader is level triggered, so leaving it in place
|
||||
# would have the loop call this again and again for the same EOF.
|
||||
asyncio.get_running_loop().remove_reader(sys.stdin)
|
||||
return
|
||||
inqueue.put_nowait(data)
|
||||
|
||||
|
||||
async def _print(data):
|
||||
bailout = False
|
||||
if not isinstance(data, str):
|
||||
bailout = True
|
||||
@@ -50,7 +61,7 @@ def _print(data):
|
||||
raise Exception(data)
|
||||
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
tcattr = termios.tcgetattr(sys.stdin)
|
||||
newtcattr = tcattr
|
||||
# TODO(jbjohnso): add our exit handler
|
||||
@@ -72,10 +83,16 @@ def main():
|
||||
|
||||
sol = console.Console(bmc=sys.argv[1], userid=sys.argv[2],
|
||||
password=passwd, iohandler=_print, force=True)
|
||||
inputthread = threading.Thread(target=_doinput, args=(sol,))
|
||||
inputthread.daemon = True
|
||||
inputthread.start()
|
||||
sol.main_loop()
|
||||
await sol.connect()
|
||||
inqueue = asyncio.Queue()
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.add_reader(sys.stdin, _got_input, inqueue)
|
||||
try:
|
||||
# gather rather than a detached task: a send that fails has to
|
||||
# reach the caller instead of being collected in silence.
|
||||
await asyncio.gather(sol.main_loop(), _feed_input(sol, inqueue))
|
||||
finally:
|
||||
loop.remove_reader(sys.stdin)
|
||||
|
||||
except Exception:
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
@@ -85,4 +102,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
sys.exit(asyncio.run(main()))
|
||||
|
||||
@@ -17,54 +17,51 @@ it isn't conceived as a general utility to actually use, just help developers
|
||||
understand how the ipmi_command class workes.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
from aiohmi.ipmi import command
|
||||
|
||||
|
||||
def docommand(args, result, ipmisession):
|
||||
async def docommand(args, ipmicmd):
|
||||
command = args[0]
|
||||
args = args[1:]
|
||||
print("Logged into %s" % ipmisession.bmc)
|
||||
if 'error' in result:
|
||||
print(result['error'])
|
||||
return
|
||||
print("Logged into %s" % ipmicmd.bmc)
|
||||
if command == 'power':
|
||||
if args:
|
||||
print(ipmisession.set_power(args[0], wait=True))
|
||||
print(await ipmicmd.set_power(args[0], wait=True))
|
||||
else:
|
||||
value = ipmisession.get_power()
|
||||
print("%s: %s" % (ipmisession.bmc, value['powerstate']))
|
||||
value = await ipmicmd.get_power()
|
||||
print("%s: %s" % (ipmicmd.bmc, value['powerstate']))
|
||||
elif command == 'bootdev':
|
||||
if args:
|
||||
print(ipmisession.set_bootdev(args[0]))
|
||||
print(await ipmicmd.set_bootdev(args[0]))
|
||||
else:
|
||||
print(ipmisession.get_bootdev())
|
||||
print(await ipmicmd.get_bootdev())
|
||||
elif command == 'sensors':
|
||||
for reading in ipmisession.get_sensor_data():
|
||||
async for reading in ipmicmd.get_sensor_data():
|
||||
print(reading)
|
||||
elif command == 'health':
|
||||
print(ipmisession.get_health())
|
||||
print(await ipmicmd.get_health())
|
||||
elif command == 'inventory':
|
||||
for item in ipmisession.get_inventory():
|
||||
async for item in ipmicmd.get_inventory():
|
||||
print(item)
|
||||
elif command == 'leds':
|
||||
for led in ipmisession.get_leds():
|
||||
async for led in ipmicmd.get_leds():
|
||||
print(led)
|
||||
elif command == 'graphical':
|
||||
print(ipmisession.get_graphical_console())
|
||||
print(await ipmicmd.get_graphical_console())
|
||||
elif command == 'net':
|
||||
print(ipmisession.get_net_configuration())
|
||||
print(await ipmicmd.get_net_configuration())
|
||||
elif command == 'raw':
|
||||
print(ipmisession.raw_command(
|
||||
print(await ipmicmd.raw_command(
|
||||
netfn=int(args[0]),
|
||||
command=int(args[1]),
|
||||
data=map(lambda x: int(x, 16), args[2:])))
|
||||
data=[int(x, 16) for x in args[2:]]))
|
||||
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
if (len(sys.argv) < 3) or 'IPMIPASSWORD' not in os.environ:
|
||||
print("Usage:")
|
||||
print(" IPMIPASSWORD=password %s bmc username <cmd> <optarg>" %
|
||||
@@ -73,20 +70,14 @@ def main():
|
||||
|
||||
password = os.environ['IPMIPASSWORD']
|
||||
os.environ['IPMIPASSWORD'] = ""
|
||||
bmc = sys.argv[1]
|
||||
bmcs = sys.argv[1].split(',')
|
||||
userid = sys.argv[2]
|
||||
|
||||
bmcs = bmc.split(',')
|
||||
ipmicmd = None
|
||||
for bmc in bmcs:
|
||||
# NOTE(etingof): is it right to have `ipmicmd` overridden?
|
||||
ipmicmd = command.Command(
|
||||
bmc=bmc, userid=userid, password=password,
|
||||
onlogon=functools.partial(docommand, sys.argv[3:]))
|
||||
|
||||
if ipmicmd:
|
||||
ipmicmd.eventloop()
|
||||
ipmicmd = await command.Command.create(
|
||||
bmc=bmc, userid=userid, password=password)
|
||||
await docommand(sys.argv[3:], ipmicmd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
sys.exit(asyncio.run(main()))
|
||||
|
||||
@@ -15,6 +15,7 @@ control a VM
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
import threading
|
||||
|
||||
@@ -38,8 +39,11 @@ def stream_callback(stream, events, console):
|
||||
data = console.stream.recv(1024)
|
||||
except Exception:
|
||||
return
|
||||
if console.sol:
|
||||
console.sol.send_data(data)
|
||||
if console.sol and console.asyncloop:
|
||||
# libvirt calls this from its own event thread, and asyncio objects
|
||||
# are not thread safe, so hand the send to the loop that owns them.
|
||||
asyncio.run_coroutine_threadsafe(console.sol.send_data(data),
|
||||
console.asyncloop)
|
||||
|
||||
|
||||
class LibvirtBmc(bmc.Bmc):
|
||||
@@ -53,6 +57,7 @@ class LibvirtBmc(bmc.Bmc):
|
||||
self.domain = self.conn.lookupByName(domain)
|
||||
self.state = self.domain.state(0)
|
||||
self.stream = None
|
||||
self.asyncloop = None
|
||||
self.run_console = False
|
||||
self.conn.domainEventRegister(lifecycle_callback, self)
|
||||
self.sol_thread = None
|
||||
@@ -106,24 +111,63 @@ class LibvirtBmc(bmc.Bmc):
|
||||
|
||||
return self.run_console
|
||||
|
||||
def activate_payload(self, request, session):
|
||||
super(LibvirtBmc, self).activate_payload(request, session)
|
||||
async def activate_payload(self, request, session):
|
||||
# captured for stream_callback, which runs on a libvirt thread
|
||||
self.asyncloop = asyncio.get_running_loop()
|
||||
if self.sol_thread is not None and self.sol_thread.is_alive():
|
||||
# The thread from the previous console has not come back out of
|
||||
# virEventRunDefaultImpl. A second one would run a second event
|
||||
# loop against the same stream, and setting run_console below
|
||||
# would revive the first one when it finally wakes.
|
||||
return await session.send_ipmi_response(code=0x80)
|
||||
self.sol_thread = None
|
||||
wasactive = self.activated
|
||||
await super(LibvirtBmc, self).activate_payload(request, session)
|
||||
if wasactive or not self.activated:
|
||||
# the base handler refused: no io handler, or the domain is not
|
||||
# running, so activated stayed false; or a console was already up,
|
||||
# in which case activated was true before we asked and the thread
|
||||
# for it is already running. Either way there is nothing to start.
|
||||
return
|
||||
self.run_console = True
|
||||
self.sol_thread = threading.Thread(target=self.loop)
|
||||
# virEventRunDefaultImpl can wait for an event that never comes, so
|
||||
# this thread has no reliable end of its own
|
||||
self.sol_thread.daemon = True
|
||||
self.sol_thread.start()
|
||||
|
||||
def deactivate_payload(self, request, session):
|
||||
self.run_console = False
|
||||
self.sol_thread.join()
|
||||
super(LibvirtBmc, self).deactivate_payload(request, session)
|
||||
async def deactivate_payload(self, request, session):
|
||||
if self.activated and self.sol_thread:
|
||||
self.run_console = False
|
||||
# The thread only notices that after virEventRunDefaultImpl
|
||||
# returns, which is documented as possibly never. Waiting on the
|
||||
# event loop would stall every other session, so wait off it, and
|
||||
# briefly: this is only to clear the state promptly in the normal
|
||||
# case. A thread that outlives the wait stays owned here, and
|
||||
# activate_payload refuses to start another until it is gone.
|
||||
await asyncio.get_running_loop().run_in_executor(
|
||||
None, self.sol_thread.join, 1)
|
||||
if not self.sol_thread.is_alive():
|
||||
self.sol_thread = None
|
||||
await super(LibvirtBmc, self).deactivate_payload(request, session)
|
||||
|
||||
def iohandler(self, data):
|
||||
async def iohandler(self, data):
|
||||
if self.stream:
|
||||
self.stream.send(data)
|
||||
|
||||
def loop(self):
|
||||
while self.check_console():
|
||||
libvirt.virEventRunDefaultImpl()
|
||||
# virEventRunDefaultImpl waits for an event, and an idle domain can go
|
||||
# a long time without producing one. Give it a reason to return, or
|
||||
# the loop never reconsiders check_console and the thread cannot be
|
||||
# stopped at all: measured as never waking without this, and returning
|
||||
# at once with it.
|
||||
timer = libvirt.virEventAddTimeout(500, lambda *args: None, None)
|
||||
try:
|
||||
while self.check_console():
|
||||
libvirt.virEventRunDefaultImpl()
|
||||
finally:
|
||||
if timer >= 0:
|
||||
libvirt.virEventRemoveTimeout(timer)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -154,7 +198,7 @@ def main():
|
||||
hypervisor=args.hypervisor,
|
||||
domain=args.domain,
|
||||
port=args.port)
|
||||
mybmc.listen()
|
||||
asyncio.run(mybmc.listen())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -60,48 +60,48 @@ class Bmc(serversession.IpmiServer):
|
||||
def is_active(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def activate_payload(self, request, session):
|
||||
async def activate_payload(self, request, session):
|
||||
if self.iohandler is None:
|
||||
session.send_ipmi_response(code=0x81)
|
||||
await session.send_ipmi_response(code=0x81)
|
||||
elif not self.is_active():
|
||||
session.send_ipmi_response(code=0x81)
|
||||
await session.send_ipmi_response(code=0x81)
|
||||
elif self.activated:
|
||||
session.send_ipmi_response(code=0x80)
|
||||
await session.send_ipmi_response(code=0x80)
|
||||
else:
|
||||
self.activated = True
|
||||
solport = list(struct.unpack('BB', struct.pack('!H', self.port)))
|
||||
session.send_ipmi_response(
|
||||
await session.send_ipmi_response(
|
||||
data=[0, 0, 0, 0, 1, 0, 1, 0] + solport + [0xff, 0xff])
|
||||
self.sol = console.ServerConsole(session, self.iohandler)
|
||||
|
||||
def deactivate_payload(self, request, session):
|
||||
async def deactivate_payload(self, request, session):
|
||||
if self.iohandler is None:
|
||||
session.send_ipmi_response(code=0x81)
|
||||
await session.send_ipmi_response(code=0x81)
|
||||
elif not self.activated:
|
||||
session.send_ipmi_response(code=0x80)
|
||||
await session.send_ipmi_response(code=0x80)
|
||||
else:
|
||||
session.send_ipmi_response()
|
||||
await session.send_ipmi_response()
|
||||
self.sol.close()
|
||||
self.activated = False
|
||||
self.sol = None
|
||||
|
||||
@staticmethod
|
||||
def handle_missing_command(session):
|
||||
session.send_ipmi_response(code=0xc1)
|
||||
async def handle_missing_command(session):
|
||||
await session.send_ipmi_response(code=0xc1)
|
||||
|
||||
def get_chassis_status(self, session):
|
||||
async def get_chassis_status(self, session):
|
||||
try:
|
||||
powerstate = self.get_power_state()
|
||||
except NotImplementedError:
|
||||
return session.send_ipmi_response(code=0xc1)
|
||||
return await session.send_ipmi_response(code=0xc1)
|
||||
if powerstate in ipmicommand.power_states:
|
||||
powerstate = ipmicommand.power_states[powerstate]
|
||||
if powerstate not in (0, 1):
|
||||
raise Exception('BMC implementation mistake')
|
||||
statusdata = [powerstate, 0, 0]
|
||||
session.send_ipmi_response(data=statusdata)
|
||||
await session.send_ipmi_response(data=statusdata)
|
||||
|
||||
def control_chassis(self, request, session):
|
||||
async def control_chassis(self, request, session):
|
||||
rc = 0
|
||||
try:
|
||||
directive = request['data'][0]
|
||||
@@ -120,79 +120,81 @@ class Bmc(serversession.IpmiServer):
|
||||
rc = self.power_shutdown()
|
||||
if rc is None:
|
||||
rc = 0
|
||||
session.send_ipmi_response(code=rc)
|
||||
await session.send_ipmi_response(code=rc)
|
||||
except NotImplementedError:
|
||||
session.send_ipmi_response(code=0xcc)
|
||||
await session.send_ipmi_response(code=0xcc)
|
||||
|
||||
def get_boot_device(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_system_boot_options(self, request, session):
|
||||
async def get_system_boot_options(self, request, session):
|
||||
if request['data'][0] == 5: # boot flags
|
||||
try:
|
||||
bootdevice = self.get_boot_device()
|
||||
except NotImplementedError:
|
||||
session.send_ipmi_response(data=[1, 5, 0, 0, 0, 0, 0])
|
||||
# nothing more to say, and bootdevice below would be unbound
|
||||
return await session.send_ipmi_response(
|
||||
data=[1, 5, 0, 0, 0, 0, 0])
|
||||
if (type(bootdevice) != int
|
||||
and bootdevice in ipmicommand.boot_devices):
|
||||
bootdevice = ipmicommand.boot_devices[bootdevice]
|
||||
paramdata = [1, 5, 0b10000000, bootdevice, 0, 0, 0]
|
||||
return session.send_ipmi_response(data=paramdata)
|
||||
return await session.send_ipmi_response(data=paramdata)
|
||||
else:
|
||||
session.send_ipmi_response(code=0x80)
|
||||
await session.send_ipmi_response(code=0x80)
|
||||
|
||||
def set_boot_device(self, bootdevice):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_system_boot_options(self, request, session):
|
||||
async def set_system_boot_options(self, request, session):
|
||||
if request['data'][0] in (0, 3, 4):
|
||||
# for now, just smile and nod at boot flag bit clearing
|
||||
# implementing it is a burden and implementing it does more to
|
||||
# confuse users than serve a useful purpose
|
||||
session.send_ipmi_response()
|
||||
await session.send_ipmi_response()
|
||||
elif request['data'][0] == 5:
|
||||
bootdevice = (request['data'][2] >> 2) & 0b1111
|
||||
try:
|
||||
bootdevice = ipmicommand.boot_devices[bootdevice]
|
||||
except KeyError:
|
||||
session.send_ipmi_response(code=0xcc)
|
||||
await session.send_ipmi_response(code=0xcc)
|
||||
return
|
||||
self.set_boot_device(bootdevice)
|
||||
session.send_ipmi_response()
|
||||
await session.send_ipmi_response()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_raw_request(self, request, session):
|
||||
async def handle_raw_request(self, request, session):
|
||||
try:
|
||||
if request['netfn'] == 6:
|
||||
if request['command'] == 1: # get device id
|
||||
return self.send_device_id(session)
|
||||
return await self.send_device_id(session)
|
||||
elif request['command'] == 2: # cold reset
|
||||
return session.send_ipmi_response(code=self.cold_reset())
|
||||
return await session.send_ipmi_response(code=self.cold_reset())
|
||||
elif request['command'] == 0x37: # get system guid
|
||||
guid = self.get_system_guid()
|
||||
return session.send_ipmi_response(code=0x00, data=guid.bytes_le)
|
||||
return await session.send_ipmi_response(code=0x00, data=guid.bytes_le)
|
||||
elif request['command'] == 0x48: # activate payload
|
||||
return self.activate_payload(request, session)
|
||||
return await self.activate_payload(request, session)
|
||||
elif request['command'] == 0x49: # deactivate payload
|
||||
return self.deactivate_payload(request, session)
|
||||
return await self.deactivate_payload(request, session)
|
||||
elif request['netfn'] == 0:
|
||||
if request['command'] == 1: # get chassis status
|
||||
return self.get_chassis_status(session)
|
||||
return await self.get_chassis_status(session)
|
||||
elif request['command'] == 2: # chassis control
|
||||
return self.control_chassis(request, session)
|
||||
return await self.control_chassis(request, session)
|
||||
elif request['command'] == 8: # set boot options
|
||||
return self.set_system_boot_options(request, session)
|
||||
return await self.set_system_boot_options(request, session)
|
||||
elif request['command'] == 9: # get boot options
|
||||
return self.get_system_boot_options(request, session)
|
||||
session.send_ipmi_response(code=0xc1)
|
||||
return await self.get_system_boot_options(request, session)
|
||||
await session.send_ipmi_response(code=0xc1)
|
||||
except NotImplementedError:
|
||||
session.send_ipmi_response(code=0xc1)
|
||||
await session.send_ipmi_response(code=0xc1)
|
||||
except Exception:
|
||||
session._send_ipmi_net_payload(code=0xff)
|
||||
await session._send_ipmi_net_payload(code=0xff)
|
||||
traceback.print_exc()
|
||||
|
||||
@classmethod
|
||||
def listen(cls, timeout=30):
|
||||
async def listen(self, timeout=30):
|
||||
await self.bind()
|
||||
while True:
|
||||
ipmisession.Session.wait_for_rsp(timeout)
|
||||
await ipmisession.Session.wait_for_rsp(timeout)
|
||||
|
||||
@@ -19,7 +19,6 @@ from itertools import chain
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
|
||||
import asyncio
|
||||
import aiohmi.constants as const
|
||||
@@ -107,19 +106,20 @@ def _cidr_to_mask(prefix):
|
||||
return struct.pack('>I', 2 ** prefix - 1 << (32 - prefix))
|
||||
|
||||
|
||||
class Housekeeper(threading.Thread):
|
||||
"""A Maintenance thread for housekeeping
|
||||
def start_housekeeping():
|
||||
"""Run aiohmi's recurring work on the current event loop
|
||||
|
||||
Long lived use of aiohmi may warrant some recurring asynchronous behavior.
|
||||
This stock thread provides a simple minimal context for these housekeeping
|
||||
tasks to run in. To use, do 'aiohmi.ipmi.command.Maintenance().start()'
|
||||
and from that point forward, aiohmi should execute any needed ongoing
|
||||
tasks automatically as needed. This is an alternative to calling
|
||||
wait_for_rsp or eventloop in a thread of the callers design.
|
||||
Long lived use of aiohmi may warrant some recurring asynchronous
|
||||
behaviour. Call this once, from the loop the sessions belong to, and
|
||||
aiohmi will service them as needed. Cancel the returned task to stop.
|
||||
This is an alternative to calling wait_for_rsp or eventloop from the
|
||||
caller's own code.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
Command.eventloop()
|
||||
# The loop is fetched first so that calling this without one raises
|
||||
# before the coroutine exists, rather than leaving it unawaited.
|
||||
loop = asyncio.get_running_loop()
|
||||
return loop.create_task(Command.eventloop())
|
||||
|
||||
|
||||
class Command(object):
|
||||
@@ -209,7 +209,12 @@ class Command(object):
|
||||
@classmethod
|
||||
async def eventloop(cls):
|
||||
while True:
|
||||
await session.Session.wait_for_rsp()
|
||||
# A ceiling rather than no timeout at all: with nothing waiting or
|
||||
# being kept alive, wait_for_rsp has nothing to wait on and returns
|
||||
# at once, which would make this a busy loop. Sessions still shorten
|
||||
# it to their own deadlines, and an arriving packet still ends the
|
||||
# wait early.
|
||||
await session.Session.wait_for_rsp(timeout=session.MAX_IDLE)
|
||||
|
||||
@classmethod
|
||||
async def wait_for_rsp(cls, timeout):
|
||||
|
||||
@@ -73,6 +73,9 @@ class Console(object):
|
||||
password = self.password
|
||||
port = self.port
|
||||
kg = self.kg
|
||||
# Session defines an async __new__, so the constructor call is a
|
||||
# coroutine, which pyrefly does not model.
|
||||
# pyrefly: ignore[not-async]
|
||||
self.ipmi_session = await session.Session(
|
||||
bmc=bmc, userid=userid, password=password, port=port, kg=kg)
|
||||
# induce one iteration of the loop, now that we would be
|
||||
@@ -194,12 +197,12 @@ class Console(object):
|
||||
else:
|
||||
self.pendingoutput[-1] += data
|
||||
|
||||
def _got_cons_input(self, handle):
|
||||
async def _got_cons_input(self, handle):
|
||||
"""Callback for handle events detected by ipmi session"""
|
||||
|
||||
self._addpendingdata(handle.read())
|
||||
if not self.awaitingack:
|
||||
self._sendpendingoutput()
|
||||
await self._sendpendingoutput()
|
||||
|
||||
async def close(self):
|
||||
"""Shut down an SOL session"""
|
||||
@@ -301,7 +304,7 @@ class Console(object):
|
||||
async def send_payload(self, payload, payload_type=1, retry=True,
|
||||
needskeepalive=False):
|
||||
while not (self.connected or self.broken):
|
||||
session.Session.wait_for_rsp(timeout=10)
|
||||
await session.Session.wait_for_rsp(timeout=10)
|
||||
if self.ipmi_session is None or not self.ipmi_session.logged:
|
||||
await self._print_error('Session no longer connected')
|
||||
raise exc.IpmiException('Session no longer connected')
|
||||
@@ -415,7 +418,7 @@ class Console(object):
|
||||
# sooner than timeout suggests is evidently a big deal
|
||||
await self.send_payload(payload=self.lastpayload, retry=False)
|
||||
|
||||
def main_loop(self):
|
||||
async def main_loop(self):
|
||||
"""Process all events until no more sessions exist.
|
||||
|
||||
If a caller is a simple little utility, provide a function to
|
||||
@@ -428,7 +431,7 @@ class Console(object):
|
||||
# TODO(jbjohnso): wait_for_rsp is not returning a true value for our
|
||||
# own session
|
||||
while (1):
|
||||
session.Session.wait_for_rsp(timeout=600)
|
||||
await session.Session.wait_for_rsp(timeout=600)
|
||||
|
||||
|
||||
class ServerConsole(Console):
|
||||
@@ -460,8 +463,6 @@ class ServerConsole(Console):
|
||||
self.maxoutcount = 256
|
||||
self.poweredon = True
|
||||
|
||||
session.Session.wait_for_rsp(0)
|
||||
|
||||
async def _got_sol_payload(self, payload):
|
||||
"""SOL payload callback"""
|
||||
|
||||
@@ -506,7 +507,7 @@ class ServerConsole(Console):
|
||||
# and might be hard to decide what to do in the context of
|
||||
# retry situation
|
||||
try:
|
||||
self.send_payload(ackpayload, retry=False)
|
||||
await self.send_payload(ackpayload, retry=False)
|
||||
except exc.IpmiException:
|
||||
# if the session is broken, then close the SOL session
|
||||
self.close()
|
||||
@@ -524,7 +525,7 @@ class ServerConsole(Console):
|
||||
else:
|
||||
self.pendingoutput = [newtext] + self.pendingoutput
|
||||
# self._sendpendingoutput() checks len(self._sendpendingoutput)
|
||||
self._sendpendingoutput()
|
||||
await self._sendpendingoutput()
|
||||
elif ackseq != 0 and self.awaitingack:
|
||||
# if an ack packet came in, but did not match what we
|
||||
# expected, retry our payload now.
|
||||
@@ -534,16 +535,19 @@ class ServerConsole(Console):
|
||||
# try to mitigate by avoiding overeager retries
|
||||
# occasional retry of a packet
|
||||
# sooner than timeout suggests is evidently a big deal
|
||||
self.send_payload(payload=self.lastpayload)
|
||||
await self.send_payload(payload=self.lastpayload)
|
||||
|
||||
def send_payload(self, payload, payload_type=1, retry=True,
|
||||
needskeepalive=False):
|
||||
async def send_payload(self, payload, payload_type=1, retry=True,
|
||||
needskeepalive=False):
|
||||
while not (self.connected or self.broken):
|
||||
session.Session.wait_for_rsp(timeout=10)
|
||||
self.ipmi_session.send_payload(payload,
|
||||
payload_type=payload_type,
|
||||
retry=retry,
|
||||
needskeepalive=needskeepalive)
|
||||
await session.Session.wait_for_rsp(timeout=10)
|
||||
# retry is not passed on: a ServerSession has no retry timer, it
|
||||
# never runs Session.__init__ and its _timedout does nothing, so
|
||||
# asking for one only reaches for a timeout attribute it lacks.
|
||||
await self.ipmi_session.send_payload(payload,
|
||||
payload_type=payload_type,
|
||||
retry=False,
|
||||
needskeepalive=needskeepalive)
|
||||
|
||||
def close(self):
|
||||
"""Shut down an SOL session"""
|
||||
|
||||
@@ -86,19 +86,28 @@ class ServerSession(ipmisession.Session):
|
||||
ipmisession.Session.bmc_handlers[clientaddr] = {bmc.port: self}
|
||||
else:
|
||||
ipmisession.Session.bmc_handlers[clientaddr][bmc.port] = self
|
||||
response = self.create_open_session_response(bytearray(request))
|
||||
self.send_payload(response,
|
||||
constants.payload_types['rmcpplusopenresponse'],
|
||||
retry=False)
|
||||
self.initrequest = request
|
||||
|
||||
def _got_rmcp_openrequest(self, data):
|
||||
async def send_open_session_response(self):
|
||||
"""Answer the open session request this was created for
|
||||
|
||||
Sending is a coroutine now, so it cannot happen in __init__ where the
|
||||
conversation used to begin.
|
||||
"""
|
||||
response = self.create_open_session_response(
|
||||
bytearray(self.initrequest))
|
||||
await self.send_payload(response,
|
||||
constants.payload_types['rmcpplusopenresponse'],
|
||||
retry=False)
|
||||
|
||||
async def _got_rmcp_openrequest(self, data):
|
||||
response = self.create_open_session_response(
|
||||
struct.pack('B' * len(data), *data))
|
||||
self.send_payload(response,
|
||||
constants.payload_types['rmcpplusopenresponse'],
|
||||
retry=False)
|
||||
await self.send_payload(response,
|
||||
constants.payload_types['rmcpplusopenresponse'],
|
||||
retry=False)
|
||||
|
||||
def _got_rakp1(self, data):
|
||||
async def _got_rakp1(self, data):
|
||||
clienttag = data[0]
|
||||
self.Rm = data[8:24]
|
||||
self.rolem = data[24]
|
||||
@@ -130,14 +139,14 @@ class ServerSession(ipmisession.Session):
|
||||
# a human
|
||||
newmessage = (bytearray([clienttag, 0, 0, 0]) + self.clientsessionid
|
||||
+ self.Rc + uuidbytes + authcode)
|
||||
self.send_payload(newmessage, constants.payload_types['rakp2'],
|
||||
retry=False)
|
||||
await self.send_payload(newmessage, constants.payload_types['rakp2'],
|
||||
retry=False)
|
||||
|
||||
def _got_rakp2(self, data):
|
||||
async def _got_rakp2(self, data):
|
||||
# stub, server should not think about rakp2
|
||||
pass
|
||||
|
||||
def _got_rakp3(self, data):
|
||||
async def _got_rakp3(self, data):
|
||||
# for now drop rakp3 with bad authcode
|
||||
# respond correctly a TODO(jjohnson2), since Kg being used
|
||||
# yet incorrect is a scenario why rakp3 could be bad
|
||||
@@ -165,9 +174,9 @@ class ServerSession(ipmisession.Session):
|
||||
return
|
||||
self.localsid = struct.unpack('<I', self.managedsessionid)[0]
|
||||
self.ipmicallback = self.handle_client_request
|
||||
self._send_rakp4(clienttag, 0)
|
||||
await self._send_rakp4(clienttag, 0)
|
||||
|
||||
def handle_client_request(self, request):
|
||||
async def handle_client_request(self, request):
|
||||
if request['netfn'] == 6 and request['command'] == 0x3b:
|
||||
pendingpriv = request['data'][0]
|
||||
returncode = 0
|
||||
@@ -176,37 +185,37 @@ class ServerSession(ipmisession.Session):
|
||||
returncode = 0x81
|
||||
else:
|
||||
self.clientpriv = request['data'][0]
|
||||
self._send_ipmi_net_payload(code=returncode,
|
||||
data=[self.clientpriv])
|
||||
await self._send_ipmi_net_payload(code=returncode,
|
||||
data=[self.clientpriv])
|
||||
elif request['netfn'] == 6 and request['command'] == 0x3c:
|
||||
self.send_ipmi_response()
|
||||
await self.send_ipmi_response()
|
||||
self.close_server_session()
|
||||
else:
|
||||
self.bmc.handle_raw_request(request, self)
|
||||
await self.bmc.handle_raw_request(request, self)
|
||||
|
||||
def close_server_session(self):
|
||||
pass
|
||||
|
||||
def _send_rakp4(self, tagvalue, statuscode):
|
||||
async def _send_rakp4(self, tagvalue, statuscode):
|
||||
payload = bytearray(
|
||||
[tagvalue, statuscode, 0, 0]) + self.clientsessionid
|
||||
hmacdata = self.Rm + self.managedsessionid + self.uuiddata
|
||||
hmacdata = struct.pack('%dB' % len(hmacdata), *hmacdata)
|
||||
authdata = hmac.new(self.sik, hmacdata, hashlib.sha1).digest()[:12]
|
||||
payload += authdata
|
||||
self.send_payload(payload, constants.payload_types['rakp4'],
|
||||
retry=False)
|
||||
await self.send_payload(payload, constants.payload_types['rakp4'],
|
||||
retry=False)
|
||||
self.confalgo = 'aes'
|
||||
self.integrityalgo = 'sha1'
|
||||
self.sequencenumber = 1
|
||||
self.sessionid = struct.unpack(
|
||||
'<I', struct.pack('4B', *self.clientsessionid))[0]
|
||||
|
||||
def _got_rakp4(self, data):
|
||||
async def _got_rakp4(self, data):
|
||||
# stub, server should not think about rakp4
|
||||
pass
|
||||
|
||||
def _timedout(self):
|
||||
async def _timedout(self):
|
||||
"""Expire a client session after a period of inactivity
|
||||
|
||||
After the session inactivity timeout, this invalidate the client
|
||||
@@ -223,10 +232,10 @@ class ServerSession(ipmisession.Session):
|
||||
"""
|
||||
pass
|
||||
|
||||
def send_ipmi_response(self, data=[], code=0):
|
||||
self._send_ipmi_net_payload(data=data, code=code)
|
||||
async def send_ipmi_response(self, data=[], code=0):
|
||||
await self._send_ipmi_net_payload(data=data, code=code)
|
||||
|
||||
def logout(self):
|
||||
async def logout(self, sessionok=True):
|
||||
pass
|
||||
|
||||
|
||||
@@ -272,9 +281,20 @@ class IpmiServer(object):
|
||||
self.kg = None
|
||||
self.timeout = 60
|
||||
self.port = port
|
||||
addrinfo = socket.getaddrinfo(address, port, 0,
|
||||
socket.SOCK_DGRAM)[0]
|
||||
self.serversocket = ipmisession.Session._assignsocket(addrinfo)
|
||||
self.addrinfo = socket.getaddrinfo(address, port, 0,
|
||||
socket.SOCK_DGRAM)[0]
|
||||
self.serversocket = None
|
||||
|
||||
async def bind(self):
|
||||
"""Open the socket this server listens on
|
||||
|
||||
Deferred from __init__ because assigning a socket is a coroutine.
|
||||
listen() does it, so a caller that uses listen() need not care.
|
||||
"""
|
||||
if self.serversocket is not None:
|
||||
return
|
||||
self.serversocket = await ipmisession.Session._assignsocket(
|
||||
self.addrinfo)
|
||||
ipmisession.Session.bmc_handlers[self.serversocket] = {0: self}
|
||||
|
||||
def send_auth_cap(self, myaddr, mylun, clientaddr, clientlun, clientseq,
|
||||
@@ -290,10 +310,10 @@ class IpmiServer(object):
|
||||
header.append(ipmisession._checksum(*bodydata))
|
||||
ipmisession._io_sendto(self.serversocket, header, sockaddr)
|
||||
|
||||
def process_pktqueue(self):
|
||||
async def process_pktqueue(self):
|
||||
while self.pktqueue:
|
||||
pkt = self.pktqueue.popleft()
|
||||
self.sessionless_data(pkt[0], pkt[1])
|
||||
await self.sessionless_data(pkt[0], pkt[1])
|
||||
|
||||
def send_cipher_suites(self, myaddr, mylun, clientaddr, clientlun,
|
||||
clientseq, data, sockaddr):
|
||||
@@ -316,7 +336,7 @@ class IpmiServer(object):
|
||||
pkt = header + ipmihdr + rq
|
||||
ipmisession._io_sendto(self.serversocket, pkt, sockaddr)
|
||||
|
||||
def sessionless_data(self, data, sockaddr):
|
||||
async def sessionless_data(self, data, sockaddr):
|
||||
"""Examines unsolocited packet and decides appropriate action.
|
||||
|
||||
For a listening IpmiServer, a packet without an active session
|
||||
@@ -335,9 +355,10 @@ class IpmiServer(object):
|
||||
if payloadtype not in (0, 16):
|
||||
return
|
||||
if payloadtype == 16: # new session to handle conversation
|
||||
ServerSession(self.authdata, self.kg, sockaddr,
|
||||
self.serversocket, data[16:], self.uuid,
|
||||
bmc=self)
|
||||
newsession = ServerSession(self.authdata, self.kg, sockaddr,
|
||||
self.serversocket, data[16:],
|
||||
self.uuid, bmc=self)
|
||||
await newsession.send_open_session_response()
|
||||
return
|
||||
# ditch two byte, because ipmi2 header is two
|
||||
# bytes longer than ipmi1 (payload type added, payload length 2).
|
||||
@@ -379,17 +400,17 @@ class IpmiServer(object):
|
||||
except AttributeError:
|
||||
self.kg = kg
|
||||
|
||||
def send_device_id(self, session):
|
||||
async def send_device_id(self, session):
|
||||
response = [self.deviceid, self.revision, self.firmwaremajor,
|
||||
self.firmwareminor, self.ipmiversion,
|
||||
self.additionaldevices]
|
||||
response += struct.unpack('4B', struct.pack('<I', self.mfgid))
|
||||
response += struct.unpack('4B', struct.pack('<I', self.prodid))
|
||||
session.send_ipmi_response(data=response)
|
||||
await session.send_ipmi_response(data=response)
|
||||
|
||||
def handle_raw_request(self, request, session):
|
||||
async def handle_raw_request(self, request, session):
|
||||
# per table 5-2, completion code 0xc1 is 'unrecognized'
|
||||
session.send_ipmi_response(code=0xc1)
|
||||
await session.send_ipmi_response(code=0xc1)
|
||||
|
||||
def logout(self):
|
||||
async def logout(self, sessionok=True):
|
||||
pass
|
||||
|
||||
@@ -1354,6 +1354,9 @@ class Session(object):
|
||||
# deregister
|
||||
continue
|
||||
if callable(cmd):
|
||||
# registered callables are coroutine functions,
|
||||
# but the registry itself is untyped
|
||||
# pyrefly: ignore[not-async]
|
||||
await cmd()
|
||||
continue
|
||||
keptalive = True
|
||||
|
||||
@@ -206,15 +206,15 @@ class OEMHandler(generic.OEMHandler):
|
||||
retcfg[opt]['sortid'] = self.fwo[opt]['sortid']
|
||||
return retcfg
|
||||
|
||||
def set_system_configuration(self, changeset, fishclient):
|
||||
async def set_system_configuration(self, changeset, fishclient):
|
||||
if not self.fwc:
|
||||
self.fwc = config.LenovoFirmwareConfig(self, useipmi=False)
|
||||
fetchimm = False
|
||||
if not self.fwo or util._monotonic_time() - self.fwovintage > 30:
|
||||
try:
|
||||
self.fwo = self.fwc.get_fw_options(fetchimm=fetchimm)
|
||||
self.fwo = await self.fwc.get_fw_options(fetchimm=fetchimm)
|
||||
except config.Unsupported:
|
||||
return super(OEMHandler, self).set_system_configuration(
|
||||
return await super(OEMHandler, self).set_system_configuration(
|
||||
changeset, fishclient)
|
||||
self.fwovintage = util._monotonic_time()
|
||||
for key in list(changeset):
|
||||
@@ -231,7 +231,7 @@ class OEMHandler(generic.OEMHandler):
|
||||
found = True
|
||||
if not found and not fetchimm:
|
||||
fetchimm = True
|
||||
self.fwo = self.fwc.get_fw_options(fetchimm=fetchimm)
|
||||
self.fwo = await self.fwc.get_fw_options(fetchimm=fetchimm)
|
||||
if key in self.fwo:
|
||||
continue
|
||||
else:
|
||||
@@ -254,7 +254,7 @@ class OEMHandler(generic.OEMHandler):
|
||||
self.merge_changeset(changeset)
|
||||
if changeset:
|
||||
try:
|
||||
self.fwc.set_fw_options(self.fwo)
|
||||
await self.fwc.set_fw_options(self.fwo)
|
||||
finally:
|
||||
self.fwo = None
|
||||
self.fwovintage = 0
|
||||
|
||||
@@ -357,7 +357,7 @@ class WebConnection:
|
||||
return rsp, status
|
||||
|
||||
async def grab_response_with_status(self, url, data=None, referer=None,
|
||||
headers=None, method=None, expect_type=None):
|
||||
headers=None, method=None, expect_type=None, allow_redirects=True):
|
||||
if not headers:
|
||||
headers = self.stdheaders.copy()
|
||||
else:
|
||||
@@ -378,6 +378,7 @@ class WebConnection:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
elif data is not None:
|
||||
kwargs['data'] = data
|
||||
kwargs['allow_redirects'] = allow_redirects
|
||||
async with thefunc(url, headers=headers, ssl=self.ssl, **kwargs) as rsp:
|
||||
if rsp.status >= 200 and rsp.status < 300:
|
||||
if expect_type == 'json':
|
||||
|
||||
@@ -113,5 +113,5 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]]}
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
asyncio.run(testr.config(sys.argv[2]))
|
||||
|
||||
|
||||
@@ -357,4 +357,4 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]] }
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
asyncio.run(testr.config(sys.argv[2]))
|
||||
|
||||
@@ -38,6 +38,10 @@ def fromstring(inputdata):
|
||||
# The measures above should filter out the risky facets of xml
|
||||
# We don't need sophisticated feature support
|
||||
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
|
||||
if not isinstance(inputdata, bytes):
|
||||
# lxml refuses a str that declares an encoding, and the SMM declares
|
||||
# one when answering a login
|
||||
inputdata = inputdata.encode('utf8')
|
||||
return etree.fromstring(inputdata, parser=parser)
|
||||
|
||||
def fixuuid(baduuid):
|
||||
@@ -49,6 +53,20 @@ def fixuuid(baduuid):
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).lower()
|
||||
|
||||
|
||||
async def _post(wc, url, data, headers=None):
|
||||
if headers is None:
|
||||
headers = wc.stdheaders.copy()
|
||||
if data:
|
||||
# What WebConnection.request used to fill in, and what the SMM
|
||||
# expects of its /data endpoints. grab_response_with_status adds
|
||||
# nothing, so aiohttp would label a str body text/plain instead.
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
body, _, _ = await wc.grab_response_with_status(
|
||||
url, data, headers=headers, method='POST', allow_redirects=False)
|
||||
return body
|
||||
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
is_enclosure = True
|
||||
devname = 'SMM'
|
||||
@@ -62,7 +80,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
uuid = fixuuid(uuid[0])
|
||||
self.info['uuid'] = uuid
|
||||
|
||||
def _webconfigrules(self, wc):
|
||||
async def _webconfigrules(self, wc):
|
||||
rules = []
|
||||
for rule in self.ruleset.split(','):
|
||||
if '=' not in rule:
|
||||
@@ -80,19 +98,16 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
rules.append('passwordReuseCheckNum:' + value)
|
||||
if rules:
|
||||
apirequest = 'set={0}'.format(','.join(rules))
|
||||
wc.request('POST', '/data', apirequest)
|
||||
wc.getresponse().read()
|
||||
await _post(wc, '/data', apirequest)
|
||||
|
||||
async def _webconfignet(self, wc, nodename):
|
||||
cfg = self.configmanager
|
||||
if 'service:lenovo-smm2' in self.info.get('services', []):
|
||||
# need to enable ipmi for now..
|
||||
wc.request('POST', '/data', 'set=DoCmd(0x06,0x40,0x01,0x82,0x84)')
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
wc.request('POST', '/data', 'set=DoCmd(0x06,0x40,0x01,0x42,0x44)')
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
await _post(wc, '/data',
|
||||
'set=DoCmd(0x06,0x40,0x01,0x82,0x84)')
|
||||
await _post(wc, '/data',
|
||||
'set=DoCmd(0x06,0x40,0x01,0x42,0x44)')
|
||||
cd = cfg.get_node_attributes(
|
||||
nodename, ['hardwaremanagement.manager'])
|
||||
smmip = cd.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None)
|
||||
@@ -103,9 +118,8 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
smmip = smmip[-1][0]
|
||||
if smmip and ':' in smmip:
|
||||
raise exc.NotImplementedException('IPv6 not supported')
|
||||
wc.request('POST', '/data', 'get=hostname')
|
||||
rsp = wc.getresponse()
|
||||
rspdata = fromstring(util.stringify(rsp.read()))
|
||||
rspdata = fromstring(util.stringify(
|
||||
await _post(wc, '/data', 'get=hostname')))
|
||||
currip = rspdata.find('netConfig').find('ifConfigEntries').find(
|
||||
'ifConfig').find('v4IPAddr').text
|
||||
if currip == smmip:
|
||||
@@ -116,9 +130,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
gateway = netconfig.get('ipv4_gateway', None)
|
||||
if gateway:
|
||||
setdata += ',v4Gateway:{0}'.format(gateway)
|
||||
wc.request('POST', '/data', setdata)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
rspdata = util.stringify(await _post(wc, '/data', setdata))
|
||||
if '<statusCode>0' not in rspdata:
|
||||
raise Exception("Error configuring SMM Network")
|
||||
return
|
||||
@@ -128,48 +140,45 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
await cfg.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
|
||||
def _webconfigcreds(self, username, password):
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
async def _webconfigcreds(self, username, password):
|
||||
ip, port = await self.get_web_port_and_ip()
|
||||
wc = webclient.WebConnection(ip, port, verifycallback=self.validate_cert)
|
||||
wc.connect()
|
||||
authdata = { # start by trying factory defaults
|
||||
'user': 'USERID',
|
||||
'password': 'PASSW0RD',
|
||||
}
|
||||
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
rspdata = util.stringify(
|
||||
await _post(wc, '/data/login', urlencode(authdata), headers))
|
||||
if 'authResult>0' not in rspdata:
|
||||
# default credentials are refused, try with the actual
|
||||
authdata['user'] = username
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
rspdata = util.stringify(
|
||||
await _post(wc, '/data/login', urlencode(authdata), headers))
|
||||
if 'renew_account' in rspdata:
|
||||
tmppassword = 'Tmp42' + password[5:]
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
wc.request('POST', '/data/changepwd', 'oripwd={0}&newpwd={1}'.format(password, tmppassword))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read().decode('utf8')
|
||||
rspdata = await _post(
|
||||
wc, '/data/changepwd',
|
||||
'oripwd={0}&newpwd={1}'.format(password, tmppassword))
|
||||
rspdata = rspdata.decode('utf8')
|
||||
bdata = 'user={0}&password={1}'.format(username, tmppassword)
|
||||
wc.request('POST', '/data/login', bdata, headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read().decode('utf8')
|
||||
rspdata = await _post(wc, '/data/login', bdata, headers)
|
||||
rspdata = rspdata.decode('utf8')
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
rules = 'set=passwordChangeInterval:0,passwordReuseCheckNum:0'
|
||||
wc.request('POST', '/data', rules)
|
||||
wc.getresponse().read()
|
||||
wc.request('POST', '/data/changepwd', 'oripwd={0}&newpwd={1}'.format(tmppassword, password))
|
||||
wc.getresponse().read()
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
await _post(wc, '/data', rules)
|
||||
await _post(
|
||||
wc, '/data/changepwd',
|
||||
'oripwd={0}&newpwd={1}'.format(tmppassword, password))
|
||||
rspdata = util.stringify(
|
||||
await _post(wc, '/data/login', urlencode(authdata),
|
||||
headers))
|
||||
if 'authResult>0' not in rspdata:
|
||||
raise Exception('Unknown username/password on SMM')
|
||||
tokens = fromstring(rspdata)
|
||||
@@ -181,29 +190,24 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
wc.request('POST', '/data/changepwd', urlencode(passwdchange))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = await _post(
|
||||
wc, '/data/changepwd', urlencode(passwdchange))
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
rspdata = util.stringify(
|
||||
await _post(wc, '/data/login', urlencode(authdata), headers))
|
||||
if 'authResult>0' in rspdata:
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
if username == 'USERID':
|
||||
return wc
|
||||
wc.request('POST', '/data', 'set=user(2,1,{0},511,,4,15,0)'.format(username))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
wc.request('POST', '/data/logout')
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = await _post(
|
||||
wc, '/data',
|
||||
'set=user(2,1,{0},511,,4,15,0)'.format(username))
|
||||
rspdata = await _post(wc, '/data/logout', None)
|
||||
authdata['user'] = username
|
||||
wc.request('POST', '/data/login', urlencode(authdata, headers))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = await _post(
|
||||
wc, '/data/login', urlencode(authdata), headers)
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
@@ -245,8 +249,8 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
raise Exception('Using the default password is no longer supported')
|
||||
else:
|
||||
# Switch to full web based configuration, to mitigate risks with the SMM
|
||||
wc = self._webconfigcreds(username, passwd)
|
||||
self._webconfigrules(wc)
|
||||
wc = await self._webconfigcreds(username, passwd)
|
||||
await self._webconfigrules(wc)
|
||||
await self._webconfignet(wc, nodename)
|
||||
|
||||
|
||||
|
||||
@@ -246,4 +246,4 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]] }
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
asyncio.run(testr.config(sys.argv[2]))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import codecs
|
||||
import confluent.discovery.handlers.redfishbmc as redfishbmc
|
||||
import confluent.util as util
|
||||
@@ -90,7 +91,7 @@ class NodeHandler(redfishbmc.NodeHandler):
|
||||
|
||||
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
async def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
@@ -102,7 +103,7 @@ def remote_nodecfg(nodename, cfm):
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
await nh.config(nodename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -112,5 +113,5 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]]}
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
asyncio.run(testr.config(sys.argv[2]))
|
||||
|
||||
|
||||
@@ -480,4 +480,4 @@ from pprint import pprint
|
||||
if __name__ == '__main__':
|
||||
def printit(rsp):
|
||||
print(repr(rsp))
|
||||
snoop(pprint)
|
||||
asyncio.run(snoop(pprint))
|
||||
|
||||
@@ -1004,4 +1004,4 @@ async def consider_discover(info, packet, sock, cfg, reqview, nodeguess, addr=No
|
||||
if __name__ == '__main__':
|
||||
def testsnoop(info):
|
||||
print(repr(info))
|
||||
snoop(testsnoop)
|
||||
asyncio.run(snoop(testsnoop))
|
||||
|
||||
@@ -698,4 +698,4 @@ async def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
if __name__ == '__main__':
|
||||
def testsnoop(a):
|
||||
print(repr(a))
|
||||
snoop(testsnoop)
|
||||
asyncio.run(snoop(testsnoop))
|
||||
|
||||
@@ -411,7 +411,6 @@ if __name__ == '__main__':
|
||||
# a quick one-shot test, args are switch and snmpv1 string for now
|
||||
# (should do three argument form for snmpv3 test
|
||||
import sys
|
||||
_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True))
|
||||
asyncio.run(_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True)))
|
||||
print(repr(_neighdata))
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ class TsmConsole(conapi.Console):
|
||||
self.nodeconfig = config
|
||||
self.connected = False
|
||||
self.recvr = None
|
||||
self.clisess = None
|
||||
|
||||
|
||||
async def recvdata(self):
|
||||
@@ -108,17 +109,23 @@ class TsmConsole(conapi.Console):
|
||||
kv = util.TLSCertVerifier(
|
||||
self.nodeconfig, self.node, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
try:
|
||||
rc = rcmd.Command(self.origbmc, self.username,
|
||||
self.password,
|
||||
verifycallback=kv)
|
||||
await rc.await_redirect()
|
||||
rc = await rcmd.Command.create(self.origbmc, self.username,
|
||||
self.password,
|
||||
verifycallback=kv)
|
||||
rcoem = await rc.oem()
|
||||
wc = await rcoem.get_wc()
|
||||
except Exception as e:
|
||||
raise cexc.TargetEndpointUnreachable(str(e))
|
||||
self.ssl = CustomVerifier(kv)
|
||||
self.clisess = aiohttp.ClientSession(cookie_jar=rc.oem.wc.cookies)
|
||||
self.ws = await self.clisess.ws_connect(
|
||||
'wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rc.oem.csrftok),
|
||||
ssl=self.ssl)
|
||||
self.clisess = aiohttp.ClientSession(cookie_jar=wc.cookies)
|
||||
try:
|
||||
self.ws = await self.clisess.ws_connect(
|
||||
'wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rcoem.csrftok),
|
||||
ssl=self.ssl)
|
||||
except Exception:
|
||||
await self.clisess.close()
|
||||
self.clisess = None
|
||||
raise
|
||||
self.connected = True
|
||||
self.recvr = tasks.spawn_task(self.recvdata())
|
||||
return
|
||||
@@ -136,6 +143,10 @@ class TsmConsole(conapi.Console):
|
||||
self.recvr = None
|
||||
if self.ws:
|
||||
await self.ws.close()
|
||||
self.ws = None
|
||||
if self.clisess:
|
||||
await self.clisess.close()
|
||||
self.clisess = None
|
||||
self.connected = False
|
||||
self.datacallback = None
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class PDUClient(object):
|
||||
raise exc.TargetEndpointBadCredentials()
|
||||
|
||||
|
||||
def logout(self):
|
||||
async def logout(self):
|
||||
self.wc.grab_response('/logout_wait.htm')
|
||||
|
||||
async def get_outlet(self, outlet):
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#TODO: ASYNC asyncio conversion
|
||||
import asyncio
|
||||
import base64
|
||||
import confluent.util as util
|
||||
import confluent.messages as msg
|
||||
import aiohmi.util.webclient as wc
|
||||
import http.client as httplib
|
||||
import http.cookies as Cookie
|
||||
import re
|
||||
import hashlib
|
||||
import json
|
||||
@@ -55,86 +56,93 @@ def answer_challenge(username, password, data):
|
||||
return {'sessionKey': skey.decode('utf8'), 'szResponse': rsp.decode('utf8'), 'szResponseValue': s2rsp.decode('utf8')}
|
||||
|
||||
|
||||
import http.client as httplib
|
||||
import http.cookies as Cookie
|
||||
# carried over verbatim from the transport these replace
|
||||
_fixedheaders = {
|
||||
'Host': 'pdu.cluster.net',
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Connection': 'close',
|
||||
'Referer': 'http://pdu.cluster.net/setting_admin.htm',
|
||||
}
|
||||
|
||||
# Delta PDU webserver always closes connection,
|
||||
# replace conditionals with always close
|
||||
|
||||
class SecureWebConnection(wc.WebConnection):
|
||||
"""The https transport, on the loop: the cert verifier needs it"""
|
||||
|
||||
def __init__(self, host, verifycallback):
|
||||
wc.WebConnection.__init__(self, host, 443,
|
||||
verifycallback=verifycallback)
|
||||
for header in _fixedheaders:
|
||||
self.set_header(header, _fixedheaders[header])
|
||||
|
||||
async def grab_response(self, url, body=None, method=None):
|
||||
# unfollowed, as http.client left them
|
||||
rsp, status, _ = await self.grab_response_with_status(
|
||||
url, body, method=method, allow_redirects=False)
|
||||
return rsp, status
|
||||
|
||||
|
||||
# the device closes the connection whatever it said it would do
|
||||
class WebResponse(httplib.HTTPResponse):
|
||||
def _check_close(self):
|
||||
return True
|
||||
|
||||
class WebConnection(wc.WebConnection):
|
||||
|
||||
class PlainWebConnection(httplib.HTTPConnection):
|
||||
"""The http transport, for a PDU with TLS not turned on
|
||||
|
||||
http.client, because the socket wants a smaller segment size set before
|
||||
connect and aiohttp only takes a socket factory from 3.12 on, which is
|
||||
newer than most distros ship. Requests run in a thread, which is safe
|
||||
here: no cert to verify, and the credentials arrive already read.
|
||||
"""
|
||||
|
||||
response_class = WebResponse
|
||||
def __init__(self, host, secure, verifycallback):
|
||||
if secure:
|
||||
port = 443
|
||||
else:
|
||||
port = 80
|
||||
wc.WebConnection.__init__(self, host, port, verifycallback=verifycallback)
|
||||
self.secure = secure
|
||||
|
||||
def __init__(self, host):
|
||||
# http.client would otherwise wait forever
|
||||
httplib.HTTPConnection.__init__(self, host, 80, timeout=60)
|
||||
self.stdheaders = dict(_fixedheaders)
|
||||
self.cookies = {}
|
||||
|
||||
async def connect(self):
|
||||
if self.secure:
|
||||
return super(WebConnection, self).connect()
|
||||
addrinfo = (await asyncio.get_running_loop().getaddrinfo(self.host, self.port))[0]
|
||||
# workaround problems of too large mtu, moderately frequent occurance
|
||||
# in this space
|
||||
def connect(self):
|
||||
addrinfo = socket.getaddrinfo(self.host, self.port, 0,
|
||||
socket.SOCK_STREAM)[0]
|
||||
plainsock = socket.socket(addrinfo[0])
|
||||
plainsock.settimeout(self.mytimeout)
|
||||
plainsock.settimeout(self.timeout)
|
||||
try:
|
||||
# workaround problems of too large mtu, moderately frequent
|
||||
# occurance in this space
|
||||
plainsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
|
||||
except socket.error:
|
||||
pass
|
||||
plainsock.connect(addrinfo[4])
|
||||
self.sock = plainsock
|
||||
|
||||
def getresponse(self):
|
||||
try:
|
||||
rsp = super(WebConnection, self).getresponse()
|
||||
try:
|
||||
hdrs = [x.split(':', 1) for x in rsp.msg.headers]
|
||||
except AttributeError:
|
||||
hdrs = rsp.msg.items()
|
||||
for hdr in hdrs:
|
||||
if hdr[0] == 'Set-Cookie':
|
||||
c = Cookie.BaseCookie(hdr[1])
|
||||
for k in c:
|
||||
self.cookies[k] = c[k].value
|
||||
except httplib.BadStatusLine:
|
||||
self.broken = True
|
||||
raise
|
||||
return rsp
|
||||
|
||||
def request(self, method, url, body=None):
|
||||
headers = {}
|
||||
def _blocking_grab(self, url, body, method):
|
||||
headers = dict(self.stdheaders)
|
||||
if body:
|
||||
headers['Content-Length'] = len(body)
|
||||
cookies = []
|
||||
for cookie in self.cookies:
|
||||
cookies.append('{0}={1}'.format(cookie, self.cookies[cookie]))
|
||||
headers['Cookie'] = ';'.join(cookies)
|
||||
headers['Host'] = 'pdu.cluster.net'
|
||||
headers['Accept'] = '*/*'
|
||||
headers['Accept-Language'] = 'en-US,en;q=0.9'
|
||||
headers['Connection'] = 'close'
|
||||
headers['Referer'] = 'http://pdu.cluster.net/setting_admin.htm'
|
||||
return super(WebConnection, self).request(method, url, body, headers)
|
||||
if self.cookies:
|
||||
headers['Cookie'] = ';'.join(
|
||||
['{0}={1}'.format(k, self.cookies[k]) for k in self.cookies])
|
||||
self.request(method, url, body, headers)
|
||||
rsp = self.getresponse()
|
||||
for hdr, value in rsp.getheaders():
|
||||
if hdr.lower() == 'set-cookie':
|
||||
cookie = Cookie.BaseCookie(value)
|
||||
for key in cookie:
|
||||
self.cookies[key] = cookie[key].value
|
||||
return rsp.read(), rsp.status
|
||||
|
||||
def grab_response(self, url, body=None, method=None):
|
||||
async def grab_response(self, url, body=None, method=None):
|
||||
if method is None:
|
||||
method = 'GET' if body is None else 'POST'
|
||||
if body:
|
||||
self.request(method, url, body)
|
||||
else:
|
||||
self.request(method, url)
|
||||
rsp = self.getresponse()
|
||||
body = rsp.read()
|
||||
return body, rsp.status
|
||||
return await asyncio.to_thread(self._blocking_grab, url, body, method)
|
||||
|
||||
|
||||
_sensors_by_node = {}
|
||||
def get_sensor_data(element, node, configmanager):
|
||||
async def get_sensor_data(element, node, configmanager):
|
||||
category, name = element[-2:]
|
||||
justnames = False
|
||||
readings = []
|
||||
@@ -149,9 +157,9 @@ def get_sensor_data(element, node, configmanager):
|
||||
if not sn or sn[1] < time.time():
|
||||
gc = PDUClient(node, configmanager)
|
||||
try:
|
||||
sdata = gc.get_sensor_data()
|
||||
sdata = await gc.get_sensor_data()
|
||||
finally:
|
||||
gc.logout()
|
||||
await gc.logout()
|
||||
_sensors_by_node[node] = [sdata, time.time() + 1]
|
||||
sn = _sensors_by_node.get(node, None)
|
||||
for outlet in sn[0]:
|
||||
@@ -183,6 +191,11 @@ class PDUClient(object):
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
# set by connect(); only login() reads it without connecting first
|
||||
return self._wc
|
||||
|
||||
async def connect(self):
|
||||
# logging in is a coroutine, so this cannot be the wc property
|
||||
if self._wc:
|
||||
return self._wc
|
||||
targcfg = self.configmanager.get_node_attributes(self.node,
|
||||
@@ -197,18 +210,18 @@ class PDUClient(object):
|
||||
verifier = util.TLSCertVerifier(
|
||||
self.configmanager, self.node, 'pubkeys.tls_hardwaremanager')
|
||||
try:
|
||||
self._wc = WebConnection(target, secure=True, verifycallback=verifier.verify_cert)
|
||||
self.login(self.configmanager)
|
||||
self._wc = SecureWebConnection(target, verifier.verify_cert)
|
||||
await self.login(self.configmanager)
|
||||
except socket.error:
|
||||
pkey = self.configmanager.get_node_attributes(self.node, 'pubkeys.tls_hardwaremanager')
|
||||
pkey = pkey.get(self.node, {}).get('pubkeys.tls_hardwaremanager', {}).get('value', None)
|
||||
if pkey:
|
||||
raise
|
||||
self._wc = WebConnection(target, secure=False, verifycallback=verifier.verify_cert)
|
||||
self.login(self.configmanager)
|
||||
self._wc = PlainWebConnection(target)
|
||||
await self.login(self.configmanager)
|
||||
return self._wc
|
||||
|
||||
def login(self, configmanager):
|
||||
async def login(self, configmanager):
|
||||
credcfg = configmanager.get_node_attributes(self.node,
|
||||
['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'],
|
||||
@@ -226,7 +239,7 @@ class PDUClient(object):
|
||||
raise Exception('Missing username or password')
|
||||
b64user = base64.b64encode(username.encode('utf8')).decode('utf8')
|
||||
b64pass = base64.b64encode(passwd.encode('utf8')).decode('utf8')
|
||||
rsp = self.wc.grab_response('/config/gateway?page=cgi_authentication&login={}&_dc={}'.format(b64user, int(time.time())))
|
||||
rsp = await self.wc.grab_response('/config/gateway?page=cgi_authentication&login={}&_dc={}'.format(b64user, int(time.time())))
|
||||
rsp = json.loads(sanitize_json(rsp[0]))
|
||||
self.sessid = rsp['data'][0]
|
||||
if rsp['data'][-1] == 'password':
|
||||
@@ -246,22 +259,24 @@ class PDUClient(object):
|
||||
parms['szResponseValue'],
|
||||
int(time.time()),
|
||||
)
|
||||
rsp = self.wc.grab_response(url)
|
||||
rsp = await self.wc.grab_response(url)
|
||||
rsp = json.loads(sanitize_json(rsp[0]))
|
||||
if not rsp['success']:
|
||||
raise Exception('Failed to login to device')
|
||||
rsp = self.wc.grab_response('/config/gateway?page=cgi_checkUserSession&sessionId={}&_dc={}'.format(self.sessid, int(time.time())))
|
||||
rsp = await self.wc.grab_response('/config/gateway?page=cgi_checkUserSession&sessionId={}&_dc={}'.format(self.sessid, int(time.time())))
|
||||
|
||||
def do_request(self, suburl):
|
||||
wc = self.wc
|
||||
async def do_request(self, suburl):
|
||||
wc = await self.connect()
|
||||
url = '/config/gateway?page={}&sessionId={}&_dc={}'.format(suburl, self.sessid, int(time.time()))
|
||||
return wc.grab_response(url)
|
||||
return await wc.grab_response(url)
|
||||
|
||||
def logout(self):
|
||||
self.do_request('cgi_logout')
|
||||
async def logout(self):
|
||||
if self.sessid:
|
||||
await self.do_request('cgi_logout')
|
||||
self.sessid = None
|
||||
|
||||
def get_outlet(self, outlet):
|
||||
rsp = self.do_request('cgi_pdu_outlets')
|
||||
async def get_outlet(self, outlet):
|
||||
rsp = await self.do_request('cgi_pdu_outlets')
|
||||
data = sanitize_json(rsp[0])
|
||||
data = json.loads(data)
|
||||
data = data['data'][0]
|
||||
@@ -271,8 +286,8 @@ class PDUClient(object):
|
||||
return 'on' if outdata[3] else 'off'
|
||||
return
|
||||
|
||||
def get_sensor_data(self):
|
||||
rsp = self.do_request('cgi_pdu_outlets')
|
||||
async def get_sensor_data(self):
|
||||
rsp = await self.do_request('cgi_pdu_outlets')
|
||||
data = sanitize_json(rsp[0])
|
||||
data = json.loads(data)
|
||||
data = data['data'][0]
|
||||
@@ -293,8 +308,9 @@ class PDUClient(object):
|
||||
sdata[outletname] = outsense
|
||||
return sdata
|
||||
|
||||
def set_outlet(self, outlet, state):
|
||||
rsp = self.do_request('cgi_pdu_outlets')
|
||||
async def set_outlet(self, outlet, state):
|
||||
wc = await self.connect()
|
||||
rsp = await self.do_request('cgi_pdu_outlets')
|
||||
data = sanitize_json(rsp[0])
|
||||
data = json.loads(data)
|
||||
data = data['data'][0]
|
||||
@@ -303,14 +319,14 @@ class PDUClient(object):
|
||||
outdata = outdata[0]
|
||||
if outdata[0] == outlet:
|
||||
payload = "<SET_OBJECT><OBJECT name='PDU.OutletSystem.Outlet[{}].DelayBefore{}'>0</OBJECT>".format(idx, 'Startup' if state == 'on' else 'Shutdown')
|
||||
rsp = self.wc.grab_response('/config/set_object_mass.xml?sessionId={}'.format(self.sessid), payload)
|
||||
rsp = await wc.grab_response('/config/set_object_mass.xml?sessionId={}'.format(self.sessid), payload)
|
||||
return
|
||||
idx += 1
|
||||
|
||||
async def retrieve(nodes, element, configmanager, inputdata):
|
||||
if element[0] == 'sensors':
|
||||
for node in nodes:
|
||||
for res in get_sensor_data(element, node, configmanager):
|
||||
async for res in get_sensor_data(element, node, configmanager):
|
||||
yield res
|
||||
return
|
||||
elif 'outlets' not in element:
|
||||
|
||||
@@ -481,7 +481,7 @@ async def create(nodes, element, configmanager, inputdata):
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def _selftest():
|
||||
import sys
|
||||
import os
|
||||
myuser = os.environ['PMXUSER']
|
||||
@@ -489,12 +489,16 @@ if __name__ == '__main__':
|
||||
vc = PmxApiClient(sys.argv[1], myuser, mypass, None)
|
||||
vm = sys.argv[2]
|
||||
if sys.argv[3] == 'setboot':
|
||||
vc.set_vm_bootdev(vm, sys.argv[4])
|
||||
vc.get_vm_bootdev(vm)
|
||||
await vc.set_vm_bootdev(vm, sys.argv[4])
|
||||
await vc.get_vm_bootdev(vm)
|
||||
elif sys.argv[3] == 'power':
|
||||
vc.set_vm_power(vm, sys.argv[4])
|
||||
await vc.set_vm_power(vm, sys.argv[4])
|
||||
elif sys.argv[3] == 'getinfo':
|
||||
print(repr(list(vc.get_vm_inventory(vm))))
|
||||
print("Bootdev: " + vc.get_vm_bootdev(vm))
|
||||
print("Power: " + vc.get_vm_power(vm))
|
||||
print(repr([datum async for datum in vc.get_vm_inventory(vm)]))
|
||||
print("Bootdev: " + await vc.get_vm_bootdev(vm))
|
||||
print("Power: " + await vc.get_vm_power(vm))
|
||||
#print("Serial: " + repr(vc.get_vm_serial(vm)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(_selftest())
|
||||
|
||||
@@ -1133,23 +1133,23 @@ class IpmiHandler:
|
||||
return await self._show_all_storage()
|
||||
|
||||
|
||||
def handle_sensors(self):
|
||||
async def handle_sensors(self):
|
||||
if self.element[-1] == '':
|
||||
self.element = self.element[:-1]
|
||||
if len(self.element) < 3:
|
||||
return
|
||||
self.sensorcategory = self.element[2]
|
||||
if self.sensorcategory == 'normalized':
|
||||
return self.read_normalized(self.element[-1])
|
||||
return await self.read_normalized(self.element[-1])
|
||||
# list sensors per category
|
||||
if len(self.element) == 3 and self.element[-2] == 'hardware':
|
||||
if self.sensorcategory == 'leds':
|
||||
return self.list_leds()
|
||||
return self.list_sensors()
|
||||
return await self.list_leds()
|
||||
return await self.list_sensors()
|
||||
elif len(self.element) == 4: # resource requested
|
||||
if self.sensorcategory == 'leds':
|
||||
return self.read_leds(self.element[-1])
|
||||
return self.read_sensors(self.element[-1])
|
||||
return await self.read_leds(self.element[-1])
|
||||
return await self.read_sensors(self.element[-1])
|
||||
|
||||
def match_sensor(self, sensor):
|
||||
if self.sensorcategory == 'all':
|
||||
|
||||
@@ -411,7 +411,7 @@ async def create(nodes, element, configmanager, inputdata):
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def _selftest():
|
||||
import sys
|
||||
import os
|
||||
myuser = os.environ['VMWUSER']
|
||||
@@ -419,12 +419,16 @@ if __name__ == '__main__':
|
||||
vc = VmwApiClient(sys.argv[1], myuser, mypass, None)
|
||||
vm = sys.argv[2]
|
||||
if sys.argv[3] == 'setboot':
|
||||
vc.set_vm_bootdev(vm, sys.argv[4])
|
||||
vc.get_vm_bootdev(vm)
|
||||
await vc.set_vm_bootdev(vm, sys.argv[4])
|
||||
await vc.get_vm_bootdev(vm)
|
||||
elif sys.argv[3] == 'power':
|
||||
vc.set_vm_power(vm, sys.argv[4])
|
||||
await vc.set_vm_power(vm, sys.argv[4])
|
||||
elif sys.argv[3] == 'getinfo':
|
||||
vc.get_vm(vm)
|
||||
print("Bootdev: " + vc.get_vm_bootdev(vm))
|
||||
print("Power: " + vc.get_vm_power(vm))
|
||||
print("Serial: " + repr(vc.get_vm_serial(vm)))
|
||||
await vc.get_vm(vm)
|
||||
print("Bootdev: " + await vc.get_vm_bootdev(vm))
|
||||
print("Power: " + await vc.get_vm_power(vm))
|
||||
print("Serial: " + repr(await vc.get_vm_serial(vm)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(_selftest())
|
||||
|
||||
@@ -156,7 +156,7 @@ async def sessionhdl(connection, authname, skipauth=False, cert=None):
|
||||
return await collective.handle_connection(
|
||||
connection, None, request['collective'], local=True)
|
||||
else:
|
||||
tlvdata.send(
|
||||
await tlvdata.send(
|
||||
connection,
|
||||
{'collective': {
|
||||
'error': 'collective management commands '
|
||||
@@ -288,7 +288,7 @@ async def start_term(authname, cfm, connection, params, path, authdata, skipauth
|
||||
sessionid = elems[5]
|
||||
else:
|
||||
sessionid = None
|
||||
consession = shellserver.ShellSession(
|
||||
consession = await shellserver.ShellSession.create(
|
||||
node=node, configmanager=cfm, username=authname,
|
||||
datacallback=ccons.sendall, skipreplay=skipreplay,
|
||||
sessionid=sessionid, width=params.get('width', 80),
|
||||
|
||||
@@ -271,8 +271,13 @@ def ca_exists():
|
||||
return os.path.exists('/etc/confluent/ssh/ca')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
initialize_root_key(True)
|
||||
async def _selftest():
|
||||
await initialize_root_key(True)
|
||||
if not ca_exists():
|
||||
initialize_ca()
|
||||
print(repr(sign_host_key(open('/etc/ssh/ssh_host_ed25519_key.pub').read(), collective.get_myname())))
|
||||
await initialize_ca()
|
||||
print(repr(await sign_host_key(
|
||||
open('/etc/ssh/ssh_host_ed25519_key.pub').read(), collective.get_myname())))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(_selftest())
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
# Pyrefly configuration for confluent.
|
||||
#
|
||||
# The whole policy is the two error kinds enabled at the bottom of this file:
|
||||
# a coroutine that is never awaited, where the call silently does nothing, and
|
||||
# an await on something that is not awaitable, which raises TypeError as soon
|
||||
# as the line runs. Those are the mistakes the asyncio port can make that no
|
||||
# other check in CI can see. Everything else is off, because a full check
|
||||
# reports several thousand errors on this largely unannotated tree, mostly
|
||||
# attribute lookups it cannot verify.
|
||||
#
|
||||
# The policy lives here rather than in a --only flag on the command line, so
|
||||
# that CI, a local run and the editor integration all behave the same way.
|
||||
#
|
||||
# A baseline file is deliberately not used. Pyrefly matches baselined findings
|
||||
# by file, error kind and column, so a new mistake that happens to land at the
|
||||
# same indentation as an existing one in the same file is silently accepted.
|
||||
|
||||
project-includes = [
|
||||
# Every *.py in the tree, so this matches what ruff and compileall see.
|
||||
".",
|
||||
# Pyrefly walks *.py only, and a glob in this list does not lift that
|
||||
# restriction: it expands the glob and then drops everything without the
|
||||
# extension. So every extensionless tool has to be named. This list is
|
||||
# the one ruff.toml arrives at through extend-include, and is kept in
|
||||
# step with it; anything missing here is skipped without a word.
|
||||
"confluent_client/bin/collate",
|
||||
"confluent_client/bin/confetty",
|
||||
"confluent_client/bin/confluent2ansible",
|
||||
"confluent_client/bin/confluent2dnsmasq",
|
||||
"confluent_client/bin/confluent2hosts",
|
||||
"confluent_client/bin/confluent2lxca",
|
||||
"confluent_client/bin/confluent2xcat",
|
||||
"confluent_client/bin/dir2img",
|
||||
"confluent_client/bin/nodeapply",
|
||||
"confluent_client/bin/nodeattrib",
|
||||
"confluent_client/bin/nodebmcpassword",
|
||||
"confluent_client/bin/nodebmcreset",
|
||||
"confluent_client/bin/nodeboot",
|
||||
"confluent_client/bin/nodecertutil",
|
||||
"confluent_client/bin/nodeconfig",
|
||||
"confluent_client/bin/nodeconsole",
|
||||
"confluent_client/bin/nodedefine",
|
||||
"confluent_client/bin/nodedeploy",
|
||||
"confluent_client/bin/nodediscover",
|
||||
"confluent_client/bin/nodeeventlog",
|
||||
"confluent_client/bin/nodefirmware",
|
||||
"confluent_client/bin/nodegroupattrib",
|
||||
"confluent_client/bin/nodegroupdefine",
|
||||
"confluent_client/bin/nodegrouplist",
|
||||
"confluent_client/bin/nodegroupremove",
|
||||
"confluent_client/bin/nodegrouprename",
|
||||
"confluent_client/bin/nodehealth",
|
||||
"confluent_client/bin/nodeidentify",
|
||||
"confluent_client/bin/nodeinventory",
|
||||
"confluent_client/bin/nodel2traceroute",
|
||||
"confluent_client/bin/nodelicense",
|
||||
"confluent_client/bin/nodelist",
|
||||
"confluent_client/bin/nodemedia",
|
||||
"confluent_client/bin/nodeping",
|
||||
"confluent_client/bin/nodepower",
|
||||
"confluent_client/bin/noderemove",
|
||||
"confluent_client/bin/noderename",
|
||||
"confluent_client/bin/nodereseat",
|
||||
"confluent_client/bin/nodersync",
|
||||
"confluent_client/bin/noderun",
|
||||
"confluent_client/bin/nodesensors",
|
||||
"confluent_client/bin/nodesetboot",
|
||||
"confluent_client/bin/nodeshell",
|
||||
"confluent_client/bin/nodestorage",
|
||||
"confluent_client/bin/nodesupport",
|
||||
"confluent_client/bin/stats",
|
||||
"confluent_client/setup.py.tmpl",
|
||||
"confluent_common/setup.py.tmpl",
|
||||
"confluent_osdeploy/bluefield/bfb-autoinstall",
|
||||
"confluent_osdeploy/bluefield/hostscripts/bfb-autoinstall",
|
||||
"confluent_osdeploy/bluefield/profiles/default/nodedeploy-bfb",
|
||||
"confluent_osdeploy/common/initramfs/opt/confluent/bin/apiclient",
|
||||
"confluent_osdeploy/common/profile/scripts/autoconsole",
|
||||
"confluent_osdeploy/common/profile/scripts/confignet",
|
||||
"confluent_osdeploy/common/profile/scripts/getinstalldisk",
|
||||
"confluent_osdeploy/common/profile/scripts/syncfileclient",
|
||||
"confluent_osdeploy/debian/profiles/default/scripts/confignet",
|
||||
"confluent_osdeploy/el10-diskless/profiles/default/scripts/add_local_repositories",
|
||||
"confluent_osdeploy/el7-diskless/profiles/default/scripts/syncfileclient",
|
||||
"confluent_osdeploy/el7/profiles/default/scripts/add_local_repositories",
|
||||
"confluent_osdeploy/el7/profiles/default/scripts/configbmc",
|
||||
"confluent_osdeploy/el7/profiles/default/scripts/syncfileclient",
|
||||
"confluent_osdeploy/el8-diskless/profiles/default/scripts/add_local_repositories",
|
||||
"confluent_osdeploy/el8/profiles/default/scripts/add_local_repositories",
|
||||
"confluent_osdeploy/el8/profiles/default/scripts/configbmc",
|
||||
"confluent_osdeploy/el9-diskless/profiles/default/scripts/add_local_repositories",
|
||||
"confluent_osdeploy/esxi7/profiles/hypervisor/scripts/getinstalldisk",
|
||||
"confluent_osdeploy/esxi7/profiles/hypervisor/scripts/makeksnet",
|
||||
"confluent_osdeploy/genesis/profiles/default/scripts/configbmc",
|
||||
"confluent_osdeploy/ubuntu22.04/profiles/default/scripts/mergetime",
|
||||
"confluent_server/bin/collective",
|
||||
"confluent_server/bin/confluent",
|
||||
"confluent_server/bin/confluentdbutil",
|
||||
"confluent_server/bin/confluent_selfcheck",
|
||||
"confluent_server/bin/osdeploy",
|
||||
"confluent_server/setup.py.tmpl",
|
||||
"imgutil/imgutil",
|
||||
"misc/filterpasswd",
|
||||
"misc/getipsfromswitchport",
|
||||
"misc/getusbnicaddr",
|
||||
]
|
||||
|
||||
project-excludes = [
|
||||
# A symlink to a unit file that only exists on a deployed system.
|
||||
# Pyrefly refuses to walk the tree at all when it meets this.
|
||||
"confluent_osdeploy/coreos/initramfs/etc/systemd/system/initrd-root-fs.target.requires/confluent-rootfs.service",
|
||||
]
|
||||
|
||||
# project-includes only picks the files to check. Imports are resolved against
|
||||
# search-path, and without these roots pyrefly cannot tell that, for instance,
|
||||
# aiohmi.util.webclient.request is async, and a large share of the findings,
|
||||
# among them everything in the SMM discovery handler, goes unreported. The two
|
||||
# trees both hold a package named confluent but share no module names, so one
|
||||
# search path for each works; that would need revisiting if they ever overlap.
|
||||
search-path = [
|
||||
"confluent_server",
|
||||
"confluent_client",
|
||||
]
|
||||
|
||||
# The oldest interpreter parts of this tree still run on (el8, sles15).
|
||||
python-version = "3.6"
|
||||
|
||||
preset = "off"
|
||||
# Both of these are load bearing rather than decoration. With unannotated
|
||||
# bodies unchecked the check reports nothing at all, since almost nothing here
|
||||
# is annotated, and without inferred return types it misses a good part of the
|
||||
# rest.
|
||||
check-unannotated-defs = true
|
||||
infer-return-types = "checked"
|
||||
|
||||
[errors]
|
||||
unused-coroutine = "error"
|
||||
not-async = "error"
|
||||
Reference in New Issue
Block a user