2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-21 16:39:32 +00:00

Fix user deletion, media insertion, firmware categories and reseat

Deleting a user gave up after one attempt and reported why the fallback of
blanking the name failed rather than why the delete did. MegaRAC reports a
timeout for a delete that a second ask completes, so retry, check whether the
account went away, and keep the original error.

Attaching media judged a device free by ConnectedVia, which describes how the
device is wired to the host rather than whether anything is in it. Every
device on this bmc reports a fixed value there, so nothing was ever selected
and the attach reported success having done nothing. Judge by whether an image
is loaded, fall back to the properties when an advertised insert action is not
served, and say so when no device would take the image.

The firmware category was passed to the library and ignored, so core,
adapters, disks and misc all returned the same full list. Classify by what
each entry is related to, and answer only for core when a platform says
nothing about where its firmware belongs.

reseat_bay reached for a hardcoded Nvidia action on Chassis_0, so it failed
with a not found for that url instead of saying reseat is unsupported.
This commit is contained in:
Markus Hilger
2026-08-13 18:10:18 +02:00
parent b30ee29ff6
commit 6361fd6578
3 changed files with 77 additions and 18 deletions
+37 -8
View File
@@ -1268,10 +1268,23 @@ class Command(object):
return
fwlist = await self._do_web_request(await self.get_fwinventory())
fwurls = [x['@odata.id'] for x in fwlist.get('Members', [])]
wantcategory = category if category not in (None, 'all') else None
entries = []
async for res in self._do_bulk_requests(fwurls):
res = self._extract_fwinfo(res)
entries.append((self._fwcategory(res[0]), self._extract_fwinfo(res)))
categorised = any(x[0] for x in entries)
for fwcategory, res in entries:
if res[0] is None:
continue
if wantcategory:
if categorised:
if fwcategory != wantcategory:
continue
elif wantcategory != 'core':
# This platform does not say what any of its firmware
# belongs to, and an uncategorised inventory on a bmc is
# system firmware, so it can only answer for 'core'
continue
yield res
def _extract_fwinfo(self, inf):
@@ -1577,32 +1590,48 @@ class Command(object):
if 'Authorization' in self.wc.stdheaders:
del self.wc.stdheaders['Authorization']
return
attached = False
for vmurl in vmurls:
vminfo = await self._do_web_request(vmurl, cache=False)
if vminfo.get('ConnectedVia', None) != 'NotConnected':
# ConnectedVia describes how the device is wired to the host rather
# than whether anything is in it, and some implementations report a
# permanent value there, so judge free by whether an image is loaded
if vminfo.get('Image', None) or vminfo.get('Inserted', False):
continue
inserturl = vminfo.get(
'Actions', {}).get(
'#VirtualMedia.InsertMedia', {}).get('target', None)
if inserturl:
await self._do_web_request(inserturl, {'Image': url})
else:
try:
await self._do_web_request(inserturl, {'Image': url})
attached = True
except (exc.RedfishError, exc.PyghmiException):
# Some implementations advertise the insert action without
# serving it, so fall back to setting the properties
inserturl = None
if not inserturl:
try:
await self._do_web_request(vmurl,
{'Image': url, 'Inserted': True},
'PATCH')
'PATCH', etag='*')
except exc.RedfishError as re:
if re.msgid.endswith(u'PropertyUnknown'):
await self._do_web_request(vmurl, {'Image': url}, 'PATCH')
await self._do_web_request(vmurl, {'Image': url}, 'PATCH',
etag='*')
else:
raise
attached = True
break
if suspendedxauth:
self.wc.stdheaders['X-Auth-Token'] = self.xauthtoken
if 'Authorization' in self.wc.stdheaders:
del self.wc.stdheaders['Authorization']
del self.wc.stdheaders['Authorization']
if not attached:
raise exc.UnsupportedFunctionality(
'No virtual media device on this platform was able to accept '
'the image')
async for res in oem.list_media(self, cache=False):
pass
pass
async def detach_remote_media(self):
oem = await self.oem()
@@ -38,14 +38,25 @@ class OEMHandler(generic.OEMHandler):
self._certverify = webclient.verifycallback
return self
# Auxiliary power cycle, offered by the chassis on the megarac based systems
# that have an aux power domain to cycle
_auxresetaction = '#NvidiaChassis.AuxPowerReset'
async def reseat_bay(self, bay):
if bay != -1:
raise pygexc.UnsupportedFunctionality(
'This is not an enclosure manager')
await self._do_web_request('/redfish/v1/Chassis/Chassis_0/Actions/Oem/NvidiaChassis.AuxPowerReset', {
"ResetType": "AuxPowerCycle"
})
chassiscol = await self._do_web_request('/redfish/v1/Chassis')
for chassis in chassiscol.get('Members', []):
chassisinfo = await self._do_web_request(chassis['@odata.id'])
action = chassisinfo.get('Actions', {}).get('Oem', {}).get(
self._auxresetaction, {}).get('target', None)
if action:
await self._do_web_request(action,
{'ResetType': 'AuxPowerCycle'})
return
raise pygexc.UnsupportedFunctionality(
'Reseat is not supported on this platform')
def format_messages(self, response):
msgs = response.get('Messages', [])
+25 -6
View File
@@ -713,14 +713,33 @@ class OEMHandler(object):
# Blanking the username seems to be the convention
# First, set a bogus password in case the implementation does honor
# blank user, at least render such an account harmless
accinfo = await fishclient._account_url_info_by_id(uid)
if not accinfo:
raise Exception("No such account found")
accounturl = accinfo[0]
delerr = None
# Some implementations take longer to delete an account than they allow
# themselves, and report a timeout for a delete that is really underway
# or that would work on a second ask, so give the delete another go and
# then check whether the account is actually gone before concluding that
# this implementation cannot delete at all
for _ in range(3):
try:
await self._do_web_request(accounturl, method='DELETE')
return True
except Exception as de:
if delerr is None:
delerr = de
await asyncio.sleep(3)
if not await fishclient._account_url_info_by_id(uid):
return True
try:
accinfo = await fishclient._account_url_info_by_id(uid)
if not accinfo:
raise Exception("No such account found")
await self._do_web_request(accinfo[0], method='DELETE')
except Exception: # fall back to old ipmi-like behavior for such implementations
await fishclient.set_user_password(uid, base64.b64encode(os.urandom(15)))
await fishclient.set_user_name(uid, '')
await fishclient.set_user_name(uid, '')
except Exception:
# Report why deleting failed rather than why the fallback failed,
# since the delete is what was asked for
raise delerr
return True
async def set_bootdev(self, bootdev, persist=False, uefiboot=None,