Virtual Machine feature (#122)

Add ability to create Virtual Machine in netbox with an auto-detecting feature (currently VirtualBox, Xen, Hyper-V, VMWare, GCP)

One caveat, I made the choice not to report the disk as Netbox model allow only one size but VM often have multiples.

Also, lot of code refactoring to be able to use create_or_update function style, fixing a lot of corner case
This commit is contained in:
Solvik 2020-04-19 12:28:49 +02:00 committed by GitHub
commit f06da32fc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 367 additions and 315 deletions

View file

@ -6,7 +6,7 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
# Features # Features
* Create servers, chassis and blade through standard tools (`dmidecode`) * Create virtual machines, servers, chassis and blade through standard tools (`dmidecode`)
* Create physical, bonding and vlan network interfaces with IPs (IPv4 & IPv6) * Create physical, bonding and vlan network interfaces with IPs (IPv4 & IPv6)
* Create IPMI interface if found * Create IPMI interface if found
* Create or get existing VLAN and associate it to interfaces * Create or get existing VLAN and associate it to interfaces
@ -52,6 +52,12 @@ network:
# enable auto-cabling # enable auto-cabling
lldp: true lldp: true
# virtual:
# # not mandatory, can be guessed
# enabled: True
# # see https://netbox.company.com/virtualization/clusters/
# cluster_name: my_vm_cluster
datacenter_location: datacenter_location:
driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]" driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]"
regex: "DATACENTER: (?P<datacenter>[A-Za-z0-9]+)" regex: "DATACENTER: (?P<datacenter>[A-Za-z0-9]+)"
@ -94,6 +100,14 @@ In order to handle this case, user need to set Netbox IP's mode to `Anycast` so
Tested on: Tested on:
## Virtual Machines
* Hyper-V
* VMWare
* VirtualBox
* AWS
* GCP
## Dell Inc. ## Dell Inc.
### Blades ### Blades

View file

