use lshw for inventory (#58)
* Use lshw for inventory * Add motherboard to inventory * Handle non raid devices * Handle nvme drives
This commit is contained in:
parent
7049434612
commit
b7b22ae316
7 changed files with 500 additions and 157 deletions
|
@ -1,16 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import pynetbox
|
||||||
import re
|
|
||||||
|
|
||||||
from netbox_agent.config import netbox_instance as nb, config
|
from netbox_agent.config import netbox_instance as nb, config
|
||||||
from netbox_agent.misc import is_tool
|
from netbox_agent.misc import is_tool, get_vendor
|
||||||
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'},
|
||||||
|
'interface': {'name': 'hw:interface', 'slug': 'hw-interface'},
|
||||||
|
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
|
||||||
|
'motherboard': {'name': 'hw:motherboard', 'slug': 'hw-motherboard'},
|
||||||
'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'},
|
'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +44,13 @@ class Inventory():
|
||||||
self.create_netbox_tags()
|
self.create_netbox_tags()
|
||||||
self.server = server
|
self.server = server
|
||||||
netbox_server = self.server.get_netbox_server()
|
netbox_server = self.server.get_netbox_server()
|
||||||
|
|
||||||
self.device_id = netbox_server.id if netbox_server else None
|
self.device_id = netbox_server.id if netbox_server else None
|
||||||
self.raid = None
|
self.raid = None
|
||||||
self.disks = []
|
self.disks = []
|
||||||
|
|
||||||
|
self.lshw = LSHW()
|
||||||
|
|
||||||
def create_netbox_tags(self):
|
def create_netbox_tags(self):
|
||||||
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(
|
||||||
|
@ -58,42 +63,155 @@ class Inventory():
|
||||||
comments=tag['name'],
|
comments=tag['name'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_cpus(self):
|
def find_or_create_manufacturer(self, name):
|
||||||
model = None
|
if name is None:
|
||||||
nb = None
|
return None
|
||||||
|
|
||||||
output = subprocess.getoutput('lscpu')
|
manufacturer = nb.dcim.manufacturers.get(
|
||||||
model_re = re.search(r'Model name: (.*)', output)
|
name=name,
|
||||||
if len(model_re.groups()) > 0:
|
)
|
||||||
model = model_re.groups()[0].strip()
|
if not manufacturer:
|
||||||
socket_re = re.search(r'Socket\(s\): (.*)', output)
|
logging.info('Creating missing manufacturer {name}'.format(name=name))
|
||||||
if len(socket_re.groups()) > 0:
|
manufacturer = nb.dcim.manufacturers.create(
|
||||||
nb = int(socket_re.groups()[0].strip())
|
name=name,
|
||||||
return nb, model
|
slug=name.replace(' ', '-').replace('.', '').lower(),
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info('Creating missing manufacturer {name}'.format(name=name))
|
||||||
|
|
||||||
|
return manufacturer
|
||||||
|
|
||||||
|
def get_netbox_inventory(self, device_id, tag):
|
||||||
|
try:
|
||||||
|
items = nb.dcim.inventory_items.filter(
|
||||||
|
device_id=device_id,
|
||||||
|
tag=tag
|
||||||
|
)
|
||||||
|
except pynetbox.core.query.RequestError:
|
||||||
|
logging.info('Tag {tag} is missing, returning empty array.'.format(tag=tag))
|
||||||
|
items = []
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
def create_netbox_inventory_item(self, device_id, tags, vendor, name, serial, description):
|
||||||
|
manufacturer = self.find_or_create_manufacturer(vendor)
|
||||||
|
|
||||||
|
_ = nb.dcim.inventory_items.create(
|
||||||
|
device=device_id,
|
||||||
|
manufacturer=manufacturer.id,
|
||||||
|
discovered=True,
|
||||||
|
tags=tags,
|
||||||
|
name='{}'.format(name),
|
||||||
|
serial='{}'.format(serial),
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info('Creating inventory item {} {}/{} {} '.format(
|
||||||
|
vendor,
|
||||||
|
name,
|
||||||
|
serial,
|
||||||
|
description)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_hw_motherboards(self):
|
||||||
|
motherboards = []
|
||||||
|
|
||||||
|
m = {}
|
||||||
|
m['serial'] = self.lshw.motherboard_serial
|
||||||
|
m['vendor'] = self.lshw.vendor
|
||||||
|
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.motherboard)
|
||||||
|
m['description'] = '{} Motherboard'.format(self.lshw.motherboard)
|
||||||
|
|
||||||
|
motherboards.append(m)
|
||||||
|
|
||||||
|
return motherboards
|
||||||
|
|
||||||
|
def do_netbox_motherboard(self):
|
||||||
|
|
||||||
|
motherboards = self.get_hw_motherboards()
|
||||||
|
nb_motherboards = self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tag=INVENTORY_TAG['motherboard']['slug'])
|
||||||
|
|
||||||
|
for nb_motherboard in nb_motherboards:
|
||||||
|
if nb_motherboard.serial not in [x['serial'] for x in motherboards]:
|
||||||
|
logging.info('Deleting unknown motherboard {vendor} {motherboard}/{serial}'.format(
|
||||||
|
motherboard=self.lshw.motherboard,
|
||||||
|
serial=nb_motherboard.serial,
|
||||||
|
))
|
||||||
|
nb_motherboard.delete()
|
||||||
|
|
||||||
|
# create interfaces that are not in netbox
|
||||||
|
for motherboard in motherboards:
|
||||||
|
if motherboard.get('serial') not in [x.serial for x in nb_motherboards]:
|
||||||
|
self.create_netbox_inventory_item(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tags=[INVENTORY_TAG['motherboard']['name']],
|
||||||
|
vendor='{}'.format(motherboard.get('vendor', 'N/A')),
|
||||||
|
serial='{}'.format(motherboard.get('serial', 'No SN')),
|
||||||
|
name='{}'.format(motherboard.get('name')),
|
||||||
|
description='{}'.format(motherboard.get('description'))
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_netbox_interface(self, iface):
|
||||||
|
manufacturer = self.find_or_create_manufacturer(iface["vendor"])
|
||||||
|
_ = nb.dcim.inventory_items.create(
|
||||||
|
device=self.device_id,
|
||||||
|
manufacturer=manufacturer.id,
|
||||||
|
discovered=True,
|
||||||
|
tags=[INVENTORY_TAG['interface']['name']],
|
||||||
|
name="{}".format(iface['product']),
|
||||||
|
serial='{}'.format(iface['serial']),
|
||||||
|
description='{} {}'.format(iface['description'], iface['name'])
|
||||||
|
)
|
||||||
|
|
||||||
|
def do_netbox_interfaces(self):
|
||||||
|
nb_interfaces = self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tag=INVENTORY_TAG['interface']['slug'])
|
||||||
|
interfaces = self.lshw.interfaces
|
||||||
|
|
||||||
|
# delete interfaces that are in netbox but not locally
|
||||||
|
# use the serial_number has the comparison element
|
||||||
|
for nb_interface in nb_interfaces:
|
||||||
|
if nb_interface.serial not in [x['serial'] for x in interfaces]:
|
||||||
|
logging.info('Deleting unknown interface {serial}'.format(
|
||||||
|
serial=nb_interface.serial,
|
||||||
|
))
|
||||||
|
nb_interface.delete()
|
||||||
|
|
||||||
|
# create interfaces that are not in netbox
|
||||||
|
for iface in interfaces:
|
||||||
|
if iface.get('serial') not in [x.serial for x in nb_interfaces]:
|
||||||
|
self.create_netbox_interface(iface)
|
||||||
|
|
||||||
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='CPU {}'.format(cpu['location']),
|
||||||
|
# asset_tag=cpu['location']
|
||||||
)
|
)
|
||||||
logging.info('Creating CPU model {model}'.format(model=model))
|
|
||||||
|
|
||||||
def update_netbox_cpus(self):
|
logging.info('Creating CPU model {}'.format(cpu['product']))
|
||||||
cpus_number, model = self.get_cpus()
|
|
||||||
nb_cpus = nb.dcim.inventory_items.filter(
|
def do_netbox_cpus(self):
|
||||||
|
cpus = self.lshw.get_hw_linux('cpu')
|
||||||
|
nb_cpus = self.get_netbox_inventory(
|
||||||
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()
|
||||||
|
|
||||||
def get_raid_cards(self):
|
def get_raid_cards(self):
|
||||||
|
@ -111,31 +229,11 @@ class Inventory():
|
||||||
if len(self.raid.get_controllers()):
|
if len(self.raid.get_controllers()):
|
||||||
return controllers
|
return controllers
|
||||||
|
|
||||||
def get_netbox_raid_cards(self):
|
|
||||||
raid_cards = nb.dcim.inventory_items.filter(
|
|
||||||
device_id=self.device_id,
|
|
||||||
tag=INVENTORY_TAG['raid_card']['slug'],
|
|
||||||
)
|
|
||||||
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()
|
||||||
)
|
)
|
||||||
|
|
||||||
name = raid_card.get_product_name()
|
name = raid_card.get_product_name()
|
||||||
serial = raid_card.get_serial_number()
|
serial = raid_card.get_serial_number()
|
||||||
nb_raid_card = nb.dcim.inventory_items.create(
|
nb_raid_card = nb.dcim.inventory_items.create(
|
||||||
|
@ -154,7 +252,10 @@ class Inventory():
|
||||||
return nb_raid_card
|
return nb_raid_card
|
||||||
|
|
||||||
def create_netbox_raid_cards(self):
|
def create_netbox_raid_cards(self):
|
||||||
for raid_card in self.get_raid_cards():
|
for raid_card in self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tag=[INVENTORY_TAG['raid_card']['slug']]
|
||||||
|
):
|
||||||
self.create_netbox_raid_card(raid_card)
|
self.create_netbox_raid_card(raid_card)
|
||||||
|
|
||||||
def update_netbox_raid_cards(self):
|
def update_netbox_raid_cards(self):
|
||||||
|
@ -168,7 +269,10 @@ class Inventory():
|
||||||
We only need to handle destroy and new cards
|
We only need to handle destroy and new cards
|
||||||
"""
|
"""
|
||||||
|
|
||||||
nb_raid_cards = self.get_netbox_raid_cards()
|
nb_raid_cards = self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tag=[INVENTORY_TAG['raid_card']['slug']]
|
||||||
|
)
|
||||||
raid_cards = self.get_raid_cards()
|
raid_cards = self.get_raid_cards()
|
||||||
|
|
||||||
# delete cards that are in netbox but not locally
|
# delete cards that are in netbox but not locally
|
||||||
|
@ -185,41 +289,104 @@ class Inventory():
|
||||||
if raid_card.get_serial_number() not in [x.serial for x in nb_raid_cards]:
|
if raid_card.get_serial_number() not in [x.serial for x in nb_raid_cards]:
|
||||||
self.create_netbox_raid_card(raid_card)
|
self.create_netbox_raid_card(raid_card)
|
||||||
|
|
||||||
def get_disks(self):
|
def is_virtual_disk(self, disk):
|
||||||
ret = []
|
logicalname = disk.get('logicalname')
|
||||||
|
description = disk.get('description')
|
||||||
|
size = disk.get('size')
|
||||||
|
product = disk.get('product')
|
||||||
|
|
||||||
|
non_raid_disks = [
|
||||||
|
'MR9361-8i',
|
||||||
|
]
|
||||||
|
|
||||||
|
if size is None and logicalname is None or \
|
||||||
|
'virtual' in product.lower() or 'logical' in product.lower() or \
|
||||||
|
product in non_raid_disks or \
|
||||||
|
description == 'SCSI Enclosure' or \
|
||||||
|
'volume' in description.lower():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_hw_disks(self):
|
||||||
|
disks = []
|
||||||
|
|
||||||
|
for disk in self.lshw.get_hw_linux("storage"):
|
||||||
|
if self.is_virtual_disk(disk):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logicalname = disk.get('logicalname')
|
||||||
|
description = disk.get('description')
|
||||||
|
size = disk.get('size', 0)
|
||||||
|
product = disk.get('product')
|
||||||
|
serial = disk.get('serial')
|
||||||
|
|
||||||
|
d = {}
|
||||||
|
d["name"] = ""
|
||||||
|
d['Size'] = '{} GB'.format(int(size/1024/1024/1024))
|
||||||
|
d['logicalname'] = logicalname
|
||||||
|
d['description'] = description
|
||||||
|
d['SN'] = serial
|
||||||
|
d['Model'] = product
|
||||||
|
if disk.get('vendor'):
|
||||||
|
d['Vendor'] = disk['vendor']
|
||||||
|
else:
|
||||||
|
d['Vendor'] = get_vendor(disk['product'])
|
||||||
|
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
|
|
||||||
|
|
||||||
def get_netbox_disks(self):
|
# remove duplicate serials
|
||||||
disks = nb.dcim.inventory_items.filter(
|
seen = set()
|
||||||
device_id=self.device_id,
|
uniq = [x for x in disks if x['SN'] not in seen and not seen.add(x['SN'])]
|
||||||
tag=INVENTORY_TAG['disk']['slug'],
|
return uniq
|
||||||
)
|
|
||||||
return disks
|
|
||||||
|
|
||||||
def create_netbox_disks(self):
|
def create_netbox_disk(self, disk):
|
||||||
for disk in self.get_disks():
|
manufacturer = None
|
||||||
_ = nb.dcim.inventory_items.create(
|
if "Vendor" in disk:
|
||||||
device=self.device_id,
|
manufacturer = self.find_or_create_manufacturer(disk["Vendor"])
|
||||||
discovered=True,
|
|
||||||
tags=[INVENTORY_TAG['disk']['name']],
|
|
||||||
name='{} ({})'.format(disk['Model'], disk['Size']),
|
|
||||||
serial=disk['SN'],
|
|
||||||
)
|
|
||||||
logging.info('Creating Disk {model} {serial}'.format(
|
|
||||||
model=disk['Model'],
|
|
||||||
serial=disk['SN'],
|
|
||||||
))
|
|
||||||
|
|
||||||
def update_netbox_disks(self):
|
logicalname = disk.get('logicalname')
|
||||||
nb_disks = self.get_netbox_disks()
|
desc = disk.get('description')
|
||||||
disks = self.get_disks()
|
# nonraid disk
|
||||||
|
if logicalname and desc:
|
||||||
|
if type(logicalname) is list:
|
||||||
|
logicalname = logicalname[0]
|
||||||
|
name = '{} - {} ({})'.format(
|
||||||
|
desc,
|
||||||
|
logicalname,
|
||||||
|
disk.get('Size', 0))
|
||||||
|
description = 'Device {}'.format(disk.get('logicalname', 'Unknown'))
|
||||||
|
else:
|
||||||
|
name = '{} ({})'.format(disk['Model'], disk['Size'])
|
||||||
|
description = '{}'.format(disk['Type'])
|
||||||
|
|
||||||
|
_ = nb.dcim.inventory_items.create(
|
||||||
|
device=self.device_id,
|
||||||
|
discovered=True,
|
||||||
|
tags=[INVENTORY_TAG['disk']['name']],
|
||||||
|
name=name,
|
||||||
|
serial=disk['SN'],
|
||||||
|
part_id=disk['Model'],
|
||||||
|
description=description,
|
||||||
|
manufacturer=manufacturer.id if manufacturer else None
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info('Creating Disk {model} {serial}'.format(
|
||||||
|
model=disk['Model'],
|
||||||
|
serial=disk['SN'],
|
||||||
|
))
|
||||||
|
|
||||||
|
def do_netbox_disks(self):
|
||||||
|
nb_disks = self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
|
tag=INVENTORY_TAG['disk']['slug'])
|
||||||
|
disks = self.get_hw_disks()
|
||||||
|
|
||||||
# delete disks that are in netbox but not locally
|
# delete disks that are in netbox but not locally
|
||||||
# use the serial_number has the comparison element
|
# use the serial_number has the comparison element
|
||||||
for nb_disk in nb_disks:
|
for nb_disk in nb_disks:
|
||||||
if nb_disk.serial not in [x['SN'] for x in disks]:
|
if nb_disk.serial not in [x['SN'] for x in disks if x.get('SN')]:
|
||||||
logging.info('Deleting unknown locally Disk {serial}'.format(
|
logging.info('Deleting unknown locally Disk {serial}'.format(
|
||||||
serial=nb_disk.serial,
|
serial=nb_disk.serial,
|
||||||
))
|
))
|
||||||
|
@ -227,105 +394,67 @@ class Inventory():
|
||||||
|
|
||||||
# create disks that are not in netbox
|
# create disks that are not in netbox
|
||||||
for disk in disks:
|
for disk in disks:
|
||||||
if disk['SN'] not in [x.serial for x in nb_disks]:
|
if disk.get('SN') not in [x.serial for x in nb_disks]:
|
||||||
nb_disk = nb.dcim.inventory_items.create(
|
self.create_netbox_disk(disk)
|
||||||
device=self.device_id,
|
|
||||||
discovered=True,
|
|
||||||
tags=[INVENTORY_TAG['disk']['name']],
|
|
||||||
name='{} ({})'.format(disk['Model'], disk['Size']),
|
|
||||||
serial=disk['SN'],
|
|
||||||
description=disk.get('Type', ''),
|
|
||||||
)
|
|
||||||
logging.info('Creating Disk {model} {serial}'.format(
|
|
||||||
model=disk['Model'],
|
|
||||||
serial=disk['SN'],
|
|
||||||
))
|
|
||||||
|
|
||||||
def get_memory(self):
|
|
||||||
memories = []
|
|
||||||
for _, value in self.server.dmi.parse().items():
|
|
||||||
if value['DMIName'] == 'Memory Device' and \
|
|
||||||
value['Size'] != 'No Module Installed':
|
|
||||||
memories.append({
|
|
||||||
'Manufacturer': value['Manufacturer'].strip(),
|
|
||||||
'Size': value['Size'].strip(),
|
|
||||||
'PN': value['Part Number'].strip(),
|
|
||||||
'SN': value['Serial Number'].strip(),
|
|
||||||
'Locator': value['Locator'].strip(),
|
|
||||||
'Type': value['Type'].strip(),
|
|
||||||
})
|
|
||||||
return memories
|
|
||||||
|
|
||||||
def get_memory_total_size(self):
|
|
||||||
total_size = 0
|
|
||||||
for memory in self.get_memory():
|
|
||||||
total_size += int(memory['Size'].split()[0])
|
|
||||||
return total_size
|
|
||||||
|
|
||||||
def get_netbox_memory(self):
|
|
||||||
memories = nb.dcim.inventory_items.filter(
|
|
||||||
device_id=self.device_id,
|
|
||||||
tag=INVENTORY_TAG['memory']['slug'],
|
|
||||||
)
|
|
||||||
return memories
|
|
||||||
|
|
||||||
def create_netbox_memory(self, memory):
|
def create_netbox_memory(self, memory):
|
||||||
manufacturer = nb.dcim.manufacturers.get(
|
manufacturer = self.find_or_create_manufacturer(memory['vendor'])
|
||||||
name=memory['Manufacturer']
|
|
||||||
)
|
|
||||||
if not manufacturer:
|
|
||||||
manufacturer = nb.dcim.manufacturers.create(
|
|
||||||
name=memory['Manufacturer'],
|
|
||||||
slug=memory['Manufacturer'].lower(),
|
|
||||||
)
|
|
||||||
nb_memory = nb.dcim.inventory_items.create(
|
nb_memory = nb.dcim.inventory_items.create(
|
||||||
device=self.device_id,
|
device=self.device_id,
|
||||||
discovered=True,
|
discovered=True,
|
||||||
manufacturer=manufacturer.id,
|
manufacturer=manufacturer.id,
|
||||||
tags=[INVENTORY_TAG['memory']['name']],
|
tags=[INVENTORY_TAG['memory']['name']],
|
||||||
name='{} ({} {})'.format(memory['Locator'], memory['Size'], memory['Type']),
|
name='{} ({}GB)'.format(memory['description'], memory['size']),
|
||||||
part_id=memory['PN'],
|
part_id=memory['product'],
|
||||||
serial=memory['SN'],
|
serial=memory['serial'],
|
||||||
description='RAM',
|
description='Slot {}'.format(memory['slot']),
|
||||||
)
|
)
|
||||||
logging.info('Creating Memory {type} {size}'.format(
|
|
||||||
type=memory['Type'],
|
logging.info('Creating Memory {location} {type} {size}GB'.format(
|
||||||
size=memory['Size'],
|
location=memory['slot'],
|
||||||
|
type=memory['product'],
|
||||||
|
size=memory['size'],
|
||||||
))
|
))
|
||||||
|
|
||||||
return nb_memory
|
return nb_memory
|
||||||
|
|
||||||
def create_netbox_memories(self):
|
def do_netbox_memories(self):
|
||||||
for memory in self.get_memory():
|
memories = self.lshw.memories
|
||||||
self.create_netbox_memory(memory)
|
nb_memories = self.get_netbox_inventory(
|
||||||
|
device_id=self.device_id,
|
||||||
def update_netbox_memory(self):
|
tag=INVENTORY_TAG['memory']['slug']
|
||||||
memories = self.get_memory()
|
)
|
||||||
nb_memories = self.get_netbox_memory()
|
|
||||||
|
|
||||||
for nb_memory in nb_memories:
|
for nb_memory in nb_memories:
|
||||||
if nb_memory.serial not in [x['SN'] for x in memories]:
|
if nb_memory.serial not in [x['serial'] for x in memories]:
|
||||||
logging.info('Deleting unknown locally Memory {serial}'.format(
|
logging.info('Deleting unknown locally Memory {serial}'.format(
|
||||||
serial=nb_memory.serial,
|
serial=nb_memory.serial,
|
||||||
))
|
))
|
||||||
nb_memory.delete()
|
nb_memory.delete()
|
||||||
|
|
||||||
for memory in memories:
|
for memory in memories:
|
||||||
if memory['SN'] not in [x.serial for x in nb_memories]:
|
if memory.get('serial') not in [x.serial for x in nb_memories]:
|
||||||
self.create_netbox_memory(memory)
|
self.create_netbox_memory(memory)
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
if config.inventory is None:
|
if config.inventory is None:
|
||||||
return False
|
return False
|
||||||
self.create_netbox_cpus()
|
self.do_netbox_cpus()
|
||||||
self.create_netbox_memory()
|
self.do_netbox_memories()
|
||||||
self.create_netbox_raid_cards()
|
self.create_netbox_raid_cards()
|
||||||
self.create_netbox_disks()
|
self.do_netbox_disks()
|
||||||
|
self.do_netbox_interfaces()
|
||||||
|
self.do_netbox_motherboard()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
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.do_netbox_cpus()
|
||||||
self.update_netbox_memory()
|
self.do_netbox_memories()
|
||||||
self.update_netbox_raid_cards()
|
self.update_netbox_raid_cards()
|
||||||
self.update_netbox_disks()
|
self.do_netbox_disks()
|
||||||
|
self.do_netbox_interfaces()
|
||||||
|
self.do_netbox_motherboard()
|
||||||
return True
|
return True
|
||||||
|
|
151
netbox_agent/lshw.py
Normal file
151
netbox_agent/lshw.py
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from netbox_agent.misc import is_tool
|
||||||
|
|
||||||
|
|
||||||
|
class LSHW():
|
||||||
|
def __init__(self):
|
||||||
|
if not is_tool('lshw'):
|
||||||
|
logging.error('lshw does not seem to be installed')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
data = subprocess.getoutput(
|
||||||
|
'lshw -quiet -json'
|
||||||
|
)
|
||||||
|
self.hw_info = json.loads(data)
|
||||||
|
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].get("serial", "No S/N")
|
||||||
|
self.motherboard = self.hw_info["children"][0].get("product", "Motherboard")
|
||||||
|
|
||||||
|
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.get("logicalname")
|
||||||
|
d["product"] = device.get("product")
|
||||||
|
d["serial"] = device.get("serial")
|
||||||
|
d["version"] = device.get("version")
|
||||||
|
d["size"] = device.get("size")
|
||||||
|
d["description"] = device.get("description")
|
||||||
|
|
||||||
|
self.disks.append(d)
|
||||||
|
|
||||||
|
elif "nvme" in obj["configuration"]["driver"]:
|
||||||
|
nvme = json.loads(
|
||||||
|
subprocess.check_output(["nvme", '-list', '-o', 'json'],
|
||||||
|
encoding='utf8')) # noqa: E128
|
||||||
|
|
||||||
|
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):
|
||||||
|
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.get("slot")
|
||||||
|
d["description"] = dimm.get("description")
|
||||||
|
d["id"] = dimm.get("id")
|
||||||
|
d["serial"] = dimm.get("serial", 'N/A')
|
||||||
|
d["vendor"] = dimm.get("vendor")
|
||||||
|
d["product"] = dimm.get("product")
|
||||||
|
d["size"] = dimm.get("size", 0) / 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
|
|
@ -4,3 +4,25 @@ from shutil import which
|
||||||
def is_tool(name):
|
def is_tool(name):
|
||||||
'''Check whether `name` is on PATH and marked as executable.'''
|
'''Check whether `name` is on PATH and marked as executable.'''
|
||||||
return which(name) is not None
|
return which(name) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def get_vendor(name):
|
||||||
|
vendors = {
|
||||||
|
'ST': 'Seagate',
|
||||||
|
'CRUCIAL': 'Crucial',
|
||||||
|
'MICRON': 'Micron',
|
||||||
|
'INTEL': 'Intel',
|
||||||
|
'SAMSUNG': 'Samsung',
|
||||||
|
'EH0': 'HP',
|
||||||
|
'HGST': 'HGST',
|
||||||
|
'HUH': 'HGST',
|
||||||
|
'MB': 'Toshiba',
|
||||||
|
'MC': 'Toshiba',
|
||||||
|
'MD': 'Toshiba',
|
||||||
|
'MG': 'Toshiba',
|
||||||
|
'WD': 'WDC'
|
||||||
|
}
|
||||||
|
for key, value in vendors.items():
|
||||||
|
if name.upper().startswith(key):
|
||||||
|
return value
|
||||||
|
return name
|
||||||
|
|
|
@ -2,6 +2,7 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from netbox_agent.raid.base import Raid, RaidController
|
from netbox_agent.raid.base import Raid, RaidController
|
||||||
|
from netbox_agent.misc import get_vendor
|
||||||
|
|
||||||
REGEXP_CONTROLLER_HP = re.compile(r'Smart Array ([a-zA-Z0-9- ]+) in Slot ([0-9]+)')
|
REGEXP_CONTROLLER_HP = re.compile(r'Smart Array ([a-zA-Z0-9- ]+) in Slot ([0-9]+)')
|
||||||
|
|
||||||
|
@ -120,12 +121,23 @@ class HPRaidController(RaidController):
|
||||||
key = next(iter(info_dict))
|
key = next(iter(info_dict))
|
||||||
for array, physical_disk in info_dict[key].items():
|
for array, physical_disk in info_dict[key].items():
|
||||||
for _, pd_attr in physical_disk.items():
|
for _, pd_attr in physical_disk.items():
|
||||||
|
model = pd_attr.get('Model', '').strip()
|
||||||
|
vendor = None
|
||||||
|
if model.startswith('HP'):
|
||||||
|
vendor = 'HP'
|
||||||
|
elif len(model.split()) > 1:
|
||||||
|
vendor = get_vendor(model.split()[1])
|
||||||
|
else:
|
||||||
|
vendor = get_vendor(model)
|
||||||
|
|
||||||
ret.append({
|
ret.append({
|
||||||
'Model': pd_attr.get('Model', '').strip(),
|
'Model': model,
|
||||||
|
'Vendor': vendor,
|
||||||
'SN': pd_attr.get('Serial Number', '').strip(),
|
'SN': pd_attr.get('Serial Number', '').strip(),
|
||||||
'Size': pd_attr.get('Size', '').strip(),
|
'Size': pd_attr.get('Size', '').strip(),
|
||||||
'Type': 'SSD' if pd_attr.get('Interface Type') == 'Solid State SATA'
|
'Type': 'SSD' if pd_attr.get('Interface Type') == 'Solid State SATA'
|
||||||
else 'HDD',
|
else 'HDD',
|
||||||
|
'_src': self.__class__.__name__,
|
||||||
})
|
})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from netbox_agent.misc import get_vendor
|
||||||
from netbox_agent.raid.base import Raid, RaidController
|
from netbox_agent.raid.base import Raid, RaidController
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,11 +39,14 @@ class StorcliController(RaidController):
|
||||||
)
|
)
|
||||||
drive_attr = drive_infos['{} - Detailed Information'.format(drive_identifier)][
|
drive_attr = drive_infos['{} - Detailed Information'.format(drive_identifier)][
|
||||||
'{} Device attributes'.format(drive_identifier)]
|
'{} Device attributes'.format(drive_identifier)]
|
||||||
|
model = drive_attr.get('Model Number', '').strip()
|
||||||
ret.append({
|
ret.append({
|
||||||
'Model': drive_attr.get('Model Number', '').strip(),
|
'Model': model,
|
||||||
|
'Vendor': get_vendor(model),
|
||||||
'SN': drive_attr.get('SN', '').strip(),
|
'SN': drive_attr.get('SN', '').strip(),
|
||||||
'Size': size,
|
'Size': size,
|
||||||
'Type': media_type,
|
'Type': media_type,
|
||||||
|
'_src': self.__class__.__name__,
|
||||||
})
|
})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,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
|
||||||
|
|
||||||
|
|
38
netbox_agent/vendors/supermicro.py
vendored
38
netbox_agent/vendors/supermicro.py
vendored
|
@ -1,6 +1,20 @@
|
||||||
|
|
||||||
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 +22,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 +35,25 @@ 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())
|
||||||
|
|
Loading…
Reference in a new issue