2
0
mirror of https://opendev.org/x/pyghmi synced 2025-01-14 03:37:47 +00:00

Add system X firmware information

This commit adds support for fetching extended System X firmware
information.  This includes, as supported by the model,
extended IMM info, backup IMM, UEFI (primary, backup, and pending),
as well as available firmware from agentless system x options.

Change-Id: Iea09af0dd54938dbfe54b64c0a1084cb7ad2264f
This commit is contained in:
Jarrod Johnson 2016-04-18 14:00:08 -04:00
parent 40eb4c014d
commit 8a7a909b2e
5 changed files with 209 additions and 5 deletions

View File

@ -1690,7 +1690,10 @@ class Command(object):
"""Retrieve OEM Firmware information
"""
self.oem_init()
return self._oem.get_oem_firmware()
mcinfo = self.xraw_command(netfn=6, command=1)
bmcver = '{0}.{1}'.format(
ord(mcinfo['data'][2]), hex(ord(mcinfo['data'][3]))[2:])
return self._oem.get_oem_firmware(bmcver)
def get_capping_enabled(self):
"""Get PSU based power capping status

View File

@ -175,10 +175,13 @@ class OEMHandler(object):
fru['oem_parser'] = None
return fru
def get_oem_firmware(self):
def get_oem_firmware(self, bmcver):
"""Get Firmware information.
"""
return ()
# Here the bmc version is passed into the OEM handler, to allow
# the handler to enrich the data. For the generic case, just
# provide the generic BMC version, which is all that is possible
yield ('BMC Version', {'version': bmcver})
def get_oem_capping_enabled(self):
"""Get PSU based power capping status

View File

@ -30,6 +30,7 @@ from pyghmi.ipmi.oem.lenovo import dimm
from pyghmi.ipmi.oem.lenovo import drive
from pyghmi.ipmi.oem.lenovo import firmware
from pyghmi.ipmi.oem.lenovo import imm
from pyghmi.ipmi.oem.lenovo import inventory
from pyghmi.ipmi.oem.lenovo import nextscale
from pyghmi.ipmi.oem.lenovo import pci
@ -37,6 +38,7 @@ from pyghmi.ipmi.oem.lenovo import psu
from pyghmi.ipmi.oem.lenovo import raid_controller
from pyghmi.ipmi.oem.lenovo import raid_drive
import pyghmi.util.webclient as wc
import socket
@ -132,6 +134,8 @@ class OEMHandler(generic.OEMHandler):
self._has_megarac = None
self.oem_inventory_info = None
self._mrethidx = None
self._hasimm = None
self._immbuildinfo = None
@property
def _megarac_eth_index(self):
@ -419,11 +423,31 @@ class OEMHandler(generic.OEMHandler):
fru['oem_parser'] = None
return fru
def get_oem_firmware(self):
@property
def has_imm(self):
if self._hasimm is not None:
return self._hasimm
try:
bdata = self.ipmicmd.xraw_command(netfn=0x3a, command=0x50)
except pygexc.IpmiException:
self._hasimm = False
return False
if len(bdata['data'][:]) != 30:
self._hasimm = False
return False
self._hasimm = True
self._immbuildinfo = bdata['data'][:]
return True
def get_oem_firmware(self, bmcver):
if self.has_tsm:
command = firmware.get_categories()["firmware"]
rsp = self.ipmicmd.xraw_command(**command["command"])
return command["parser"](rsp["data"])
elif self.has_imm:
return imm.get_firmware_inventory(self.ipmicmd, bmcver,
self._immbuildinfo,
self._certverify)
return ()
def get_oem_capping_enabled(self):

View File