@ -6,6 +6,7 @@ from netbox_agent.vendors.generic import GenericHost
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.virtualmachine import VirtualMachine, is_vm
MANUFACTURERS = { MANUFACTURERS = {
'Dell Inc.': DellHost, 'Dell Inc.': DellHost,
@ -19,20 +20,23 @@ MANUFACTURERS = {
def run(config): def run(config):
dmi = dmidecode.parse() dmi = dmidecode.parse()
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
try: if config.virtual.enabled or is_vm(dmi):
server = MANUFACTURERS[manufacturer](dmi=dmi) if not config.virtual.cluster_name:
except KeyError: raise Exception('virtual.cluster_name parameter is mandatory because it\'s a VM')
server = GenericHost(dmi=dmi) server = VirtualMachine(dmi=dmi)
else:
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
try:
server = MANUFACTURERS[manufacturer](dmi=dmi)
except KeyError:
server = GenericHost(dmi=dmi)
if config.debug: if config.debug:
server.print_debug() server.print_debug()
if config.register: if config.register or config.update_all or config.update_network or config.update_location or \
server.netbox_create(config)
if config.update_all or config.update_network or config.update_location or \
config.update_inventory or config.update_psu: config.update_inventory or config.update_psu:
server.netbox_update(config) server.netbox_create_or_update(config)
return True return True

View file

@ -30,6 +30,8 @@ def get_config():
p.add_argument('--log_level', default='debug') p.add_argument('--log_level', default='debug')
p.add_argument('--netbox.url', help='Netbox URL') p.add_argument('--netbox.url', help='Netbox URL')
p.add_argument('--netbox.token', help='Netbox API Token') p.add_argument('--netbox.token', help='Netbox API Token')
p.add_argument('--virtual.enabled', action='store_true', help='Is a virtual machine or not')
p.add_argument('--virtual.cluster_name', help='Cluster name of VM')
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('--datacenter_location.driver', p.add_argument('--datacenter_location.driver',

View file

@ -438,18 +438,7 @@ class Inventory():
if memory.get('serial') not in [x.serial for x in nb_memories]: if memory.get('serial') not in [x.serial for x in nb_memories]:
self.create_netbox_memory(memory) self.create_netbox_memory(memory)
def create(self): def create_or_update(self):
if config.inventory is None:
return False
self.do_netbox_cpus()
self.do_netbox_memories()
self.do_netbox_raid_cards()
self.do_netbox_disks()
self.do_netbox_interfaces()
self.do_netbox_motherboard()
return True
def update(self):
if config.inventory is None or config.update_inventory is None: if config.inventory is None or config.update_inventory is None:
return False return False
self.do_netbox_cpus() self.do_netbox_cpus()

View file

@ -1,6 +1,8 @@
import logging import logging
import subprocess import subprocess
from netaddr import IPNetwork
class IPMI(): class IPMI():
""" """
@ -40,11 +42,27 @@ class IPMI():
logging.error('Cannot get ipmi info: {}'.format(self.output)) logging.error('Cannot get ipmi info: {}'.format(self.output))
def parse(self): def parse(self):
ret = {} _ipmi = {}
if self.ret != 0: if self.ret != 0:
return ret return _ipmi
for line in self.output.splitlines(): for line in self.output.splitlines():
key = line.split(':')[0].strip() key = line.split(':')[0].strip()
if key not in ['802.1q VLAN ID', 'IP Address', 'Subnet Mask', 'MAC Address']:
continue
value = ':'.join(line.split(':')[1:]).strip() value = ':'.join(line.split(':')[1:]).strip()
ret[key] = value _ipmi[key] = value
ret = {}
ret['name'] = 'IPMI'
ret['bonding'] = False
ret['mac'] = _ipmi['MAC Address']
ret['vlan'] = int(_ipmi['802.1q VLAN ID']) \
if _ipmi['802.1q VLAN ID'] != 'Disabled' else None
ip = _ipmi['IP Address']
netmask = _ipmi['Subnet Mask']
address = str(IPNetwork('{}/{}'.format(ip, netmask)))
ret['ip'] = [address]
ret['ipmi'] = True
return ret return ret

View file

@ -3,8 +3,7 @@ import logging
from netbox_agent.config import config from netbox_agent.config import config
logger = logging.getLogger() logger = logging.getLogger()
if config.log_level.lower() == 'debug':
if config.log_level == 'debug':
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
else: else:
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)

View file

@ -1,3 +1,5 @@
import socket
import subprocess
from shutil import which from shutil import which
@ -29,3 +31,9 @@ def get_vendor(name):
if name.upper().startswith(key): if name.upper().startswith(key):
return value return value
return name return name
def get_hostname(config):
if config.hostname_cmd is None:
return '{}'.format(socket.gethostname())
return subprocess.getoutput(config.hostname_cmd)

View file

@ -4,7 +4,7 @@ import re
from itertools import chain from itertools import chain
import netifaces import netifaces
from netaddr import IPAddress, IPNetwork from netaddr import IPAddress
from netbox_agent.config import config from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb from netbox_agent.config import netbox_instance as nb
@ -13,15 +13,13 @@ from netbox_agent.ipmi import IPMI
from netbox_agent.lldp import LLDP from netbox_agent.lldp import LLDP
class Network(): class Network(object):
def __init__(self, server, *args, **kwargs): def __init__(self, server, *args, **kwargs):
self.nics = [] self.nics = []
self.server = server
self.device = self.server.get_netbox_server()
self.lldp = LLDP() if config.network.lldp else None self.lldp = LLDP() if config.network.lldp else None
self.scan() self.nics = self.scan()
self.ipmi = None
self.dcim_choices = {} self.dcim_choices = {}
dcim_c = nb.dcim.choices() dcim_c = nb.dcim.choices()
@ -38,7 +36,11 @@ class Network():
for c in ipam_c[choice]: for c in ipam_c[choice]:
self.ipam_choices[choice][c['label']] = c['value'] self.ipam_choices[choice][c['label']] = c['value']
def get_network_type():
return NotImplementedError
def scan(self): def scan(self):
nics = []
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)):
@ -102,7 +104,8 @@ class Network():
'bonding': bonding, 'bonding': bonding,
'bonding_slaves': bonding_slaves, 'bonding_slaves': bonding_slaves,
} }
self.nics.append(nic) nics.append(nic)
return nics
def _set_bonding_interfaces(self): def _set_bonding_interfaces(self):
bonding_nics = (x for x in self.nics if x['bonding']) bonding_nics = (x for x in self.nics if x['bonding'])
@ -130,25 +133,30 @@ class Network():
def get_netbox_network_card(self, nic): def get_netbox_network_card(self, nic):
if nic['mac'] is None: if nic['mac'] is None:
interface = nb.dcim.interfaces.get( interface = self.nb_net.interfaces.get(
device_id=self.device.id,
name=nic['name'], name=nic['name'],
**self.custom_arg_id,
) )
else: else:
interface = nb.dcim.interfaces.get( interface = self.nb_net.interfaces.get(
device_id=self.device.id,
mac_address=nic['mac'], mac_address=nic['mac'],
name=nic['name'], name=nic['name'],
**self.custom_arg_id,
) )
return interface return interface
def get_netbox_network_cards(self): def get_netbox_network_cards(self):
return nb.dcim.interfaces.filter( return self.nb_net.interfaces.filter(
device_id=self.device.id, **self.custom_arg_id,
mgmt_only=False,
) )
def get_netbox_type_for_nic(self, nic): def get_netbox_type_for_nic(self, nic):
if self.get_network_type() == 'virtual':
return self.dcim_choices['interface:type']['Virtual']
if nic.get('bonding'):
return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)']
if nic.get('bonding'): if nic.get('bonding'):
return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)'] return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)']
if nic.get('ethtool') is None: if nic.get('ethtool') is None:
@ -165,17 +173,6 @@ class Network():
return self.dcim_choices['interface:type']['1000BASE-T (1GE)'] return self.dcim_choices['interface:type']['1000BASE-T (1GE)']
return self.dcim_choices['interface:type']['Other'] return self.dcim_choices['interface:type']['Other']
def get_ipmi(self):
ipmi = IPMI().parse()
return ipmi
def get_netbox_ipmi(self):
ipmi = self.get_ipmi()
mac = ipmi['MAC Address']
return nb.dcim.interfaces.get(
mac=mac
)
def get_or_create_vlan(self, vlan_id): def get_or_create_vlan(self, vlan_id):
# FIXME: we may need to specify the datacenter # FIXME: we may need to specify the datacenter
# since users may have same vlan id in multiple dc # since users may have same vlan id in multiple dc
@ -206,9 +203,10 @@ class Network():
# if it's a vlan interface # if it's a vlan interface
elif vlan_id and ( elif vlan_id and (
interface.mode is None or interface.mode is None or
interface.mode.value != self.dcim_choices['interface:mode']['Access'] or type(interface.mode) is not int and (
len(interface.tagged_vlans) != 1 or interface.mode.value == self.dcim_choices['interface:mode']['Access'] or
interface.tagged_vlans[0].vid != vlan_id): len(interface.tagged_vlans) != 1 or
interface.tagged_vlans[0].vid != vlan_id)):
logging.info('Resetting tagged VLAN(s) on interface {interface}'.format( logging.info('Resetting tagged VLAN(s) on interface {interface}'.format(
interface=interface)) interface=interface))
update = True update = True
@ -232,42 +230,6 @@ class Network():
interface.untagged_vlan = nb_vlan.id interface.untagged_vlan = nb_vlan.id
return update, interface return update, interface
def create_or_update_ipmi(self):
ipmi = self.get_ipmi()
mac = ipmi['MAC Address']
ip = ipmi['IP Address']
netmask = ipmi['Subnet Mask']
vlan = int(ipmi['802.1q VLAN ID']) if ipmi['802.1q VLAN ID'] != 'Disabled' else None
address = str(IPNetwork('{}/{}'.format(ip, netmask)))
interface = nb.dcim.interfaces.get(
device_id=self.device.id,
mgmt_only=True,
)
nic = {
'name': 'IPMI',
'mac': mac,
'vlan': vlan,
'ip': [address],
}
if interface is None:
interface = self.create_netbox_nic(nic, mgmt=True)
self.create_or_update_netbox_ip_on_interface(address, interface)
else:
# let the user chose the name of mgmt ?
# guess it with manufacturer (IDRAC, ILO, ...) ?
update = False
self.create_or_update_netbox_ip_on_interface(address, interface)
update, interface = self.reset_vlan_on_interface(nic, interface)
if mac.upper() != interface.mac_address:
logging.info('IPMI mac changed from {old_mac} to {new_mac}'.format(
old_mac=interface.mac_address, new_mac=mac.upper()))
interface.mac_address = mac
update = True
if update:
interface.save()
return interface
def create_netbox_nic(self, nic, mgmt=False): def create_netbox_nic(self, nic, mgmt=False):
# TODO: add Optic Vendor, PN and Serial # TODO: add Optic Vendor, PN and Serial
type = self.get_netbox_type_for_nic(nic) type = self.get_netbox_type_for_nic(nic)
@ -275,12 +237,12 @@ class Network():
name=nic['name'], mac=nic['mac'], device=self.device.name)) name=nic['name'], mac=nic['mac'], device=self.device.name))
nb_vlan = None nb_vlan = None
interface = nb.dcim.interfaces.create( interface = self.nb_net.interfaces.create(
device=self.device.id,
name=nic['name'], name=nic['name'],
mac_address=nic['mac'], mac_address=nic['mac'],
type=type, type=type,
mgmt_only=mgmt, mgmt_only=mgmt,
**self.custom_arg,
) )
if nic['vlan']: if nic['vlan']:
@ -380,6 +342,112 @@ class Network():
netbox_ip.save() netbox_ip.save()
return netbox_ip return netbox_ip
def create_or_update_netbox_network_cards(self):
if config.update_all is None or config.update_network is None:
return None
logging.debug('Creating/Updating NIC...')
# delete unknown interface
nb_nics = self.get_netbox_network_cards()
local_nics = [x['name'] for x in self.nics]
for nic in nb_nics[:]:
if nic.name not in local_nics:
logging.info('Deleting netbox interface {name} because not present locally'.format(
name=nic.name
))
nb_nics.remove(nic)
nic.delete()
# delete IP on netbox that are not known on this server
if len(nb_nics):
netbox_ips = nb.ipam.ip_addresses.filter(
interface_id=[x.id for x in nb_nics],
)
all_local_ips = list(chain.from_iterable([
x['ip'] for x in self.nics if x['ip'] is not None
]))
for netbox_ip in netbox_ips:
if netbox_ip.address not in all_local_ips:
logging.info('Unassigning IP {ip} from {interface}'.format(
ip=netbox_ip.address, interface=netbox_ip.interface))
netbox_ip.interface = None
netbox_ip.save()
# update each nic
for nic in self.nics:
interface = self.get_netbox_network_card(nic)
if not interface:
logging.info('Interface {mac_address} not found, creating..'.format(
mac_address=nic['mac'])
)
interface = self.create_netbox_nic(nic)
nic_update = 0
if nic['name'] != interface.name:
logging.info('Updating interface {interface} name to: {name}'.format(
interface=interface, name=nic['name']))
interface.name = nic['name']
nic_update += 1
ret, interface = self.reset_vlan_on_interface(nic, interface)
nic_update += ret
_type = self.get_netbox_type_for_nic(nic)
if not interface.type or \
_type != interface.type.value:
logging.info('Interface type is wrong, resetting')
interface.type = _type
nic_update += 1
if hasattr(interface, 'lag') and interface.lag is not None:
local_lag_int = next(
item for item in self.nics if item['name'] == interface.lag.name
)
if nic['name'] not in local_lag_int['bonding_slaves']:
logging.info('Interface has no LAG, resetting')
nic_update += 1
interface.lag = None
# cable the interface
if config.network.lldp:
switch_ip = self.lldp.get_switch_ip(interface.name)
switch_interface = self.lldp.get_switch_port(interface.name)
if switch_ip and switch_interface:
ret, interface = self.create_or_update_cable(
switch_ip, switch_interface, interface
)
nic_update += ret
if nic['ip']:
# sync local IPs
for ip in nic['ip']:
self.create_or_update_netbox_ip_on_interface(ip, interface)
if nic_update > 0:
interface.save()
self._set_bonding_interfaces()
logging.debug('Finished updating NIC!')
class ServerNetwork(Network):
def __init__(self, server, *args, **kwargs):
super(ServerNetwork, self).__init__(server, args, kwargs)
self.ipmi = self.get_ipmi()
if self.ipmi:
self.nics.append(self.ipmi)
self.server = server
self.device = self.server.get_netbox_server()
self.nb_net = nb.dcim
self.custom_arg = {'device': self.device.id}
self.custom_arg_id = {'device_id': self.device.id}
def get_network_type(self):
return 'server'
def get_ipmi(self):
ipmi = IPMI().parse()
return ipmi
def connect_interface_to_switch(self, switch_ip, switch_interface, nb_server_interface): def connect_interface_to_switch(self, switch_ip, switch_interface, nb_server_interface):
logging.info('Interface {} is not connected to switch, trying to connect..'.format( logging.info('Interface {} is not connected to switch, trying to connect..'.format(
nb_server_interface.name nb_server_interface.name
@ -483,105 +551,22 @@ class Network():
) )
return update, nb_server_interface return update, nb_server_interface
def create_netbox_network_cards(self):
logging.debug('Creating NIC...')
for nic in self.nics:
interface = self.get_netbox_network_card(nic)
# if network doesn't exist we create it
if not interface:
new_interface = self.create_netbox_nic(nic)
if nic['ip']:
# for each ip, we try to find it
# assign the device's interface to it
# or simply create it
for ip in nic['ip']:
self.create_or_update_netbox_ip_on_interface(ip, new_interface)
self._set_bonding_interfaces()
self.create_or_update_ipmi()
logging.debug('Finished creating NIC!')
def update_netbox_network_cards(self): class VirtualNetwork(Network):
if config.update_all is None or config.update_network is None: def __init__(self, server, *args, **kwargs):
return None super(VirtualNetwork, self).__init__(server, args, kwargs)
logging.debug('Updating NIC...') self.server = server
self.device = self.server.get_netbox_vm()
self.nb_net = nb.virtualization
self.custom_arg = {'virtual_machine': self.device.id}
self.custom_arg_id = {'virtual_machine_id': self.device.id}
# delete unknown interface dcim_c = nb.virtualization.choices()
nb_nics = self.get_netbox_network_cards()
local_nics = [x['name'] for x in self.nics]
for nic in nb_nics:
if nic.name not in local_nics:
logging.info('Deleting netbox interface {name} because not present locally'.format(
name=nic.name
))
nic.delete()
# delete IP on netbox that are not known on this server for choice in dcim_c:
netbox_ips = nb.ipam.ip_addresses.filter( self.dcim_choices[choice] = {}
device_id=self.device.id, for c in dcim_c[choice]:
interface_id=[x.id for x in nb_nics], self.dcim_choices[choice][c['label']] = c['value']
)
all_local_ips = list(chain.from_iterable([
x['ip'] for x in self.nics if x['ip'] is not None
]))
for netbox_ip in netbox_ips:
if netbox_ip.address not in all_local_ips:
logging.info('Unassigning IP {ip} from {interface}'.format(
ip=netbox_ip.address, interface=netbox_ip.interface))
netbox_ip.interface = None
netbox_ip.save()
# update each nic def get_network_type(self):
for nic in self.nics: return 'virtual'
interface = self.get_netbox_network_card(nic)
if not interface:
logging.info('Interface {mac_address} not found, creating..'.format(
mac_address=nic['mac'])
)
interface = self.create_netbox_nic(nic)
nic_update = 0
if nic['name'] != interface.name:
logging.info('Updating interface {interface} name to: {name}'.format(
interface=interface, name=nic['name']))
interface.name = nic['name']
nic_update += 1
ret, interface = self.reset_vlan_on_interface(nic, interface)
nic_update += ret
type = self.get_netbox_type_for_nic(nic)
if not interface.type or \
type != interface.type.value:
logging.info('Interface type is wrong, resetting')
interface.type = type
nic_update += 1
if interface.lag is not None:
local_lag_int = next(
item for item in self.nics if item['name'] == interface.lag.name
)
if nic['name'] not in local_lag_int['bonding_slaves']:
logging.info('Interface has no LAG, resetting')
nic_update += 1
interface.lag = None
# cable the interface
if config.network.lldp:
switch_ip = self.lldp.get_switch_ip(interface.name)
switch_interface = self.lldp.get_switch_port(interface.name)
if switch_ip and switch_interface:
ret, interface = self.create_or_update_cable(
switch_ip, switch_interface, interface
)
nic_update += ret
if nic['ip']:
# sync local IPs
for ip in nic['ip']:
self.create_or_update_netbox_ip_on_interface(ip, interface)
if nic_update > 0:
interface.save()
self._set_bonding_interfaces()
self.create_or_update_ipmi()
logging.debug('Finished updating NIC!')

View file

@ -8,7 +8,7 @@ from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb from netbox_agent.config import netbox_instance as nb
from netbox_agent.inventory import Inventory from netbox_agent.inventory import Inventory
from netbox_agent.location import Datacenter, Rack from netbox_agent.location import Datacenter, Rack
from netbox_agent.network import Network from netbox_agent.network import ServerNetwork
from netbox_agent.power import PowerSupply from netbox_agent.power import PowerSupply
@ -136,7 +136,7 @@ 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_chassis(self, datacenter, rack):
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('Server Chassis')
serial = self.get_chassis_service_tag() serial = self.get_chassis_service_tag()
@ -172,27 +172,6 @@ class ServerBase():
) )
return new_blade return new_blade
def _netbox_set_blade_slot(self, chassis, server):
slot = self.get_blade_slot()
# Find the slot and update it with our blade
device_bays = nb.dcim.device_bays.filter(
device_id=chassis.id,
name=slot,
)
if len(device_bays) > 0:
logging.info(
'Setting device ({serial}) new slot on {slot} '
'(Chassis {chassis_serial})..'.format(
serial=server.serial, slot=slot, chassis_serial=chassis.serial
))
device_bay = device_bays[0]
device_bay.installed_device = server
device_bay.save()
else:
logging.error('Could not find slot {slot} for chassis'.format(
slot=slot
))
def _netbox_create_server(self, datacenter, rack): def _netbox_create_server(self, datacenter, rack):
device_role = get_device_role('Server') device_role = get_device_role('Server')
device_type = get_device_type(self.get_product_name()) device_type = get_device_type(self.get_product_name())
@ -215,108 +194,89 @@ class ServerBase():
def get_netbox_server(self): def get_netbox_server(self):
return nb.dcim.devices.get(serial=self.get_service_tag()) return nb.dcim.devices.get(serial=self.get_service_tag())
def netbox_create(self, config): def _netbox_set_or_update_blade_slot(self, server, chassis, datacenter):
logging.debug('Creating Server..') # before everything check if right chassis
actual_device_bay = server.parent_device.device_bay if server.parent_device else None
actual_chassis = actual_device_bay.device if actual_device_bay else None
slot = self.get_blade_slot()
if actual_chassis and \
actual_chassis.serial == chassis.serial and \
actual_device_bay.name == slot:
return
real_device_bays = nb.dcim.device_bays.filter(
device_id=chassis.id,
name=slot,
)
if len(real_device_bays) > 0:
logging.info(
'Setting device ({serial}) new slot on {slot} '
'(Chassis {chassis_serial})..'.format(
serial=server.serial, slot=slot, chassis_serial=chassis.serial
))
# reset actual device bay if set
if actual_device_bay:
actual_device_bay.installed_device = None
actual_device_bay.save()
# setup new device bay
real_device_bay = real_device_bays[0]
real_device_bay.installed_device = server
real_device_bay.save()
else:
logging.error('Could not find slot {slot} for chassis'.format(
slot=slot
))
def netbox_create_or_update(self, config):
"""
Netbox method to create or update info about our server/blade
Handle:
* new chassis for a blade
* new slot for a blade
* hostname update
* Network infos
* Inventory management
* PSU management
"""
datacenter = self.get_netbox_datacenter() datacenter = self.get_netbox_datacenter()
rack = self.get_netbox_rack() rack = self.get_netbox_rack()
if self.is_blade():
# let's find the blade
serial = self.get_service_tag()
blade = nb.dcim.devices.get(serial=serial)
chassis = nb.dcim.devices.get(serial=self.get_chassis_service_tag())
# if it doesn't exist, create it
if not blade:
# check if the chassis exist before
# if it doesn't exist, create it
chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag()
)
if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter, rack)
blade = self._netbox_create_blade(chassis, datacenter, rack) if self.is_blade():
chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag()
)
# Chassis does not exist
if not chassis:
chassis = self._netbox_create_chassis(datacenter, rack)
server = nb.dcim.devices.get(serial=self.get_service_tag())
if not server:
server = self._netbox_create_blade(chassis, datacenter, rack)
# Set slot for blade # Set slot for blade
self._netbox_set_blade_slot(chassis, blade) self._netbox_set_or_update_blade_slot(server, chassis, datacenter)
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) self._netbox_create_server(datacenter, rack)
self.network = Network(server=self)
self.network.create_netbox_network_cards()
self.power = PowerSupply(server=self)
self.power.create_or_update_power_supply()
if config.inventory:
self.inventory = Inventory(server=self)
self.inventory.create()
logging.debug('Server created!')
def _netbox_update_chassis_for_blade(self, server, datacenter):
chassis = server.parent_device.device_bay.device
device_bay = nb.dcim.device_bays.get(
server.parent_device.device_bay.id
)
parent_chassis = nb.dcim.devices.get(
chassis.id
)
netbox_chassis_serial = parent_chassis.serial
move_device_bay = False
# check chassis serial with dmidecode
if netbox_chassis_serial != self.get_chassis_service_tag():
move_device_bay = True
# try to find the new netbox chassis
chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag()
)
if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter)
if move_device_bay or device_bay.name != self.get_blade_slot():
logging.info('Device ({serial}) seems to have moved, reseting old slot..'.format(
serial=server.serial))
device_bay.installed_device = None
device_bay.save()
# Set slot for blade
self._netbox_set_blade_slot(chassis, server)
def netbox_update(self, config):
"""
Netbox method to update info about our server/blade
Handle:
* new chasis for a blade
* new slot for a bblade
* hostname update
* new network infos
"""
logging.debug('Updating Server...') logging.debug('Updating Server...')
# check network cards
if config.register or config.update_all or config.update_network:
self.network = ServerNetwork(server=self)
self.network.create_or_update_netbox_network_cards()
# update inventory if feature is enabled
if config.inventory and (config.register or config.update_all or config.update_inventory):
self.inventory = Inventory(server=self)
self.inventory.create_or_update()
# update psu
if config.register or config.update_all or config.update_psu:
self.power = PowerSupply(server=self)
self.power.create_or_update_power_supply()
self.power.report_power_consumption()
server = nb.dcim.devices.get(serial=self.get_service_tag())
if not server:
raise Exception("The server (Serial: {}) isn't yet registered in Netbox, register"
'it before updating it'.format(self.get_service_tag()))
update = 0 update = 0
if self.is_blade():
datacenter = self.get_netbox_datacenter()
# if it's already linked to a chassis
if server.parent_device:
self._netbox_update_chassis_for_blade(server, datacenter)
else:
logging.info('Blade is not in a chassis, fixing...')
chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag()
)
if not chassis:
chassis = self._netbox_create_blade_chassis(datacenter)
# Set slot for blade
self._netbox_set_blade_slot(chassis, server)
# for every other specs # for every other specs
# check hostname # check hostname
if server.name != self.get_hostname(): if server.name != self.get_hostname():
@ -327,25 +287,12 @@ class ServerBase():
ret, server = self.update_netbox_location(server) ret, server = self.update_netbox_location(server)
update += ret update += ret
# check network cards
if config.update_all or config.update_network:
self.network = Network(server=self)
self.network.update_netbox_network_cards()
# update inventory
if config.update_all or config.update_inventory:
self.inventory = Inventory(server=self)
self.inventory.update()
# update psu
if config.update_all or config.update_psu:
self.power = PowerSupply(server=self)
self.power.create_or_update_power_supply()
self.power.report_power_consumption()
if update: if update:
server.save() server.save()
logging.debug('Finished updating Server!') logging.debug('Finished updating Server!')
def print_debug(self): def print_debug(self):
self.network = Network(server=self) self.network = ServerNetwork(server=self)
print('Datacenter:', self.get_datacenter()) print('Datacenter:', self.get_datacenter())
print('Netbox Datacenter:', self.get_netbox_datacenter()) print('Netbox Datacenter:', self.get_netbox_datacenter())
print('Rack:', self.get_rack()) print('Rack:', self.get_rack())

