diff --git a/pyghmi/redfish/command.py b/pyghmi/redfish/command.py index 14c985a1..2a216820 100644 --- a/pyghmi/redfish/command.py +++ b/pyghmi/redfish/command.py @@ -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() diff --git a/pyghmi/redfish/oem/__init__.py b/pyghmi/redfish/oem/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyghmi/redfish/oem/generic.py b/pyghmi/redfish/oem/generic.py new file mode 100644 index 00000000..40bc3e06 --- /dev/null +++ b/pyghmi/redfish/oem/generic.py @@ -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] diff --git a/pyghmi/redfish/oem/lenovo/__init__.py b/pyghmi/redfish/oem/lenovo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyghmi/redfish/oem/lenovo/main.py b/pyghmi/redfish/oem/lenovo/main.py new file mode 100644 index 00000000..cc176437 --- /dev/null +++ b/pyghmi/redfish/oem/lenovo/main.py @@ -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) diff --git a/pyghmi/redfish/oem/lenovo/xcc.py b/pyghmi/redfish/oem/lenovo/xcc.py new file mode 100644 index 00000000..b639ca1a --- /dev/null +++ b/pyghmi/redfish/oem/lenovo/xcc.py @@ -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} diff --git a/pyghmi/redfish/oem/lookup.py b/pyghmi/redfish/oem/lookup.py new file mode 100644 index 00000000..7b7ea6a1 --- /dev/null +++ b/pyghmi/redfish/oem/lookup.py @@ -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) diff --git a/setup.py.tmpl b/setup.py.tmpl index 48626467..654e2c6b 100644 --- a/setup.py.tmpl +++ b/setup.py.tmpl @@ -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')