Added OpenVswitch support (OVS)

Added gbics to inventory.
Added ability to change chassis, blade, and server roles
Added Generic Host type - this can be used for any server.
Added tag support for the host.
Added sudo support - agent can run as a non-root user, and use root
      only for the collection programs.

Bugfix - running -u (update) on a blade server failed
This commit is contained in:
Thomas Davis 2020-01-08 21:19:55 -08:00
commit 57400e3207
13 changed files with 336 additions and 32 deletions

View file

@ -5,6 +5,7 @@ from netbox_agent.config import config
from netbox_agent.vendors.hp import HPHost from netbox_agent.vendors.hp import HPHost
from netbox_agent.vendors.qct import QCTHost from netbox_agent.vendors.qct import QCTHost
from netbox_agent.vendors.supermicro import SupermicroHost from netbox_agent.vendors.supermicro import SupermicroHost
from netbox_agent.vendors.generic import GenericHost
MANUFACTURERS = { MANUFACTURERS = {
'Dell Inc.': DellHost, 'Dell Inc.': DellHost,
@ -12,11 +13,14 @@ MANUFACTURERS = {
'HPE': HPHost, 'HPE': HPHost,
'Supermicro': SupermicroHost, 'Supermicro': SupermicroHost,
'Quanta Cloud Technology Inc.': QCTHost, 'Quanta Cloud Technology Inc.': QCTHost,
'Generic': GenericHost,
'Default string': GenericHost
} }
def run(config): def run(config):
manufacturer = dmidecode.get_by_type('Chassis')[0].get('Manufacturer') manufacturer = dmidecode.get_by_type('Chassis')[0].get('Manufacturer')
server = MANUFACTURERS[manufacturer](dmi=dmidecode) server = MANUFACTURERS[manufacturer](dmi=dmidecode)
if config.debug: if config.debug:

View file

@ -29,6 +29,20 @@ def get_config():
p.add_argument('--netbox.token', help='Netbox API Token') p.add_argument('--netbox.token', help='Netbox API Token')
p.add_argument('--hostname_cmd', default=None, p.add_argument('--hostname_cmd', default=None,
help="Command to output hostname, used as Device's name in netbox") 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 device')
p.add_argument('--device.blade_role', default=r'Blade',
help='device_role to use for blade servers')
p.add_argument('--device.chassis_role', default=r'Server Chassis',
help='device_role to use for a chassis')
p.add_argument('--device.server_role', default=r'Server',
help='device_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 custom driver file path')
p.add_argument('--tenant.regex',
help='tenant regex to extract Netbox tenant slug')
p.add_argument('--datacenter_location.driver', p.add_argument('--datacenter_location.driver',
help='Datacenter location driver, ie: cmd, file') help='Datacenter location driver, ie: cmd, file')
p.add_argument('--datacenter_location.driver_file', p.add_argument('--datacenter_location.driver_file',

View file

@ -138,7 +138,7 @@ def _execute_cmd():
logging.error('Dmidecode does not seem to be present on your system. Add it your path or ' logging.error('Dmidecode does not seem to be present on your system. Add it your path or '
'check the compatibility of this project with your distro.') 'check the compatibility of this project with your distro.')
sys.exit(1) sys.exit(1)
return _subprocess.check_output(['dmidecode', ], stderr=_subprocess.PIPE) return _subprocess.check_output(['sudo', '/usr/sbin/dmidecode', ], stderr=_subprocess.PIPE)
def _parse(buffer): def _parse(buffer):

View file

@ -1,9 +1,22 @@
import re import re
from shutil import which from shutil import which
from pprint import pprint
import subprocess import subprocess
# Originally from https://github.com/opencoff/useful-scripts/blob/master/linktest.py # Originally from https://github.com/opencoff/useful-scripts/blob/master/linktest.py
# 'Connector':'connector',
# 'Transceiver type': 'transciever_type',
module_map = {
'Identifier' : 'identifier',
'Extended identifier': 'extended_identifier',
'Vendor name': 'vendor',
'Vendor PN': 'partnumber',
'Vendor SN': 'serialnumber',
'Vendor rev': 'revision',
}
# mapping fields from ethtool output to simple names # mapping fields from ethtool output to simple names
field_map = { field_map = {
'Supported ports': 'ports', 'Supported ports': 'ports',
@ -39,7 +52,7 @@ class Ethtool():
parse ethtool output parse ethtool output
""" """
output = subprocess.getoutput('ethtool {}'.format(self.interface)) output = subprocess.getoutput('sudo /usr/sbin/ethtool {}'.format(self.interface))
fields = {} fields = {}
field = '' field = ''
@ -62,17 +75,75 @@ class Ethtool():
fields[field] += ' ' + line.strip() fields[field] += ' ' + line.strip()
return fields return fields
def _parse_ethtool_module_output(self): def _parse_ethtool_info_output(self):
status, output = subprocess.getstatusoutput('ethtool -m {}'.format(self.interface)) status, output = subprocess.getstatusoutput('sudo /usr/sbin/ethtool -i {}'.format(self.interface))
if status != 0: if status != 0:
return {} return {}
r = re.search(r'Identifier.*\((\w+)\)', output)
if r and len(r.groups()) > 0: fields = {}
return {'form_factor': r.groups()[0]} field = ''
for line in output.split('\n'):
line = line.rstrip()
r = line.find(':')
if r > 0:
field = line[:r].strip()
output = line[r+1:].strip()
fields[field] = output
return fields
def _parse_ethtool_module_output(self):
"""
ethtool output is a mess.. good for human reading, bad for parsing.
we massage the output to make it move useful for inventory purposes
ie, connector and type, plus dropping un needed information.
"""
status, output = subprocess.getstatusoutput('sudo /usr/sbin/ethtool -m {}'.format(self.interface))
if status != 0:
return {}
fields = {}
field = ''
transciever = []
for line in output.split('\n'):
line = line.rstrip()
r = line.find(':')
if r > 0:
field = line[:r].strip()
if 'Identifier' in field:
c = re.match(r'.*\((?P<identifier>\w+)\).*', line[r+1:].strip())
fields['identifier'] = c.group('identifier')
continue
if 'Connector' in field:
c = re.match(r'.*\((?P<connector>\w+)\).*', line[r+1:].strip())
fields['connector'] = c.group('connector')
continue
if 'type' in field:
field = line[r+1:].strip().replace('10G Ethernet: ', '')
field = field.strip().replace('100G Ethernet: ', '')
field = field.strip().replace('40G Ethernet: ', '')
field = field.strip().replace('Ethernet: ', '')
if 'FC:' in field:
continue
transciever.append(field)
continue
if field not in module_map:
continue
fields[module_map[field]] = line[r+1:].strip()
j = ", "
fields['transciever_type'] = j.join(transciever)
return fields
def parse(self): def parse(self):
if which('ethtool') is None: if which('ethtool') is None:
return None return None
output = self._parse_ethtool_output() output = self._parse_ethtool_output()
output.update(self._parse_ethtool_info_output())
output.update(self._parse_ethtool_module_output()) output.update(self._parse_ethtool_module_output())
return output return output

View file

@ -8,10 +8,12 @@ from netbox_agent.raid.hp import HPRaid
from netbox_agent.raid.omreport import OmreportRaid from netbox_agent.raid.omreport import OmreportRaid
from netbox_agent.raid.storcli import StorcliRaid from netbox_agent.raid.storcli import StorcliRaid
from netbox_agent.lshw import LSHW from netbox_agent.lshw import LSHW
from netbox_agent.ethtool import Ethtool
INVENTORY_TAG = { INVENTORY_TAG = {
'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'}, 'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'},
'disk': {'name': 'hw:disk', 'slug': 'hw-disk'}, 'disk': {'name': 'hw:disk', 'slug': 'hw-disk'},
'gbic': {'name': 'hw:gbic', 'slug': 'hw-gbic' },
'interface': {'name': 'hw:interface', 'slug': 'hw-interface'}, 'interface': {'name': 'hw:interface', 'slug': 'hw-interface'},
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'}, 'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
'motherboard': {'name': 'hw:motherboard', 'slug': 'hw-motherboard'}, 'motherboard': {'name': 'hw:motherboard', 'slug': 'hw-motherboard'},
@ -119,10 +121,23 @@ class Inventory():
motherboards = [] motherboards = []
m = {} m = {}
m['serial'] = self.lshw.motherboard_serial
m['vendor'] = self.lshw.vendor m['vendor'] = self.lshw.vendor
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.motherboard)
m['description'] = '{} Motherboard'.format(self.lshw.motherboard) if "Default string" in self.lshw.motherboard_serial:
m['serial'] = 'No S/N'
else:
m['serial'] = self.lshw.motherboard_serial
if "Default string" in self.lshw.motherboard:
m['description'] = 'Motherboard'
else:
m['description'] = '{} Motherboard'.format(self.lshw.motherboard)
if "Default" in self.lshw.motherboard:
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.product)
else:
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.motherboard)
motherboards.append(m) motherboards.append(m)
@ -143,7 +158,7 @@ class Inventory():
)) ))
nb_motherboard.delete() nb_motherboard.delete()
# create interfaces that are not in netbox # create motherboards that are not in netbox
for motherboard in motherboards: for motherboard in motherboards:
if motherboard.get('serial') not in [x.serial for x in nb_motherboards]: if motherboard.get('serial') not in [x.serial for x in nb_motherboards]:
self.create_netbox_inventory_item( self.create_netbox_inventory_item(
@ -155,7 +170,24 @@ class Inventory():
description='{}'.format(motherboard.get('description')) description='{}'.format(motherboard.get('description'))
) )
def create_netbox_interface_gbic(self, iface, info):
manufacturer = self.find_or_create_manufacturer(info["vendor"])
_ = nb.dcim.inventory_items.create(
device=self.device_id,
manufacturer=manufacturer.id,
discovered=True,
tags=[INVENTORY_TAG['gbic']['name']],
name="{}/GBIC in interface {}".format(info.get('identifier'),iface.get('name')),
part_id="{}".format(info.get('partnumber')),
serial='{}'.format(info.get('serialnumber')),
description='{}/{} connector GBIC/{}'.format(info.get('transciever_type'), info.get('connector'), info.get('identifier'))
)
def create_netbox_interface(self, iface): def create_netbox_interface(self, iface):
if "Controller" in iface['product']:
iface['product'] = iface['product'].replace(" Controller", "")
print("length %d" % len(iface["product"]))
manufacturer = self.find_or_create_manufacturer(iface["vendor"]) manufacturer = self.find_or_create_manufacturer(iface["vendor"])
_ = nb.dcim.inventory_items.create( _ = nb.dcim.inventory_items.create(
device=self.device_id, device=self.device_id,
@ -186,6 +218,9 @@ class Inventory():
for iface in interfaces: for iface in interfaces:
if iface.get('serial') not in [x.serial for x in nb_interfaces]: if iface.get('serial') not in [x.serial for x in nb_interfaces]:
self.create_netbox_interface(iface) self.create_netbox_interface(iface)
info = Ethtool(iface['name']).parse()
if "FIBRE" in info.get("port"):
self.create_netbox_interface_gbic(iface, Ethtool(iface['name']).parse())
def create_netbox_cpus(self): def create_netbox_cpus(self):
for cpu in self.lshw.get_hw_linux('cpu'): for cpu in self.lshw.get_hw_linux('cpu'):

View file

@ -34,7 +34,7 @@ class IPMI():
Bad Password Threshold : Not Available Bad Password Threshold : Not Available
""" """
def __init__(self): def __init__(self):
self.ret, self.output = subprocess.getstatusoutput('ipmitool lan print') self.ret, self.output = subprocess.getstatusoutput('sudo /usr/bin/ipmitool lan print')
if self.ret != 0: if self.ret != 0:
logging.error('Cannot get ipmi info: {}'.format(self.output)) logging.error('Cannot get ipmi info: {}'.format(self.output))

View file

@ -3,7 +3,7 @@ import subprocess
class LLDP(): class LLDP():
def __init__(self): def __init__(self):
self.output = subprocess.getoutput('lldpctl -f keyvalue') self.output = subprocess.getoutput('sudo /usr/sbin/lldpctl -f keyvalue')
self.data = self.parse() self.data = self.parse()
def parse(self): def parse(self):

View file

@ -49,6 +49,17 @@ class LocationBase():
return getattr(self.driver, 'get')(self.driver_value, self.regex) 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): class Datacenter(LocationBase):
def __init__(self): def __init__(self):
driver = config.datacenter_location.driver.split(':')[0] if \ driver = config.datacenter_location.driver.split(':')[0] if \

View file

@ -13,7 +13,7 @@ class LSHW():
sys.exit(1) sys.exit(1)
data = subprocess.getoutput( data = subprocess.getoutput(
'lshw -quiet -json' 'sudo /usr/sbin/lshw -quiet -json'
) )
self.hw_info = json.loads(data) self.hw_info = json.loads(data)
self.info = {} self.info = {}
@ -23,7 +23,10 @@ class LSHW():
self.power = [] self.power = []
self.disks = [] self.disks = []
self.vendor = self.hw_info["vendor"] self.vendor = self.hw_info["vendor"]
self.product = self.hw_info["product"] if "(XXXXXX)" in self.hw_info["product"]:
self.product = self.hw_info['product'].replace(" (XXXXXX)", "")
else:
self.product = self.hw_info["product"]
self.chassis_serial = self.hw_info["serial"] self.chassis_serial = self.hw_info["serial"]
self.motherboard_serial = self.hw_info["children"][0].get("serial", "No S/N") self.motherboard_serial = self.hw_info["children"][0].get("serial", "No S/N")
self.motherboard = self.hw_info["children"][0].get("product", "Motherboard") self.motherboard = self.hw_info["children"][0].get("product", "Motherboard")
@ -86,7 +89,7 @@ class LSHW():
elif "nvme" in obj["configuration"]["driver"]: elif "nvme" in obj["configuration"]["driver"]:
nvme = json.loads( nvme = json.loads(
subprocess.check_output(["nvme", '-list', '-o', 'json'], subprocess.check_output(["sudo", "/usr/sbin/nvme", '-list', '-o', 'json'],
encoding='utf8')) # noqa: E128 encoding='utf8')) # noqa: E128
d = {} d = {}

View file

@ -5,12 +5,15 @@ import re
from netaddr import IPAddress, IPNetwork from netaddr import IPAddress, IPNetwork
import netifaces import netifaces
from pprint import pprint
from netbox_agent.config import netbox_instance as nb, config from netbox_agent.config import netbox_instance as nb, config
from netbox_agent.ethtool import Ethtool from netbox_agent.ethtool import Ethtool
from netbox_agent.ipmi import IPMI from netbox_agent.ipmi import IPMI
from netbox_agent.lldp import LLDP from netbox_agent.lldp import LLDP
from netbox_agent.ovs import OVS
IFACE_TYPE_VIRTUAL = 0
IFACE_TYPE_100ME_FIXED = 800 IFACE_TYPE_100ME_FIXED = 800
IFACE_TYPE_1GE_FIXED = 1000 IFACE_TYPE_1GE_FIXED = 1000
IFACE_TYPE_1GE_GBIC = 1050 IFACE_TYPE_1GE_GBIC = 1050
@ -46,10 +49,13 @@ class Network():
self.server = server self.server = server
self.device = self.server.get_netbox_server() self.device = self.server.get_netbox_server()
self.datacenter = self.server.get_datacenter()
self.tenant = self.server.get_netbox_tenant()
self.lldp = LLDP() if config.network.lldp else None self.lldp = LLDP() if config.network.lldp else None
self.scan() self.scan()
def scan(self): def scan(self):
ovs = OVS()
for interface in os.listdir('/sys/class/net/'): for interface in os.listdir('/sys/class/net/'):
# ignore if it's not a link (ie: bonding_masters etc) # ignore if it's not a link (ie: bonding_masters etc)
if not os.path.islink('/sys/class/net/{}'.format(interface)): if not os.path.islink('/sys/class/net/{}'.format(interface)):
@ -89,6 +95,7 @@ class Network():
ip_addr.pop(i) ip_addr.pop(i)
mac = open('/sys/class/net/{}/address'.format(interface), 'r').read().strip() mac = open('/sys/class/net/{}/address'.format(interface), 'r').read().strip()
mtu = open('/sys/class/net/{}/mtu'.format(interface), 'r').read().strip()
vlan = None vlan = None
if len(interface.split('.')) > 1: if len(interface.split('.')) > 1:
vlan = int(interface.split('.')[1]) vlan = int(interface.split('.')[1])
@ -102,6 +109,7 @@ class Network():
nic = { nic = {
'name': interface, 'name': interface,
'mac': mac if mac != '00:00:00:00:00:00' else None, 'mac': mac if mac != '00:00:00:00:00:00' else None,
'mtu': mtu if mtu != 0 else 1500,
'ip': [ 'ip': [
'{}/{}'.format( '{}/{}'.format(
x['addr'], x['addr'],
@ -109,10 +117,20 @@ class Network():
) for x in ip_addr ) for x in ip_addr
] if ip_addr else None, # FIXME: handle IPv6 addresses ] if ip_addr else None, # FIXME: handle IPv6 addresses
'ethtool': Ethtool(interface).parse(), 'ethtool': Ethtool(interface).parse(),
'ovs': ovs.get_info(interface),
'vlan': vlan, 'vlan': vlan,
'bonding': bonding, 'bonding': bonding,
'bonding_slaves': bonding_slaves, 'bonding_slaves': bonding_slaves,
} }
if nic["vlan"] is None:
if nic["ovs"] != None:
ovs_info = nic.get("ovs")
nic["vlan"] = ovs_info.get("vlan")
if nic["ovs"] != None:
nic["description"] = "OVS internal interface"
self.nics.append(nic) self.nics.append(nic)
def _set_bonding_interfaces(self): def _set_bonding_interfaces(self):
@ -140,6 +158,9 @@ class Network():
return self.nics return self.nics
def get_netbox_network_card(self, nic): def get_netbox_network_card(self, nic):
if self.device is None:
return
if nic['mac'] is None: if nic['mac'] is None:
interface = nb.dcim.interfaces.get( interface = nb.dcim.interfaces.get(
device_id=self.device.id, device_id=self.device.id,
@ -160,6 +181,10 @@ class Network():
) )
def get_netbox_type_for_nic(self, nic): def get_netbox_type_for_nic(self, nic):
o = nic.get('ovs')
if o:
if o.get("type") == "internal":
return IFACE_TYPE_VIRTUAL
if nic.get('bonding'): if nic.get('bonding'):
return IFACE_TYPE_LAG return IFACE_TYPE_LAG
if nic.get('ethtool') is None: if nic.get('ethtool') is None:
@ -190,6 +215,7 @@ class Network():
# since users may have same vlan id in multiple dc # since users may have same vlan id in multiple dc
vlan = nb.ipam.vlans.get( vlan = nb.ipam.vlans.get(
vid=vlan_id, vid=vlan_id,
site=self.datacenter
) )
if vlan is None: if vlan is None:
vlan = nb.ipam.vlans.create( vlan = nb.ipam.vlans.create(
@ -242,6 +268,8 @@ class Network():
def create_or_update_ipmi(self): def create_or_update_ipmi(self):
ipmi = self.get_ipmi() ipmi = self.get_ipmi()
if "MAC Address" not in ipmi:
return
mac = ipmi['MAC Address'] mac = ipmi['MAC Address']
ip = ipmi['IP Address'] ip = ipmi['IP Address']
netmask = ipmi['Subnet Mask'] netmask = ipmi['Subnet Mask']
@ -285,8 +313,9 @@ class Network():
nb_vlan = None nb_vlan = None
interface = nb.dcim.interfaces.create( interface = nb.dcim.interfaces.create(
device=self.device.id, device=self.device.id,
name=nic['name'], name=nic.get('name'),
mac_address=nic['mac'], mac_address=nic.get('mac'),
mtu=nic.get('mtu'),
type=type, type=type,
mgmt_only=mgmt, mgmt_only=mgmt,
) )
@ -344,6 +373,7 @@ class Network():
address=ip, address=ip,
interface=interface.id, interface=interface.id,
status=1, status=1,
tenant=self.tenant.id if self.tenant else None,
) )
else: else:
netbox_ip = netbox_ips[0] netbox_ip = netbox_ips[0]

59
netbox_agent/ovs.py Normal file
View file

@ -0,0 +1,59 @@
import subprocess
import json
import logging
import sys
from shutil import which
from pprint import pprint
from netbox_agent.misc import is_tool
class OVS():
def __init__(self):
self.fields = {}
field = ''
if which('ovs-vsctl') is None:
print("could not find ovs-vsctl")
return
status, output = subprocess.getstatusoutput("sudo ovs-vsctl show")
if status != 0:
print("ovs-vsctl failed.")
return
for line in output.split('\n'):
line = line.rstrip()
r = line.split(" ")[-2:]
if len(r) < 2:
self.fields["info"] = {}
self.fields["info"]["switch_uuid"] = r[0]
if "Bridge" in r[0]:
bridge = r[1]
if "Port" in r[0]:
port = r[1]
self.fields[port] = {}
self.fields[port]["port"] = r[1]
self.fields[port]["bridge"] = bridge
if "tag" in r[0]:
self.fields[port]["vlan"] = r[1]
if "Interface" in r[0]:
self.fields[port]["interface"] = r[1]
if "type" in r[0]:
self.fields[port]["type"] = r[1]
if "options" in r[0]:
self.fields[port]["options"] = r[1]
if "ovs_version" in r[0]:
self.fields["info"]["ovs_version"] = r[1]
def get_info(self, interface):
for iface in self.fields:
if "interface" in self.fields[iface]:
if interface in self.fields[iface]["interface"]:
return(self.fields[iface])

View file

@ -5,7 +5,7 @@ import subprocess
from netbox_agent.config import netbox_instance as nb, config from netbox_agent.config import netbox_instance as nb, config
import netbox_agent.dmidecode as dmidecode import netbox_agent.dmidecode as dmidecode
from netbox_agent.location import Datacenter, Rack from netbox_agent.location import Datacenter, Tenant, Rack
from netbox_agent.inventory import Inventory from netbox_agent.inventory import Inventory
from netbox_agent.network import Network from netbox_agent.network import Network
from netbox_agent.power import PowerSupply from netbox_agent.power import PowerSupply
@ -43,6 +43,30 @@ class ServerBase():
self.network = None self.network = None
def create_netbox_tags(self, tags):
for tag in tags:
print("checking tag %s" % tag)
nb_tag = nb.extras.tags.get(
name=tag
)
if not nb_tag:
print("tag not found, creating")
nb_tag = nb.extras.tags.create(
name=tag,
slug=tag
)
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): def get_datacenter(self):
dc = Datacenter() dc = Datacenter()
return dc.get() return dc.get()
@ -101,6 +125,8 @@ class ServerBase():
""" """
Return the Service Tag from dmidecode info Return the Service Tag from dmidecode info
""" """
if "Default string" in self.system[0]['Serial Number']:
self.system[0]['Serial Number'] = "000000"
return self.system[0]['Serial Number'].strip() return self.system[0]['Serial Number'].strip()
def get_hostname(self): def get_hostname(self):
@ -135,9 +161,14 @@ class ServerBase():
def get_power_consumption(self): def get_power_consumption(self):
raise NotImplementedError raise NotImplementedError
def _netbox_create_blade_chassis(self, datacenter, rack): def _netbox_create_blade_chassis(self, datacenter, tenant, rack):
tags = config.device.tags.split(",")
self.create_netbox_tags(tags)
device_type = get_device_type(self.get_chassis()) 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() serial = self.get_chassis_service_tag()
logging.info('Creating chassis blade (serial: {serial})'.format( logging.info('Creating chassis blade (serial: {serial})'.format(
serial=serial)) serial=serial))
@ -147,12 +178,18 @@ class ServerBase():
serial=serial, serial=serial,
device_role=device_role.id, device_role=device_role.id,
site=datacenter.id if datacenter else None, site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None, rack=rack.id if rack else None,
tags=tags,
) )
return new_chassis return new_chassis
def _netbox_create_blade(self, chassis, datacenter, rack): def _netbox_create_blade(self, chassis, datacenter, tenant, rack):
device_role = get_device_role('Blade') tags = config.device.tags.split(",")
self.create_netbox_tags(tags)
device_role = get_device_role(config.device.blade_role)
device_type = get_device_type(self.get_product_name()) device_type = get_device_type(self.get_product_name())
serial = self.get_service_tag() serial = self.get_service_tag()
hostname = self.get_hostname() hostname = self.get_hostname()
@ -167,7 +204,9 @@ class ServerBase():
device_type=device_type.id, device_type=device_type.id,
parent_device=chassis.id, parent_device=chassis.id,
site=datacenter.id if datacenter else None, site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None, rack=rack.id if rack else None,
tags=tags,
) )
return new_blade return new_blade
@ -192,13 +231,18 @@ class ServerBase():
slot=slot slot=slot
)) ))
def _netbox_create_server(self, datacenter, rack): def _netbox_create_server(self, datacenter, tenant, rack):
device_role = get_device_role('Server') tags = config.device.tags.split(",")
self.create_netbox_tags(tags)
device_role = get_device_role(config.device.server_role)
device_type = get_device_type(self.get_product_name()) device_type = get_device_type(self.get_product_name())
if not device_type: if not device_type:
raise Exception('Chassis "{}" doesn\'t exist'.format(self.get_chassis())) raise Exception('Chassis "{}" doesn\'t exist'.format(self.get_chassis()))
serial = self.get_service_tag() serial = self.get_service_tag()
hostname = self.get_hostname() hostname = self.get_hostname()
logging.info('Creating server (serial: {serial}) {hostname}'.format( logging.info('Creating server (serial: {serial}) {hostname}'.format(
serial=serial, hostname=hostname)) serial=serial, hostname=hostname))
new_server = nb.dcim.devices.create( new_server = nb.dcim.devices.create(
@ -207,7 +251,9 @@ class ServerBase():
device_role=device_role.id, device_role=device_role.id,
device_type=device_type.id, device_type=device_type.id,
site=datacenter.id if datacenter else None, site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None, rack=rack.id if rack else None,
tags=tags,
) )
return new_server return new_server
@ -217,6 +263,7 @@ class ServerBase():
def netbox_create(self, config): def netbox_create(self, config):
logging.debug('Creating Server..') logging.debug('Creating Server..')
datacenter = self.get_netbox_datacenter() datacenter = self.get_netbox_datacenter()
tenant = self.get_netbox_tenant()
rack = self.get_netbox_rack() rack = self.get_netbox_rack()
if self.is_blade(): if self.is_blade():
# let's find the blade # let's find the blade
@ -231,16 +278,16 @@ class ServerBase():
serial=self.get_chassis_service_tag() serial=self.get_chassis_service_tag()
) )
if not chassis: if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter, rack) chassis = self._netbox_create_blade_chassis(datacenter, tenant, rack)
blade = self._netbox_create_blade(chassis, datacenter, rack) blade = self._netbox_create_blade(chassis, datacenter, tenant, rack)
# Set slot for blade # Set slot for blade
self._netbox_set_blade_slot(chassis, blade) self._netbox_set_blade_slot(chassis, blade)
else: else:
server = nb.dcim.devices.get(serial=self.get_service_tag()) server = nb.dcim.devices.get(serial=self.get_service_tag())
if not server: if not server:
self._netbox_create_server(datacenter, rack) server = self._netbox_create_server(datacenter, tenant, rack)
self.network = Network(server=self) self.network = Network(server=self)
self.network.create_netbox_network_cards() self.network.create_netbox_network_cards()
@ -253,12 +300,18 @@ class ServerBase():
self.inventory.create() self.inventory.create()
logging.debug('Server created!') logging.debug('Server created!')
def _netbox_update_chassis_for_blade(self, server, datacenter): def _netbox_update_chassis_for_blade(self, server, datacenter, tenant):
chassis = server.parent_device.device_bay.device chassis = server.parent_device.device_bay.device
device_bay = nb.dcim.device_bays.get( device_bay = nb.dcim.device_bays.get(
server.parent_device.device_bay.id server.parent_device.device_bay.id
) )
netbox_chassis_serial = server.parent_device.device_bay.device.serial
parent_chassis = nb.dcim.devices.get(
chassis.id
)
# netbox_chassis_serial = server.parent_device.device_bay.device.serial
netbox_chassis_serial = parent_chassis.serial
move_device_bay = False move_device_bay = False
# check chassis serial with dmidecode # check chassis serial with dmidecode
@ -269,7 +322,7 @@ class ServerBase():
serial=self.get_chassis_service_tag() serial=self.get_chassis_service_tag()
) )
if not chassis: if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter) chassis = self._netbox_create_blade_chassis(datacenter, tenant)
if move_device_bay or device_bay.name != self.get_blade_slot(): if move_device_bay or device_bay.name != self.get_blade_slot():
logging.info('Device ({serial}) seems to have moved, reseting old slot..'.format( logging.info('Device ({serial}) seems to have moved, reseting old slot..'.format(
serial=server.serial)) serial=server.serial))
@ -298,16 +351,17 @@ class ServerBase():
update = 0 update = 0
if self.is_blade(): if self.is_blade():
datacenter = self.get_netbox_datacenter() datacenter = self.get_netbox_datacenter()
tenant = self.get_netbox_tenant()
# if it's already linked to a chassis # if it's already linked to a chassis
if server.parent_device: if server.parent_device:
self._netbox_update_chassis_for_blade(server, datacenter) self._netbox_update_chassis_for_blade(server, datacenter, tenant)
else: else:
logging.info('Blade is not in a chassis, fixing...') logging.info('Blade is not in a chassis, fixing...')
chassis = nb.dcim.devices.get( chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag() serial=self.get_chassis_service_tag()
) )
if not chassis: if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter) chassis = self._netbox_create_blade_chassis(datacenter, tenant)
# Set slot for blade # Set slot for blade
self._netbox_set_blade_slot(chassis, server) self._netbox_set_blade_slot(chassis, server)

23
netbox_agent/vendors/generic.py vendored Normal file
View file

@ -0,0 +1,23 @@
import netbox_agent.dmidecode as dmidecode
from netbox_agent.server import ServerBase
class GenericHost(ServerBase):
def __init__(self, *args, **kwargs):
super(GenericHost, self).__init__(*args, **kwargs)
self.manufacturer = dmidecode.get_by_type('Baseboard')[0].get('Manufacturer')
def is_blade(self):
return None
def get_blade_slot(self):
return None
def get_chassis_name(self):
return None
def get_chassis(self):
return self.get_product_name()
def get_chassis_service_tag(self):
return self.get_service_tag()