View file

@ -0,0 +1,86 @@
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.logging import logging # NOQA
from netbox_agent.misc import get_hostname
from netbox_agent.network import VirtualNetwork
def is_vm(dmi):
bios = dmidecode.get_by_type(dmi, 'BIOS')
system = dmidecode.get_by_type(dmi, 'System')
if 'Hyper-V' in bios[0]['Version'] or \
'Xen' in bios[0]['Version'] or \
'Google Compute Engine' in system[0]['Product Name'] or \
'VirtualBox' in bios[0]['Version'] or \
'VMware' in system[0]['Manufacturer']:
return True
return False
class VirtualMachine(object):
def __init__(self, dmi=None):
if dmi:
self.dmi = dmi
else:
self.dmi = dmidecode.parse()
self.network = None
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
return int(mem_gib)
def get_vcpus(self):
return os.cpu_count()
def get_netbox_vm(self):
hostname = get_hostname(config)
vm = nb.virtualization.virtual_machines.get(
name=hostname
)
return vm
def get_netbox_cluster(self, name):
cluster = nb.virtualization.clusters.get(
name=name,
)
return cluster
def netbox_create_or_update(self, config):
logging.debug('It\'s a virtual machine')
created = False
updated = 0
hostname = get_hostname(config)
vm = self.get_netbox_vm()
vcpus = self.get_vcpus()
memory = self.get_memory()
if not vm:
logging.debug('Creating Virtual machine..')
cluster = self.get_netbox_cluster(config.virtual.cluster_name)
vm = nb.virtualization.virtual_machines.create(
name=hostname,
cluster=cluster.id,
vcpus=vcpus,
memory=memory,
)
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 updated:
vm.save()