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:
parent
957e5f4fb9
commit
57400e3207
13 changed files with 336 additions and 32 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'):
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
|
|
||||||
|
|
@ -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
59
netbox_agent/ovs.py
Normal 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])
|
||||||
|
|
@ -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
23
netbox_agent/vendors/generic.py
vendored
Normal 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()
|
||||||
Loading…
Add table
editor.link_modal.header
Reference in a new issue