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 <tdavis@nersc.gov>
Co-authored-by: Solvik <solvik@solvik.fr>
This commit is contained in:
ThomasADavis 2020-07-01 09:54:58 -07:00 committed by GitHub
parent bd5037996b
commit a60c0cd70c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 35 deletions

View file

@ -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

View file

@ -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<datacenter>[A-Za-z0-9]+)"

View file

@ -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',

View file

@ -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 \

View file

@ -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,
)

View file

@ -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:

View file

@ -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

View file

@ -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()