diff --git a/netbox_agent/config.py b/netbox_agent/config.py index 48a910de..6537973d 100644 --- a/netbox_agent/config.py +++ b/netbox_agent/config.py @@ -69,6 +69,11 @@ def get_config(): default=None, help="Command to output hostname, used as Device's name in netbox", ) + p.add_argument( + "--fqdn_cmd", + default=None, + help="Command to output fully qualified domain name, used as Device's DNS name in netbox", + ) p.add_argument( "--device.platform", default=None, @@ -90,6 +95,7 @@ def get_config(): "--device.chassis_role", default=r"Server Chassis", help="role to use for a chassis" ) p.add_argument("--device.server_role", default=r"Server", help="role to use for a server") + p.add_argument("--device.autocreate_device_type", default=True, help="Define whether a device type should be create when it doesnt exist.") p.add_argument("--tenant.driver", help="tenant driver, ie cmd, file") p.add_argument("--tenant.driver_file", help="tenant driver custom driver file path") p.add_argument("--tenant.regex", help="tenant regex to extract Netbox tenant slug") @@ -126,6 +132,8 @@ def get_config(): default="name", help="What property to use as NIC identifier", ) + p.add_argument("--network.vrf", default=None, help="Set VRF for network interfaces") + p.add_argument("--network.status", default="Active", help="Set the status for IP Addresses") p.add_argument( "--network.primary_mac", choices=("permanent", "temp"), diff --git a/netbox_agent/dmidecode.py b/netbox_agent/dmidecode.py index a6fc8eb1..887e8791 100644 --- a/netbox_agent/dmidecode.py +++ b/netbox_agent/dmidecode.py @@ -62,7 +62,8 @@ def parse(output=None): """ - parse the full output of the dmidecode + parse the full output of the dmidecode on normal systems, + or raspinfo for raspberry pi command and return a dic containing the parsed information """ if output: @@ -71,7 +72,14 @@ def parse(output=None): buffer = _execute_cmd() if isinstance(buffer, bytes): buffer = buffer.decode("utf-8") - _data = _parse(buffer) + if buffer.splitlines()[1] == "# No SMBIOS nor DMI entry point found, sorry.": + logging.info( + "Dmidecode does not seem to be finding information on your system. " + "Using raspinfo instead." + ) + _data = _rpi_parse(buffer) + else: + _data = _parse(buffer) return _data @@ -149,6 +157,51 @@ def _execute_cmd(): stderr=_subprocess.PIPE, ) +def _execute_cmd_pi(): + return _subprocess.check_output([ + "raspinfo", + ], + stderr=_subprocess.PIPE, + ) + +def _execute_cmd_os_pi(): + return _subprocess.check_output([ + "cat", + "/etc/os-release", + "|", + "head", + "-4", + ], + stderr=_subprocess.PIPE, shell=True + ) + +def _execute_cmd_serial_pi(): + output_var = str( _subprocess.check_output([ + "cat", + "/proc/cpuinfo", + ], + stderr=_subprocess.PIPE + ), 'utf-8' ) + return output_var.splitlines()[-3:] + +def _execute_cmd_ram_pi(): + return _subprocess.check_output([ + "cat", + "/proc/meminfo", + "|", + "head", + "-1" + ], + stderr=_subprocess.PIPE, shell=True + ) + +def _execute_cmd_bios_pi(): + return _subprocess.check_output([ + "vcgencmd", + "version", + ], + stderr=_subprocess.PIPE, + ) def _parse(buffer): output_data = {} @@ -224,6 +277,57 @@ def _parse(buffer): return output_data +def _rpi_parse(buffer): + # 0 BIOS + # 1 System + # 2 Baseboard + # 3 Chassis + + handles = [ + "0x001A", + "0x0001", + "0x0000", + "0x001D", + ] + + names = [ + "BIOS", + "System", + "Baseboard", + "Chassis", + ] + + output_data = {} + + for index,cur_name in enumerate(names): + output_data[handles[index]]= {} + output_data[handles[index]]["DMIType"] = int(index) + output_data[handles[index]]["DMISize"] = int(28) + output_data[handles[index]]["DMIName"] = names[index] + + # Define BIOS info + output_data[handles[0]][names[0]] = _execute_cmd_bios_pi() + output_data[handles[0]]["Version"] = str(_execute_cmd_bios_pi(),"utf-8").splitlines()[-1].split(" ")[1].strip() + + # Define System info + serial_data = _execute_cmd_serial_pi() + revision_number = serial_data[0].split(":")[1].strip() + serial_number = serial_data[1].split(":")[1].strip() + model = serial_data[2].split(":")[1].strip() + output_data[handles[1]]["Product Name"] = model + output_data[handles[1]]["Serial Number"] = serial_number + output_data[handles[1]]["Manufacturer"] = "Raspberry Pi Foundation" + + + # Define Chassis INfo + output_data[handles[3]]["Manufacturer"] = "Raspberry Pi Foundation" + + + if not output_data: + raise ParseError("Unable to parse 'dmidecode' output") + + return output_data + class ParseError(Exception): pass diff --git a/netbox_agent/inventory.py b/netbox_agent/inventory.py index 57b0de53..c9a96761 100644 --- a/netbox_agent/inventory.py +++ b/netbox_agent/inventory.py @@ -159,12 +159,14 @@ def do_netbox_motherboard(self): def create_netbox_interface(self, iface): manufacturer = self.find_or_create_manufacturer(iface["vendor"]) + # Netbox name character limit is 64 characters + char_limit = 64 _ = nb.dcim.inventory_items.create( device=self.device_id, manufacturer=manufacturer.id, discovered=True, tags=[{"name": INVENTORY_TAG["interface"]["name"]}], - name="{}".format(iface["product"]), + name="{}".format((iface["product"][:char_limit-3] + '..') if (len(iface["product"]) > char_limit) else iface["product"]), serial="{}".format(iface["serial"]), description="{} {}".format(iface["description"], iface["name"]), ) diff --git a/netbox_agent/ipmi.py b/netbox_agent/ipmi.py index 8f394b7d..96cdcb04 100644 --- a/netbox_agent/ipmi.py +++ b/netbox_agent/ipmi.py @@ -55,6 +55,7 @@ def parse(self): ret["name"] = "IPMI" ret["mtu"] = 1500 ret["bonding"] = False + ret["bridged"] = False try: ret["mac"] = _ipmi["MAC Address"] if ret["mac"]: diff --git a/netbox_agent/lldp.py b/netbox_agent/lldp.py index 7b6fa39f..c117f9bd 100644 --- a/netbox_agent/lldp.py +++ b/netbox_agent/lldp.py @@ -62,7 +62,12 @@ def get_switch_port(self, interface): return None if self.data["lldp"][interface]["port"].get("ifname"): return self.data["lldp"][interface]["port"]["ifname"] - return self.data["lldp"][interface]["port"]["descr"] + if self.data["lldp"][interface]["port"].get("local"): + return self.data["lldp"][interface]["port"]["local"] + if self.data["lldp"][interface]["port"].get("descr"): + return self.data["lldp"][interface]["port"]["descr"] + return None + def get_switch_vlan(self, interface): # lldp.eth0.vlan.vlan-id=296 diff --git a/netbox_agent/lshw.py b/netbox_agent/lshw.py index 381c5414..6d13b2bd 100644 --- a/netbox_agent/lshw.py +++ b/netbox_agent/lshw.py @@ -3,6 +3,7 @@ import logging import json import sys +import re class LSHW: @@ -26,7 +27,11 @@ def __init__(self): self.power = [] self.disks = [] self.gpus = [] - self.vendor = self.hw_info["vendor"] + if hasattr(self.hw_info, "vendor"): + vendor = self.hw_info["vendor"] + else: + vendor = "Not Specified" + self.vendor = 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") @@ -94,20 +99,7 @@ def find_network(self, obj): ) def find_storage(self, obj): - if "children" in obj: - for device in obj["children"]: - self.disks.append( - { - "logicalname": device.get("logicalname"), - "product": device.get("product"), - "serial": device.get("serial"), - "version": device.get("version"), - "size": device.get("size"), - "description": device.get("description"), - "type": device.get("description"), - } - ) - elif "driver" in obj["configuration"] and "nvme" in obj["configuration"]["driver"]: + if "driver" in obj["configuration"] and "nvme" in obj["configuration"]["driver"]: if not is_tool("nvme"): logging.error("nvme-cli >= 1.0 does not seem to be installed") return @@ -131,22 +123,88 @@ def find_storage(self, obj): self.disks.append(d) except Exception: pass + elif "children" in obj: + for device in obj["children"]: + self.disks.append( + { + "logicalname": device.get("logicalname"), + "product": device.get("product"), + "serial": device.get("serial"), + "version": device.get("version"), + "size": device.get("size"), + "description": device.get("description"), + "type": device.get("description"), + } + ) def find_cpus(self, obj): if "product" in obj: - self.cpus.append( - { - "product": obj.get("product", "Unknown CPU"), - "vendor": obj.get("vendor", "Unknown vendor"), - "description": obj.get("description", ""), - "location": obj.get("slot", ""), - } - ) + if (bool(re.search(r'cpu\:\d', obj.get("id"))) and obj.get("product") == "cpu"): + # First trey to get more information with lscpu + vendor_name = "Unknown vendor" + cpu_name = "Unknown CPU" + description_detail = "" + if not is_tool("lscpu"): + logging.error("lscpu does not seem to be installed") + try: + lscpu = json.loads( + subprocess.check_output(["lscpu", "-J"], encoding="utf8") + ) + for device in lscpu["lscpu"]: + if device["field"] == "Vendor ID:": + vendor_name = device["data"] + if device["field"] == "Model name:": + cpu_name = device["data"] + if device["field"] == "Architecture:": + description_detail = description_detail + "Architecture: " + device["data"] + " " + if device["field"] == "Flags:": + description_detail = description_detail + "Flags: " + device["data"] + except Exception: + pass + + # In this case each CPU core is counted as a separate entity; overwrite cputoappend entity + temp_cpu_name = obj.get("product", cpu_name) + temp_description = obj.get("description", description_detail), + if temp_cpu_name == "cpu" and cpu_name != "Unknown CPU": + # cpu this is the default name, dont use it if better data is available + temp_cpu_name = cpu_name + if temp_description[0] == "CPU" and description_detail != "": + # CPU this is the default description, dont use it if better data is available + temp_description = description_detail + self.cpus = [ { + "product": temp_cpu_name + " (" + str(int(obj.get("id").split(":")[1])+1) + " Core, " + str(obj.get("size")/1000000) + " MHz)", + "vendor": obj.get("vendor", vendor_name), + "description": temp_description, + "location": obj.get("slot", ""), + }] + else: + self.cpus.append( + { + "product": obj.get("product", "Unknown CPU"), + "vendor": obj.get("vendor", "Unknown vendor"), + "description": obj.get("description", ""), + "location": obj.get("slot", ""), + } + ) def find_memories(self, obj): if "children" not in obj: - # print("not a DIMM memory.") - return + if obj.get("description") == "System memory": + self.memories.append( + { + # This is probably embedded memory as for a Raspberry pi + "slot": obj.get("slot", "Integrated Memory"), + "description": obj.get("description"), + "id": obj.get("id"), + "serial": obj.get("serial", "N/A"), + "vendor": obj.get("vendor", "N/A"), + "product": obj.get("product", "N/A"), + "size": obj.get("size", 0) / 2**20 / 1024, + }) + return + else: + # print("not a DIMM memory or integrated memory.") + return for dimm in obj["children"]: if "empty" in dimm["description"]: diff --git a/netbox_agent/misc.py b/netbox_agent/misc.py index ccfae61b..1fd15735 100644 --- a/netbox_agent/misc.py +++ b/netbox_agent/misc.py @@ -1,9 +1,11 @@ from contextlib import suppress +from netbox_agent.config import config from netbox_agent.config import netbox_instance as nb from slugify import slugify from shutil import which import distro import subprocess +import logging import socket import re @@ -23,7 +25,12 @@ def get_device_role(role): def get_device_type(type): device_type = nb.dcim.device_types.get(model=type) if device_type is None: - raise Exception('DeviceType "{}" does not exist, please create it'.format(type)) + if config.device.autocreate_device_type: + logging.info( + 'DeviceType "{}" does not yet exist, it will be created'.format(type) + ) + else: + raise Exception('DeviceType "{}" does not exist, please create it, or set device.autocreate_device_type to true'.format(type)) return device_type @@ -81,6 +88,10 @@ def get_hostname(config): return "{}".format(socket.gethostname()) return subprocess.getoutput(config.hostname_cmd) +def get_fqdn(config): + if config.fqdn_cmd is None: + return "{}".format(socket.getfqdn()) + return subprocess.getoutput(config.fqdn_cmd) def create_netbox_tags(tags): ret = [] diff --git a/netbox_agent/network.py b/netbox_agent/network.py index c2706108..da75f754 100644 --- a/netbox_agent/network.py +++ b/netbox_agent/network.py @@ -1,10 +1,13 @@ import logging import os import re +import glob +import sys from itertools import chain, islice from pathlib import Path import netifaces +import subprocess from netaddr import IPAddress from packaging import version @@ -13,9 +16,27 @@ from netbox_agent.ethtool import Ethtool from netbox_agent.ipmi import IPMI from netbox_agent.lldp import LLDP +from netbox_agent.misc import is_tool,get_fqdn VIRTUAL_NET_FOLDER = Path("/sys/devices/virtual/net") +def _execute_brctl_cmd(interface_name): + if not is_tool("brctl"): + logging.error( + "Brctl does not seem to be present on your system. Add it your path or " + "check the compatibility of this project with your distro." + ) + sys.exit(1) + return subprocess.getoutput( + "brctl show " + str(interface_name) + ) + +def _execute_basename_cmd(interface_name): + parent_int = None + parent_list = glob.glob("/sys/class/net/" + str(interface_name) + "/lower_*") + if len(parent_list)>0: + parent_int = os.path.basename(parent_list[0]).split("_")[1] + return parent_int class Network(object): def __init__(self, server, *args, **kwargs): @@ -108,16 +129,41 @@ def scan(self): if len(interface.split(".")) > 1: vlan = int(interface.split(".")[1]) + virtual = Path(f"/sys/class/net/{interface}").resolve().parent == VIRTUAL_NET_FOLDER + bonding = False bonding_slaves = [] if os.path.isdir("/sys/class/net/{}/bonding".format(interface)): bonding = True + virtual = False bonding_slaves = ( open("/sys/class/net/{}/bonding/slaves".format(interface)).read().split() ) - virtual = Path(f"/sys/class/net/{interface}").resolve().parent == VIRTUAL_NET_FOLDER - + bridging = False + bridge_parents = [] + outputbrctl = _execute_brctl_cmd(interface) + lineparse_output = outputbrctl.splitlines() + if len(lineparse_output)>1: + headers = lineparse_output[0].replace("\t\t", "\t").split("\t") + brctl = dict((key, []) for key in headers) + # Interface is a bridge + bridging = True + virtual = False + for spec_line in lineparse_output[1:]: + cleaned_spec_line = spec_line.replace("\t\t\t\t\t\t\t", "\t\t\t\t\t\t") + cleaned_spec_line = cleaned_spec_line.replace("\t\t", "\t").split("\t") + for col, val in enumerate(cleaned_spec_line): + if val: + brctl[headers[col]].append(val) + + bridge_parents = brctl[headers[-1]] + + parent = None + if virtual: + parent = _execute_basename_cmd(interface) + if not parent: + parent = None nic = { "name": interface, "mac": mac, @@ -132,6 +178,9 @@ def scan(self): "mtu": mtu, "bonding": bonding, "bonding_slaves": bonding_slaves, + "parent": parent, + "bridged": bridging, + "bridge_parents": bridge_parents } nics.append(nic) return nics @@ -158,6 +207,30 @@ def _set_bonding_interfaces(self): return False return True + def _set_bridged_interfaces(self): + bridged_nics = (x for x in self.nics if x["bridged"]) + for nic in bridged_nics: + bridged_int = self.get_netbox_network_card(nic) + logging.debug("Setting bridge interface and properties for {name}".format(name=bridged_int.name)) + bridged_int.type = "bridge" + for parent_int in ( + self.get_netbox_network_card(parent_bridge_nic) + for parent_bridge_nic in self.nics + if parent_bridge_nic["name"] in nic["bridge_parents"] + ): + logging.debug( + "Setting interface {parent} as a bridge to {name}".format( + name=bridged_int.name, parent=parent_int.name + ) + ) + parent_int.bridge = bridged_int + parent_int.save() + bridged_int.bridge = {"name": parent_int.name, "id": parent_int.id} + bridged_int.save() + else: + return False + return True + def get_network_cards(self): return self.nics @@ -181,6 +254,9 @@ def get_netbox_type_for_nic(self, nic): if nic.get("virtual"): return self.dcim_choices["interface:type"]["Virtual"] + if nic.get("bridge"): + return self.dcim_choices["interface:type"]["Bridge"] + if nic.get("ethtool") is None: return self.dcim_choices["interface:type"]["Other"] @@ -223,6 +299,13 @@ def get_or_create_vlan(self, vlan_id): ) return vlan + def get_vrf_id(self, vrf_name): + vrf = nb.ipam.vrfs.get(name=vrf_name, **self.custom_arg_id) + if vrf: + return vrf.id + else: + return None + def reset_vlan_on_interface(self, nic, interface): update = False vlan_id = nic["vlan"] @@ -366,10 +449,11 @@ def create_netbox_nic(self, nic, mgmt=False): switch_ip, switch_interface, interface ) if nic_update: + logging.debug("Saving changes to interface {interface}".format(interface=interface)) interface.save() return interface - def create_or_update_netbox_ip_on_interface(self, ip, interface): + def create_or_update_netbox_ip_on_interface(self, ip, interface, vrf): """ Two behaviors: - Anycast IP @@ -392,6 +476,9 @@ def create_or_update_netbox_ip_on_interface(self, ip, interface): "status": "active", "assigned_object_type": self.assigned_object_type, "assigned_object_id": interface.id, + "vrf": vrf, + "status": config.network.status, + "dns_name": get_fqdn(config), } netbox_ip = nb.ipam.ip_addresses.create(**query_params) @@ -410,6 +497,9 @@ def create_or_update_netbox_ip_on_interface(self, ip, interface): logging.info("Assigning existing Anycast IP {} to interface".format(ip)) netbox_ip = unassigned_anycast_ip[0] netbox_ip.interface = interface + netbox_ip.vrf = vrf + netbox_ip.dns_name = get_fqdn(config) + netbox_ip.status = config.network.status netbox_ip.save() # or if everything is assigned to other servers elif not len(assigned_anycast_ip): @@ -421,6 +511,9 @@ def create_or_update_netbox_ip_on_interface(self, ip, interface): "tenant": self.tenant.id if self.tenant else None, "assigned_object_type": self.assigned_object_type, "assigned_object_id": interface.id, + "vrf": vrf, + "status": config.network.status, + "dns_name": get_fqdn(config), } netbox_ip = nb.ipam.ip_addresses.create(**query_params) return netbox_ip @@ -448,6 +541,9 @@ def create_or_update_netbox_ip_on_interface(self, ip, interface): netbox_ip.assigned_object_type = self.assigned_object_type netbox_ip.assigned_object_id = interface.id + netbox_ip.vrf = vrf + netbox_ip.dns_name = get_fqdn(config) + netbox_ip.status = config.network.status netbox_ip.save() def _nic_identifier(self, nic): @@ -513,7 +609,8 @@ def batched(it, n): netbox_ip.assigned_object_id = None netbox_ip.save() - # update each nic + # create each nic + interfaces = [] for nic in self.nics: interface = self.get_netbox_network_card(nic) @@ -522,7 +619,11 @@ def batched(it, n): "Interface {nic} not found, creating..".format(nic=self._nic_identifier(nic)) ) interface = self.create_netbox_nic(nic) + interfaces.append(interface) + #restart loop once everything has been create for updates + for index, nic in enumerate(self.nics): + interface = interfaces[index] nic_update = 0 ret, interface = self.reset_vlan_on_interface(nic, interface) @@ -542,6 +643,17 @@ def batched(it, n): if nic["mac"]: self.update_interface_macs(interface, [nic["mac"]]) + vrf = None + if config.network.vrf and config.network.vrf != str(interface.vrf): + logging.info( + "Updating interface {interface} VRF to: {vrf}".format( + interface=interface, vrf=config.network.vrf + ) + ) + vrf = self.get_vrf_id(config.network.vrf) + interface.vrf = vrf + nic_update += 1 + if nic["mac"] and nic["mac"] != interface.mac_address: logging.info( "Updating interface {interface} mac to: {mac}".format( @@ -551,7 +663,8 @@ def batched(it, n): if version.parse(nb.version) < version.parse("4.2"): interface.mac_address = nic["mac"] else: - interface.primary_mac_address = {"mac_address": nic["mac"]} + nb_macs = list(self.nb_net.mac_addresses.filter(interface_id=interface.id)) + interface.primary_mac_address = {"mac_address": nic["mac"], "id": nb_macs[0].id} nic_update += 1 if hasattr(interface, "mtu"): @@ -562,6 +675,19 @@ def batched(it, n): interface.mtu = nic["mtu"] nic_update += 1 + if hasattr(nic, 'virtual'): + if nic["virtual"] and nic["parent"] and nic["parent"] != interface.parent: + logging.info( + "Interface parent is wrong, updating to: {parent}".format(parent=nic["parent"]) + ) + for parent_nic in self.nics : + if parent_nic["name"] in nic["parent"]: + nic_parent = self.get_netbox_network_card(parent_nic) + break + int_parent = self.get_netbox_network_card(nic_parent) + interface.parent = {"name": int_parent.name, "id": int_parent.id} + nic_update += 1 + if not isinstance(self, VirtualNetwork) and nic.get("ethtool"): if ( nic["ethtool"]["duplex"] != "-" @@ -607,11 +733,12 @@ def batched(it, n): if nic["ip"]: # sync local IPs for ip in nic["ip"]: - self.create_or_update_netbox_ip_on_interface(ip, interface) + self.create_or_update_netbox_ip_on_interface(ip, interface, vrf) if nic_update > 0: interface.save() self._set_bonding_interfaces() + self._set_bridged_interfaces() logging.debug("Finished updating NIC!") @@ -659,7 +786,7 @@ def connect_interface_to_switch(self, switch_ip, switch_interface, nb_server_int switch_ip, nb_switch.id ) ) - except KeyError: + except (KeyError, AttributeError): logging.error( "Switch IP {} is found but not associated to a Netbox Switch Device".format( switch_ip @@ -711,7 +838,10 @@ def create_or_update_cable(self, switch_ip, switch_interface, nb_server_interfac nb_sw_int = nb_server_interface.cable.b_terminations[0] nb_sw = nb_sw_int.device nb_mgmt_int = nb.dcim.interfaces.get(device_id=nb_sw.id, mgmt_only=True) - nb_mgmt_ip = nb.ipam.ip_addresses.get(interface_id=nb_mgmt_int.id) + if hasattr(nb_mgmt_int, "id"): + nb_mgmt_ip = nb.ipam.ip_addresses.get(interface_id=nb_mgmt_int.id) + else: + nb_mgmt_ip = None if nb_mgmt_ip is None: logging.error( "Switch {switch_ip} does not have IP on its management interface".format( diff --git a/netbox_agent/power.py b/netbox_agent/power.py index c574ca27..acce49d9 100644 --- a/netbox_agent/power.py +++ b/netbox_agent/power.py @@ -79,7 +79,7 @@ def create_or_update_power_supply(self): nb_psu.save() for psu in psus: - if psu["name"] not in [x.name for x in nb_psus]: + if (psu["name"] not in [x.name for x in nb_psus]) and ( psu["name"] not in "__all__") : logging.info("Creating PSU {name} ({description}), {maximum_draw}W".format(**psu)) nb_psu = nb.dcim.power_ports.create(**psu) diff --git a/netbox_agent/server.py b/netbox_agent/server.py index c25be6ff..322c1c33 100644 --- a/netbox_agent/server.py +++ b/netbox_agent/server.py @@ -13,11 +13,12 @@ from netbox_agent.network import ServerNetwork from netbox_agent.power import PowerSupply from pprint import pprint +from slugify import slugify import subprocess import logging import socket import sys - +import re class ServerBase: def __init__(self, dmi=None): @@ -119,6 +120,24 @@ def update_netbox_expansion_location(self, server, expansion): update = True return update + 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=re.sub("[^A-Za-z0-9]+", "-", name).lower(), + ) + + logging.info("Creating missing manufacturer {name}".format(name=name)) + + return manufacturer + def get_rack(self): rack = Rack() return rack.get() @@ -143,6 +162,13 @@ def get_product_name(self): """ return self.system[0]["Product Name"].strip() + def get_manufacturer(self): + """ + Return the Manufacturer from dmidecode info, and create it if needed + """ + manufacturer = self.find_or_create_manufacturer(self.system[0]["Manufacturer"].strip()) + return manufacturer + def get_service_tag(self): """ Return the Service Tag from dmidecode info @@ -191,8 +217,29 @@ def get_power_consumption(self): def get_expansion_product(self): raise NotImplementedError + def _netbox_create_device_type(self): + manufacturer = self.get_manufacturer() + model = self.get_product_name() + logging.info( + "Creating device type {model}.".format( + model=model + ) + ) + new_device_type = nb.dcim.device_types.create( + manufacturer = nb.dcim.manufacturers.get(name=manufacturer).id, + model = model, + slug=slugify(model) + ) + return new_device_type + def _netbox_create_chassis(self, datacenter, tenant, rack): device_type = get_device_type(self.get_chassis()) + if not device_type: + if config.device.autocreate_device_type: + device_type_create = self._netbox_create_device_type() + device_type = get_device_type(self.get_chassis()) + else: + raise Exception('Chassis "{}" doesn\'t exist'.format(self.get_chassis())) device_role = get_device_role(config.device.chassis_role) serial = self.get_chassis_service_tag() logging.info("Creating chassis blade (serial: {serial})".format(serial=serial)) @@ -212,6 +259,12 @@ def _netbox_create_chassis(self, datacenter, tenant, rack): def _netbox_create_blade(self, chassis, datacenter, tenant, rack): device_role = get_device_role(config.device.blade_role) device_type = get_device_type(self.get_product_name()) + if not device_type: + if config.device.autocreate_device_type: + device_type_create = self._netbox_create_device_type() + device_type = get_device_type(self.get_product_name()) + else: + raise Exception('Blade "{}" type doesn\'t exist'.format(self.get_product_name())) serial = self.get_service_tag() hostname = self.get_hostname() logging.info( @@ -236,6 +289,12 @@ def _netbox_create_blade(self, chassis, datacenter, tenant, rack): def _netbox_create_blade_expansion(self, chassis, datacenter, tenant, rack): device_role = get_device_role(config.device.blade_role) device_type = get_device_type(self.get_expansion_product()) + if not device_type: + if config.device.autocreate_device_type: + device_type_create = self._netbox_create_device_type() + device_type = get_device_type(self.get_expansion_product()) + else: + raise Exception('Blade expansion type"{}" doesn\'t exist'.format(self.get_expansion_product())) serial = self.get_expansion_service_tag() hostname = self.get_hostname() + " expansion" logging.info( @@ -271,7 +330,11 @@ def _netbox_create_server(self, datacenter, tenant, rack): device_role = get_device_role(config.device.server_role) device_type = get_device_type(self.get_product_name()) if not device_type: - raise Exception('Chassis "{}" doesn\'t exist'.format(self.get_chassis())) + if config.device.autocreate_device_type: + device_type_create = self._netbox_create_device_type() + device_type = get_device_type(self.get_product_name()) + else: + raise Exception('Server type "{}" doesn\'t exist'.format(self.get_chassis())) serial = self.get_service_tag() hostname = self.get_hostname() logging.info( @@ -506,11 +569,28 @@ def netbox_create_or_update(self, config): myips = nb.ipam.ip_addresses.filter(device_id=server.id) update = 0 + # Deal with IPMI for ip in myips: if ip.assigned_object.display == "IPMI" and ip != server.oob_ip: server.oob_ip = ip.id update += 1 break + # Deal with iPV4 + myip4s = nb.ipam.ip_addresses.filter(device_id=server.id) + for ip in myip4s: + print(ip.family.value) + print(ip.assigned_object.display) + if ip.assigned_object.display != "IPMI" and ip.family.value == 4 and ip != server.primary_ip4: + server.primary_ip4 = ip.id + update += 1 + break + # Deal with iPV6 + myip6s = nb.ipam.ip_addresses.filter(device_id=server.id) + for ip in myip6s: + if ip.assigned_object.display != "IPMI" and ip.family.value == 6 and ip != server.primary_ip6: + server.primary_ip6 = ip.id + update += 1 + break if update: server.save()