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:
parent
40eb4c014d
commit
8a7a909b2e
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
151
pyghmi/ipmi/oem/lenovo/imm.py
Normal file
151
pyghmi/ipmi/oem/lenovo/imm.py
Normal 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')
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user