diff --git a/netbox_agent/cli.py b/netbox_agent/cli.py index 3621828..85b5497 100644 --- a/netbox_agent/cli.py +++ b/netbox_agent/cli.py @@ -1,6 +1,7 @@ import netbox_agent.dmidecode as dmidecode from netbox_agent.config import config from netbox_agent.logging import logging # NOQA +from netbox_agent.virtualmachine import VirtualMachine from netbox_agent.vendors.dell import DellHost from netbox_agent.vendors.generic import GenericHost from netbox_agent.vendors.hp import HPHost @@ -19,12 +20,15 @@ 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: + 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() diff --git a/netbox_agent/config.py b/netbox_agent/config.py index 4626fbd..30d4675 100644 --- a/netbox_agent/config.py +++ b/netbox_agent/config.py @@ -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', diff --git a/netbox_agent/logging.py b/netbox_agent/logging.py index 8767feb..a0f7e20 100644 --- a/netbox_agent/logging.py +++ b/netbox_agent/logging.py @@ -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) diff --git a/netbox_agent/misc.py b/netbox_agent/misc.py index bf0922e..0e59956 100644 --- a/netbox_agent/misc.py +++ b/netbox_agent/misc.py @@ -1,4 +1,15 @@ +import os +import re +import netifaces +from netaddr import IPAddress, IPNetwork from shutil import which +import socket + + +from netbox_agent.ethtool import Ethtool +from netbox_agent.ipmi import IPMI +from netbox_agent.lldp import LLDP +from netbox_agent.logging import logging # NOQA def is_tool(name): @@ -29,3 +40,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) diff --git a/netbox_agent/network.py b/netbox_agent/network.py index 825f460..eec8093 100644 --- a/netbox_agent/network.py +++ b/netbox_agent/network.py @@ -13,12 +13,10 @@ 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() @@ -38,6 +36,28 @@ class Network(): for c in ipam_c[choice]: self.ipam_choices[choice][c['label']] = c['value'] + def get_network_type(): + return NotImplementedError + + def get_netbox_network_cards(self): + return self.nb_net.interfaces.filter( + **self.custom_arg_id, + ) + + def get_netbox_network_card(self, nic): + if nic['mac'] is None: + interface = self.nb_net.interfaces.get( + name=nic['name'], + **self.custom_arg_id, + ) + else: + interface = self.nb_net.interfaces.get( + mac_address=nic['mac'], + name=nic['name'], + **self.custom_arg_id, + ) + return interface + def scan(self): for interface in os.listdir('/sys/class/net/'): # ignore if it's not a link (ie: bonding_masters etc) @@ -128,27 +148,13 @@ class Network(): def get_network_cards(self): return self.nics - def get_netbox_network_card(self, nic): - if nic['mac'] is None: - interface = nb.dcim.interfaces.get( - device_id=self.device.id, - name=nic['name'], - ) - else: - interface = nb.dcim.interfaces.get( - device_id=self.device.id, - mac_address=nic['mac'], - name=nic['name'], - ) - return interface - - def get_netbox_network_cards(self): - return nb.dcim.interfaces.filter( - device_id=self.device.id, - mgmt_only=False, - ) - def get_netbox_type_for_nic(self, nic): + if config.virtual.enabled: + 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 +171,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 @@ -232,42 +227,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 +234,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 +339,169 @@ class Network(): netbox_ip.save() return netbox_ip + 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() + if self.get_network_type() == 'server': + 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...') + + # 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() + + # delete IP on netbox that are not known on this server + 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() + if self.get_network_type() == 'server': + self.create_or_update_ipmi() + logging.debug('Finished updating NIC!') + + +class ServerNetwork(Network): + def __init__(self, server, *args, **kwargs): + super(VirtualNetwork, self).__init__(server, args, kwargs) + self.server = server + self.device = self.server.get_netbox_server() + self.nb_net = nb.dcim + self.custom_arg = {'device_id': self.device.id} + + def get_network_type(self): + return 'server' + + def get_ipmi(self): + ipmi = IPMI().parse() + return ipmi + + def get_netbox_ipmi(self): + ipmi = self.get_ipmi() + mac = ipmi['MAC Address'] + return self.nb_net.interfaces.get( + mac=mac + ) + + 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 = self.nb_net.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 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 +605,23 @@ 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...') - # 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() +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 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() + dcim_c = nb.virtualization.choices() - # 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) + for choice in dcim_c: + self.dcim_choices[choice] = {} + for c in dcim_c[choice]: + self.dcim_choices[choice][c['label']] = c['value'] - 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' diff --git a/netbox_agent/server.py b/netbox_agent/server.py index ffda9a0..f3c737a 100644 --- a/netbox_agent/server.py +++ b/netbox_agent/server.py @@ -243,7 +243,7 @@ class ServerBase(): if not server: self._netbox_create_server(datacenter, rack) - self.network = Network(server=self) + self.network = ServerNetwork(server=self) self.network.create_netbox_network_cards() self.power = PowerSupply(server=self) @@ -329,7 +329,7 @@ class ServerBase(): # check network cards if config.update_all or config.update_network: - self.network = Network(server=self) + self.network = ServerNetwork(server=self) self.network.update_netbox_network_cards() # update inventory if config.update_all or config.update_inventory: @@ -345,7 +345,7 @@ class ServerBase(): 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()) diff --git a/netbox_agent/virtualmachine.py b/netbox_agent/virtualmachine.py new file mode 100644 index 0000000..882f2bb --- /dev/null +++ b/netbox_agent/virtualmachine.py @@ -0,0 +1,47 @@ +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 + +class VirtualMachine(object): + def __init__(self, dmi=None): + if dmi: + self.dmi = dmi + else: + self.dmi = dmidecode.parse() + self.network = None + + 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(self, config): + hostname = get_hostname(config) + vm = self.get_netbox_vm() + + 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, + vcpu=0, + memory=0, + ) + self.network = VirtualNetwork(server=self) + self.network.update_netbox_network_cards() + else: + self.network = VirtualNetwork(server=self) + self.network.update_netbox_network_cards()