diff --git a/pyghmi/redfish/command.py b/pyghmi/redfish/command.py index 2091eff2..057f98cf 100644 --- a/pyghmi/redfish/command.py +++ b/pyghmi/redfish/command.py @@ -28,6 +28,7 @@ import time import pyghmi.exceptions as exc import pyghmi.constants as const import pyghmi.util.webclient as webclient +from pyghmi.util.parse import parse_time import pyghmi.redfish.oem.lookup as oem import re from dateutil import tz @@ -80,49 +81,6 @@ _healthmap = { } -def _parse_time(timeval): - if timeval is None: - return None - try: - retval = datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%SZ') - return retval.replace(tzinfo=tz.tzutc()) - except ValueError: - pass - try: - positive = None - offset = None - if '+' in timeval: - timeval, offset = timeval.split('+', 1) - positive = 1 - elif len(timeval.split('-')) > 3: - timeval, offset = timeval.rsplit('-', 1) - positive = -1 - if positive: - hrs, mins = offset.split(':', 1) - secs = int(hrs) * 60 + int(mins) - secs = secs * 60 * positive - ms = None - if '.' in timeval: - timeval, ms = timeval.split('.', 1) - ms = int(ms) - ms = timedelta(0, 0, 0, ms) - retval = datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%S') - if ms: - retval += ms - return retval.replace(tzinfo=tz.tzoffset('', secs)) - except ValueError: - pass - try: - return datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%S') - except ValueError: - pass - try: - return datetime.strptime(timeval, '%Y-%m-%d') - except ValueError: - pass - return None - - def _mask_to_cidr(mask): maskn = socket.inet_pton(socket.AF_INET, mask) maskn = struct.unpack('!I', maskn)[0] @@ -275,6 +233,8 @@ class Command(object): self.wc.set_header('OData-Version', '4.0') overview = self.wc.grab_json_response('/redfish/v1/') self.wc.set_basic_credentials(userid, password) + self.username = userid + self.password = password self.wc.set_header('Content-Type', 'application/json') systems = overview['Systems']['@odata.id'] res = self.wc.grab_json_response_with_status(systems) @@ -401,7 +361,7 @@ class Command(object): errmsg = ','.join(errmsg) raise exc.RedfishError(errmsg) except (ValueError, KeyError): - raise exc.PyghmiException(res[0]) + raise exc.PyghmiException(str(url) + ":" + res[0]) if payload is None and method is None: self._urlcache[url] = {'contents': res[0], 'vintage': os.times()[4]} @@ -827,6 +787,11 @@ class Command(object): return netcfg['HostName'] def get_firmware(self, components=()): + try: + for firminfo in self.oem.get_firmware_inventory(components): + yield firminfo + except exc.BypassGenericBehavior: + return fwlist = self._do_web_request(self._fwinventory) fwurls = [x['@odata.id'] for x in fwlist.get('Members', [])] self._fwnamemap = {} @@ -850,7 +815,7 @@ class Command(object): currinf['name'] = fwname currinf['id'] = fwi.get('Id', None) currinf['version'] = fwi.get('Version', 'Unknown') - currinf['date'] = _parse_time(fwi.get('ReleaseDate', '')) + currinf['date'] = parse_time(fwi.get('ReleaseDate', '')) if not (currinf['version'] or currinf['date']): return None, None # TODO: OEM extended data with buildid @@ -1017,6 +982,7 @@ class Command(object): if not self._oem: self._oem = oem.get_oem_handler( self.sysinfo, self.sysurl, self.wc, self._urlcache) + self._oem.set_credentials(self.username, self.password) return self._oem def get_description(self): @@ -1150,7 +1116,7 @@ class Command(object): newloginfo = self._do_web_request(lurl, cache=False) for log in entries.get('Members', []): record = {} - entime = _parse_time(log.get('Created', '')) + correction + entime = parse_time(log.get('Created', '')) + correction entime = entime.astimezone(tz.gettz()) record['timestamp'] = entime.strftime('%Y-%m-%dT%H:%M:%S') record['message'] = log.get('Message', None) diff --git a/pyghmi/redfish/oem/generic.py b/pyghmi/redfish/oem/generic.py index 40bc3e06..2afd03d3 100644 --- a/pyghmi/redfish/oem/generic.py +++ b/pyghmi/redfish/oem/generic.py @@ -32,6 +32,13 @@ class OEMHandler(object): def get_description(self): return {} + + def get_firmware_inventory(self, components): + return [] + + def set_credentials(self, username, password): + self.username = username + self.password = password def _do_web_request(self, url, payload=None, method=None, cache=True): res = None diff --git a/pyghmi/redfish/oem/lenovo/xcc.py b/pyghmi/redfish/oem/lenovo/xcc.py index b639ca1a..1a548bbf 100644 --- a/pyghmi/redfish/oem/lenovo/xcc.py +++ b/pyghmi/redfish/oem/lenovo/xcc.py @@ -13,9 +13,20 @@ # limitations under the License. import pyghmi.redfish.oem.generic as generic +from pyghmi.util.parse import parse_time +import errno +import json +import socket +import pyghmi.ipmi.private.util as util +import pyghmi.exceptions as pygexc class OEMHandler(generic.OEMHandler): + + def __init__(self, sysinfo, sysurl, webclient, cache): + super(OEMHandler, self).__init__(sysinfo, sysurl, webclient, cache) + self._wc = None + def get_description(self): description = self._do_web_request('/DeviceDescription.json') if description: @@ -30,3 +41,116 @@ class OEMHandler(generic.OEMHandler): slot = description.get('slot', '0') slot = int(slot) return {'height': u_height, 'slot': slot} + + def _get_agentless_firmware(self, components): + adata = self.wc.grab_json_response('/api/dataset/imm_adapters?params=pci_GetAdapters') + anames = set() + for adata in adata.get('items', []): + baseaname = adata['adapterName'] + aname = baseaname + idx = 1 + while aname in anames: + aname = '{0} {1}'.format(baseaname, idx) + idx += 1 + anames.add(aname) + donenames = set() + for fundata in adata['functions']: + for firm in fundata.get('firmwares', []): + fname = firm['firmwareName'].rstrip() + if '.' in fname: + fname = firm['description'].rstrip() + if fname in donenames: + # ignore redundant entry + continue + if not fname: + continue + donenames.add(fname) + bdata = {} + if 'versionStr' in firm and firm['versionStr']: + bdata['version'] = firm['versionStr'] + if ('releaseDate' in firm and + firm['releaseDate'] and + firm['releaseDate'] != 'N/A'): + try: + bdata['date'] = parse_time(firm['releaseDate']) + except ValueError: + pass + yield ('{0} {1}'.format(aname, fname), bdata) + + def _get_disk_firmware_single(self, diskent, prefix=''): + bdata = {} + if not prefix and diskent.get('location', '').startswith('M.2'): + prefix = 'M.2-' + diskname = 'Disk {1}{0}'.format(diskent['slotNo'], prefix) + bdata['model'] = diskent[ + 'productName'].rstrip() + bdata['version'] = diskent['fwVersion'] + return (diskname, bdata) + def _get_disk_firmware(self, coponents): + storagedata = storagedata = self.wc.grab_json_response( + '/api/function/raid_alldevices?params=storage_GetAllDisks') + for adp in storagedata.get('items', []): + for diskent in adp.get('disks', ()): + yield self._get_disk_firmware_single(diskent) + for diskent in adp.get('aimDisks', ()): + yield self._get_disk_firmware_single(diskent) + + def get_firmware_inventory(self, components): + sysinf = self.wc.grab_json_response('/api/dataset/sys_info') + for item in sysinf.get('items', {}): + for firm in item.get('firmware', []): + firminfo = { + 'version': firm['version'], + 'build': firm['build'], + 'date': parse_time(firm['release_date']), + } + if firm['type'] == 5: + yield ('XCC', firminfo) + elif firm['type'] == 6: + yield ('XCC Backup', firminfo) + elif firm['type'] == 0: + yield ('UEFI', firminfo) + elif firm['type'] == 7: + yield ('LXPM', firminfo) + elif firm['type'] == 8: + yield ('LXPM Windows Driver Bundle', firminfo) + elif firm['type'] == 9: + yield ('LXPM Linux Driver Bundle', firminfo) + for adpinfo in self._get_agentless_firmware(components): + yield adpinfo + for adpinfo in self._get_disk_firmware(components): + yield adpinfo + raise pygexc.BypassGenericBehavior() + + @property + def wc(self): + if (not self._wc or (self._wc.vintage and + self._wc.vintage < util._monotonic_time() - 30)): + self._wc = self.get_webclient() + return self._wc + + def get_webclient(self, login=True): + wc = self.webclient.dupe() + wc.vintage = util._monotonic_time() + try: + wc.connect() + except socket.error as se: + if se.errno != errno.ECONNREFUSED: + raise + return None + if not login: + return wc + adata = json.dumps({'username': self.username, + 'password': self.password + }) + headers = {'Connection': 'keep-alive', + 'Content-Type': 'application/json'} + wc.request('POST', '/api/login', adata, headers) + rsp = wc.getresponse() + if rsp.status == 200: + rspdata = json.loads(rsp.read()) + wc.set_header('Content-Type', 'application/json') + wc.set_header('Authorization', 'Bearer ' + rspdata['access_token']) + if '_csrf_token' in wc.cookies: + wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token']) + return wc diff --git a/pyghmi/util/parse.py b/pyghmi/util/parse.py new file mode 100644 index 00000000..3d476f67 --- /dev/null +++ b/pyghmi/util/parse.py @@ -0,0 +1,59 @@ +# 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. + +from dateutil import tz +from datetime import datetime, timedelta + + +def parse_time(timeval): + if timeval is None: + return None + try: + retval = datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%SZ') + return retval.replace(tzinfo=tz.tzutc()) + except ValueError: + pass + try: + positive = None + offset = None + if '+' in timeval: + timeval, offset = timeval.split('+', 1) + positive = 1 + elif len(timeval.split('-')) > 3: + timeval, offset = timeval.rsplit('-', 1) + positive = -1 + if positive: + hrs, mins = offset.split(':', 1) + secs = int(hrs) * 60 + int(mins) + secs = secs * 60 * positive + ms = None + if '.' in timeval: + timeval, ms = timeval.split('.', 1) + ms = int(ms) + ms = timedelta(0, 0, 0, ms) + retval = datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%S') + if ms: + retval += ms + return retval.replace(tzinfo=tz.tzoffset('', secs)) + except ValueError: + pass + try: + return datetime.strptime(timeval, '%Y-%m-%dT%H:%M:%S') + except ValueError: + pass + try: + return datetime.strptime(timeval, '%Y-%m-%d') + except ValueError: + pass + return None \ No newline at end of file