netbox-agent/netbox_agent/network.py

705 lines
27 KiB
Python
Raw Normal View History

import logging
2019-08-04 14:37:51 +02:00
import os
import re
from itertools import chain, islice
2019-08-04 14:37:51 +02:00
import netifaces
from netaddr import IPAddress
2019-08-04 14:37:51 +02:00
2020-02-02 20:08:56 +01:00
from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb
2019-08-04 21:48:06 +02:00
from netbox_agent.ethtool import Ethtool
from netbox_agent.ipmi import IPMI
2019-08-26 11:05:41 +02:00
from netbox_agent.lldp import LLDP
2019-08-04 21:48:06 +02:00
2021-05-18 13:59:04 +02:00
class Network(object):
2019-08-04 15:14:36 +02:00
def __init__(self, server, *args, **kwargs):
2019-08-04 14:37:51 +02:00
self.nics = []
self.server = server
self.tenant = self.server.get_netbox_tenant()
self.lldp = LLDP() if config.network.lldp else None
self.nics = self.scan()
self.ipmi = None
self.dcim_choices = {}
dcim_c = nb.dcim.interfaces.choices()
for _choice_type in dcim_c:
2024-10-21 12:55:54 +02:00
key = "interface:{}".format(_choice_type)
self.dcim_choices[key] = {}
for choice in dcim_c[_choice_type]:
2024-10-21 12:55:54 +02:00
self.dcim_choices[key][choice["display_name"]] = choice["value"]
self.ipam_choices = {}
ipam_c = nb.ipam.ip_addresses.choices()
for _choice_type in ipam_c:
2024-10-21 12:55:54 +02:00
key = "ip-address:{}".format(_choice_type)
self.ipam_choices[key] = {}
for choice in ipam_c[_choice_type]:
2024-10-21 12:55:54 +02:00
self.ipam_choices[key][choice["display_name"]] = choice["value"]
def get_network_type():
return NotImplementedError
2019-08-04 14:37:51 +02:00
def scan(self):
nics = []
2024-10-21 12:55:54 +02:00
for interface in os.listdir("/sys/class/net/"):
# ignore if it's not a link (ie: bonding_masters etc)
2024-10-21 12:55:54 +02:00
if not os.path.islink("/sys/class/net/{}".format(interface)):
continue
2024-10-21 12:55:54 +02:00
if config.network.ignore_interfaces and re.match(
config.network.ignore_interfaces, interface
):
logging.debug(
"Ignore interface {interface}".format(interface=interface)
)
continue
ip_addr = netifaces.ifaddresses(interface).get(netifaces.AF_INET, [])
ip6_addr = netifaces.ifaddresses(interface).get(netifaces.AF_INET6, [])
if config.network.ignore_ips:
2021-05-11 21:11:27 +02:00
for i, ip in enumerate(ip_addr):
2024-10-21 12:55:54 +02:00
if re.match(config.network.ignore_ips, ip["addr"]):
2021-05-11 21:11:27 +02:00
ip_addr.pop(i)
for i, ip in enumerate(ip6_addr):
2024-10-21 12:55:54 +02:00
if re.match(config.network.ignore_ips, ip["addr"]):
2021-05-11 21:11:27 +02:00
ip6_addr.pop(i)
# netifaces returns a ipv6 netmask that netaddr does not understand.
# this strips the netmask down to the correct format for netaddr,
# and remove the interface.
# ie, this:
# {
# 'addr': 'fe80::ec4:7aff:fe59:ec4a%eno1.50',
# 'netmask': 'ffff:ffff:ffff:ffff::/64'
# }
#
# becomes:
# {
# 'addr': 'fe80::ec4:7aff:fe59:ec4a',
# 'netmask': 'ffff:ffff:ffff:ffff::'
# }
#
for addr in ip6_addr:
2024-10-21 12:55:54 +02:00
addr["addr"] = addr["addr"].replace("%{}".format(interface), "")
addr["mask"] = addr["mask"].split("/")[0]
ip_addr.append(addr)
2024-10-21 12:55:54 +02:00
mac = (
open("/sys/class/net/{}/address".format(interface), "r").read().strip()
)
mtu = int(
open("/sys/class/net/{}/mtu".format(interface), "r").read().strip()
)
vlan = None
2024-10-21 12:55:54 +02:00
if len(interface.split(".")) > 1:
vlan = int(interface.split(".")[1])
bonding = False
bonding_slaves = []
2024-10-21 12:55:54 +02:00
if os.path.isdir("/sys/class/net/{}/bonding".format(interface)):
bonding = True
2024-10-21 12:55:54 +02:00
bonding_slaves = (
open("/sys/class/net/{}/bonding/slaves".format(interface))
.read()
.split()
)
# Tun and TAP support
2024-10-21 12:55:54 +02:00
virtual = os.path.isfile("/sys/class/net/{}/tun_flags".format(interface))
nic = {
2024-10-21 12:55:54 +02:00
"name": interface,
"mac": mac if mac != "00:00:00:00:00:00" else None,
"ip": (
[
"{}/{}".format(x["addr"], IPAddress(x["mask"]).netmask_bits())
for x in ip_addr
]
if ip_addr
else None
), # FIXME: handle IPv6 addresses
"ethtool": Ethtool(interface).parse(),
"virtual": virtual,
"vlan": vlan,
"mtu": mtu,
"bonding": bonding,
"bonding_slaves": bonding_slaves,
}
nics.append(nic)
return nics
2019-08-04 14:37:51 +02:00
def _set_bonding_interfaces(self):
2024-10-21 12:55:54 +02:00
bonding_nics = (x for x in self.nics if x["bonding"])
for nic in bonding_nics:
bond_int = self.get_netbox_network_card(nic)
2024-10-21 12:55:54 +02:00
logging.debug(
"Setting slave interface for {name}".format(name=bond_int.name)
)
for slave_int in (
2024-10-21 12:55:54 +02:00
self.get_netbox_network_card(slave_nic)
for slave_nic in self.nics
if slave_nic["name"] in nic["bonding_slaves"]
):
if slave_int.lag is None or slave_int.lag.id != bond_int.id:
2024-10-21 12:55:54 +02:00
logging.debug(
"Settting interface {name} as slave of {master}".format(
name=slave_int.name, master=bond_int.name
)
)
slave_int.lag = bond_int
slave_int.save()
else:
return False
return True
2019-08-04 14:37:51 +02:00
def get_network_cards(self):
return self.nics
def get_netbox_network_card(self, nic):
2024-10-21 12:55:54 +02:00
if nic["mac"] is None:
interface = self.nb_net.interfaces.get(
2024-10-21 12:55:54 +02:00
name=nic["name"], **self.custom_arg_id
)
else:
interface = self.nb_net.interfaces.get(
2024-10-21 12:55:54 +02:00
mac_address=nic["mac"], name=nic["name"], **self.custom_arg_id
)
return interface
def get_netbox_network_cards(self):
2024-10-21 12:55:54 +02:00
return self.nb_net.interfaces.filter(**self.custom_arg_id)
2019-08-04 21:48:06 +02:00
def get_netbox_type_for_nic(self, nic):
2024-10-21 12:55:54 +02:00
if self.get_network_type() == "virtual":
return self.dcim_choices["interface:type"]["Virtual"]
2024-10-21 12:55:54 +02:00
if nic.get("bonding"):
return self.dcim_choices["interface:type"]["Link Aggregation Group (LAG)"]
2024-10-21 12:55:54 +02:00
if nic.get("bonding"):
return self.dcim_choices["interface:type"]["Link Aggregation Group (LAG)"]
2024-10-21 12:55:54 +02:00
if nic.get("virtual"):
return self.dcim_choices["interface:type"]["Virtual"]
2024-10-21 12:55:54 +02:00
if nic.get("ethtool") is None:
return self.dcim_choices["interface:type"]["Other"]
2024-10-21 12:55:54 +02:00
if nic["ethtool"]["speed"] == "10000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP+ (10GE)"]
return self.dcim_choices["interface:type"]["10GBASE-T (10GE)"]
2024-10-21 12:55:54 +02:00
elif nic["ethtool"]["speed"] == "25000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP28 (25GE)"]
2021-05-12 15:50:16 +02:00
2024-10-21 12:55:54 +02:00
elif nic["ethtool"]["speed"] == "1000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP (1GE)"]
return self.dcim_choices["interface:type"]["1000BASE-T (1GE)"]
2021-05-12 15:50:16 +02:00
2024-10-21 12:55:54 +02:00
return self.dcim_choices["interface:type"]["Other"]
2019-08-04 21:48:06 +02:00
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
vlan = nb.ipam.vlans.get(
vid=vlan_id,
)
if vlan is None:
vlan = nb.ipam.vlans.create(
2024-10-21 12:55:54 +02:00
name="VLAN {}".format(vlan_id),
vid=vlan_id,
)
return vlan
2019-08-26 11:05:41 +02:00
def reset_vlan_on_interface(self, nic, interface):
update = False
2024-10-21 12:55:54 +02:00
vlan_id = nic["vlan"]
lldp_vlan = (
self.lldp.get_switch_vlan(nic["name"])
if config.network.lldp and isinstance(self, ServerNetwork)
else None
)
# For strange reason, we need to get the object from scratch
# The object returned by pynetbox's save isn't always working (since pynetbox 6)
2022-07-21 08:32:24 +02:00
interface = self.nb_net.interfaces.get(id=interface.id)
2019-08-26 11:05:41 +02:00
# Handle the case were the local interface isn't an interface vlan as reported by Netbox
# and that LLDP doesn't report a vlan-id
2024-10-21 12:55:54 +02:00
if (
vlan_id is None
and lldp_vlan is None
and (interface.mode is not None or len(interface.tagged_vlans) > 0)
):
logging.info(
"Interface {interface} is not tagged, reseting mode".format(
interface=interface
)
)
update = True
interface.mode = None
interface.tagged_vlans = []
2019-08-26 11:05:41 +02:00
interface.untagged_vlan = None
# if the local interface is configured with a vlan, it's supposed to be taggued
2021-05-18 13:59:04 +02:00
# if mode is either not set or not correctly configured or vlan are not
# correctly configured, we reset the vlan
elif vlan_id and (
2024-10-21 12:55:54 +02:00
interface.mode is None
or not isinstance(interface.mode, int)
and (
hasattr(interface.mode, "value")
and interface.mode.value
== self.dcim_choices["interface:mode"]["Access"]
or len(interface.tagged_vlans) != 1
or int(interface.tagged_vlans[0].vid) != int(vlan_id)
)
):
logging.info(
"Resetting tagged VLAN(s) on interface {interface}".format(
interface=interface
)
)
update = True
nb_vlan = self.get_or_create_vlan(vlan_id)
2024-10-21 12:55:54 +02:00
interface.mode = self.dcim_choices["interface:mode"]["Tagged"]
interface.tagged_vlans = [nb_vlan] if nb_vlan else []
2019-08-26 11:05:41 +02:00
interface.untagged_vlan = None
# Finally if LLDP reports a vlan-id with the pvid attribute
2019-09-09 15:32:14 +02:00
elif lldp_vlan:
2024-10-21 12:55:54 +02:00
pvid_vlan = [key for (key, value) in lldp_vlan.items() if value["pvid"]]
2019-09-09 15:32:14 +02:00
if len(pvid_vlan) > 0 and (
2024-10-21 12:55:54 +02:00
interface.mode is None
or interface.mode.value != self.dcim_choices["interface:mode"]["Access"]
or interface.untagged_vlan is None
or interface.untagged_vlan.vid != int(pvid_vlan[0])
):
logging.info(
"Resetting access VLAN on interface {interface}".format(
interface=interface
)
)
2019-09-09 15:32:14 +02:00
update = True
nb_vlan = self.get_or_create_vlan(pvid_vlan[0])
2024-10-21 12:55:54 +02:00
interface.mode = self.dcim_choices["interface:mode"]["Access"]
2019-09-09 15:32:14 +02:00
interface.untagged_vlan = nb_vlan.id
return update, interface
def create_netbox_nic(self, nic, mgmt=False):
2019-08-04 21:48:06 +02:00
# TODO: add Optic Vendor, PN and Serial
nic_type = self.get_netbox_type_for_nic(nic)
2024-10-21 12:55:54 +02:00
logging.info(
"Creating NIC {name} ({mac}) on {device}".format(
name=nic["name"], mac=nic["mac"], device=self.device.name
)
)
nb_vlan = None
params = dict(self.custom_arg)
2024-10-21 12:55:54 +02:00
params.update(
{
"name": nic["name"],
"type": nic_type,
"mgmt_only": mgmt,
}
)
if nic["mac"]:
params["mac_address"] = nic["mac"]
2024-10-21 12:55:54 +02:00
if nic["mtu"]:
params["mtu"] = nic["mtu"]
interface = self.nb_net.interfaces.create(**params)
2019-08-04 21:48:06 +02:00
2024-10-21 12:55:54 +02:00
if nic["vlan"]:
nb_vlan = self.get_or_create_vlan(nic["vlan"])
interface.mode = self.dcim_choices["interface:mode"]["Tagged"]
2019-08-26 11:05:41 +02:00
interface.tagged_vlans = [nb_vlan.id]
interface.save()
2024-10-21 12:55:54 +02:00
elif config.network.lldp and self.lldp.get_switch_vlan(nic["name"]) is not None:
2019-08-26 11:05:41 +02:00
# if lldp reports a vlan on an interface, tag the interface in access and set the vlan
2019-09-09 15:32:14 +02:00
# report only the interface which has `pvid=yes` (ie: lldp.eth3.vlan.pvid=yes)
# if pvid is not present, it'll be processed as a vlan tagged interface
2024-10-21 12:55:54 +02:00
vlans = self.lldp.get_switch_vlan(nic["name"])
2019-09-09 15:32:14 +02:00
for vid, vlan_infos in vlans.items():
nb_vlan = self.get_or_create_vlan(vid)
2024-10-21 12:55:54 +02:00
if vlan_infos.get("vid"):
interface.mode = self.dcim_choices["interface:mode"]["Access"]
2019-09-09 15:32:14 +02:00
interface.untagged_vlan = nb_vlan.id
2019-08-26 11:05:41 +02:00
interface.save()
# cable the interface
if config.network.lldp and isinstance(self, ServerNetwork):
2019-08-26 11:05:41 +02:00
switch_ip = self.lldp.get_switch_ip(interface.name)
switch_interface = self.lldp.get_switch_port(interface.name)
if switch_ip and switch_interface:
2019-08-26 11:05:41 +02:00
nic_update, interface = self.create_or_update_cable(
switch_ip, switch_interface, interface
)
if nic_update:
interface.save()
return interface
def create_or_update_netbox_ip_on_interface(self, ip, interface):
2024-10-21 12:55:54 +02:00
"""
2019-08-30 11:06:16 +02:00
Two behaviors:
- Anycast IP
* If IP exists and is in Anycast, create a new Anycast one
* If IP exists and isn't assigned, take it
* If server is decomissioned, then free IP will be taken
- Normal IP (can be associated only once)
* If IP doesn't exist, create it
* If IP exists and isn't assigned, take it
* If IP exists and interface is wrong, change interface
2024-10-21 12:55:54 +02:00
"""
2019-08-30 11:06:16 +02:00
netbox_ips = nb.ipam.ip_addresses.filter(
address=ip,
)
Added disks extended attributes This patch brings some of the physical and virtual drive attributes as `custom_fields` to the disks inventory. The goal is to have this information present to ease disks maintenance when a drive becomes unavailable and its attributes can't be read anymore from the RAID controller. It also helps to standardize the extended disk attributes across the different manufacturers. As the disk physical identifers were not available under the correct format (hexadecimal format using the `xml` output as opposed as `X:Y:Z` format using the default `list` format), the command line parser has been refactored to read the `list` format, rather than `xml` one in the `omreport` raid controller parser. As the custom fields have to be created prior being able to register the disks extended attributes, this feature is only activated using the `--process-virtual-drives` command line parameter, or by setting `process_virtual_drives` to `true` in the configuration file. The custom fields to create as `DCIM > inventory item` `Text` are described below. NAME LABEL DESCRIPTION mount_point Mount point Device mount point(s) pd_identifier Physical disk identifier Physical disk identifier in the RAID controller vd_array Virtual drive array Virtual drive array the disk is member of vd_consistency Virtual drive consistency Virtual disk array consistency vd_device Virtual drive device Virtual drive system device vd_raid_type Virtual drive RAID Virtual drive array RAID type vd_size Virtual drive size Virtual drive array size In the current implementation, the disks attributes ore not updated: if a disk with the correct serial number is found, it's sufficient to consider it as up to date. To force the reprocessing of the disks extended attributes, the `--force-disk-refresh` command line option can be used: it removes all existing disks to before populating them with the correct parsing. Unless this option is specified, the extended attributes won't be modified unless a disk is replaced. It is possible to dump the physical/virtual disks map on the filesystem under the JSON notation to ease or automate disks management. The file path has to be provided using the `--dump-disks-map` command line parameter.
2022-02-25 18:43:09 +01:00
if not netbox_ips:
2024-10-21 12:55:54 +02:00
logging.info(
"Create new IP {ip} on {interface}".format(ip=ip, interface=interface)
)
query_params = {
2024-10-21 12:55:54 +02:00
"address": ip,
"status": "active",
"assigned_object_type": self.assigned_object_type,
"assigned_object_id": interface.id,
}
2024-10-21 12:55:54 +02:00
netbox_ip = nb.ipam.ip_addresses.create(**query_params)
return netbox_ip
Added disks extended attributes This patch brings some of the physical and virtual drive attributes as `custom_fields` to the disks inventory. The goal is to have this information present to ease disks maintenance when a drive becomes unavailable and its attributes can't be read anymore from the RAID controller. It also helps to standardize the extended disk attributes across the different manufacturers. As the disk physical identifers were not available under the correct format (hexadecimal format using the `xml` output as opposed as `X:Y:Z` format using the default `list` format), the command line parser has been refactored to read the `list` format, rather than `xml` one in the `omreport` raid controller parser. As the custom fields have to be created prior being able to register the disks extended attributes, this feature is only activated using the `--process-virtual-drives` command line parameter, or by setting `process_virtual_drives` to `true` in the configuration file. The custom fields to create as `DCIM > inventory item` `Text` are described below. NAME LABEL DESCRIPTION mount_point Mount point Device mount point(s) pd_identifier Physical disk identifier Physical disk identifier in the RAID controller vd_array Virtual drive array Virtual drive array the disk is member of vd_consistency Virtual drive consistency Virtual disk array consistency vd_device Virtual drive device Virtual drive system device vd_raid_type Virtual drive RAID Virtual drive array RAID type vd_size Virtual drive size Virtual drive array size In the current implementation, the disks attributes ore not updated: if a disk with the correct serial number is found, it's sufficient to consider it as up to date. To force the reprocessing of the disks extended attributes, the `--force-disk-refresh` command line option can be used: it removes all existing disks to before populating them with the correct parsing. Unless this option is specified, the extended attributes won't be modified unless a disk is replaced. It is possible to dump the physical/virtual disks map on the filesystem under the JSON notation to ease or automate disks management. The file path has to be provided using the `--dump-disks-map` command line parameter.
2022-02-25 18:43:09 +01:00
netbox_ip = list(netbox_ips)[0]
# If IP exists in anycast
2024-10-21 12:55:54 +02:00
if netbox_ip.role and netbox_ip.role.label == "Anycast":
logging.debug("IP {} is Anycast..".format(ip))
unassigned_anycast_ip = [x for x in netbox_ips if x.interface is None]
2024-10-21 12:55:54 +02:00
assigned_anycast_ip = [
x for x in netbox_ips if x.interface and x.interface.id == interface.id
]
# use the first available anycast ip
if len(unassigned_anycast_ip):
2024-10-21 12:55:54 +02:00
logging.info("Assigning existing Anycast IP {} to interface".format(ip))
netbox_ip = unassigned_anycast_ip[0]
netbox_ip.interface = interface
netbox_ip.save()
# or if everything is assigned to other servers
elif not len(assigned_anycast_ip):
2024-10-21 12:55:54 +02:00
logging.info(
"Creating Anycast IP {} and assigning it to interface".format(ip)
)
query_params = {
"address": ip,
"status": "active",
2024-10-21 12:55:54 +02:00
"role": self.ipam_choices["ip-address:role"]["Anycast"],
"tenant": self.tenant.id if self.tenant else None,
"assigned_object_type": self.assigned_object_type,
2024-10-21 12:55:54 +02:00
"assigned_object_id": interface.id,
}
netbox_ip = nb.ipam.ip_addresses.create(**query_params)
return netbox_ip
2019-08-30 11:06:16 +02:00
else:
2024-10-21 12:55:54 +02:00
ip_interface = getattr(netbox_ip, "interface", None)
assigned_object = getattr(netbox_ip, "assigned_object", None)
if not ip_interface or not assigned_object:
2024-10-21 12:55:54 +02:00
logging.info(
"Assigning existing IP {ip} to {interface}".format(
ip=ip, interface=interface
)
)
elif (ip_interface and ip_interface.id != interface.id) or (
assigned_object and assigned_object.id != interface.id
):
old_interface = getattr(netbox_ip, "assigned_object", "n/a")
logging.info(
2024-10-21 12:55:54 +02:00
"Detected interface change for ip {ip}: old interface is "
"{old_interface} (id: {old_id}), new interface is {new_interface} "
" (id: {new_id})".format(
old_interface=old_interface,
new_interface=interface,
old_id=netbox_ip.id,
new_id=interface.id,
ip=netbox_ip.address,
)
)
2019-08-30 11:06:16 +02:00
else:
return netbox_ip
netbox_ip.assigned_object_type = self.assigned_object_type
netbox_ip.assigned_object_id = interface.id
netbox_ip.save()
def create_or_update_netbox_network_cards(self):
if config.update_all is None or config.update_network is None:
return None
2024-10-21 12:55:54 +02:00
logging.debug("Creating/Updating NIC...")
# delete unknown interface
nb_nics = list(self.get_netbox_network_cards())
2024-10-21 12:55:54 +02:00
local_nics = [x["name"] for x in self.nics]
for nic in nb_nics:
if nic.name not in local_nics:
2024-10-21 12:55:54 +02:00
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):
2024-10-21 12:55:54 +02:00
def batched(it, n):
2024-10-21 12:55:54 +02:00
while batch := tuple(islice(it, n)):
yield batch
netbox_ips = []
for ids in batched((x.id for x in nb_nics), 25):
2024-10-21 12:55:54 +02:00
netbox_ips += list(nb.ipam.ip_addresses.filter(**{self.intf_type: ids}))
2024-10-21 12:55:54 +02:00
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:
2024-10-21 12:55:54 +02:00
logging.info(
"Unassigning IP {ip} from {interface}".format(
ip=netbox_ip.address, interface=netbox_ip.assigned_object
)
)
netbox_ip.assigned_object_type = None
2024-10-21 12:55:54 +02:00
netbox_ip.ssigned_object_id = None
netbox_ip.save()
# update each nic
for nic in self.nics:
interface = self.get_netbox_network_card(nic)
if not interface:
2024-10-21 12:55:54 +02:00
logging.info(
"Interface {mac_address} not found, creating..".format(
mac_address=nic["mac"]
)
)
interface = self.create_netbox_nic(nic)
nic_update = 0
2024-10-21 12:55:54 +02:00
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
2024-10-21 12:55:54 +02:00
if hasattr(interface, "mtu"):
if nic["mtu"] != interface.mtu:
logging.info(
"Interface mtu is wrong, updating to: {mtu}".format(
mtu=nic["mtu"]
)
)
interface.mtu = nic["mtu"]
nic_update += 1
2024-10-21 12:55:54 +02:00
if hasattr(interface, "type"):
_type = self.get_netbox_type_for_nic(nic)
2024-10-21 12:55:54 +02:00
if not interface.type or _type != interface.type.value:
logging.info("Interface type is wrong, resetting")
interface.type = _type
nic_update += 1
2024-10-21 12:55:54 +02:00
if hasattr(interface, "lag") and interface.lag is not None:
local_lag_int = next(
2024-10-21 12:55:54 +02:00
item for item in self.nics if item["name"] == interface.lag.name
)
2024-10-21 12:55:54 +02:00
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 and isinstance(self, ServerNetwork):
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
2024-10-21 12:55:54 +02:00
if nic["ip"]:
# sync local IPs
2024-10-21 12:55:54 +02:00
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()
2024-10-21 12:55:54 +02:00
logging.debug("Finished updating NIC!")
class ServerNetwork(Network):
def __init__(self, server, *args, **kwargs):
super(ServerNetwork, self).__init__(server, args, kwargs)
if config.network.ipmi:
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
2024-10-21 12:55:54 +02:00
self.custom_arg = {"device": getattr(self.device, "id", None)}
self.custom_arg_id = {"device_id": getattr(self.device, "id", None)}
self.intf_type = "interface_id"
self.assigned_object_type = "dcim.interface"
def get_network_type(self):
2024-10-21 12:55:54 +02:00
return "server"
def get_ipmi(self):
ipmi = IPMI().parse()
return ipmi
2024-10-21 12:55:54 +02:00
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
)
)
2019-08-26 11:05:41 +02:00
nb_mgmt_ip = nb.ipam.ip_addresses.get(
address=switch_ip,
)
if not nb_mgmt_ip:
2024-10-21 12:55:54 +02:00
logging.error("Switch IP {} cannot be found in Netbox".format(switch_ip))
2019-08-26 11:05:41 +02:00
return nb_server_interface
try:
nb_switch = nb_mgmt_ip.assigned_object.device
2024-10-21 12:55:54 +02:00
logging.info(
"Found a switch in Netbox based on LLDP infos: {} (id: {})".format(
switch_ip, nb_switch.id
)
)
2019-08-26 11:05:41 +02:00
except KeyError:
logging.error(
2024-10-21 12:55:54 +02:00
"Switch IP {} is found but not associated to a Netbox Switch Device".format(
2019-08-26 11:05:41 +02:00
switch_ip
)
)
return nb_server_interface
switch_interface = self.lldp.get_switch_port(nb_server_interface.name)
nb_switch_interface = nb.dcim.interfaces.get(
device_id=nb_switch.id,
2019-08-26 11:05:41 +02:00
name=switch_interface,
)
if nb_switch_interface is None:
2024-10-21 12:55:54 +02:00
logging.error(
"Switch interface {} cannot be found".format(switch_interface)
)
2019-08-26 11:05:41 +02:00
return nb_server_interface
2024-10-21 12:55:54 +02:00
logging.info(
"Found interface {} on switch {}".format(
switch_interface,
switch_ip,
)
)
2019-08-26 11:05:41 +02:00
cable = nb.dcim.cables.create(
a_terminations=[
{"object_type": "dcim.interface", "object_id": nb_server_interface.id},
],
b_terminations=[
{"object_type": "dcim.interface", "object_id": nb_switch_interface.id},
],
2019-08-26 11:05:41 +02:00
)
nb_server_interface.cable = cable
logging.info(
2024-10-21 12:55:54 +02:00
"Connected interface {interface} with {switch_interface} of {switch_ip}".format(
2019-08-26 11:05:41 +02:00
interface=nb_server_interface.name,
switch_interface=switch_interface,
switch_ip=switch_ip,
)
)
return nb_server_interface
def create_or_update_cable(self, switch_ip, switch_interface, nb_server_interface):
update = False
if nb_server_interface.cable is None:
update = True
nb_server_interface = self.connect_interface_to_switch(
switch_ip, switch_interface, nb_server_interface
)
else:
nb_sw_int = nb_server_interface.cable.b_terminations[0]
2019-08-26 11:05:41 +02:00
nb_sw = nb_sw_int.device
2024-10-21 12:55:54 +02:00
nb_mgmt_int = nb.dcim.interfaces.get(device_id=nb_sw.id, mgmt_only=True)
nb_mgmt_ip = nb.ipam.ip_addresses.get(interface_id=nb_mgmt_int.id)
2019-08-26 11:05:41 +02:00
if nb_mgmt_ip is None:
logging.error(
2024-10-21 12:55:54 +02:00
"Switch {switch_ip} does not have IP on its management interface".format(
2019-08-26 11:05:41 +02:00
switch_ip=switch_ip,
)
)
return update, nb_server_interface
# Netbox IP is always IP/Netmask
2024-10-21 12:55:54 +02:00
nb_mgmt_ip = nb_mgmt_ip.address.split("/")[0]
if nb_mgmt_ip != switch_ip or nb_sw_int.name != switch_interface:
logging.info("Netbox cable is not connected to correct ports, fixing..")
2019-08-26 11:05:41 +02:00
logging.info(
2024-10-21 12:55:54 +02:00
"Deleting cable {cable_id} from {interface} to {switch_interface} of "
"{switch_ip}".format(
2019-08-26 11:05:41 +02:00
cable_id=nb_server_interface.cable.id,
interface=nb_server_interface.name,
switch_interface=nb_sw_int.name,
switch_ip=nb_mgmt_ip,
)
)
2024-10-21 12:55:54 +02:00
cable = nb.dcim.cables.get(nb_server_interface.cable.id)
2019-08-26 11:05:41 +02:00
cable.delete()
update = True
nb_server_interface = self.connect_interface_to_switch(
switch_ip, switch_interface, nb_server_interface
)
return update, nb_server_interface
2019-08-05 16:51:01 +02:00
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
2024-10-21 12:55:54 +02:00
self.custom_arg = {"virtual_machine": getattr(self.device, "id", None)}
self.custom_arg_id = {"virtual_machine_id": getattr(self.device, "id", None)}
self.intf_type = "vminterface_id"
self.assigned_object_type = "virtualization.vminterface"
dcim_c = nb.virtualization.interfaces.choices()
for _choice_type in dcim_c:
2024-10-21 12:55:54 +02:00
key = "interface:{}".format(_choice_type)
self.dcim_choices[key] = {}
for choice in dcim_c[_choice_type]:
2024-10-21 12:55:54 +02:00
self.dcim_choices[key][choice["display_name"]] = choice["value"]
def get_network_type(self):
2024-10-21 12:55:54 +02:00
return "virtual"