wip: use lshw for inventory, do cpu/disks/memory/network/motherboard; update supermicro fatwin support.

This commit is contained in:
Thomas Davis 2019-09-03 14:04:25 -07:00
commit c312396326
4 changed files with 263 additions and 47 deletions

View file

@ -6,11 +6,13 @@ from netbox_agent.config import netbox_instance as nb, config
from netbox_agent.misc import is_tool from netbox_agent.misc import is_tool
from netbox_agent.raid.hp import HPRaid from netbox_agent.raid.hp import HPRaid
from netbox_agent.raid.storcli import StorcliRaid from netbox_agent.raid.storcli import StorcliRaid
from netbox_agent.lshw import LSHW
INVENTORY_TAG = { INVENTORY_TAG = {
'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'}, 'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'},
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
'disk': {'name': 'hw:disk', 'slug': 'hw-disk'}, 'disk': {'name': 'hw:disk', 'slug': 'hw-disk'},
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
'network':{'name': 'hw:network', 'slug':'hw-network'},
'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'}, 'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'},
} }
@ -46,6 +48,25 @@ class Inventory():
self.raid = None self.raid = None
self.disks = [] self.disks = []
<<<<<<< HEAD
self.lshw = LSHW()
def find_or_create_manufacturer(self, name):
if name is None:
return None
manufacturer = nb.dcim.manufacturers.get(
name=name,
)
if not manufacturer:
logging.info('Creating missing manufacturer {name}'.format(name=name))
manufacturer = nb.dcim.manufacturers.create(
name=name,
slug=name.replace(' ','-').lower(),
)
logging.info('Creating missing manufacturer {name}'.format(name=name))
=======
def create_netbox_tags(): def create_netbox_tags():
for key, tag in INVENTORY_TAG.items(): for key, tag in INVENTORY_TAG.items():
nb_tag = nb.extras.tags.get( nb_tag = nb.extras.tags.get(
@ -61,37 +82,34 @@ class Inventory():
def get_cpus(self): def get_cpus(self):
model = None model = None
nb = None nb = None
>>>>>>> d39b692985797707ab33ee927e9d45bef0a73638
output = subprocess.getoutput('lscpu') return manufacturer
model_re = re.search(r'Model name: (.*)', output)
if len(model_re.groups()) > 0:
model = model_re.groups()[0].strip()
socket_re = re.search(r'Socket\(s\): (.*)', output)
if len(socket_re.groups()) > 0:
nb = int(socket_re.groups()[0].strip())
return nb, model
def create_netbox_cpus(self): def create_netbox_cpus(self):
nb_cpus, model = self.get_cpus() for cpu in self.lshw.get_hw_linux('cpu'):
for i in range(nb_cpus): manufacturer = self.find_or_create_manufacturer(cpu["vendor"])
_ = nb.dcim.inventory_items.create( _ = nb.dcim.inventory_items.create(
device=self.device_id, device=self.device_id,
tags=[INVENTORY_TAG['cpu']['name']], manufacturer = manufacturer.id,
name=model,
discovered=True, discovered=True,
description='CPU', tags=[INVENTORY_TAG['cpu']['name']],
name=cpu['product'],
description='{}'.format(cpu['location']),
asset_tag=cpu['location']
) )
logging.info('Creating CPU model {model}'.format(model=model))
logging.info('Creating CPU model {}'.format(cpu['product']))
def update_netbox_cpus(self): def update_netbox_cpus(self):
cpus_number, model = self.get_cpus() cpus = self.lshw.get_hw_linux('cpu')
nb_cpus = nb.dcim.inventory_items.filter( nb_cpus = nb.dcim.inventory_items.filter(
device_id=self.device_id, device_id=self.device_id,
tag=INVENTORY_TAG['cpu']['slug'], tag=INVENTORY_TAG['cpu']['slug'],
) )
if not len(nb_cpus) or \ if not len(nb_cpus) or \
len(nb_cpus) and cpus_number != len(nb_cpus): len(nb_cpus) and len(cpus) != len(nb_cpus):
for x in nb_cpus: for x in nb_cpus:
x.delete() x.delete()
self.create_netbox_cpus() self.create_netbox_cpus()
@ -118,20 +136,6 @@ class Inventory():
) )
return raid_cards return raid_cards
def find_or_create_manufacturer(self, name):
if name is None:
return None
manufacturer = nb.dcim.manufacturers.get(
name=name,
)
if not manufacturer:
manufacturer = nb.dcim.manufacturers.create(
name=name,
slug=name.lower(),
)
logging.info('Creating missing manufacturer {name}'.format(name=name))
return manufacturer
def create_netbox_raid_card(self, raid_card): def create_netbox_raid_card(self, raid_card):
manufacturer = self.find_or_create_manufacturer( manufacturer = self.find_or_create_manufacturer(
raid_card.get_manufacturer() raid_card.get_manufacturer()
@ -186,10 +190,41 @@ class Inventory():
self.create_netbox_raid_card(raid_card) self.create_netbox_raid_card(raid_card)
def get_disks(self): def get_disks(self):
ret = [] disks = []
for disk in self.lshw.get_hw_linux("storage"):
print(disk)
d = {}
d["name"] = ""
d['Size'] = '{} GB'.format(int(disk['size']/1024/1024/1024))
d['Model'] = disk['product']
d['logicalname'] = disk['logicalname']
d['description'] = disk['description']
if 'serial' in disk:
d['SN'] = disk['serial']
if 'vendor' in disk:
d['vendor'] = disk['vendor']
if disk['product'].startswith('ST'):
d['vendor'] = 'Seagate'
if disk['product'].startswith('Crucial'):
d['vendor'] = 'Crucial'
if disk['product'].startswith('INTEL'):
d['vendor'] = 'Intel'
if disk['product'].startswith('Samsung'):
d['vendor'] = 'Samsung'
disks.append(d)
for raid_card in self.get_raid_cards(): for raid_card in self.get_raid_cards():
ret += raid_card.get_physical_disks() disks += raid_card.get_physical_disks()
return ret
return disks
def get_netbox_disks(self): def get_netbox_disks(self):
disks = nb.dcim.inventory_items.filter( disks = nb.dcim.inventory_items.filter(
@ -200,12 +235,24 @@ class Inventory():
def create_netbox_disks(self): def create_netbox_disks(self):
for disk in self.get_disks(): for disk in self.get_disks():
m_id = 0
if "vendor" in disk:
manufacturer = self.find_or_create_manufacturer(disk["vendor"])
m_id = manufacturer.id
print("inserting disk")
print(disk)
_ = nb.dcim.inventory_items.create( _ = nb.dcim.inventory_items.create(
device=self.device_id, device=self.device_id,
discovered=True, discovered=True,
tags=[INVENTORY_TAG['disk']['name']], tags=[INVENTORY_TAG['disk']['name']],
name='{} ({})'.format(disk['Model'], disk['Size']), name='{} - {} ({})'.format(disk.get('description', 'Unknown'), disk.get('logicalname', 'Unknown'), disk.get('Size', 0)),
serial=disk['SN'], serial=disk['SN'],
part_id=disk['Model'],
description='{}'.format(disk.get('logicalname', 'Unknown')),
# asset_tag=disk['logicalname'],
manufacturer=m_id
) )
logging.info('Creating Disk {model} {serial}'.format( logging.info('Creating Disk {model} {serial}'.format(
model=disk['Model'], model=disk['Model'],
@ -298,7 +345,7 @@ class Inventory():
for memory in self.get_memory(): for memory in self.get_memory():
self.create_netbox_memory(memory) self.create_netbox_memory(memory)
def update_netbox_memory(self): def update_netbox_memories(self):
memories = self.get_memory() memories = self.get_memory()
nb_memories = self.get_netbox_memory() nb_memories = self.get_netbox_memory()
@ -316,7 +363,7 @@ class Inventory():
if config.inventory is None: if config.inventory is None:
return False return False
self.create_netbox_cpus() self.create_netbox_cpus()
self.create_netbox_memory() self.create_netbox_memories()
self.create_netbox_raid_cards() self.create_netbox_raid_cards()
self.create_netbox_disks() self.create_netbox_disks()
return True return True
@ -325,7 +372,7 @@ class Inventory():
if config.inventory is None or config.update_inventory is None: if config.inventory is None or config.update_inventory is None:
return False return False
self.update_netbox_cpus() self.update_netbox_cpus()
self.update_netbox_memory() self.update_netbox_memories()
self.update_netbox_raid_cards() self.update_netbox_raid_cards()
self.update_netbox_disks() self.update_netbox_disks()
return True return True

145
netbox_agent/lshw.py Normal file
View file

@ -0,0 +1,145 @@
import logging
import subprocess
import getpass
import json
from pprint import pprint
class LSHW():
def __init__(self):
self.hw_info = json.loads(subprocess.check_output(["lshw", "-quiet", "-json"],encoding='utf8'))
self.info = {}
self.memories = []
self.interfaces = []
self.cpus = []
self.power = []
self.disks = []
self.vendor = self.hw_info["vendor"]
self.product = self.hw_info["product"]
self.chassis_serial = self.hw_info["serial"]
self.motherboard_serial = self.hw_info["children"][0]["serial"]
self.motherboard = self.hw_info["children"][0]["product"]
for k in self.hw_info["children"]:
if k["class"] == "power":
# self.power[k["id"]] = k
self.power.append(k)
if "children" in k:
for j in k["children"]:
if j["class"] == "generic":
continue
if j["class"] == "storage":
self.find_storage(j)
if j["class"] == "memory":
self.find_memories(j)
if j["class"] == "processor":
self.find_cpus(j)
if j["class"] == "bridge":
self.walk_bridge(j)
def get_hw_linux(self, hwclass):
if hwclass == "cpu":
return self.cpus
if hwclass == "network":
return self.interfaces
if hwclass == 'storage':
return self.disks
if hwclass == 'memory':
return self.memories
def find_network(self, obj):
d = {}
d["name"] = obj["logicalname"]
d["macaddress"] = obj["serial"]
d["serial"] = obj["serial"]
d["product"] = obj["product"]
d["vendor"] = obj["vendor"]
d["description"] = obj["description"]
self.interfaces.append(d)
def find_storage(self, obj):
if "children" in obj:
for device in obj["children"]:
d = {}
d["logicalname"] = device["logicalname"]
d["product"] = device["product"]
d["serial"] = device["serial"]
d["version"] = device["version"]
d["size"] = device["size"]
d["description"] = device["description"]
self.disks.append(d)
elif "nvme" in obj["configuration"]["driver"]:
nvme = json.loads(subprocess.check_output(["nvme", '-list', '-o', 'json'],encoding='utf8'))
d = {}
d["vendor"] = obj["vendor"]
d["version"] = obj["version"]
d["product"] = obj["product"]
d['description'] = "NVME Disk"
d['product'] = nvme["Devices"][0]["ModelNumber"]
d['size'] = nvme["Devices"][0]["PhysicalSize"]
d['serial'] = nvme["Devices"][0]["SerialNumber"]
d['logicalname'] = nvme["Devices"][0]["DevicePath"]
self.disks.append(d)
def find_cpus(self, obj):
pprint(obj)
c = {}
c["product"] = obj["product"]
c["vendor"] = obj["vendor"]
c["description"] = obj["description"]
c["location"] = obj["slot"]
self.cpus.append(c)
def find_memories(self, obj):
if "children" not in obj:
print("not a DIMM memory.")
return
for dimm in obj["children"]:
if "empty" in dimm["description"]:
continue
d = {}
d["slot"] = dimm["slot"]
d["description"] = dimm["description"]
d["id"] = dimm["id"]
d["serial"] = dimm["serial"]
d["vendor"] = dimm["vendor"]
d["product"] = dimm["product"]
d["size"] = dimm["size"] / 2 ** 20 / 1024
self.memories.append(d)
def walk_bridge(self, obj):
if "children" not in obj:
return
for bus in obj["children"]:
if bus["class"] == "storage":
self.find_storage(bus)
if "children" in bus:
for b in bus["children"]:
if b["class"] == "storage":
self.find_storage(b)
if b["class"] == "network":
self.find_network(b)
if __name__ == "__main__":
pass

View file

@ -15,8 +15,11 @@ class ServerBase():
self.dmi = dmi self.dmi = dmi
else: else:
self.dmi = dmidecode.parse() self.dmi = dmidecode.parse()
self.system = self.dmi.get_by_type('System')
self.baseboard = self.dmi.get_by_type('Baseboard')
self.bios = self.dmi.get_by_type('BIOS') self.bios = self.dmi.get_by_type('BIOS')
self.chassis = self.dmi.get_by_type('Chassis')
self.system = self.dmi.get_by_type('System')
self.network = None self.network = None

View file

@ -1,6 +1,18 @@
from netbox_agent.location import Slot from netbox_agent.location import Slot
from netbox_agent.server import ServerBase from netbox_agent.server import ServerBase
"""
Supermicro DMI can be messed up. They depend on the vendor
to set the correct values. The endusers cannot
change them without buying a license from Supermicro.
There are 3 serial numbers in the system
1) System - this is used for the chassis information.
2) Baseboard - this is used for the blade.
3) Chassis - this is ignored.
"""
class SupermicroHost(ServerBase): class SupermicroHost(ServerBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -8,8 +20,8 @@ class SupermicroHost(ServerBase):
self.manufacturer = 'Supermicro' self.manufacturer = 'Supermicro'
def is_blade(self): def is_blade(self):
blade = self.get_product_name().startswith('SBI') blade = self.system[0]['Product Name'].startswith('SBI')
blade |= self.get_product_name().startswith('SYS') blade |= self.system[0]['Product Name'].startswith('SYS')
return blade return blade
def get_blade_slot(self): def get_blade_slot(self):
@ -21,17 +33,26 @@ class SupermicroHost(ServerBase):
# No supermicro on hands # No supermicro on hands
return None return None
def get_chassis_name(self): def get_service_tag(self):
if not self.is_blade(): return self.baseboard[0]['Serial Number'].strip()
return None
return 'Chassis {}'.format(self.get_service_tag()) def get_product_name(self):
if self.is_blade():
return self.baseboard[0]['Product Name'].strip()
return self.system[0]['Product Name'].strip()
def get_chassis(self): def get_chassis(self):
if self.is_blade(): if self.is_blade():
return self.dmi.get_by_type('Chassis')[0]['Version'] return self.system[0]['Product Name'].strip()
return self.get_product_name() return self.get_product_name()
def get_chassis_service_tag(self): def get_chassis_service_tag(self):
if self.is_blade(): if self.is_blade():
return self.dmi.get_by_type('Chassis')[0]['Serial Number'] return self.system[0]['Serial Number'].strip()
return self.get_service_tag() return self.get_service_tag()
def get_chassis_name(self):
if not self.is_blade():
return None
return 'Chassis {}'.format(self.get_chassis_service_tag())