From 94e0437427588bfb7e87e552136e4297a4dd0906 Mon Sep 17 00:00:00 2001 From: Solvik Blum Date: Fri, 2 Aug 2019 18:39:05 +0200 Subject: [PATCH] wip - should change the name of manufacturers, maybe other classes than Server etc --- netbox_agent/__init__.py | 6 +++ netbox_agent/dell/__init__.py | 0 netbox_agent/dell/dell.py | 86 +++++++++++++++++++++++++++++++ netbox_agent/dmidecode.py | 96 +++++++++++++++++++++++++++++++++++ netbox_agent/hp/__init__.py | 0 netbox_agent/hp/hp.py | 4 ++ netbox_agent/main.py | 22 ++++++++ netbox_agent/server.py | 63 +++++++++++++++++++++++ 8 files changed, 277 insertions(+) create mode 100644 netbox_agent/__init__.py create mode 100644 netbox_agent/dell/__init__.py create mode 100644 netbox_agent/dell/dell.py create mode 100644 netbox_agent/dmidecode.py create mode 100644 netbox_agent/hp/__init__.py create mode 100644 netbox_agent/hp/hp.py create mode 100644 netbox_agent/main.py create mode 100644 netbox_agent/server.py diff --git a/netbox_agent/__init__.py b/netbox_agent/__init__.py new file mode 100644 index 0000000..ca5fd98 --- /dev/null +++ b/netbox_agent/__init__.py @@ -0,0 +1,6 @@ +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + pass diff --git a/netbox_agent/dell/__init__.py b/netbox_agent/dell/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_agent/dell/dell.py b/netbox_agent/dell/dell.py new file mode 100644 index 0000000..3cec587 --- /dev/null +++ b/netbox_agent/dell/dell.py @@ -0,0 +1,86 @@ +import socket +from pprint import pprint + +from netbox_agent.server import ServerBase +from netbox_agent.config import netbox_instance as nb + +class DellHost(ServerBase): + def is_blade(self): + return self.get_product_name().startswith('PowerEdge M') + + def get_blade_slot(self): + ''' + Return blade slot + dmidecode output is: + ` Location In Chassis: Slot 03` + ''' + if self.is_blade(): + return int(self.dmi.get('base board')[0].get('Location In Chassis').split()[1]) + return None + + def get_chassis(self): + if self.is_blade(): + return self.dmi.get('chassis')[0]['Version'] + return self.get_product_name() + + def get_chassis_service_tag(self): + if self.is_blade(): + return self.dmi.get('chassis')[0]['Serial Number'] + return self.get_service_tag + + def netbox_create(self): + if self.is_blade(): + # let's find the bblade + blade = nb.dcim.devices.get(serial=self.get_service_tag()) + chassis = nb.dcim.devices.get(serial=self.get_chassis_service_tag()) + # if it doesn't exist, create it + if not blade: + # check if the chassis exist before + # if it doesn't exist, create it + if not chassis: + device_type = nb.dcim.device_types.get( + model=self.get_chassis(), + ) + device_role = nb.dcim.device_roles.get( + name='Server Chassis', + ) + datacenter = nb.dcim.sites.get( + name='DC3' + ) + new_chassis = nb.dcim.devices.create( + name=''.format(), + device_type=device_type.id, + serial=self.get_chassis_service_tag(), + device_role=device_role.id, + site=datacenter.id, + ) + chassis = new_chassis + + device_role = nb.dcim.device_roles.get( + name='Blade', + ) + device_type = nb.dcim.device_types.get( + model=self.get_product_name(), + ) + + new_blade = nb.dcim.devices.create( + name='{}'.format(socket.gethostname()), + serial=self.get_service_tag(), + device_role=device_role.id, + device_type=device_type.id, + parent_device=chassis.id, + site='1', + ) + blade = new_blade + + # Find the slot and update it with our blade + device_bays = nb.dcim.device_bays.filter( + device_id=chassis.id, + name='Blade {}'.format(self.get_blade_slot()), + ) + if len(device_bays) > 0: + device_bay = device_bays[0] + device_bay.installed_device = blade + device_bay.save() + else: + # FIXME : handle pizza box diff --git a/netbox_agent/dmidecode.py b/netbox_agent/dmidecode.py new file mode 100644 index 0000000..49d1c3a --- /dev/null +++ b/netbox_agent/dmidecode.py @@ -0,0 +1,96 @@ +import subprocess + +class Dmidecode(): + def __init__(self): + self.types = { + 0: 'bios', + 1: 'system', + 2: 'base board', + 3: 'chassis', + 4: 'processor', + 7: 'cache', + 8: 'port connector', + 9: 'system slot', + 10: 'on board device', + 11: 'OEM strings', + #13: 'bios language', + 15: 'system event log', + 16: 'physical memory array', + 17: 'memory device', + 19: 'memory array mapped address', + 24: 'hardware security', + 25: 'system power controls', + 27: 'cooling device', + 32: 'system boot', + 41: 'onboard device', + } + self.content = self._get_output() + self.info = self.parse_dmi() + + def parse_dmi(self): + """ + Parse the whole dmidecode output. + Returns a list of tuples of (type int, value dict). + """ + self.info = [] + lines = iter(self.content.strip().splitlines()) + while True: + try: + line = next(lines) + except StopIteration: + break + + if line.startswith('Handle 0x'): + typ = int(line.split(',', 2)[1].strip()[len('DMI type'):]) + if typ in self.types: + self.info.append( + (self.types[typ], self._parse_handle_section(lines)) + ) + return self.info + + + def _parse_handle_section(self, lines): + """ + Parse a section of dmidecode output + * 1st line contains address, type and size + * 2nd line is title + * line started with one tab is one option and its value + * line started with two tabs is a member of list + """ + data = { + '_title': next(lines).rstrip(), + } + + for line in lines: + line = line.rstrip() + if line.startswith('\t\t'): + if isinstance(data[k], list): + data[k].append(line.lstrip()) + elif line.startswith('\t'): + k, v = [i.strip() for i in line.lstrip().split(':', 1)] + if v: + data[k] = v + else: + data[k] = [] + else: + break + + return data + + + def _get_output(self): + try: + output = subprocess.check_output( + 'PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin ' + 'sudo dmidecode', shell=True) + except Exception as e: + print(e, file=sys.stderr) + if str(e).find("command not found") == -1: + print("please install dmidecode", file=sys.stderr) + print("e.g. sudo apt install dmidecode",file=sys.stderr) + + sys.exit(1) + return output.decode() + + def get(self, i): + return [v for j, v in self.info if j == i] diff --git a/netbox_agent/hp/__init__.py b/netbox_agent/hp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_agent/hp/hp.py b/netbox_agent/hp/hp.py new file mode 100644 index 0000000..cde9e4f --- /dev/null +++ b/netbox_agent/hp/hp.py @@ -0,0 +1,4 @@ +from netbox_agent.server import ServerBase + +class HPHost(): + pass diff --git a/netbox_agent/main.py b/netbox_agent/main.py new file mode 100644 index 0000000..5ff4ac4 --- /dev/null +++ b/netbox_agent/main.py @@ -0,0 +1,22 @@ +from netbox_agent.dmidecode import Dmidecode +from netbox_agent.dell.dell import DellHost +from netbox_agent.hp.hp import HPHost + +MANUFACTURERS = { + 'Dell Inc.': DellHost, + 'HP': HPHost, + 'HPE': HPHost, + } + +def main(): + dmi = Dmidecode() + manufacturer = dmi.get('chassis')[0].get('Manufacturer') + server = MANUFACTURERS[manufacturer](dmi) + print(server.get_chassis()) + print(server.get_service_tag()) + print(server.get_chassis_service_tag()) + server.netbox_create() + print(server.get_network_cards()) + +if __name__ == '__main__': + main() diff --git a/netbox_agent/server.py b/netbox_agent/server.py new file mode 100644 index 0000000..77cf86f --- /dev/null +++ b/netbox_agent/server.py @@ -0,0 +1,63 @@ +import re +import os +from netbox_agent.dmidecode import Dmidecode + +# Regex to match base interface name +# Doesn't match vlan interfaces and other loopback etc +INTERFACE_REGEX = re.compile('^(eth[0-9]+|ens[0-9]+|enp[0-9]+s[0-9]f[0-9])$') + +class ServerBase(): + def __init__(self, dmi=None): + if dmi: + self.dmi = dmi + else: + self.dmi = Dmidecode() + self.system = self.dmi.get('system') + self.bios = self.dmi.get('bios') + + self.network_cards = [] + + def get_product_name(self): + ''' + Return the Chassis Name from dmidecode info + ''' + return self.system[0]['Product Name'] + + def get_service_tag(self): + ''' + Return the Service Tag from dmidecode info + ''' + return self.system[0]['Serial Number'] + + def is_blade(self): + raise NotImplementedError + + def get_blade_slot(self): + raise NotImplementedError + + def get_chassis(self): + raise NotImplementedError + + def get_chassis_service_tag(self): + raise NotImplementedError + + def get_bios_version(self): + raise NotImplementedError + + def get_bios_version_attr(self): + raise NotImplementedError + + def get_bios_release_date(self): + raise NotImplementedError + + def get_network_cards(self): + nics = [] + for interface in os.listdir('/sys/class/net/'): + if re.match(INTERFACE_REGEX, interface): + nic = { + 'name': interface, + 'mac': open('/sys/class/net/{}/address'.format(interface), 'r').read().strip(), + 'ip': None, #FIXME + } + nics.append(nic) + return nics