mirror of
https://opendev.org/x/pyghmi
synced 2025-01-27 03:17:50 +00:00
Add OEM extensibility framework to pyghmi redfish
This enables OEM specific override for enhancements beyond the specification when avaible. Change-Id: I6a10f1fa58a425b2abfbeb9c85dd72c3b7d4cbba
This commit is contained in:
parent
a05c044874
commit
f710b1d30a
@ -18,15 +18,16 @@
|
||||
# for redfish compliant endpoints
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import tz
|
||||
import json
|
||||
import os
|
||||
import pyghmi.exceptions as exc
|
||||
import pyghmi.constants as const
|
||||
import pyghmi.util.webclient as webclient
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import pyghmi.exceptions as exc
|
||||
import pyghmi.constants as const
|
||||
import pyghmi.util.webclient as webclient
|
||||
import pyghmi.redfish.oem.lookup as oem
|
||||
from dateutil import tz
|
||||
|
||||
powerstates = {
|
||||
'on': 'On',
|
||||
@ -71,6 +72,7 @@ _healthmap = {
|
||||
'OK': const.Health.Ok,
|
||||
}
|
||||
|
||||
|
||||
def _parse_time(timeval):
|
||||
if timeval is None:
|
||||
return None
|
||||
@ -116,10 +118,11 @@ def _mask_to_cidr(mask):
|
||||
maskn >>= 1
|
||||
return cidr
|
||||
|
||||
|
||||
def _cidr_to_mask(cidr):
|
||||
return socket.inet_ntop(
|
||||
socket.AF_INET, struct.pack(
|
||||
'!I', (2**32-1) ^ (2**(32-cidr)-1)))
|
||||
'!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
|
||||
|
||||
|
||||
class SensorReading(object):
|
||||
@ -132,6 +135,7 @@ class SensorReading(object):
|
||||
self.imprecision = None
|
||||
self.units = None
|
||||
|
||||
|
||||
class Command(object):
|
||||
|
||||
def __init__(self, bmc, userid, password, verifycallback, sysurl=None,
|
||||
@ -149,6 +153,7 @@ class Command(object):
|
||||
self._varresetbmcurl = None
|
||||
self._varupdateservice = None
|
||||
self._varfwinventory = None
|
||||
self._oem = None
|
||||
self._gpool = pool
|
||||
self.wc.set_header('Accept', 'application/json')
|
||||
self.wc.set_header('User-Agent', 'pyghmi')
|
||||
@ -165,10 +170,10 @@ class Command(object):
|
||||
break
|
||||
else:
|
||||
raise exc.PyghmiException(
|
||||
'Specified sysurl not found: '.format(sysurl))
|
||||
'Specified sysurl not found: {0}'.format(sysurl))
|
||||
else:
|
||||
if len(systems) != 1:
|
||||
raise pygexc.PyghmiException(
|
||||
raise exc.PyghmiException(
|
||||
'Multi system manager, sysurl is required parameter')
|
||||
self.sysurl = systems[0]['@odata.id']
|
||||
self.powerurl = self.sysinfo.get('Actions', {}).get(
|
||||
@ -196,15 +201,10 @@ class Command(object):
|
||||
'BMC does not implement extended firmware information')
|
||||
return self._varfwinventory
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def sysinfo(self):
|
||||
return self._do_web_request(self.sysurl)
|
||||
|
||||
|
||||
def get_power(self):
|
||||
currinfo = self._do_web_request(self.sysurl, cache=False)
|
||||
return {'powerstate': str(currinfo['PowerState'].lower())}
|
||||
@ -226,8 +226,8 @@ class Command(object):
|
||||
if reqpowerstate in ('softoff', 'shutdown'):
|
||||
reqpowerstate = 'off'
|
||||
timeout = os.times()[4] + 300
|
||||
while (self.get_power()['powerstate'] != reqpowerstate and
|
||||
os.times()[4] < timeout):
|
||||
while (self.get_power()['powerstate'] != reqpowerstate
|
||||
and os.times()[4] < timeout):
|
||||
time.sleep(1)
|
||||
if self.get_power()['powerstate'] != reqpowerstate:
|
||||
raise exc.PyghmiException(
|
||||
@ -264,7 +264,7 @@ class Command(object):
|
||||
wc = self.wc.dupe()
|
||||
res = wc.grab_json_response_with_status(url, payload,
|
||||
method=method)
|
||||
if res[1] < 200 or res[1] >=300:
|
||||
if res[1] < 200 or res[1] >= 300:
|
||||
raise exc.PyghmiException(res[0])
|
||||
if payload is None and method is None:
|
||||
self._urlcache[url] = {'contents': res[0],
|
||||
@ -288,19 +288,19 @@ class Command(object):
|
||||
elif overridestate == 'Continuous':
|
||||
persistent = True
|
||||
else:
|
||||
raise exc.PyghmiException('Unrecognized Boot state: ' +
|
||||
repr(overridestate))
|
||||
raise exc.PyghmiException('Unrecognized Boot state: '
|
||||
+ repr(overridestate))
|
||||
uefimode = result.get('Boot', {}).get('BootSourceOverrideMode', None)
|
||||
if uefimode == 'UEFI':
|
||||
uefimode = True
|
||||
elif uefimode == 'Legacy':
|
||||
elif uefimode == 'Legacy':
|
||||
uefimode = False
|
||||
else:
|
||||
raise exc.PyghmiException('Unrecognized mode: ' + uefimode)
|
||||
bootdev = result.get('Boot', {}).get('BootSourceOverrideTarget', None)
|
||||
if bootdev not in boot_devices_read:
|
||||
raise exc.PyghmiException('Unrecognized boot target: ' +
|
||||
repr(bootdev))
|
||||
raise exc.PyghmiException('Unrecognized boot target: '
|
||||
+ repr(bootdev))
|
||||
bootdev = boot_devices_read[bootdev]
|
||||
return {'bootdev': bootdev, 'persistent': persistent,
|
||||
'uefimode': uefimode}
|
||||
@ -325,16 +325,17 @@ class Command(object):
|
||||
:returns: dict or True -- If callback is not provided, the response
|
||||
"""
|
||||
reqbootdev = bootdev
|
||||
if (bootdev not in boot_devices_write and
|
||||
bootdev not in boot_devices_read):
|
||||
raise exc.InvalidParameterValue('Unsupported device ' +
|
||||
repr(bootdev))
|
||||
if (bootdev not in boot_devices_write
|
||||
and bootdev not in boot_devices_read):
|
||||
raise exc.InvalidParameterValue('Unsupported device '
|
||||
+ repr(bootdev))
|
||||
bootdev = boot_devices_write.get(bootdev, bootdev)
|
||||
if bootdev == 'None':
|
||||
payload = {'Boot': {'BootSourceOverrideEnabled': 'Disabled'}}
|
||||
else:
|
||||
payload = {'Boot': {
|
||||
'BootSourceOverrideEnabled': 'Continuous' if persist else 'Once',
|
||||
'BootSourceOverrideEnabled': 'Continuous' if persist
|
||||
else 'Once',
|
||||
'BootSourceOverrideTarget': bootdev,
|
||||
}}
|
||||
if uefiboot is not None:
|
||||
@ -347,7 +348,7 @@ class Command(object):
|
||||
def _biosurl(self):
|
||||
if not self._varbiosurl:
|
||||
self._varbiosurl = self.sysinfo.get('Bios', {}).get('@odata.id',
|
||||
None)
|
||||
None)
|
||||
if self._varbiosurl is None:
|
||||
raise exc.UnsupportedFunctionality(
|
||||
'Bios management not detected on this platform')
|
||||
@ -359,7 +360,7 @@ class Command(object):
|
||||
biosinfo = self._do_web_request(self._biosurl)
|
||||
self._varsetbiosurl = biosinfo.get(
|
||||
'@Redfish.Settings', {}).get('SettingsObject', {}).get(
|
||||
'@odata.id', None)
|
||||
'@odata.id', None)
|
||||
if self._varsetbiosurl is None:
|
||||
raise exc.UnsupportedFunctionality('Ability to set BIOS settings '
|
||||
'not detected on this platform')
|
||||
@ -397,7 +398,6 @@ class Command(object):
|
||||
self._varbmcnicurl = lastnicurl
|
||||
return self._varbmcnicurl
|
||||
|
||||
|
||||
@property
|
||||
def _bmcreseturl(self):
|
||||
if not self._varresetbmcurl:
|
||||
@ -502,8 +502,8 @@ class Command(object):
|
||||
ipinfo['Gateway'] = ipv4_gateway
|
||||
if ipv4_configuration.lower() == 'dhcp':
|
||||
patch['DHCPv4'] = {'DHCPEnabled': True}
|
||||
elif (ipv4_configuration == 'static' or
|
||||
'IPv4StaticAddresses' in patch):
|
||||
elif (ipv4_configuration == 'static'
|
||||
or 'IPv4StaticAddresses' in patch):
|
||||
patch['DHCPv4'] = {'DHCPEnabled': False}
|
||||
if patch:
|
||||
self._do_web_request(self._bmcnicurl, patch, 'PATCH')
|
||||
@ -515,8 +515,9 @@ class Command(object):
|
||||
raise exc.PyghmiException('Unable to locate network information')
|
||||
retval = {}
|
||||
if len(netcfg['IPv4Addresses']) != 1:
|
||||
netcfg['IPv4Addresses'] = [x for x in
|
||||
netcfg['IPv4Addresses'] if x['Address'] != '0.0.0.0']
|
||||
netcfg['IPv4Addresses'] = [
|
||||
x for x in netcfg['IPv4Addresses']
|
||||
if x['Address'] != '0.0.0.0']
|
||||
if len(netcfg['IPv4Addresses']) != 1:
|
||||
raise exc.PyghmiException('Multiple IP addresses not supported')
|
||||
currip = netcfg['IPv4Addresses'][0]
|
||||
@ -608,8 +609,8 @@ class Command(object):
|
||||
def _get_adp_inventory(self, onlyname=False, withids=False, urls=None):
|
||||
if not urls:
|
||||
urls = self._get_adp_urls()
|
||||
if not urls:
|
||||
return
|
||||
if not urls:
|
||||
return
|
||||
for inf in self._do_bulk_requests(urls):
|
||||
adpinfo, url = inf
|
||||
aname = adpinfo.get('Name', 'Unknown')
|
||||
@ -622,7 +623,8 @@ class Command(object):
|
||||
if onlyname:
|
||||
if withids:
|
||||
yield aname, adpinfo.get('Id', aname)
|
||||
yield aname
|
||||
else:
|
||||
yield aname
|
||||
continue
|
||||
functions = adpinfo.get('Links', {}).get('PCIeFunctions', [])
|
||||
nicidx = 1
|
||||
@ -659,6 +661,16 @@ class Command(object):
|
||||
urls = []
|
||||
return urls
|
||||
|
||||
@property
|
||||
def oem(self):
|
||||
if not self._oem:
|
||||
self._oem = oem.get_oem_handler(
|
||||
self.sysinfo, self.sysurl, self.wc, self._urlcache)
|
||||
return self._oem
|
||||
|
||||
def get_description(self):
|
||||
return self.oem.get_description()
|
||||
|
||||
def _get_cpu_inventory(self, onlynames=False, withids=False, urls=None):
|
||||
if not urls:
|
||||
urls = self._get_cpu_urls()
|
||||
|
0
pyghmi/redfish/oem/__init__.py
Normal file
0
pyghmi/redfish/oem/__init__.py
Normal file
51
pyghmi/redfish/oem/generic.py
Normal file
51
pyghmi/redfish/oem/generic.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright 2019 Lenovo Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import pyghmi.exceptions as exc
|
||||
|
||||
|
||||
class OEMHandler(object):
|
||||
def __init__(self, sysinfo, sysurl, webclient, cache):
|
||||
self._varsysinfo = sysinfo
|
||||
self._varsysurl = sysurl
|
||||
self._urlcache = cache
|
||||
self.webclient = webclient
|
||||
|
||||
def _get_cache(self, url):
|
||||
now = os.times()[4]
|
||||
cachent = self._urlcache.get(url, None)
|
||||
if cachent and cachent['vintage'] > now - 30:
|
||||
return cachent['contents']
|
||||
return None
|
||||
|
||||
def get_description(self):
|
||||
return {}
|
||||
|
||||
def _do_web_request(self, url, payload=None, method=None, cache=True):
|
||||
res = None
|
||||
if cache and payload is None and method is None:
|
||||
res = self._get_cache(url)
|
||||
if res:
|
||||
return res
|
||||
wc = self.webclient.dupe()
|
||||
res = wc.grab_json_response_with_status(url, payload, method=method)
|
||||
if res[1] < 200 or res[1] >= 300:
|
||||
raise exc.PyghmiException(res[0])
|
||||
if payload is None and method is None:
|
||||
self._urlcache[url] = {
|
||||
'contents': res[0],
|
||||
'vintage': os.times()[4]
|
||||
}
|
||||
return res[0]
|
0
pyghmi/redfish/oem/lenovo/__init__.py
Normal file
0
pyghmi/redfish/oem/lenovo/__init__.py
Normal file
24
pyghmi/redfish/oem/lenovo/main.py
Normal file
24
pyghmi/redfish/oem/lenovo/main.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2019 Lenovo Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pyghmi.redfish.oem.generic as generic
|
||||
from pyghmi.redfish.oem.lenovo import xcc
|
||||
|
||||
|
||||
def get_handler(sysinfo, sysurl, webclient, cache):
|
||||
leninf = sysinfo.get('Oem', {}).get('Lenovo', {})
|
||||
if 'FrontPanelUSB' in leninf:
|
||||
return xcc.OEMHandler(sysinfo, sysurl, webclient, cache)
|
||||
else:
|
||||
return generic.OEMHandler(sysinfo, sysurl, webclient, cache)
|
32
pyghmi/redfish/oem/lenovo/xcc.py
Normal file
32
pyghmi/redfish/oem/lenovo/xcc.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright 2019 Lenovo Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pyghmi.redfish.oem.generic as generic
|
||||
|
||||
|
||||
class OEMHandler(generic.OEMHandler):
|
||||
def get_description(self):
|
||||
description = self._do_web_request('/DeviceDescription.json')
|
||||
if description:
|
||||
description = description[0]
|
||||
u_height = description.get('u-height', '')
|
||||
if not u_height and description.get(
|
||||
'enclosure-machinetype-model', '').startswith('7Y36'):
|
||||
u_height = '2'
|
||||
if not u_height:
|
||||
return {}
|
||||
u_height = int(u_height)
|
||||
slot = description.get('slot', '0')
|
||||
slot = int(slot)
|
||||
return {'height': u_height, 'slot': slot}
|
26
pyghmi/redfish/oem/lookup.py
Normal file
26
pyghmi/redfish/oem/lookup.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2019 Lenovo Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pyghmi.redfish.oem.generic as generic
|
||||
import pyghmi.redfish.oem.lenovo.main as lenovo
|
||||
|
||||
OEMMAP = {
|
||||
'Lenovo': lenovo,
|
||||
}
|
||||
|
||||
def get_oem_handler(sysinfo, sysurl, webclient, cache):
|
||||
for oem in sysinfo.get('Oem', {}):
|
||||
if oem in OEMMAP:
|
||||
return OEMMAP[oem].get_handler(sysinfo, sysurl, webclient, cache)
|
||||
return generic.OEMHandler(sysinfo, sysurl, webclient, cache)
|
@ -25,6 +25,7 @@ setuptools.setup(
|
||||
author_email='jjohnson2@lenovo.com',
|
||||
packages=['pyghmi', 'pyghmi.util', 'pyghmi.ipmi', 'pyghmi.cmd',
|
||||
'pyghmi.redfish', 'pyghmi.ipmi.private', 'pyghmi.ipmi.oem',
|
||||
'pyghmi.ipmi.oem.lenovo'],
|
||||
'pyghmi.ipmi.oem.lenovo', 'pyghmi.redfish.oem',
|
||||
'pyghmi.redfish.oem.lenovo'],
|
||||
license='Apache License, Version 2.0')
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user