diff --git a/hwinfo/pci/__init__.py b/hwinfo/pci/__init__.py index 7742ce8..2005457 100644 --- a/hwinfo/pci/__init__.py +++ b/hwinfo/pci/__init__.py @@ -2,6 +2,8 @@ class PCIDevice(object): + NONE_VALUE = 'unknown' + def __init__(self, record): self.rec = record @@ -9,58 +11,81 @@ def lookup_value(self, k): if k in self.rec: return self.rec[k] else: - return "Unknown" + return None + + def _fmt(self, value, wrap=None): + if not value: + return self.NONE_VALUE + else: + if wrap: + return "%s%s%s" % (wrap, value, wrap) + else: + return value def get_device_name(self): + wrap = None name = self.lookup_value('pci_device_name') + + # Fall back to using pci_device_string if it exists. + if not name: + name = self.lookup_value('pci_device_string') + wrap = '-' + if name == 'Device': # If the input has come from lspci, this is the value for # not being able to find a key in the pciids db. return '[Device %s]' % self.get_device_id() else: - return name + return self._fmt(name, wrap) def get_device_id(self): - return self.lookup_value('pci_device_id') + return self._fmt(self.lookup_value('pci_device_id')) def get_vendor_name(self): - return self.lookup_value('pci_vendor_name') + return self._fmt(self.lookup_value('pci_vendor_name')) def get_vendor_id(self): - return self.lookup_value('pci_vendor_id') + return self._fmt(self.lookup_value('pci_vendor_id')) def get_subdevice_name(self): name = self.lookup_value('pci_subdevice_name') + wrap = None + + # Fall back to using pci_device_string if it exists. + if not name: + name = self.lookup_value('pci_device_sub_string') + wrap = '-' + if name == 'Device': # If the input has come from lspci, this is the value for # not being able to find a key in the pciids db. return '[Device %s]' % self.get_subdevice_id() else: - return name + return self._fmt(name, wrap) def get_subdevice_id(self): - return self.lookup_value('pci_subdevice_id') + return self._fmt(self.lookup_value('pci_subdevice_id')) def get_subvendor_name(self): - return self.lookup_value('pci_subvendor_name') + return self._fmt(self.lookup_value('pci_subvendor_name')) def get_subvendor_id(self): - return self.lookup_value('pci_subvendor_id') + return self._fmt(self.lookup_value('pci_subvendor_id')) def get_pci_id(self): return "%s:%s %s:%s" % ( - self.lookup_value('pci_vendor_id'), - self.lookup_value('pci_device_id'), - self.lookup_value('pci_subvendor_id'), - self.lookup_value('pci_subdevice_id'), + self._fmt(self.lookup_value('pci_vendor_id')), + self._fmt(self.lookup_value('pci_device_id')), + self._fmt(self.lookup_value('pci_subvendor_id')), + self._fmt(self.lookup_value('pci_subdevice_id')), ) def get_pci_class(self): - return self.lookup_value('pci_device_class') + return self._fmt(self.lookup_value('pci_device_class')) def is_subdevice(self): - return self.lookup_value('pci_subvendor_id') and self.lookup_value('pci_subdevice_id') + return self.lookup_value('pci_subvendor_id') and self.lookup_value('pci_subdevice_id') or self.lookup_value('pci_device_sub_string') def get_info(self): diff --git a/hwinfo/pci/lspci.py b/hwinfo/pci/lspci.py index 30f6323..4e2d0ba 100644 --- a/hwinfo/pci/lspci.py +++ b/hwinfo/pci/lspci.py @@ -5,11 +5,15 @@ class ParserException(Exception): pass +LABEL_REGEX = r'[\w+\ \.\-\/\[\]\(\)]+' +CODE_REGEX = r'[0-9a-fA-F]{4}' +BUSID_REGEX = r'[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]' + 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(' + BUSID_REGEX + r'))\ (?P' + LABEL_REGEX + r'):\ (?P(.*))\n', r'Product\ Name:\ (?P(.)*)\n', r'Subsystem:\ (?P(.)*)\n', ] @@ -27,7 +31,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(' + BUSID_REGEX + r'))\ (?P' + CODE_REGEX + r'):\ (?P' + CODE_REGEX + r'):(?P' + CODE_REGEX + r')', ] ITEM_SEPERATOR = "\n" @@ -39,17 +43,13 @@ class LspciNParser(CommandParser): 'pci_device_class', ] - -LABEL_REGEX = r'[\w+\ \.\-\/\[\]\(\)]+' -CODE_REGEX = r'[0-9a-fA-F]{4}' - class LspciNNMMParser(CommandParser): """Parser object for the output of lspci -nnmm""" #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(' + BUSID_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')\])*', ] diff --git a/hwinfo/pci/tests/test_lspci.py b/hwinfo/pci/tests/test_lspci.py index 73e4b35..3b28848 100644 --- a/hwinfo/pci/tests/test_lspci.py +++ b/hwinfo/pci/tests/test_lspci.py @@ -62,6 +62,13 @@ def setUp(self): def test_parse_all_devices(self): recs = self.parser.parse_items() self.assertEqual(len(recs), 58) + found = False + for rec in recs: + print rec + if rec['pci_device_bus_id'] == '02:00.0': + self.assertEqual(rec['pci_device_class_name'], 'VGA compatible controller') + found = True + self.assertEqual(found, True) class TestSingleDeviceNParse(unittest.TestCase): diff --git a/hwinfo/tools/inspector.py b/hwinfo/tools/inspector.py index 97b981b..5c35ab8 100644 --- a/hwinfo/tools/inspector.py +++ b/hwinfo/tools/inspector.py @@ -5,6 +5,7 @@ import paramiko import subprocess import os +import sys from hwinfo.pci import PCIDevice from hwinfo.pci.lspci import * @@ -76,11 +77,14 @@ def get_cpu_info(self): parser = cpuinfo.CPUInfoParser(data) return parser.parse_items() +class FileNotFound(Exception): + pass + def search_for_file(dirname, filename): for root, _, files in os.walk(dirname): if filename in files: return os.path.join(root, filename) - raise Exception("Could not find '%s' in directory '%s'" % (filename, dirname)) + raise FileNotFound("Could not find '%s' in directory '%s'" % (filename, dirname)) def read_from_file(filename): fh = open(filename, 'r') @@ -88,6 +92,25 @@ def read_from_file(filename): fh.close() return data +def parse_data(parser, data): + p = parser(data) + return p.parse_items() + +def combine_recs(rec_list, key): + """Use a common key to combine a list of recs""" + final_recs = {} + for rec in rec_list: + rec_key = rec[key] + if rec_key in final_recs: + for k, v in rec.iteritems(): + if k in final_recs[rec_key] and final_recs[rec_key][k] != v: + raise Exception("Mis-match for key '%s'" % k) + final_recs[rec_key][k] = v + else: + final_recs[rec_key] = rec + return final_recs.values() + + class HostFromLogs(Host): def __init__(self, dirname): @@ -106,6 +129,20 @@ def get_dmidecode_data(self): def get_cpuinfo_data(self): return self._load_from_file('cpuinfo') + def get_pci_devices(self): + try: + devs = super(HostFromLogs, self).get_pci_devices() + return devs + except FileNotFound: + # Fall back to looking for the file lspci-vv.out + print "***lspci-nnm.out found. Falling back to looking for lspci-vv.out and lspci-n.out.***" + lspci_vv_recs = parse_data(LspciVVParser, self._load_from_file('lspci-vv.out')) + lspci_n_recs = parse_data(LspciNParser, self._load_from_file('lspci-n.out')) + all_recs = lspci_vv_recs + lspci_n_recs + recs = combine_recs(all_recs, 'pci_device_bus_id') + return [PCIDevice(rec) for rec in recs] + + def pci_filter(devices, types): res = [] for device in devices: @@ -167,6 +204,12 @@ def tabulate_cpu_recs(recs): ] return tabulate_recs(recs, header) +def print_unit(title, content): + print "%s" % title + print "" + print content + print "" + def main(): """Entry Point""" @@ -184,6 +227,10 @@ def main(): if args.logs: host = HostFromLogs(args.logs) else: + if args.machine and not args.username or not args.password: + print "Error: you must specify a username and password to query a remote machine." + sys.exit(1) + host = Host(args.machine, args.username, args.password) options = [] @@ -196,35 +243,20 @@ def main(): options = filter_choices if 'bios' in options: - print "Bios Info:" - print "" - print rec_to_table(host.get_info()) - print "" + print_unit("Bios Info:", rec_to_table(host.get_info())) if 'cpu' in options: - print "CPU Info:" - print "" - print tabulate_cpu_recs(host.get_cpu_info()) - print "" + print_unit("CPU Info:", tabulate_cpu_recs(host.get_cpu_info())) 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 "" + print_unit("Ethernet Controller Info:", tabulate_pci_recs([dev.get_rec() for dev in devices])) 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 "" + print_unit("Storage Controller Info:", tabulate_pci_recs([dev.get_rec() for dev in devices])) 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 "" + print_unit("GPU Info:", tabulate_pci_recs([dev.get_rec() for dev in devices])) diff --git a/hwinfo/tools/tests/test_inspector.py b/hwinfo/tools/tests/test_inspector.py index 3e3ec6f..8762777 100644 --- a/hwinfo/tools/tests/test_inspector.py +++ b/hwinfo/tools/tests/test_inspector.py @@ -142,4 +142,117 @@ def test_rec_to_table(self, mock_pt_cls): ] mock_table.add_row.assert_has_calls(expected_calls, any_order=True) +class CombineRecsTests(unittest.TestCase): + + + def _validate_rec(self, rec, key, value): + self.assertEqual(rec[key], value) + + def test_combine_two_recs(self): + recs = [ + { + 'name':'rec1', + 'valuea': '10', + 'valueb': '11', + 'valuec': '12', + }, + { + 'name': 'rec1', + 'valued': '5', + 'valuee': '8', + }, + ] + + combined_recs = inspector.combine_recs(recs, 'name') + self.assertEqual(len(combined_recs), 1) + rec = combined_recs[0] + self._validate_rec(rec, 'valuea', '10') + self._validate_rec(rec, 'valueb', '11') + self._validate_rec(rec, 'valuec', '12') + self._validate_rec(rec, 'valued', '5') + self._validate_rec(rec, 'valuee', '8') + + def test_combine_three_recs(self): + recs = [ + { + 'name':'rec1', + 'valuea': '10', + 'valueb': '11', + 'valuec': '12', + }, + { + 'name': 'rec1', + 'valued': '5', + 'valuee': '8', + }, + { + 'name': 'rec1', + 'valuef': '1', + 'valueg': '2', + }, + ] + + combined_recs = inspector.combine_recs(recs, 'name') + self.assertEqual(len(combined_recs), 1) + rec = combined_recs[0] + self._validate_rec(rec, 'valuea', '10') + self._validate_rec(rec, 'valueb', '11') + self._validate_rec(rec, 'valuec', '12') + self._validate_rec(rec, 'valued', '5') + self._validate_rec(rec, 'valuee', '8') + self._validate_rec(rec, 'valuef', '1') + self._validate_rec(rec, 'valueg', '2') + + def test_combine_three_recs_to_two(self): + recs = [ + { + 'name':'rec1', + 'valuea': '10', + 'valueb': '11', + 'valuec': '12', + }, + { + 'name': 'rec2', + 'valued': '5', + 'valuee': '8', + }, + { + 'name': 'rec1', + 'valuef': '1', + 'valueg': '2', + }, + ] + + combined_recs = inspector.combine_recs(recs, 'name') + self.assertEqual(len(combined_recs), 2) + for rec in combined_recs: + if rec['name'] == 'rec1': + self._validate_rec(rec, 'valuea', '10') + self._validate_rec(rec, 'valueb', '11') + self._validate_rec(rec, 'valuec', '12') + self._validate_rec(rec, 'valuef', '1') + self._validate_rec(rec, 'valueg', '2') + elif rec['name'] == 'rec2': + self._validate_rec(rec, 'valued', '5') + self._validate_rec(rec, 'valuee', '8') + else: + raise Exception("Unexpected rec: %s" % rec) + + def test_colliding_values(self): + recs = [ + { + 'name':'rec1', + 'valuea': '10', + 'valueb': '11', + 'valuec': '12', + }, + { + 'name': 'rec1', + 'valuea': '5', + 'valuee': '8', + }, + ] + with self.assertRaises(Exception) as context: + combined_recs = inspector.combine_recs(recs, 'name') + self.assertEqual(context.exception.message, "Mis-match for key 'valuea'")