From 083facefe3e3fece7fae0222b86083765bd91120 Mon Sep 17 00:00:00 2001 From: Rob Dobson Date: Mon, 14 Jul 2014 16:27:29 +0100 Subject: [PATCH 1/3] Adding inspector for looking at hardware in a remote host. Signed-off-by: Rob Dobson --- hwinfo/tools/__init__.py | 0 hwinfo/tools/inspector.py | 67 +++++++++++++++++++++++++++++++++++++++ setup.py | 8 +++++ 3 files changed, 75 insertions(+) create mode 100644 hwinfo/tools/__init__.py create mode 100644 hwinfo/tools/inspector.py diff --git a/hwinfo/tools/__init__.py b/hwinfo/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hwinfo/tools/inspector.py b/hwinfo/tools/inspector.py new file mode 100644 index 0000000..f238a65 --- /dev/null +++ b/hwinfo/tools/inspector.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +from argparse import ArgumentParser +import paramiko + +from hwinfo.pci import PCIDevice +from hwinfo.pci.lspci import * + +def remote_command(host, username, password, cmd): + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(host, username=username, password=password, timeout=10) + cmdstr = ' '.join(cmd) + print "Executing '%s' on host '%s'" % (cmdstr, host) + _, stdout, stderr = client.exec_command(cmdstr) + output = stdout.readlines() + error = stderr.readlines() + if error: + print "stderr: %s" % error + client.close() + return ''.join(output) + +def local_command(cmd): + return cmd + +class Host(object): + + def __init__(self, host, username, password): + self.host = host + self.username = username + self.password = password + + def exec_command(self, cmd): + assert not self.host + return local_command(cmd) + + def get_pci_devices(self): + data = self.exec_command(['lspci', '-nnmm']) + parser = LspciNNMMParser(data) + devices = parser.parse_items() + return [PCIDevice(device) for device in devices] + +class RemoteHost(Host): + + def exec_command(self, cmd): + return remote_command(self.host, self.username, self.password, cmd) + + +def main(): + """Entry Point""" + + parser = ArgumentParser(prog="hwinfo") + parser.add_argument("cmd") + parser.add_argument("host") + parser.add_argument("username") + parser.add_argument("password") + + args = parser.parse_args() + + host = RemoteHost(args.host, args.username, args.password) + + if args.cmd == 'list': + devices = host.get_pci_devices() + for device in devices: + print device.get_info() + + diff --git a/setup.py b/setup.py index 2587f85..64017bd 100644 --- a/setup.py +++ b/setup.py @@ -6,4 +6,12 @@ name='python-hwinfo', author='Rob Dobson', packages=find_packages(), + entry_points = { + 'console_scripts': [ + 'hwinfo = hwinfo.tools.inspector:main', + ] + }, + install_requires = [ + 'paramiko', + ], ) From 0c1e4be4bf6460062bedc54f5f0b9212b897b982 Mon Sep 17 00:00:00 2001 From: Rob Dobson Date: Mon, 14 Jul 2014 17:13:35 +0100 Subject: [PATCH 2/3] Renaming pci_device_typ to pci_device_class to be more helpful. Signed-off-by: Rob Dobson --- hwinfo/pci/__init__.py | 3 +++ hwinfo/pci/lspci.py | 10 +++++----- hwinfo/pci/tests/test_lspci.py | 33 +++++++++++++++++---------------- hwinfo/pci/tests/test_pci.py | 8 ++++++-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/hwinfo/pci/__init__.py b/hwinfo/pci/__init__.py index aa6f92b..f51edf5 100644 --- a/hwinfo/pci/__init__.py +++ b/hwinfo/pci/__init__.py @@ -56,6 +56,9 @@ def get_pci_id(self): self.lookup_value('pci_subdevice_id'), ) + def get_pci_class(self): + return self.lookup_value('pci_device_class') + def is_subdevice(self): return self.lookup_value('pci_subvendor_id') and self.lookup_value('pci_subdevice_id') diff --git a/hwinfo/pci/lspci.py b/hwinfo/pci/lspci.py index 68aa23a..4e8e012 100644 --- a/hwinfo/pci/lspci.py +++ b/hwinfo/pci/lspci.py @@ -9,7 +9,7 @@ class LspciVVParser(CommandParser): """Parser object for the output of lspci -vv""" ITEM_REGEXS = [ - r'(?P([0-9][0-9]:[0-9][0-9]\.[0-9]))\ (?P[\w\ ]*):\ (?P(.*))\n', + r'(?P([0-9][0-9]:[0-9][0-9]\.[0-9]))\ (?P[\w\ ]*):\ (?P(.*))\n', r'Product\ Name:\ (?P(.)*)\n', r'Subsystem:\ (?P(.)*)\n', ] @@ -18,7 +18,7 @@ class LspciVVParser(CommandParser): MUST_HAVE_FIELDS = [ 'pci_device_bus_id', - 'pci_device_type', + 'pci_device_class_name', 'pci_device_string', ] @@ -27,7 +27,7 @@ class LspciNParser(CommandParser): #ff:0d.1 0880: 8086:0ee3 (rev 04) ITEM_REGEXS = [ - r'(?P([0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]\.[0-9a-f]))\ (?P[0-9a-f]{4}):\ (?P[0-9a-f]{4}):(?P[0-9a-f]{4})', + r'(?P([0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]\.[0-9a-f]))\ (?P[0-9a-f]{4}):\ (?P[0-9a-f]{4}):(?P[0-9a-f]{4})', ] ITEM_SEPERATOR = "\n" @@ -36,7 +36,7 @@ class LspciNParser(CommandParser): 'pci_device_bus_id', 'pci_device_id', 'pci_vendor_id', - 'pci_device_type_id', + 'pci_device_class', ] @@ -49,7 +49,7 @@ class LspciNNMMParser(CommandParser): #02:00.1 "Ethernet controller [0200]" "Broadcom Corporation [14e4]" "NetXtreme II BCM5716 Gigabit Ethernet [163b]" -r20 "Dell [1028]" "Device [02a3]" ITEM_REGEXS = [ - r'(?P([0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]))\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"' \ + r'(?P([0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]))\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"' \ + r'\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"' \ + r'\ .*\ "((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*"\ "((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*', ] diff --git a/hwinfo/pci/tests/test_lspci.py b/hwinfo/pci/tests/test_lspci.py index e5f0633..19a05e8 100644 --- a/hwinfo/pci/tests/test_lspci.py +++ b/hwinfo/pci/tests/test_lspci.py @@ -12,7 +12,8 @@ class TestSingleDeviceVVParse(unittest.TestCase): DEVICE_REC = { 'pci_device_string': 'Broadcom Corporation NetXtreme II BCM5716 Gigabit Ethernet (rev 20)', - 'pci_device_type': 'Ethernet controller', + 'pci_device_class': '0200', + 'pci_device_class_name': 'Ethernet controller', 'pci_device_bus_id': '02:00.0', 'pci_device_sub_string': 'Dell Device 0488', 'pci_device_vpd_product_name': 'Broadcom NetXtreme II Ethernet Controller', @@ -36,9 +37,9 @@ def test_pci_device_bus_id(self): rec = self.parser.parse_items().pop() self._assert_rec_key(rec, 'pci_device_bus_id') - def test_pci_device_type(self): + def test_pci_device_class_name(self): rec = self.parser.parse_items().pop() - self._assert_rec_key(rec, 'pci_device_type') + self._assert_rec_key(rec, 'pci_device_class_name') def test_pci_device_sub_string(self): rec = self.parser.parse_items().pop() @@ -70,7 +71,7 @@ class TestSingleDeviceNParse(unittest.TestCase): 'pci_device_bus_id': 'ff:10.5', 'pci_vendor_id': '8086', 'pci_device_id': '0eb5', - 'pci_device_type_id': '0880', + 'pci_device_class': '0880', } def setUp(self): @@ -89,8 +90,8 @@ def test_pci_vendor_id(self): def test_pci_device_id(self): self._assert_rec_key('pci_device_id') - def test_pci_device_type_id(self): - self._assert_rec_key('pci_device_type_id') + def test_pci_device_class(self): + self._assert_rec_key('pci_device_class') class TestMultiDeviceNParse(unittest.TestCase): @@ -113,8 +114,8 @@ class TestSingleDeviceNNMMParse(unittest.TestCase): DEVICE_REC = { 'pci_device_bus_id': '02:00.0', - 'pci_device_type_id': '0200', - 'pci_device_type_name': 'Ethernet controller', + 'pci_device_class': '0200', + 'pci_device_class_name': 'Ethernet controller', 'pci_vendor_name': 'Broadcom Corporation', 'pci_vendor_id': '14e4', 'pci_device_id': '163b', @@ -135,11 +136,11 @@ def _assert_rec_key(self, key): def test_pci_device_bus_id(self): self._assert_rec_key('pci_device_bus_id') - def test_pci_device_type_id(self): - self._assert_rec_key('pci_device_type_id') + def test_pci_device_class(self): + self._assert_rec_key('pci_device_class') - def test_pci_device_type_name(self): - self._assert_rec_key('pci_device_type_name') + def test_pci_device_class_name(self): + self._assert_rec_key('pci_device_class_name') def test_pci_vendor_name(self): self._assert_rec_key('pci_vendor_name') @@ -168,8 +169,8 @@ class LsiDeviceParse(TestSingleDeviceNNMMParse): DEVICE_REC = { 'pci_device_bus_id': '03:00.0', - 'pci_device_type_id': '0100', - 'pci_device_type_name': 'SCSI storage controller', + 'pci_device_class': '0100', + 'pci_device_class_name': 'SCSI storage controller', 'pci_vendor_name': 'LSI Logic / Symbios Logic', 'pci_vendor_id': '1000', 'pci_device_id': '0058', @@ -187,8 +188,8 @@ class IntelUSBControllerDeviceParse(TestSingleDeviceNNMMParse): DEVICE_REC = { 'pci_device_bus_id': '00:1d.0', - 'pci_device_type_id': '0c03', - 'pci_device_type_name': 'USB controller', + 'pci_device_class': '0c03', + 'pci_device_class_name': 'USB controller', 'pci_vendor_name': 'Intel Corporation', 'pci_vendor_id': '8086', 'pci_device_id': '3b34', diff --git a/hwinfo/pci/tests/test_pci.py b/hwinfo/pci/tests/test_pci.py index 2b6afff..f43c68b 100644 --- a/hwinfo/pci/tests/test_pci.py +++ b/hwinfo/pci/tests/test_pci.py @@ -7,8 +7,8 @@ class TestPCIDeviceObject(unittest.TestCase): DEVICE_REC = { 'pci_device_bus_id': '02:00.0', - 'pci_device_type_id': '0200', - 'pci_device_type_name': 'Ethernet controller', + 'pci_device_class': '0200', + 'pci_device_class_name': 'Ethernet controller', 'pci_vendor_name': 'Broadcom Corporation', 'pci_vendor_id': '14e4', 'pci_device_id': '163b', @@ -64,3 +64,7 @@ def test_is_subdevice(self): def test_get_device_info(self): info = self.device.get_info() self.assertEqual(info, 'Dell [Device 02a3] (Broadcom Corporation NetXtreme II BCM5716 Gigabit Ethernet)') + + def test_get_device_class(self): + pci_class = self.device.get_pci_class() + self.assertEqual(pci_class, '0200') From 1a05bbb63ca6296f927b606549914c5de97b65c5 Mon Sep 17 00:00:00 2001 From: Rob Dobson Date: Mon, 14 Jul 2014 19:04:42 +0100 Subject: [PATCH 3/3] Improving the CLI and adding a couple of host specific tests. Signed-off-by: Rob Dobson --- hwinfo/pci/__init__.py | 14 ++++ hwinfo/pci/lspci.py | 4 +- hwinfo/pci/tests/test_lspci.py | 55 +++++++++++++ hwinfo/tools/inspector.py | 137 ++++++++++++++++++++++++++++----- setup.py | 1 + tox.ini | 6 +- 6 files changed, 193 insertions(+), 24 deletions(-) diff --git a/hwinfo/pci/__init__.py b/hwinfo/pci/__init__.py index f51edf5..7742ce8 100644 --- a/hwinfo/pci/__init__.py +++ b/hwinfo/pci/__init__.py @@ -68,3 +68,17 @@ def get_info(self): return "%s %s (%s %s)" % (self.get_subvendor_name(), self.get_subdevice_name(), self.get_vendor_name(), self.get_device_name()) else: return "%s %s" % (self.get_vendor_name(), self.get_device_name()) + + def get_rec(self): + rec = {} + rec['vendor_name'] = self.get_vendor_name() + rec['device_name'] = self.get_device_name() + rec['vendor_id'] = self.get_vendor_id() + rec['device_id'] = self.get_device_id() + rec['class'] = self.get_pci_class() + rec['subvendor_name'] = self.get_subvendor_name() + rec['subdevice_name'] = self.get_subdevice_name() + rec['subvendor_id'] = self.get_subvendor_id() + rec['subdevice_id'] = self.get_subdevice_id() + + return rec diff --git a/hwinfo/pci/lspci.py b/hwinfo/pci/lspci.py index 4e8e012..30f6323 100644 --- a/hwinfo/pci/lspci.py +++ b/hwinfo/pci/lspci.py @@ -40,7 +40,7 @@ class LspciNParser(CommandParser): ] -LABEL_REGEX = r'[\w+\ \.\-\/]+' +LABEL_REGEX = r'[\w+\ \.\-\/\[\]\(\)]+' CODE_REGEX = r'[0-9a-fA-F]{4}' class LspciNNMMParser(CommandParser): @@ -51,7 +51,7 @@ class LspciNNMMParser(CommandParser): ITEM_REGEXS = [ r'(?P([0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]))\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"' \ + r'\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"\ "(?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\]"' \ - + r'\ .*\ "((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*"\ "((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*', + + r'\ .*\"((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*"\ "((?P' + LABEL_REGEX + r')\ \[(?P' + CODE_REGEX + r')\])*', ] ITEM_SEPERATOR = "\n" diff --git a/hwinfo/pci/tests/test_lspci.py b/hwinfo/pci/tests/test_lspci.py index 19a05e8..82d1483 100644 --- a/hwinfo/pci/tests/test_lspci.py +++ b/hwinfo/pci/tests/test_lspci.py @@ -200,6 +200,61 @@ class IntelUSBControllerDeviceParse(TestSingleDeviceNNMMParse): 'pci_subdevice_id': '02a3', } +class EmulexNicDeviceParse(TestSingleDeviceNNMMParse): + + SAMPLE_DATA = '0c:00.0 "Ethernet controller [0200]" "Emulex Corporation [19a2]" "OneConnect 10Gb NIC (be3) [0710]" -r02 "Emulex Corporation [10df]" "Device [e70b]"' + + DEVICE_REC = { + 'pci_device_bus_id': '0c:00.0', + 'pci_device_class': '0200', + 'pci_device_class_name': 'Ethernet controller', + 'pci_vendor_name': 'Emulex Corporation', + 'pci_vendor_id': '19a2', + 'pci_device_id': '0710', + 'pci_device_name': 'OneConnect 10Gb NIC (be3)', + 'pci_subvendor_name': 'Emulex Corporation', + 'pci_subvendor_id': '10df', + 'pci_subdevice_name': 'Device', + 'pci_subdevice_id': 'e70b', + } + +class LsiSASDeviceParse(TestSingleDeviceNNMMParse): + + SAMPLE_DATA = '06:00.0 "Serial Attached SCSI controller [0107]" "LSI Logic / Symbios Logic [1000]" "SAS2004 PCI-Express Fusion-MPT SAS-2 [Spitfire] [0070]" -r03 "IBM [1014]" "Device [03f8]"' + + DEVICE_REC = { + 'pci_device_bus_id': '06:00.0', + 'pci_device_class': '0107', + 'pci_device_class_name': 'Serial Attached SCSI controller', + 'pci_vendor_name': 'LSI Logic / Symbios Logic', + 'pci_vendor_id': '1000', + 'pci_device_id': '0070', + 'pci_device_name': 'SAS2004 PCI-Express Fusion-MPT SAS-2 [Spitfire]', + 'pci_subvendor_name': 'IBM', + 'pci_subvendor_id': '1014', + 'pci_subdevice_name': 'Device', + 'pci_subdevice_id': '03f8', + } + +class BroadcomNetDeviceParse(TestSingleDeviceNNMMParse): + + SAMPLE_DATA = '01:00.0 "Ethernet controller [0200]" "Broadcom Corporation [14e4]" "NetXtreme BCM5720 Gigabit Ethernet PCIe [165f]" "Dell [1028]" "Device [1f5b]"' + + DEVICE_REC = { + 'pci_device_bus_id': '01:00.0', + 'pci_device_class': '0200', + 'pci_device_class_name': 'Ethernet controller', + 'pci_vendor_name': 'Broadcom Corporation', + 'pci_vendor_id': '14e4', + 'pci_device_id': '165f', + 'pci_device_name': 'NetXtreme BCM5720 Gigabit Ethernet PCIe', + 'pci_subvendor_name': 'Dell', + 'pci_subvendor_id': '1028', + 'pci_subdevice_name': 'Device', + 'pci_subdevice_id': '1f5b', + } + + class TestMultiDeviceNNMMParse(unittest.TestCase): SAMPLE_FILE = '%s/lspci-nnmm' % DATA_DIR diff --git a/hwinfo/tools/inspector.py b/hwinfo/tools/inspector.py index f238a65..e2498dd 100644 --- a/hwinfo/tools/inspector.py +++ b/hwinfo/tools/inspector.py @@ -1,17 +1,19 @@ #!/usr/bin/env python from argparse import ArgumentParser +from prettytable import PrettyTable import paramiko from hwinfo.pci import PCIDevice from hwinfo.pci.lspci import * +from hwinfo.host.dmidecode import * def remote_command(host, username, password, cmd): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(host, username=username, password=password, timeout=10) cmdstr = ' '.join(cmd) - print "Executing '%s' on host '%s'" % (cmdstr, host) + #print "Executing '%s' on host '%s'" % (cmdstr, host) _, stdout, stderr = client.exec_command(cmdstr) output = stdout.readlines() error = stderr.readlines() @@ -25,14 +27,16 @@ def local_command(cmd): class Host(object): - def __init__(self, host, username, password): + def __init__(self, host='localhost', username=None, password=None): self.host = host self.username = username self.password = password def exec_command(self, cmd): - assert not self.host - return local_command(cmd) + if self.host == 'localhost': + return local_command(cmd) + else: + return remote_command(self.host, self.username, self.password, cmd) def get_pci_devices(self): data = self.exec_command(['lspci', '-nnmm']) @@ -40,28 +44,119 @@ def get_pci_devices(self): devices = parser.parse_items() return [PCIDevice(device) for device in devices] -class RemoteHost(Host): - - def exec_command(self, cmd): - return remote_command(self.host, self.username, self.password, cmd) - + def get_info(self): + data = self.exec_command(['dmidecode']) + parser = DmidecodeParser(data) + rec = parser.parse() + return rec + +def pci_filter(devices, types): + res = [] + for device in devices: + for t in types: + if device.get_pci_class().startswith(t): + res.append(device) + break + return res + +def pci_filter_for_nics(devices): + nic_types = ['02'] + return pci_filter(devices, nic_types) + +def pci_filter_for_storage(devices): + storage_types = ['00', '01'] + return pci_filter(devices, storage_types) + +def pci_filter_for_gpu(devices): + gpu_types = ['03'] + return pci_filter(devices, gpu_types) + +def print_lines(lines): + max_len = 0 + output = [] + for line in lines: + output.append(line) + if len(line) > max_len: + max_len = len(line) + print "" + print "-" * max_len + print '\n'.join(output) + print "-" * max_len + print "" + +def rec_to_table(rec): + table = PrettyTable(["Key", "Value"]) + table.align['Key'] = 'l' + table.align['Value'] = 'l' + for k, v in rec.iteritems(): + table.add_row([k, v]) + return table + +def tabulate_pci_recs(recs): + header = [ + 'vendor_name', + 'vendor_id', + 'device_name', + 'device_id', + 'subvendor_name', + 'subvendor_id', + 'subdevice_name', + 'subdevice_id', + ] + table = PrettyTable(header) + for rec in recs: + vls = [rec[k] for k in header] + table.add_row(vls) + return table def main(): """Entry Point""" parser = ArgumentParser(prog="hwinfo") - parser.add_argument("cmd") - parser.add_argument("host") - parser.add_argument("username") - parser.add_argument("password") - args = parser.parse_args() - - host = RemoteHost(args.host, args.username, args.password) - - if args.cmd == 'list': - devices = host.get_pci_devices() - for device in devices: - print device.get_info() + filter_choices = ['bios', 'nic', 'storage', 'gpu'] + parser.add_argument("-f", "--filter", choices=filter_choices) + parser.add_argument("-m", "--machine", default='localhost') + parser.add_argument("-u", "--username") + parser.add_argument("-p", "--password") + args = parser.parse_args() + host = Host(args.machine, args.username, args.password) + + options = [] + + if args.filter: + filter_args = args.filter.split(',') + for arg in filter_args: + options.append(arg.strip()) + else: + options = filter_choices + + if 'bios' in options: + print "Bios Info:" + print "" + print rec_to_table(host.get_info()) + print "" + + if 'nic' in options: + devices = pci_filter_for_nics(host.get_pci_devices()) + print "Ethernet Controller Info:" + print "" + print tabulate_pci_recs([dev.get_rec() for dev in devices]) + print "" + + if 'storage' in options: + devices = pci_filter_for_storage(host.get_pci_devices()) + print "Storage Controller Info:" + print "" + print tabulate_pci_recs([dev.get_rec() for dev in devices]) + print "" + + if 'gpu' in options: + devices = pci_filter_for_gpu(host.get_pci_devices()) + if devices: + print "GPU Info:" + print "" + print tabulate_pci_recs([dev.get_rec() for dev in devices]) + print "" diff --git a/setup.py b/setup.py index 64017bd..e088bcc 100644 --- a/setup.py +++ b/setup.py @@ -13,5 +13,6 @@ }, install_requires = [ 'paramiko', + 'prettytable', ], ) diff --git a/tox.ini b/tox.ini index 4322459..a753882 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,8 @@ deps= nose coverage mock + prettytable + paramiko commands= coverage @@ -14,6 +16,8 @@ commands= [testenv:lint] basepython=python2.7 -deps=pylint +deps= + pylint + prettytable commands= pylint -r n --rcfile=.pylint.rc hwinfo