2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-08-04 00:17:01 +00:00

Add redfish OEM implementation for EUREKA Chassis

This commit is contained in:
gosforthcross
2026-06-30 11:28:45 +02:00
parent cacfce214f
commit 5435acd23e
3 changed files with 177 additions and 0 deletions
@@ -0,0 +1,154 @@
# Copyright 2026 MEGWARE Computer Vertrieb und Service GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
OEM handler for Megware EUREKA chassis Redfish service.
The EUREKA chassis is a software-only Redfish service (not a BMC).
It manages up to 12 nodes with individual BMCs and up to 10 PSUs.
"""
import aiohmi.redfish.oem.generic as generic
import aiohmi.exceptions as exc
import aiohmi.constants as const
class OEMHandler(generic.OEMHandler):
usegenericsensors = True
async def get_default_sysurl(self):
"""Return the system URL for the first available node.
The EUREKA enclosure has up to 12 nodes (Node1-Node12).
Returns the first enabled/inserted node, or the first node overall.
"""
if not self._varsysurl and 'Systems' in self._rootinfo:
systems = self._rootinfo['Systems']['@odata.id']
res = await self.webclient.grab_json_response_with_status(systems)
if res[1] == 401:
raise exc.PyghmiException('Access Denied')
elif res[1] < 200 or res[1] >= 300:
raise exc.PyghmiException(repr(res[0]))
members = res[0].get('Members', [])
if len(members) == 1:
self._varsysurl = members[0]['@odata.id']
elif len(members) > 1:
for member in members:
murlinfo, status = await self.webclient.grab_json_response_with_status(
member['@odata.id'])
if status == 200:
if murlinfo.get('Status', {}).get('State') == 'Enabled':
self._varsysurl = member['@odata.id']
break
if not self._varsysurl:
self._varsysurl = members[0]['@odata.id']
return self._varsysurl
async def get_system_power_watts(self, fishclient):
"""Read node input power from EUREKA sensor endpoints.
The EUREKA chassis reports node power via Node{N}InputPower
sensors under /redfish/v1/Chassis/1/Sensors/ rather than
standard Redfish PowerControl.
"""
totalwatts = 0
gotpower = False
for sysurl in self._allsysurls:
nodeid = sysurl.rstrip('/').rsplit('/', 1)[-1]
nodeid = nodeid.replace('Node', '')
if not nodeid.isdigit():
continue
try:
sensor_url = '/redfish/v1/Chassis/1/Sensors/Node{}InputPower'.format(nodeid)
sensor = await fishclient._do_web_request(sensor_url)
if sensor and 'Reading' in sensor:
totalwatts += float(sensor['Reading'])
gotpower = True
except Exception:
pass
if not gotpower:
raise exc.UnsupportedFunctionality(
'Node power sensors not available')
return totalwatts
async def _get_cpu_temps(self, fishclient):
"""Read CPU temperatures from EUREKA BMC sensor endpoints.
Reads BMC{N}CPU0Temp and BMC{N}CPU1Temp sensors from
/redfish/v1/Chassis/1/Sensors/.
"""
cputemps = []
for sysurl in self._allsysurls:
nodeid = sysurl.rstrip('/').rsplit('/', 1)[-1]
nodeid = nodeid.replace('Node', '')
if not nodeid.isdigit():
continue
for cpu in ('CPU0', 'CPU1'):
try:
sensor_url = '/redfish/v1/Chassis/1/Sensors/BMC{}Cpu{}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,
})
except Exception:
pass
return cputemps
async def reseat_bay(self, bay):
"""Power cycle a specific node in the EUREKA enclosure.
Uses ComputerSystem.Reset with ForceRestart on the target node.
bay=-1 (enclosure-level) is not supported.
"""
if bay == -1:
raise exc.UnsupportedFunctionality(
'Enclosure-level reset is not supported')
nodeurl = '/redfish/v1/Systems/Node{}'.format(bay)
await self._do_web_request(
nodeurl + '/Actions/ComputerSystem.Reset',
{'ResetType': 'ForceRestart'},
method='POST')
async def get_health(self, fishclient, verbose=True):
"""Gather health status for the EUREKA chassis and all nodes."""
issues = []
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))
except Exception:
issues.append('Cannot reach chassis health endpoint')
for sysurl in self._allsysurls:
try:
sysinfo = await self._do_web_request(sysurl)
except Exception:
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}
@@ -0,0 +1,23 @@
# Copyright 2026 MEGWARE Computer Vertrieb und Service GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
OEM handler for Megware EUREKA chassis Redfish service.
"""
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)