2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-21 08:33:23 +00:00

Ask a platform which firmware image types it takes

Some will not take an image without being told which kind it is, and the
only way to find out was to attempt an update and read the error, which
writes to the bmc before it gets that far.
This commit is contained in:
Markus Hilger
2026-08-14 19:46:24 +02:00
parent 055434a862
commit dda9b47a51
8 changed files with 88 additions and 9 deletions
+17 -1
View File
@@ -56,7 +56,7 @@ components = ['all']
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][updatestatus][update [--backup] "
"%prog <noderange> [list][updatestatus][updatetypes][update [--backup] "
"[--parameterfile <file>] <file>]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
@@ -71,6 +71,7 @@ argparser.add_option('-m', '--maxnodes', type='int',
(options, args) = argparser.parse_args()
upfile = None
querystatus = False
querytypes = False
try:
noderange = args[0]
if len(args) > 1:
@@ -82,6 +83,8 @@ try:
comps = args[2:]
elif args[1] == 'updatestatus':
querystatus = True
elif args[1] == 'updatetypes':
querytypes = True
else:
comps = args[1:]
components = []
@@ -207,6 +210,17 @@ def show_firmware(session):
','.join(components)))
def show_update_types(session):
global exitcode
for res in session.read(
'/noderange/{0}/inventory/firmware/updatetypes'.format(noderange)):
exitcode |= client.printerror(res)
for node in res.get('databynode', {}):
types = res['databynode'][node].get('types', None)
if types:
print('{0}: {1}'.format(node, ','.join(types)))
try:
session = client.Command()
if querystatus:
@@ -217,6 +231,8 @@ try:
currstat = res['databynode'][node].get('status', None)
if currstat:
print('{}: {}'.format(node, currstat))
elif querytypes:
show_update_types(session)
elif upfile is None:
show_firmware(session)
else:
+17 -5
View File
@@ -3,7 +3,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
## SYNOPSIS
`nodefirmware <noderange> [list][updatestatus][update [--backup] [--parameterfile <file>] <file>]|[<components>]`
`nodefirmware <noderange> [list][updatestatus][updatetypes][update [--backup] [--parameterfile <file>] <file>]|[<components>]`
## DESCRIPTION
@@ -20,6 +20,10 @@ the FPGA version where applicable).
The updatestatus argument will describe the state of firmware updates on the
nodes.
The updatetypes argument will name the kinds of firmware image the platform has
to be told about, for use in a parameter file. Platforms that read the kind of
firmware from the image itself have none to name and say so.
In the update form, it accepts a single file and attempts to update it using
the out of band facilities. Firmware updates can end in one of three states:
@@ -57,13 +61,21 @@ holds, and refuse the update rather than guess. On an AMI MegaRAC that is an
{"OemParameters": {"ImageType": "BIOS"}}
Attempting the update without it names the types the platform accepts in the
error, so there is no need to look them up first. The value is checked against
the same list before anything is uploaded, so a name the platform does not accept
is refused rather than sent.
`nodefirmware <noderange> updatetypes` names the accepted types without
attempting an update. The value is also checked against the same list before
anything is uploaded, so a name the platform does not accept is refused rather
than sent.
## EXAMPLES
* Ask what kinds of firmware image a node has to be told about:
`# nodefirmware n1 updatetypes`
`n1: BMC,BIOS,MB_CPLD,SCM_CPLD,BPB_CPLD,HPM_BMC,HPM_BIOS,HPM_SCP,HPM_BIOS2`
* Update the BIOS on a platform that has to be told:
`# echo '{"OemParameters": {"ImageType": "BIOS"}}' > /tmp/bios.json`
`# nodefirmware n1 update -p /tmp/bios.json /tmp/image.bin`
* Pull firmware from a node:
`# nodefirmware r1`
`r1: IMM: 3.70 (TCOO26H 2016-11-29T05:09:51)`
@@ -1811,6 +1811,15 @@ class Command(object):
oem = await self.oem()
return await oem.get_update_status()
async def get_update_types(self):
"""The kinds of firmware image this platform has to be told about
Only some implementations will not take an image without being told
which kind it is, and those are the ones with an answer here.
"""
oem = await self.oem()
return await oem.get_update_types(self)
async def update_firmware(self, file, data=None, progress=None, bank=None, otherfields=()):
"""Send file to BMC to perform firmware update
@@ -108,14 +108,32 @@ class OEMHandler(generic.OEMHandler):
'PreserveConfiguration': preserve,
}}}, method='PATCH', etag='*')
# What the parameter naming the kind of firmware is called in the action
# info. Builds differ, and the names it takes are the same vocabulary as
# the OemParameters ImageType a multipart push wants.
_imagetypeparams = ('ImageType', 'UpdateComponent')
async def _allowed_imagetypes(self):
actinfo = await self._do_web_request(
'/redfish/v1/UpdateService/SimpleUpdateActionInfo')
try:
actinfo = await self._do_web_request(
'/redfish/v1/UpdateService/SimpleUpdateActionInfo')
except pygexc.PyghmiException:
# A build that does not publish the action info still wants an
# image type, it just cannot say which names it takes
return []
for param in actinfo.get('Parameters', []):
if param.get('Name', None) == 'ImageType':
if param.get('Name', None) in self._imagetypeparams:
return param.get('AllowableValues', [])
return []
async def get_update_types(self, fishclient):
allowed = await self._allowed_imagetypes()
if not allowed:
raise pygexc.UnsupportedFunctionality(
'This bmc has to be told what kind of firmware an image holds '
'but does not publish the names it accepts')
return allowed
async def _checked_imagetype(self, imagetype, filename):
"""Check the image type the parameter file gave against what is accepted.
@@ -1525,6 +1525,16 @@ class OEMHandler(object):
msgs = response.get('Messages', [])
return ';'.join(self.format_message(x) for x in msgs)
async def get_update_types(self, fishclient):
"""The kinds of firmware image this platform has to be told about.
A platform that reads the kind from the image has none to report, which
is not the same as failing to answer.
"""
raise exc.UnsupportedFunctionality(
'This platform does not take a firmware image type, it reads the '
'kind of firmware from the image')
async def update_firmware(self, filename, data=None, progress=None, bank=None, otherfields=()):
# disable cache to make sure we trigger the token renewal logic if needed
usd, upurl, ismultipart = await self.retrieve_firmware_upload_url()
+4
View File
@@ -557,6 +557,10 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'null',
}),
'updatetypes': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'null',
}),
'updates': {
'active': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
@@ -604,6 +604,10 @@ class IpmiHandler:
await self.handle_update()
elif self.element[:3] == ['inventory', 'firmware', 'updatestatus']:
await self.handle_update_status()
elif self.element[:3] == ['inventory', 'firmware', 'updatetypes']:
# Otherwise this falls through to the inventory handler and is read
# as the name of a firmware component
raise self._unsupported()
elif self.element[0] == 'inventory':
await self.handle_inventory()
elif self.element == ['media', 'attach']:
@@ -477,6 +477,8 @@ class IpmiHandler:
await self.handle_update()
elif self.element[:3] == ['inventory', 'firmware', 'updatestatus']:
await self.handle_update_status()
elif self.element[:3] == ['inventory', 'firmware', 'updatetypes']:
await self.handle_update_types()
elif self.element[0] == 'inventory':
await self.handle_inventory()
elif self.element == ['media', 'attach']:
@@ -923,6 +925,10 @@ class IpmiHandler:
status = await self.ipmicmd.get_update_status()
await self.output.put(msg.KeyValueData({'status': status}, self.node))
async def handle_update_types(self):
await self.output.put(msg.KeyValueData(
{'types': await self.ipmicmd.get_update_types()}, self.node))
async def handle_inventory(self):
if self.element[1] == 'firmware':
if len(self.element) == 3: