2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-28 16:20:54 +00:00

Merge remote-tracking branch 'xcat/master'

This commit is contained in:
Jarrod Johnson
2026-09-25 16:14:34 -04:00
3 changed files with 75 additions and 30 deletions
@@ -27,6 +27,28 @@ import aiohmi.constants as const
class OEMHandler(generic.OEMHandler):
usegenericsensors = True
async def supports_expand(self, url):
"""Whether url answers $expand=. with its members inlined.
Only the sensor collection is worth it (668 members, one GET
each otherwise). Firmware that ignores $expand answers with
plain links, so the answer is probed once per collection and
such firmware keeps the per-member reads.
"""
if not url.rstrip('/').endswith('/Sensors'):
return False
if not hasattr(self, '_expandsupport'):
self._expandsupport = {}
if url not in self._expandsupport:
try:
rsp = await self._do_web_request(url + '?$expand=.')
except Exception:
rsp = {}
members = rsp.get('Members', [])
self._expandsupport[url] = bool(members) and all(
'Name' in member for member in members)
return self._expandsupport[url]
async def get_default_sysurl(self):
"""Return the system URL for the first available node.
@@ -86,7 +108,9 @@ class OEMHandler(generic.OEMHandler):
"""Read CPU temperatures from EUREKA BMC sensor endpoints.
Reads BMC{N}CPU0Temp and BMC{N}CPU1Temp sensors from
/redfish/v1/Chassis/1/Sensors/.
/redfish/v1/Chassis/1/Sensors/. Nodes whose BMC is not
currently reporting (HasBMCMetrics false) are skipped, as
their sensors read a meaningless 0.
"""
cputemps = []
for sysurl in self._allsysurls:
@@ -94,27 +118,33 @@ class OEMHandler(generic.OEMHandler):
nodeid = nodeid.replace('Node', '')
if not nodeid.isdigit():
continue
try:
sysinfo = await fishclient._do_web_request(sysurl)
except Exception:
continue
if not sysinfo.get('Oem', {}).get('Megware', {}).get(
'HasBMCMetrics', True):
continue
for cpu in ('CPU0', 'CPU1'):
try:
sensor_url = '/redfish/v1/Chassis/1/Sensors/BMC{}Cpu{}Temp'.format(nodeid, cpu)
sensor_url = '/redfish/v1/Chassis/1/Sensors/BMC{}{}Temp'.format(nodeid, cpu)
sensor = await fishclient._do_web_request(sensor_url)
if sensor and 'Reading' in sensor:
cputemps.append({
'name': 'CPU {} Node {}'.format(cpu, nodeid),
'value': float(sensor['Reading']),
'state_ids': [],
'units': const.SensorUnits.Celsius,
'imprecision': None,
'Name': '{} Node {}'.format(cpu, nodeid),
'ReadingCelsius': float(sensor['Reading']),
})
except Exception:
pass
return cputemps
async def reseat_bay(self, bay):
"""Power cycle a specific node in the EUREKA enclosure.
"""Reseat a specific node in the EUREKA enclosure.
Uses ComputerSystem.Reset with ForceRestart on the target node.
bay=-1 (enclosure-level) is not supported.
Uses ComputerSystem.Reset with the EUREKA specific Reseat type,
which removes all power from the slot, node BMC included.
ForceRestart only restarts the host. bay=-1 (enclosure-level)
is not supported.
"""
if bay == -1:
raise exc.UnsupportedFunctionality(
@@ -122,33 +152,43 @@ class OEMHandler(generic.OEMHandler):
nodeurl = '/redfish/v1/Systems/Node{}'.format(bay)
await self._do_web_request(
nodeurl + '/Actions/ComputerSystem.Reset',
{'ResetType': 'ForceRestart'},
{'ResetType': 'Reseat'},
method='POST')
async def get_health(self, fishclient, verbose=True):
"""Gather health status for the EUREKA chassis and all nodes."""
issues = []
summary = {'badreadings': [], 'health': const.Health.Ok}
def note_issue(name, health, state):
summary['health'] |= health
if verbose:
reading = generic.SensorReading(None, {'name': name})
reading.health = health
reading.states = [state]
summary['badreadings'].append(reading)
try:
chassis = await self._do_web_request('/redfish/v1/Chassis/1')
health = chassis.get('Status', {}).get('Health', 'OK')
if health != 'OK':
issues.append('Chassis health: {}'.format(health))
note_issue('Chassis', generic._healthmap.get(
health, const.Health.Warning), health)
except Exception:
issues.append('Cannot reach chassis health endpoint')
note_issue('Chassis', const.Health.Warning, 'Unreachable')
for sysurl in self._allsysurls:
name = sysurl.rstrip('/').rsplit('/', 1)[-1]
try:
sysinfo = await self._do_web_request(sysurl)
except Exception:
note_issue(name, const.Health.Warning, 'Unreachable')
continue
state = sysinfo.get('Status', {}).get('State', 'Absent')
name = sysurl.rstrip('/').rsplit('/', 1)[-1]
if state == 'Absent':
issues.append('{}: Absent'.format(name))
elif state != 'Enabled':
issues.append('{}: {}'.format(name, state))
health = 0
if issues:
health = 1
return {'badreadings': [], 'health': health}
status = sysinfo.get('Status', {})
state = status.get('State', 'Absent')
health = status.get('Health', 'OK')
if state != 'Enabled':
note_issue(name, const.Health.Warning, state)
elif health != 'OK':
note_issue(name, generic._healthmap.get(
health, const.Health.Warning), health)
return summary
@@ -20,4 +20,5 @@ import aiohmi.redfish.oem.megware.eureka as eureka
async def get_handler(sysinfo, sysurl, webclient, cache, cmd, rootinfo={}):
return await eureka.OEMHandler.create(sysinfo, sysurl, webclient, cache, gpool=cmd._gpool)
return await eureka.OEMHandler.create(sysinfo, sysurl, webclient, cache,
gpool=cmd._gpool, rootinfo=rootinfo)
@@ -21,6 +21,7 @@ sensor readings. It does NOT support IPMI, NIC configuration,
firmware update, or other BMC-specific operations.
"""
import json
import confluent.discovery.handlers.generic as generic
import confluent.util as util
import aiohmi.util.webclient as webclient
@@ -105,16 +106,18 @@ class NodeHandler(generic.NodeHandler):
elif status == 401:
self.trieddefault = True
try:
errinfo = util.json_loads(body)
errinfo = json.loads(body)
for msg in errinfo.get('@Message.ExtendedInfo', []):
if 'PasswordChangeRequired' in msg.get('MessageId', ''):
chgurl = msg.get('MessageArgs', [None])[0]
if chgurl and self.targpass and self.targpass != defpass:
wc.set_basic_credentials(defuser, defpass)
wc.set_header('If-Match', '*')
# If-Match only on the PATCH: wc is handed
# back for every later request
rsp, chgstatus = await wc.grab_json_response_with_status(
chgurl,
{'Password': self.targpass},
headers=dict(wc.stdheaders, **{'If-Match': '*'}),
method='PATCH')
if chgstatus >= 200 and chgstatus < 300:
body, status, headers = \
@@ -226,9 +229,10 @@ class NodeHandler(generic.NodeHandler):
acctdata, acctstatus = \
await wc.grab_json_response_with_status(accturl)
if acctdata.get('UserName') == self.curruser:
wc.set_header('If-Match', '*')
rsp, status = await wc.grab_json_response_with_status(
accturl, authupdate, method='PATCH')
accturl, authupdate,
headers=dict(wc.stdheaders, **{'If-Match': '*'}),
method='PATCH')
if status >= 200 and status < 300:
self.curruser = user
self.currpass = passwd