diff --git a/buildrpm b/buildrpm old mode 100644 new mode 100755 diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 17f1350f..1cc86f5b 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -1490,9 +1490,7 @@ class Command(object): raise Exception('name must be less than or = 16 chars') name = name.ljust(16, "\x00") data.extend([ord(x) for x in name]) - response = self.raw_command(netfn=0x06, command=0x45, data=data) - if 'error' in response: - raise Exception(response['error']) + self.xraw_command(netfn=0x06, command=0x45, data=data) return True def get_user_name(self, uid, return_none_on_error=True): @@ -1554,12 +1552,16 @@ class Command(object): else: password = password.ljust(16, "\x00") data.extend([ord(x) for x in password]) - response = self.raw_command(netfn=0x06, command=0x47, data=data) - if 'error' in response: + try: + self.xraw_command(netfn=0x06, command=0x47, data=data) + except exc.IpmiException as ie: if mode == 'test_password': - # return false if password test failed return False - raise Exception(response['error']) + elif mode in ('enable', 'disable') and ie.ipmicode == 0xcc: + # Some BMCs see redundant calls to password disable/enable + # as invalid + return True + raise return True def get_channel_max_user_count(self, channel=None): @@ -1689,7 +1691,9 @@ class Command(object): try: # First try to set name to all \x00 explicitly self.set_user_name(uid, '') - except Exception: + except exc.IpmiException as ie: + if ie.ipmicode != 0xcc: + raise # An invalid data field in request is frequently reported. # however another convention that exists is all '\xff' # if this fails, pass up the error so that calling code knows diff --git a/pyghmi/ipmi/oem/lenovo/handler.py b/pyghmi/ipmi/oem/lenovo/handler.py index 16513bff..96948265 100755 --- a/pyghmi/ipmi/oem/lenovo/handler.py +++ b/pyghmi/ipmi/oem/lenovo/handler.py @@ -143,12 +143,16 @@ class OEMHandler(generic.OEMHandler): # will need to retain data to differentiate # variations. For example System X versus Thinkserver self.oemid = oemid + self._fpc_variant = None self.ipmicmd = weakref.proxy(ipmicmd) self._has_megarac = None self.oem_inventory_info = None self._mrethidx = None self._hasimm = None - if self.has_imm: + self._hasxcc = None + if self.has_xcc: + self.immhandler = imm.XCCClient(ipmicmd) + elif self.has_imm: self.immhandler = imm.IMMClient(ipmicmd) @property @@ -281,9 +285,15 @@ class OEMHandler(generic.OEMHandler): def is_fpc(self): """True if the target is a Lenovo nextscale fan power controller """ - fpc_ids = ((19046, 32, 1063),) - return (self.oemid['manufacturer_id'], self.oemid['device_id'], - self.oemid['product_id']) in fpc_ids + fpc_id = (19046, 32, 1063) + smm_id = (19046, 32, 1180) + currid = (self.oemid['manufacturer_id'], self.oemid['device_id'], + self.oemid['product_id']) + if currid == fpc_id: + self._fpc_variant = 6 + elif currid == smm_id: + self._fpc_variant = 2 + return self._fpc_variant @property def is_sd350(self): @@ -327,17 +337,19 @@ class OEMHandler(generic.OEMHandler): def get_sensor_data(self): if self.is_fpc: - for name in nextscale.get_sensor_names(): - yield nextscale.get_sensor_reading(name, self.ipmicmd) + for name in nextscale.get_sensor_names(self._fpc_variant): + yield nextscale.get_sensor_reading(name, self.ipmicmd, + self._fpc_variant) def get_sensor_descriptions(self): if self.is_fpc: - return nextscale.get_sensor_descriptions() + return nextscale.get_sensor_descriptions(self._fpc_variant) return () def get_sensor_reading(self, sensorname): if self.is_fpc: - return nextscale.get_sensor_reading(sensorname, self.ipmicmd) + return nextscale.get_sensor_reading(sensorname, self.ipmicmd, + self._fpc_variant) return () def get_inventory_of_component(self, component): @@ -486,6 +498,30 @@ class OEMHandler(generic.OEMHandler): fru['oem_parser'] = None return fru + @property + def has_xcc(self): + if self._hasxcc is not None: + return self._hasxcc + try: + bdata = self.ipmicmd.xraw_command(netfn=0x3a, command=0xc1) + except pygexc.IpmiException: + self._hasxcc = False + self._hasimm = False + return False + if len(bdata['data'][:]) != 3: + self._hasimm = False + self._hasxcc = False + return False + rdata = bytearray(bdata['data'][:]) + self._hasxcc = rdata[1] & 16 == 16 + if self._hasxcc: + # For now, have imm calls go to xcc, since they are providing same + # interface. Longer term the hope is that all the Lenovo + # stuff will branch at init, and not have conditionals + # in all the functions + self._hasimm = self._hasxcc + return self._hasxcc + @property def has_imm(self): if self._hasimm is not None: @@ -499,7 +535,7 @@ class OEMHandler(generic.OEMHandler): self._hasimm = False return False rdata = bytearray(bdata['data'][:]) - self._hasimm = (rdata[1] & 1 == 1) or (rdata[1] & 8 == 8) + self._hasimm = (rdata[1] & 1 == 1) or (rdata[1] & 16 == 16) return self._hasimm def get_oem_firmware(self, bmcver): @@ -509,6 +545,9 @@ class OEMHandler(generic.OEMHandler): return command["parser"](rsp["data"]) elif self.has_imm: return self.immhandler.get_firmware_inventory(bmcver) + elif self.is_fpc: + return nextscale.get_fpc_firmware(bmcver, self.ipmicmd, + self._fpc_variant) return super(OEMHandler, self).get_oem_firmware(bmcver) def get_oem_capping_enabled(self): diff --git a/pyghmi/ipmi/oem/lenovo/imm.py b/pyghmi/ipmi/oem/lenovo/imm.py index 6b282c12..5cc54d0c 100644 --- a/pyghmi/ipmi/oem/lenovo/imm.py +++ b/pyghmi/ipmi/oem/lenovo/imm.py @@ -16,13 +16,15 @@ from datetime import datetime import json -from pyghmi.ipmi.private.util import _monotonic_time +import pyghmi.ipmi.private.util as util import pyghmi.util.webclient as webclient import urllib import weakref class IMMClient(object): + logouturl = '/data/logout' + bmcname = 'IMM' def __init__(self, ipmicmd): self.ipmicmd = weakref.proxy(ipmicmd) @@ -50,6 +52,10 @@ class IMMClient(object): return datetime.strptime(strval, '%m/%d/%Y') except ValueError: pass + try: + return datetime.strptime(strval, '%Y-%m-%d') + except ValueError: + pass try: return datetime.strptime(strval, '%m %d %Y') except ValueError: @@ -133,7 +139,7 @@ class IMMClient(object): def get_cached_data(self, attribute): try: kv = self.datacache[attribute] - if kv[1] > _monotonic_time() - 30: + if kv[1] > util._monotonic_time() - 30: return kv[0] except KeyError: return None @@ -147,7 +153,7 @@ class IMMClient(object): result = self.wc.grab_json_response('/data?set', params) if result['return'] != 'Success': raise Exception(result['reason']) - self.wc.grab_json_response('/data/logout') + self.weblogout() def detach_remote_media(self): mnt = self.wc.grab_json_response( @@ -165,7 +171,7 @@ class IMMClient(object): result = self.wc.grab_json_response('/data?set', params) if result['return'] != 'Success': raise Exception(result['reason']) - self.wc.grab_json_response('/data/logout') + self.weblogout() def fetch_agentless_firmware(self): adapterdata = self.get_cached_data('lenovo_cached_adapters') @@ -175,7 +181,7 @@ class IMMClient(object): '/designs/imm/dataproviders/imm_adapters.php') if adapterdata: self.datacache['lenovo_cached_adapters'] = ( - adapterdata, _monotonic_time()) + adapterdata, util._monotonic_time()) if adapterdata and 'items' in adapterdata: for adata in adapterdata['items']: aname = adata['adapter.adapterName'] @@ -209,7 +215,7 @@ class IMMClient(object): '/designs/imm/dataproviders/raid_alldevices.php') if storagedata: self.datacache['lenovo_cached_storage'] = ( - storagedata, _monotonic_time()) + storagedata, util._monotonic_time()) if storagedata and 'items' in storagedata: for adp in storagedata['items']: if 'storage.vpd.productName' not in adp: @@ -227,9 +233,7 @@ class IMMClient(object): bdata['version'] = diskent['storage.firmwares'][0][ 'versionStr'] yield (diskname, bdata) - if self.wc: - self.wc.grab_json_response('/data/logout') - self._wc = None + self.weblogout() def get_hw_inventory(self): hwmap = self.hardware_inventory_map() @@ -250,7 +254,7 @@ class IMMClient(object): def weblogout(self): if self._wc: - self._wc.grab_json_response('/data/logout') + self._wc.grab_json_response(self.logouturl) self._wc = None def hardware_inventory_map(self): @@ -265,7 +269,7 @@ class IMMClient(object): '/designs/imm/dataproviders/imm_adapters.php') if adapterdata: self.datacache['lenovo_cached_adapters'] = ( - adapterdata, _monotonic_time()) + adapterdata, util._monotonic_time()) if adapterdata and 'items' in adapterdata: for adata in adapterdata['items']: skipadapter = False @@ -305,7 +309,8 @@ class IMMClient(object): skipadapter = True if not skipadapter: hwmap[aname] = bdata - self.datacache['lenovo_cached_hwmap'] = (hwmap, _monotonic_time()) + self.datacache['lenovo_cached_hwmap'] = (hwmap, + util._monotonic_time()) self.weblogout() return hwmap @@ -317,24 +322,26 @@ class IMMClient(object): immverdata = self.parse_imm_buildinfo(rsp['data']) bdata = { 'version': bmcver, 'build': immverdata[0], 'date': immverdata[1]} - yield ('IMM', bdata) + yield (self.bmcname, bdata) bdata = self.fetch_grouped_properties({ '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) + yield ('{0} Backup'.format(self.bmcname), bdata) bdata = self.fetch_grouped_properties({ 'build': '/v2/ibmc/trusted_buildid', }) if bdata: - yield ('IMM Trusted Image', bdata) + yield ('{0} Trusted Image'.format(self.bmcname), bdata) bdata = self.fetch_grouped_properties({ 'build': '/v2/bios/build_id', 'version': '/v2/bios/build_version', 'date': '/v2/bios/build_date'}) if bdata: yield ('UEFI', bdata) + else: + yield ('UEFI', {'version': 'unknown'}) bdata = self.fetch_grouped_properties({ 'build': '/v2/ibmc/dm/fw/bios/backup_build_id', 'version': '/v2/ibmc/dm/fw/bios/backup_build_version'}) @@ -346,5 +353,130 @@ class IMMClient(object): 'build': '/v2/bios/pending_build_id'}) if bdata: yield ('UEFI Pending Update', bdata) + fpga = self.ipmicmd.xraw_command(netfn=0x3a, command=0x6b, data=(0,)) + fpga = '{0}.{1}.{2}'.format(*[ord(x) for x in fpga['data']]) + yield ('FPGA', {'version': fpga}) for firm in self.fetch_agentless_firmware(): yield firm + + +class XCCClient(IMMClient): + logouturl = '/api/providers/logout' + bmcname = 'XCC' + + def get_webclient(self): + cv = self.ipmicmd.certverify + wc = webclient.SecureHTTPConnection(self.imm, 443, verifycallback=cv) + try: + wc.connect() + except Exception: + return None + 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 + + def attach_remote_media(self, url, user, password): + proto, host, path = util.urlsplit(url) + if proto == 'smb': + proto = 'cifs' + rq = {'Option': '', 'Domain': '', 'Write': 0} + # nfs == 1, cifs == 0 + if proto == 'nfs': + rq['Protocol'] = 1 + rq['Url'] = '{0}:{1}'.format(host, path) + elif proto == 'cifs': + rq['Protocol'] = 0 + rq['Credential'] = '{0}:{1}'.format(user, password) + rq['Url'] = '//{0}{1}'.format(host, path) + elif proto in ('http', 'https'): + rq['Protocol'] = 7 + rq['Url'] = url + else: + raise Exception('TODO') + rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_connect', + json.dumps(rq)) + if 'return' not in rt or rt['return'] != 0: + raise Exception('Unhandled return: ' + repr(rt)) + rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_mountall', + '{}') + if 'return' not in rt or rt['return'] != 0: + raise Exception('Unhandled return: ' + repr(rt)) + + def get_firmware_inventory(self, bmcver): + # 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 + rsp = self.ipmicmd.xraw_command(netfn=0x3a, command=0x50) + immverdata = self.parse_imm_buildinfo(rsp['data']) + bdata = { + 'version': bmcver, 'build': immverdata[0], 'date': immverdata[1]} + yield (self.bmcname, bdata) + bdata = self.fetch_grouped_properties({ + 'build': '/v2/ibmc/dm/fw/imm3/backup_pending_build_id', + 'version': '/v2/ibmc/dm/fw/imm3/backup_pending_build_version', + 'date': '/v2/ibmc/dm/fw/imm3/backup_pending_build_date'}) + if bdata: + yield ('{0} Backup'.format(self.bmcname), bdata) + else: + bdata = self.fetch_grouped_properties({ + 'build': '/v2/ibmc/dm/fw/imm3/backup_build_id', + 'version': '/v2/ibmc/dm/fw/imm3/backup_build_version', + 'date': '/v2/ibmc/dm/fw/imm3/backup_build_date'}) + if bdata: + yield ('{0} Backup'.format(self.bmcname), bdata) + bdata = self.fetch_grouped_properties({ + 'build': '/v2/ibmc/trusted_buildid', + }) + if bdata: + bdata = self.fetch_grouped_properties({ + 'build': '/v2/ibmc/trusted_buildid', + }) + if bdata: + yield ('{0} Trusted Image'.format(self.bmcname), bdata) + bdata = self.fetch_grouped_properties({ + 'build': '/v2/bios/build_id', + 'version': '/v2/bios/build_version', + 'date': '/v2/bios/build_date'}) + if bdata: + yield ('UEFI', bdata) + # Note that the next pending could be pending for either primary + # or backup, so can't promise where it will go + bdata = self.fetch_grouped_properties({ + 'build': '/v2/bios/pending_build_id'}) + if bdata: + yield ('UEFI Pending Update', bdata) + bdata = self.fetch_grouped_properties({ + 'build': '/v2/tdm/build_id', + 'version': '/v2/tdm/build_version', + 'date': '/v2/tdm/build_date'}) + if bdata: + yield ('LXPM', bdata) + fpga = self.ipmicmd.xraw_command(netfn=0x3a, command=0x6b, data=(0,)) + fpga = '{0}.{1}.{2}'.format(*[ord(x) for x in fpga['data']]) + yield ('FPGA', {'version': fpga}) + for firm in self.fetch_agentless_firmware(): + yield firm + + def detach_remote_media(self): + rt = self.wc.grab_json_response('/api/providers/rp_vm_remote_getdisk') + if 'items' in rt: + slots = [] + for mount in rt['items']: + slots.append(mount['slotId']) + for slot in slots: + rt = self.wc.grab_json_response( + '/api/providers/rp_vm_remote_unmount', + json.dumps({'Slot': slot})) + if 'return' not in rt or rt['return'] != 0: + raise Exception("Unrecognized return: " + repr(rt)) diff --git a/pyghmi/ipmi/oem/lenovo/nextscale.py b/pyghmi/ipmi/oem/lenovo/nextscale.py index e088809d..cdb337ca 100644 --- a/pyghmi/ipmi/oem/lenovo/nextscale.py +++ b/pyghmi/ipmi/oem/lenovo/nextscale.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2016 Lenovo +# Copyright 2016-2017 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ # limitations under the License. import pyghmi.constants as pygconst +import pyghmi.exceptions as pygexc import pyghmi.ipmi.sdr as sdr import struct @@ -27,12 +28,16 @@ except NameError: def fpc_read_ac_input(ipmicmd): rsp = ipmicmd.xraw_command(netfn=0x32, command=0x90, data=(1,)) rsp = rsp['data'] + if len(rsp) == 6: + rsp = b'\x00' + bytes(rsp) return struct.unpack_from('