Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/source_vmware.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,39 @@ Custom Fields:
VMware Guest Hostname: appprd01.corp.example.com
```

### Cables to CDP/LLDP neighbors

An ESXi host reports the switch and the switch port each of its physical interfaces (pNICs) is
connected to, if CDP or LLDP is enabled on the switch. With the option `sync_host_cables` enabled
netbox-sync uses this information to create cables in NetBox between the host interface and the
switch port.

```ini
sync_host_cables = True
```

Cables are objects which are usually maintained by hand, that's why this option is disabled by
default. With the option disabled no cable is read from or written to NetBox at all. NetBox 3.3 or
newer is needed, on older versions the option is ignored.

netbox-sync only connects things it can find, it never creates the other end of a cable:

* the device the neighbor reports as its system name must already exist in NetBox. The name is
matched exactly first, a short name is only matched against a FQDN if that match is unambiguous
* the port the neighbor reports must already exist as an interface of that device. Long and short
interface names are matched against each other, so a reported `FastEthernet0/16` also matches an
interface named `Fa0/16` in NetBox. CDP reports the port ID, LLDP additionally reports a port
description and both are tried
* both interfaces must already exist in NetBox. An interface which was just discovered gets its
cable during the next run
* neither of the two interfaces may be connected already. A cable which was created by hand or which
connects to a different port is never changed or deleted, it is reported at log level `DEBUG`
instead

Cables created by this source are tagged like every other object and are marked as orphaned and
pruned once the host stops reporting that neighbor (see `prune_enabled`). Disabling the option again
leaves all previously created cables untouched in NetBox.

### Filtering VM Disk Information
VM disks are synchronized between vCenter and NetBox. Since NetBox 3.7.0, virtual disks are tracked as separate objects
linked to VMs. In some scenarios, such as when temporary disks are attached to VMs during backup operations
Expand Down
3 changes: 2 additions & 1 deletion module/netbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
NBMACAddress,
NBFHRPGroupItem,
NBInventoryItem,
NBPowerPort
NBPowerPort,
NBCable
)

primary_tag_name = "NetBox-synced"
91 changes: 90 additions & 1 deletion module/netbox/object_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# noinspection PyUnresolvedReferences
from packaging import version

from module.common.misc import grab
from module.common.misc import grab, get_string_or_none
from module.common.logging import get_logger
from module.netbox.manufacturer_mapping import sanitize_manufacturer_name

Expand Down Expand Up @@ -2428,4 +2428,93 @@ def update(self, data=None, read_from_netbox=False, source=None):

super().update(data=data, read_from_netbox=read_from_netbox, source=source)


class NBCable(NetBoxObject):
name = "cable"
api_path = "dcim/cables"
object_type = "dcim.cable"
# a cable has no natural name, the label is the only free form text attribute it has
primary_key = "label"
prune = True
# cable terminations are lists of objects since NetBox 3.3
min_netbox_version = "3.3"

def __init__(self, *args, **kwargs):
self.data_model = {
"label": 100,
"a_terminations": list,
"b_terminations": list,
"status": ["connected", "planned", "decommissioning"],
"type": [
"cat3", "cat5", "cat5e", "cat6", "cat6a", "cat7", "cat7a", "cat8",
"dac-active", "dac-passive",
"mmf", "mmf-om1", "mmf-om2", "mmf-om3", "mmf-om4", "mmf-om5",
"smf", "smf-os1", "smf-os2", "aoc", "power", "usb", "coaxial"
],
"description": 200,
"color": str,
"length": float,
"length_unit": ["km", "m", "cm", "mi", "ft", "in"],
"tags": NBTagList
}
super().__init__(*args, **kwargs)

def format_termination(self, termination):
"""
format a single cable termination as string

Parameters
----------
termination: dict
a single entry of a cable "a_terminations"/"b_terminations" list

Returns
-------
(str, None): the name of the terminated object, None if it can't be determined
"""

if not isinstance(termination, dict):
return None

# data read from NetBox contains the terminated object, data compiled by a source only the ID
termination_object = termination.get("object")
if isinstance(termination_object, dict) and termination_object.get("display") is not None:
return f"{termination_object.get('display')}"

object_id = termination.get("object_id")
if object_id is None:
return None

# a source only knows the ID of an interface it compiled a cable for
if termination.get("object_type") == NBInterface.object_type and self.inventory is not None:
interface_object = self.inventory.get_by_id(NBInterface, nb_id=object_id)
if interface_object is not None:
return interface_object.get_display_name(including_second_key=True)

return f"{termination.get('object_type')} {object_id}"

def get_display_name(self, data=None, including_second_key=False):
"""
A cable label is optional and mostly unset. Fall back to the objects this cable
connects to get a name which actually says something.
"""

this_data = data if data is not None else self.data

label = get_string_or_none(this_data.get(self.primary_key))
if label is not None:
return label

terminations = list()
for side in ["a_terminations", "b_terminations"]:
side_names = [self.format_termination(x) for x in this_data.get(side) or list()]
side_names = [x for x in side_names if x is not None]
if len(side_names) > 0:
terminations.append(", ".join(side_names))

if len(terminations) == 0:
return None

return " <> ".join(terminations)

# EOF
10 changes: 10 additions & 0 deletions module/sources/vmware/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,16 @@ def __init__(self):
will maintain all physical nics in netbox. This option will skip this part.""" ,
default_value=False
),
ConfigOption("sync_host_cables",
bool,
description="""Create cables in NetBox between the physical interfaces (pNICs) of an
ESXi host and the switch ports which are reported as CDP/LLDP neighbors by this host.
A cable is only created if the reported switch and the reported switch port both
already exist in NetBox and if neither of the two interfaces is cabled yet. Cables
are visible objects which are usually maintained by hand, that's why this is
disabled by default.""",
default_value=False
),

# removed settings
ConfigOption("netbox_host_device_role",
Expand Down
Loading