@ -0,0 +1,151 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016 Lenovo
#
# 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 datetime import datetime
import json
import pyghmi.util.webclient as webclient
import urllib
def get_imm_property(ipmicmd, propname):
propname = propname.encode('utf-8')
proplen = len(propname) | 0b10000000
cmdlen = len(propname) + 1
cdata = bytearray([0, 0, cmdlen, proplen]) + propname
rsp = ipmicmd.xraw_command(netfn=0x3a, command=0xc4, data=cdata)
rsp['data'] = bytearray(rsp['data'])
if rsp['data'][0] != 0:
return None
propdata = rsp['data'][3:] # second two bytes are size, don't need it
if propdata[0] & 0b10000000: # string, for now assume length valid
return str(propdata[1:]).rstrip(' \x00')
else:
raise Exception('Unknown format for property: ' + repr(propdata))
def get_imm_webclient(imm, certverify, uid, password):
wc = webclient.SecureHTTPConnection(imm, 443,
verifycallback=certverify)
try:
wc.connect()
except Exception:
return None
adata = urllib.urlencode({'user': uid,
'password': password,
'SessionTimeout': 60
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded'}
wc.request('POST', '/data/login', adata, headers)
rsp = wc.getresponse()
if rsp.status == 200:
rspdata = json.loads(rsp.read())
if rspdata['authResult'] == '0' and rspdata['status'] == 'ok':
return wc
def parse_imm_buildinfo(buildinfo):
buildid = buildinfo[:9].rstrip(' \x00')
bdt = ' '.join(buildinfo[9:].replace('\x00', ' ').split())
bdate = datetime.strptime(bdt, '%Y/%m/%d %H:%M:%S')
return (buildid, bdate)
def datefromprop(propstr):
if propstr is None:
return None
return datetime.strptime(propstr, '%Y/%m/%d')
def fetch_grouped_properties(ipmicmd, groupinfo):
retdata = {}
for keyval in groupinfo:
retdata[keyval] = get_imm_property(ipmicmd, groupinfo[keyval])
if keyval == 'date':
retdata[keyval] = datefromprop(retdata[keyval])
returnit = False
for keyval in list(retdata):
if retdata[keyval] in (None, ''):
del retdata[keyval]
else:
returnit = True
if returnit:
return retdata
def fetch_adapter_firmware(wc):
wc.request('GET', '/designs/imm/dataproviders/imm_adapters.php')
rsp = wc.getresponse()
if rsp.status == 200:
adapterdata = json.loads(rsp.read())
for adata in adapterdata['items']:
aname = adata['adapter.adapterName']
donenames = set([])
for fundata in adata['adapter.functions']:
fdata = fundata.get('firmwares', ())
for firm in fdata:
fname = firm['firmwareName']
if '.' in fname:
fname = firm['description']
if fname in donenames:
# ignore redundant entry
continue
donenames.add(fname)
bdata = {}
bdata['version'] = firm['versionStr']
if 'releaseDate' in firm and firm['releaseDate'] != 'N/A':
bdata['date'] = datetime.strptime(firm['releaseDate'],
'%m/%d/%Y')
yield ('{0} {1}'.format(aname, fname), bdata)
def get_firmware_inventory(ipmicmd, bmcver, immbuildinfo, certverify):
# First we fetch the system firmware found in imm properties
# then check for agentless, if agentless, get adapter info using
# https, using the caller TLS verification scheme
immverdata = parse_imm_buildinfo(immbuildinfo)
bdata = {'version': bmcver, 'build': immverdata[0], 'date': immverdata[1]}
yield ('IMM', bdata)
bdata = fetch_grouped_properties(ipmicmd, {
'build': '/v2/ibmc/dm/fw/imm2/backup_build_id',
'version': '/v2/ibmc/dm/fw/imm2/backup_build_version',
'date': '/v2/ibmc/dm/fw/imm2/backup_build_date'})
if bdata:
yield ('IMM Backup', bdata)
bdata = fetch_grouped_properties(ipmicmd, {
'build': '/v2/bios/build_id',
'version': '/v2/bios/build_version',
'date': '/v2/bios/build_date'})
if bdata:
yield ('UEFI', bdata)
bdata = fetch_grouped_properties(ipmicmd, {
'build': '/v2/ibmc/dm/fw/bios/backup_build_id',
'version': '/v2/ibmc/dm/fw/bios/backup_build_version'})
if bdata:
yield ('UEFI Backup', bdata)
# Note that the next pending could be pending for either primary
# or backup, so can't promise where it will go
bdata = fetch_grouped_properties(ipmicmd, {
'build': '/v2/bios/pending_build_id'})
if bdata:
yield ('UEFI Pending Update', bdata)
wc = get_imm_webclient(ipmicmd.bmc, certverify,
ipmicmd.ipmi_session.userid,
ipmicmd.ipmi_session.password)
if wc:
for firm in fetch_adapter_firmware(wc):
yield firm
wc.request('GET', '/data/logout')

View File

@ -17,13 +17,15 @@
# 2.6 as is found in commonly used enterprise linux distributions.
__author__ = 'jjohnson2'
import Cookie
import httplib
import pyghmi.exceptions as pygexc
import socket
import ssl
class SecureHTTPConnection(httplib.HTTPConnection):
class SecureHTTPConnection(httplib.HTTPConnection, object):
default_port = httplib.HTTPS_PORT
def __init__(self, host, port=None, key_file=None, cert_file=None,
@ -31,6 +33,7 @@ class SecureHTTPConnection(httplib.HTTPConnection):
httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs)
self.cert_reqs = ssl.CERT_NONE # verification will be done ssh style..
self._certverify = verifycallback
self.cookies = {}
def connect(self):
plainsock = socket.create_connection((self.host, self.port))
@ -40,3 +43,23 @@ class SecureHTTPConnection(httplib.HTTPConnection):
if not self._certverify(bincert):
raise pygexc.UnrecognizedCertificate('Unknown certificate',
bincert)
def getresponse(self):
rsp = super(SecureHTTPConnection, self).getresponse()
for hdr in rsp.msg.headers:
if hdr.startswith('Set-Cookie:'):
c = Cookie.BaseCookie(hdr[11:])
for k in c:
self.cookies[k] = c[k].value
return rsp
def request(self, method, url, body=None, headers=None):
if headers is None:
headers = {}
if self.cookies:
cookies = []
for ckey in self.cookies:
cookies.append('{0}={1}'.format(ckey, self.cookies[ckey]))
headers['Cookie'] = '; '.join(cookies)
return super(SecureHTTPConnection, self).request(method, url, body,
headers)