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:
parent
996c10d95b
commit
f06da32fc3
10 changed files with 369 additions and 317 deletions
16
README.md
16
README.md
|
@ -6,7 +6,7 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
|
|||
|
||||
# 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 IPMI interface if found
|
||||
* Create or get existing VLAN and associate it to interfaces
|
||||
|
@ -52,6 +52,12 @@ network:
|
|||
# enable auto-cabling
|
||||
lldp: true
|
||||
|
||||
# virtual:
|
||||
# # not mandatory, can be guessed
|
||||
# enabled: True
|
||||
# # see https://netbox.company.com/virtualization/clusters/
|
||||
# cluster_name: my_vm_cluster
|
||||
|
||||
datacenter_location:
|
||||
driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]"
|
||||
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:
|
||||
|
||||
## Virtual Machines
|
||||
|
||||
* Hyper-V
|
||||
* VMWare
|
||||
* VirtualBox
|
||||
* AWS
|
||||
* GCP
|
||||
|
||||
## Dell Inc.
|
||||
|
||||
### Blades
|
||||
|
|
|
@ -6,6 +6,7 @@ from netbox_agent.vendors.generic import GenericHost
|
|||
from netbox_agent.vendors.hp import HPHost
|
||||
from netbox_agent.vendors.qct import QCTHost
|
||||
from netbox_agent.vendors.supermicro import SupermicroHost
|
||||
from netbox_agent.virtualmachine import VirtualMachine, is_vm
|
||||
|
||||
MANUFACTURERS = {
|
||||
'Dell Inc.': DellHost,
|
||||
|
@ -19,20 +20,23 @@ MANUFACTURERS = {
|
|||
|
||||
def run(config):
|
||||
dmi = dmidecode.parse()
|
||||
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
|
||||
|
||||
try:
|
||||
server = MANUFACTURERS[manufacturer](dmi=dmi)
|
||||
except KeyError:
|
||||
server = GenericHost(dmi=dmi)
|
||||
if config.virtual.enabled or is_vm(dmi):
|
||||
if not config.virtual.cluster_name:
|
||||
raise Exception('virtual.cluster_name parameter is mandatory because it\'s a VM')
|
||||
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:
|
||||
server.print_debug()
|
||||
if config.register:
|
||||
server.netbox_create(config)
|
||||
if config.update_all or config.update_network or config.update_location or \
|
||||
if config.register or config.update_all or config.update_network or config.update_location or \
|
||||
config.update_inventory or config.update_psu:
|
||||
server.netbox_update(config)
|
||||
server.netbox_create_or_update(config)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ def get_config():
|
|||
p.add_argument('--log_level', default='debug')
|
||||
p.add_argument('--netbox.url', help='Netbox URL')
|
||||
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,
|
||||
help="Command to output hostname, used as Device's name in netbox")
|
||||
p.add_argument('--datacenter_location.driver',
|
||||
|
|
|
@ -438,18 +438,7 @@ class Inventory():
|
|||
if memory.get('serial') not in [x.serial for x in nb_memories]:
|
||||
self.create_netbox_memory(memory)
|
||||
|
||||
def create(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):
|
||||
def create_or_update(self):
|
||||
if config.inventory is None or config.update_inventory is None:
|
||||
return False
|
||||
self.do_netbox_cpus()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import logging
|
||||
import subprocess
|
||||
|
||||
from netaddr import IPNetwork
|
||||
|
||||
|
||||
class IPMI():
|
||||
"""
|
||||
|
@ -40,11 +42,27 @@ class IPMI():
|
|||
logging.error('Cannot get ipmi info: {}'.format(self.output))
|
||||
|
||||
def parse(self):
|
||||
ret = {}
|
||||
_ipmi = {}
|
||||
if self.ret != 0:
|
||||
return ret
|
||||
return _ipmi
|
||||
|
||||
for line in self.output.splitlines():
|
||||
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()
|
||||
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
|
||||
|
|
|
@ -3,8 +3,7 @@ import logging
|
|||
from netbox_agent.config import config
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
if config.log_level == 'debug':
|
||||
if config.log_level.lower() == 'debug':
|
||||
logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import socket
|
||||
import subprocess
|
||||
from shutil import which
|
||||
|
||||
|
||||
|
@ -29,3 +31,9 @@ def get_vendor(name):
|
|||
if name.upper().startswith(key):
|
||||
return value
|
||||
return name
|
||||
|
||||
|
||||
def get_hostname(config):
|
||||
if config.hostname_cmd is None:
|
||||
return '{}'.format(socket.gethostname())
|
||||
return subprocess.getoutput(config.hostname_cmd)
|
||||
|
|
|
@ -4,7 +4,7 @@ import re
|
|||
from itertools import chain
|
||||
|
||||
import netifaces
|
||||
from netaddr import IPAddress, IPNetwork
|
||||
from netaddr import IPAddress
|
||||
|
||||
from netbox_agent.config import config
|
||||
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
|
||||
|
||||
|
||||
class Network():
|
||||
class Network(object):
|
||||
def __init__(self, server, *args, **kwargs):
|
||||
self.nics = []
|
||||
|
||||
self.server = server
|
||||
self.device = self.server.get_netbox_server()
|
||||
self.lldp = LLDP() if config.network.lldp else None
|
||||
self.scan()
|
||||
|
||||
self.nics = self.scan()
|
||||
self.ipmi = None
|
||||
self.dcim_choices = {}
|
||||
dcim_c = nb.dcim.choices()
|
||||
|
||||
|
@ -38,7 +36,11 @@ class Network():
|
|||
for c in ipam_c[choice]:
|
||||
self.ipam_choices[choice][c['label']] = c['value']
|
||||
|
||||
def get_network_type():
|
||||
return NotImplementedError
|
||||
|
||||
def scan(self):
|
||||
nics = []
|
||||
for interface in os.listdir('/sys/class/net/'):
|
||||
# ignore if it's not a link (ie: bonding_masters etc)
|
||||
if not os.path.islink('/sys/class/net/{}'.format(interface)):
|
||||
|
@ -102,7 +104,8 @@ class Network():
|
|||
'bonding': bonding,
|
||||
'bonding_slaves': bonding_slaves,
|
||||
}
|
||||
self.nics.append(nic)
|
||||
nics.append(nic)
|
||||
return nics
|
||||
|
||||
def _set_bonding_interfaces(self):
|
||||
bonding_nics = (x for x in self.nics if x['bonding'])
|
||||
|
@ -130,25 +133,30 @@ class Network():
|
|||
|
||||
def get_netbox_network_card(self, nic):
|
||||
if nic['mac'] is None:
|
||||
interface = nb.dcim.interfaces.get(
|
||||
device_id=self.device.id,
|
||||
interface = self.nb_net.interfaces.get(
|
||||
name=nic['name'],
|
||||
**self.custom_arg_id,
|
||||
)
|
||||
else:
|
||||
interface = nb.dcim.interfaces.get(
|
||||
device_id=self.device.id,
|
||||
interface = self.nb_net.interfaces.get(
|
||||
mac_address=nic['mac'],
|
||||
name=nic['name'],
|
||||
**self.custom_arg_id,
|
||||
)
|
||||
return interface
|
||||
|
||||
def get_netbox_network_cards(self):
|
||||
return nb.dcim.interfaces.filter(
|
||||
device_id=self.device.id,
|
||||
mgmt_only=False,
|
||||
return self.nb_net.interfaces.filter(
|
||||
**self.custom_arg_id,
|
||||
)
|
||||
|
||||
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'):
|
||||
return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)']
|
||||
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']['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):
|
||||
# FIXME: we may need to specify the datacenter
|
||||
# since users may have same vlan id in multiple dc
|
||||
|
@ -206,9 +203,10 @@ class Network():
|
|||
# if it's a vlan interface
|
||||
elif vlan_id and (
|
||||
interface.mode is None or
|
||||
interface.mode.value != self.dcim_choices['interface:mode']['Access'] or
|
||||
len(interface.tagged_vlans) != 1 or
|
||||
interface.tagged_vlans[0].vid != vlan_id):
|
||||
type(interface.mode) is not int and (
|
||||
interface.mode.value == self.dcim_choices['interface:mode']['Access'] or
|
||||
len(interface.tagged_vlans) != 1 or
|
||||
interface.tagged_vlans[0].vid != vlan_id)):
|
||||
logging.info('Resetting tagged VLAN(s) on interface {interface}'.format(
|
||||
interface=interface))
|
||||
update = True
|
||||
|
@ -232,42 +230,6 @@ class Network():
|
|||
interface.untagged_vlan = nb_vlan.id
|
||||
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):
|
||||
# TODO: add Optic Vendor, PN and Serial
|
||||
type = self.get_netbox_type_for_nic(nic)
|
||||
|
@ -275,12 +237,12 @@ class Network():
|
|||
name=nic['name'], mac=nic['mac'], device=self.device.name))
|
||||
|
||||
nb_vlan = None
|
||||
interface = nb.dcim.interfaces.create(
|
||||
device=self.device.id,
|
||||
interface = self.nb_net.interfaces.create(
|
||||
name=nic['name'],
|
||||
mac_address=nic['mac'],
|
||||
type=type,
|
||||
mgmt_only=mgmt,
|
||||
**self.custom_arg,
|
||||
)
|
||||
|
||||
if nic['vlan']:
|
||||
|
@ -380,6 +342,112 @@ class Network():
|
|||
netbox_ip.save()
|
||||
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):
|
||||
logging.info('Interface {} is not connected to switch, trying to connect..'.format(
|
||||
nb_server_interface.name
|
||||
|
@ -483,105 +551,22 @@ class Network():
|
|||
)
|
||||
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):
|
||||
if config.update_all is None or config.update_network is None:
|
||||
return None
|
||||
logging.debug('Updating NIC...')
|
||||
class VirtualNetwork(Network):
|
||||
def __init__(self, server, *args, **kwargs):
|
||||
super(VirtualNetwork, self).__init__(server, args, kwargs)
|
||||
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
|
||||
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()
|
||||
dcim_c = nb.virtualization.choices()
|
||||
|
||||
# delete IP on netbox that are not known on this server
|
||||
netbox_ips = nb.ipam.ip_addresses.filter(
|
||||
device_id=self.device.id,
|
||||
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()
|
||||
for choice in dcim_c:
|
||||
self.dcim_choices[choice] = {}
|
||||
for c in dcim_c[choice]:
|
||||
self.dcim_choices[choice][c['label']] = c['value']
|
||||
|
||||
# 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 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!')
|
||||
def get_network_type(self):
|
||||
return 'virtual'
|
||||
|
|
|
@ -8,7 +8,7 @@ from netbox_agent.config import config
|
|||
from netbox_agent.config import netbox_instance as nb
|
||||
from netbox_agent.inventory import Inventory
|
||||
from netbox_agent.location import Datacenter, Rack
|
||||
from netbox_agent.network import Network
|
||||
from netbox_agent.network import ServerNetwork
|
||||
from netbox_agent.power import PowerSupply
|
||||
|
||||
|
||||
|
@ -136,7 +136,7 @@ class ServerBase():
|
|||
def get_power_consumption(self):
|
||||
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_role = get_device_role('Server Chassis')
|
||||
serial = self.get_chassis_service_tag()
|
||||
|
@ -172,27 +172,6 @@ class ServerBase():
|
|||
)
|
||||
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):
|
||||
device_role = get_device_role('Server')
|
||||
device_type = get_device_type(self.get_product_name())
|
||||
|
@ -215,108 +194,89 @@ class ServerBase():
|
|||
def get_netbox_server(self):
|
||||
return nb.dcim.devices.get(serial=self.get_service_tag())
|
||||
|
||||
def netbox_create(self, config):
|
||||
logging.debug('Creating Server..')
|
||||
def _netbox_set_or_update_blade_slot(self, server, chassis, datacenter):
|
||||
# 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()
|
||||
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
|
||||
self._netbox_set_blade_slot(chassis, blade)
|
||||
self._netbox_set_or_update_blade_slot(server, chassis, datacenter)
|
||||
else:
|
||||
server = nb.dcim.devices.get(serial=self.get_service_tag())
|
||||
if not server:
|
||||
self._netbox_create_server(datacenter, rack)
|
||||
|
||||
self.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...')
|
||||
# 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
|
||||
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
|
||||
# check hostname
|
||||
if server.name != self.get_hostname():
|
||||
|
@ -327,25 +287,12 @@ class ServerBase():
|
|||
ret, server = self.update_netbox_location(server)
|
||||
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:
|
||||
server.save()
|
||||
logging.debug('Finished updating Server!')
|
||||
|
||||
def print_debug(self):
|
||||
self.network = Network(server=self)
|
||||
self.network = ServerNetwork(server=self)
|
||||
print('Datacenter:', self.get_datacenter())
|
||||
print('Netbox Datacenter:', self.get_netbox_datacenter())
|
||||
print('Rack:', self.get_rack())
|
||||
|
|
86
netbox_agent/virtualmachine.py
Normal file
86
netbox_agent/virtualmachine.py
Normal 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()
|
Loading…
Reference in a new issue