mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-28 16:20:54 +00:00
Merge pull request #305 from Obihoernchen/fix/eureka-oem
Fix MEGWARE EUREKA chassis support
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user