From a60c0cd70c8d45e7cb77b0efb319a2210e5be12d Mon Sep 17 00:00:00 2001 From: ThomasADavis Date: Wed, 1 Jul 2020 09:54:58 -0700 Subject: [PATCH] Add tags,device roles,tenants.. (#110) * Adds support for: * sets the tenant for IP addresses and Devices. * setting device tags * setting the blade, chassis, and server roles. Co-authored-by: Thomas Davis Co-authored-by: Solvik --- README.md | 17 +++++++++ netbox_agent.yaml.example | 16 +++++++++ netbox_agent/config.py | 14 ++++++++ netbox_agent/location.py | 11 ++++++ netbox_agent/misc.py | 32 +++++++++++++++++ netbox_agent/network.py | 4 +++ netbox_agent/server.py | 64 +++++++++++++++++++--------------- netbox_agent/virtualmachine.py | 42 ++++++++++++++++++---- 8 files changed, 165 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3d0bc40..7d46715 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,23 @@ network: # enable auto-cabling by parsing LLDP answers lldp: true +# +# You can use these to change the Netbox roles. +# These are the defaults. +# +#device: +# chassis_role: "Server Chassis" +# blade_role: "Blade" +# server_role: "Server" +# tags: server, blade, ,just a comma,delimited,list +# +# +# Can use this to set the tenant +# +#tenant: +# driver: "file:/tmp/tenant" +# regex: "(.*)" + ## Enable virtual machine support # virtual: # # not mandatory, can be guessed diff --git a/netbox_agent.yaml.example b/netbox_agent.yaml.example index 9c85d50..6c2f933 100644 --- a/netbox_agent.yaml.example +++ b/netbox_agent.yaml.example @@ -8,6 +8,22 @@ network: # enable auto-cabling lldp: true +# +# You can use these to change the roles. +# +#device: +# chassis_role: "Server Chassis" +# blade_role: "Blade" +# server_role: "Server" +# tags: server, blade, ,just a comma,delimited,list + +# +# Use this to set the tenant +# +#tenant: +# driver: "file:/tmp/tenant" +# regex: "(.*)" + datacenter_location: driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]" regex: "DATACENTER: (?P[A-Za-z0-9]+)" diff --git a/netbox_agent/config.py b/netbox_agent/config.py index 30d4675..72b0fbe 100644 --- a/netbox_agent/config.py +++ b/netbox_agent/config.py @@ -34,6 +34,20 @@ def get_config(): p.add_argument('--virtual.cluster_name', help='Cluster name of VM') p.add_argument('--hostname_cmd', default=None, help="Command to output hostname, used as Device's name in netbox") + p.add_argument('--device.tags', default=r'', + help='tags to use for a host') + p.add_argument('--device.blade_role', default=r'Blade', + help='role to use for a blade server') + p.add_argument('--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('--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') p.add_argument('--datacenter_location.driver', help='Datacenter location driver, ie: cmd, file') p.add_argument('--datacenter_location.driver_file', diff --git a/netbox_agent/location.py b/netbox_agent/location.py index 20d2e9f..bb1fcfc 100644 --- a/netbox_agent/location.py +++ b/netbox_agent/location.py @@ -50,6 +50,17 @@ class LocationBase(): return getattr(self.driver, 'get')(self.driver_value, self.regex) +class Tenant(LocationBase): + def __init__(self): + driver = config.tenant.driver.split(':')[0] if \ + config.tenant.driver else None + driver_value = ':'.join(config.tenant.driver.split(':')[1:]) if \ + config.tenant.driver else None + driver_file = config.tenant.driver_file + regex = config.tenant.regex + super().__init__(driver, driver_value, driver_file, regex) + + class Datacenter(LocationBase): def __init__(self): driver = config.datacenter_location.driver.split(':')[0] if \ diff --git a/netbox_agent/misc.py b/netbox_agent/misc.py index cc1873c..7656f04 100644 --- a/netbox_agent/misc.py +++ b/netbox_agent/misc.py @@ -2,12 +2,32 @@ import socket import subprocess from shutil import which +from netbox_agent.config import netbox_instance as nb + def is_tool(name): '''Check whether `name` is on PATH and marked as executable.''' return which(name) is not None +def get_device_role(role): + device_role = nb.dcim.device_roles.get( + name=role + ) + if device_role is None: + raise Exception('DeviceRole "{}" does not exist, please create it'.format(role)) + return device_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)) + return device_type + + def get_vendor(name): vendors = { 'PERC': 'Dell', @@ -37,3 +57,15 @@ def get_hostname(config): if config.hostname_cmd is None: return '{}'.format(socket.gethostname()) return subprocess.getoutput(config.hostname_cmd) + + +def create_netbox_tags(tags): + for tag in tags: + nb_tag = nb.extras.tags.get( + name=tag + ) + if not nb_tag: + nb_tag = nb.extras.tags.create( + name=tag, + slug=tag, + ) diff --git a/netbox_agent/network.py b/netbox_agent/network.py index cb163a3..334ae01 100644 --- a/netbox_agent/network.py +++ b/netbox_agent/network.py @@ -17,6 +17,9 @@ class Network(object): def __init__(self, server, *args, **kwargs): self.nics = [] + self.server = server + self.tenant = self.server.get_netbox_tenant() + self.lldp = LLDP() if config.network.lldp else None self.nics = self.scan() self.ipmi = None @@ -337,6 +340,7 @@ class Network(object): interface=interface.id, status=1, role=self.ipam_choices['ip-address:role']['Anycast'], + tenant=self.tenant.id if self.tenant else None, ) return netbox_ip else: diff --git a/netbox_agent/server.py b/netbox_agent/server.py index 687c964..cf9de93 100644 --- a/netbox_agent/server.py +++ b/netbox_agent/server.py @@ -8,29 +8,12 @@ import netbox_agent.dmidecode as dmidecode from netbox_agent.config import config from netbox_agent.config import netbox_instance as nb from netbox_agent.inventory import Inventory -from netbox_agent.location import Datacenter, Rack +from netbox_agent.location import Datacenter, Rack, Tenant +from netbox_agent.misc import create_netbox_tags, get_device_role, get_device_type from netbox_agent.network import ServerNetwork from netbox_agent.power import PowerSupply -def get_device_role(role): - device_role = nb.dcim.device_roles.get( - name=role - ) - if device_role is None: - raise Exception('DeviceRole "{}" does not exist, please create it'.format(role)) - return device_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)) - return device_type - - class ServerBase(): def __init__(self, dmi=None): if dmi: @@ -45,6 +28,20 @@ class ServerBase(): self.network = None + self.tags = list(set(config.device.tags.split(','))) if config.device.tags else [] + if self.tags and len(self.tags): + create_netbox_tags(self.tags) + + def get_tenant(self): + tenant = Tenant() + return tenant.get() + + def get_netbox_tenant(self): + tenant = nb.tenancy.tenants.get( + slug=self.get_tenant() + ) + return tenant + def get_datacenter(self): dc = Datacenter() return dc.get() @@ -153,9 +150,9 @@ class ServerBase(): def get_power_consumption(self): raise NotImplementedError - def _netbox_create_chassis(self, datacenter, rack): + def _netbox_create_chassis(self, datacenter, tenant, rack): device_type = get_device_type(self.get_chassis()) - device_role = get_device_role('Server 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)) @@ -165,12 +162,14 @@ class ServerBase(): serial=serial, device_role=device_role.id, site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, rack=rack.id if rack else None, + tags=self.tags, ) return new_chassis - def _netbox_create_blade(self, chassis, datacenter, rack): - device_role = get_device_role('Blade') + 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()) serial = self.get_service_tag() hostname = self.get_hostname() @@ -185,12 +184,14 @@ class ServerBase(): device_type=device_type.id, parent_device=chassis.id, site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, rack=rack.id if rack else None, + tags=self.tags, ) return new_blade - def _netbox_create_server(self, datacenter, rack): - device_role = get_device_role('Server') + 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())) @@ -204,7 +205,9 @@ class ServerBase(): device_role=device_role.id, device_type=device_type.id, site=datacenter.id if datacenter else None, + tenant=tenant.id if tenant else None, rack=rack.id if rack else None, + tags=self.tags, ) return new_server @@ -258,6 +261,7 @@ class ServerBase(): """ datacenter = self.get_netbox_datacenter() rack = self.get_netbox_rack() + tenant = self.get_netbox_tenant() if self.is_blade(): chassis = nb.dcim.devices.get( @@ -265,18 +269,18 @@ class ServerBase(): ) # Chassis does not exist if not chassis: - chassis = self._netbox_create_chassis(datacenter, rack) + chassis = self._netbox_create_chassis(datacenter, tenant, rack) server = nb.dcim.devices.get(serial=self.get_service_tag()) if not server: - server = self._netbox_create_blade(chassis, datacenter, rack) + server = self._netbox_create_blade(chassis, datacenter, tenant, rack) # Set slot for blade self._netbox_set_or_update_blade_slot(server, chassis, datacenter) else: server = nb.dcim.devices.get(serial=self.get_service_tag()) if not server: - self._netbox_create_server(datacenter, rack) + self._netbox_create_server(datacenter, tenant, rack) logging.debug('Updating Server...') # check network cards @@ -300,6 +304,10 @@ class ServerBase(): update += 1 server.name = self.get_hostname() + if sorted(set(server.tags)) != sorted(set(self.tags)): + server.tags = self.tags + update += 1 + if config.update_all or config.update_location: ret, server = self.update_netbox_location(server) update += ret diff --git a/netbox_agent/virtualmachine.py b/netbox_agent/virtualmachine.py index 1620ba0..1c917c5 100644 --- a/netbox_agent/virtualmachine.py +++ b/netbox_agent/virtualmachine.py @@ -3,8 +3,9 @@ import os import netbox_agent.dmidecode as dmidecode from netbox_agent.config import config from netbox_agent.config import netbox_instance as nb +from netbox_agent.location import Tenant from netbox_agent.logging import logging # NOQA -from netbox_agent.misc import get_hostname +from netbox_agent.misc import create_netbox_tags, get_hostname from netbox_agent.network import VirtualNetwork @@ -29,6 +30,10 @@ class VirtualMachine(object): self.dmi = dmidecode.parse() self.network = None + self.tags = list(set(config.device.tags.split(','))) if config.device.tags else [] + if self.tags and len(self.tags): + create_netbox_tags(self.tags) + def get_memory(self): mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') # e.g. 4015976448 mem_gib = mem_bytes / (1024.**2) # e.g. 3.74 @@ -50,6 +55,22 @@ class VirtualMachine(object): ) return cluster + def get_netbox_datacenter(self, name): + cluster = self.get_netbox_cluster() + if cluster.datacenter: + return cluster.datacenter + return None + + def get_tenant(self): + tenant = Tenant() + return tenant.get() + + def get_netbox_tenant(self): + tenant = nb.tenancy.tenants.get( + slug=self.get_tenant() + ) + return tenant + def netbox_create_or_update(self, config): logging.debug('It\'s a virtual machine') created = False @@ -60,6 +81,7 @@ class VirtualMachine(object): vcpus = self.get_vcpus() memory = self.get_memory() + tenant = self.get_netbox_tenant() if not vm: logging.debug('Creating Virtual machine..') cluster = self.get_netbox_cluster(config.virtual.cluster_name) @@ -69,18 +91,24 @@ class VirtualMachine(object): cluster=cluster.id, vcpus=vcpus, memory=memory, + tenant=tenant.id if tenant else None, + tags=self.tags, ) created = True self.network = VirtualNetwork(server=self) self.network.create_or_update_netbox_network_cards() - if not created and vm.vcpus != vcpus: - vm.vcpus = vcpus - updated += 1 - elif not created and vm.memory != memory: - vm.memory = memory - updated += 1 + if not created: + if vm.vcpus != vcpus: + vm.vcpus = vcpus + updated += 1 + if vm.memory != memory: + vm.memory = memory + updated += 1 + if sorted(set(vm.tags)) != sorted(set(self.tags)): + vm.tags = self.tags + updated += 1 if updated: vm.save()