Skip to content

fix: assign addresses with a non-unique role to every interface - #559

Open
semx wants to merge 1 commit into
developmentfrom
fix/non-unique-ip-roles
Open

semx wants to merge 1 commit into
developmentfrom
fix/non-unique-ip-roles

Conversation

@semx

@semx semx commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes the anycast half of #344.

The anycast branch logs that the address can be assigned to multiple interfaces at the same time and then skips the assignment, so the second VM sharing the address never gets it. Now this interface gets an object of its own carrying the same role, and the object already on the other interface is left alone.

Two things came out of it. The check only knew anycast, but NetBox exempts vip, vrrp, hsrp, glbp and carp from its uniqueness rule for the same reason, so all six are handled. And it read the role as data.role.value, which only matches what the API returns - an object we created ourselves holds the plain string, so our own anycast addresses were not recognised on a later run. Both shapes are read now. The removal path had the same narrow check and uses the same set.

The role is only set on an object we create, never written onto an existing one, since that is the user's to decide.

Plain duplicates without such a role still produce the old warning. That is the VLAN case from the first post in #344, and @Azmodeszer said he can live with it. It needs a config switch to change, tell me if you want one and I'll add it.

tests/test_ip_non_unique_roles.py: 7 of 8 fail on development, all pass here, including a control that a second run reuses the object instead of creating another one. Suite 112.

Comment thread module/sources/common/source_base.py Outdated
Comment on lines +45 to +75
# NetBox exempts addresses with these roles from its uniqueness check, they are
# meant to exist on more than one interface at the same time
non_unique_ip_roles = ("anycast", "vip", "vrrp", "hsrp", "glbp", "carp")

# stub function to implement a finish call for each source
def finish(self):
pass

@staticmethod
def get_ip_address_role(ip_object):
"""
Returns the role of an IP address object. The NetBox API reports it as a dict,
an object this program created itself carries the plain value.

Parameters
----------
ip_object: NBIPAddress
IP address object to get the role from

Returns
-------
(str, None): role of this IP address or None if unset
"""

role = grab(ip_object, "data.role")

if isinstance(role, dict):
return role.get("value")

return role

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as this code is specific to NBIPAddress I would add it as object method like ip_object.get_role(). What do you think?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — done. Moved it onto NBIPAddress as get_role(), right next to get_interface()/get_device_vm(), so the role normalization lives with the object instead of the shared base. Both call sites now read ip.get_role(), and I added a couple of unit tests for the dict-vs-plain-value cases.

@semx
semx force-pushed the fix/non-unique-ip-roles branch from 180ccec to 55621a0 Compare September 15, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants