netbox-community / ansible_modules

NetBox modules for Ansible using Ansible Collections
GNU General Public License v3.0
329 stars 209 forks source link

[Bug]: Cannot assign existing IP to network interface #975

Open cfiehe opened 1 year ago

cfiehe commented 1 year ago

Ansible NetBox Collection version

v3.12.0

Ansible version

ansible [core 2.14.1]
  config file = /home/ansible/automation/projects/provisioning/ansible.cfg
  configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ansible/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/ansible/automation/projects/provisioning/collections
  executable location = /home/ansible/.local/bin/ansible
  python version = 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True

NetBox version

v3.4.7

Python version

3.10

Steps to Reproduce

If an IP address already exists in our Netbox, it cannot be assigned to an interface. The module always instructs the Netbox to create a new IP. This results in a Duplicate IP address exception, when Enforce unique space is enabled, or in a duplicate IP.

- name: Create an IP address
  netbox.netbox.netbox_ip_address:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      address: 10.1.60.232/24
      tenant: my_company
      vrf: my_company-RFC1918
      status: Active
    state: present

- name: Assign IP address to existing interface
  netbox.netbox.netbox_ip_address:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      address: 10.1.60.232/24
      assigned_object:
        virtual_machine: any_vm.local
        name: ens224
      tenant: my_company
      vrf: my_company-RFC1918
      status: Active
    state: present

Expected Behavior

The existing IP should be modified and should be attached to the specified interface.

Observed Behavior

The VRF my_company-RFC1918 uses the option Enforce unique space, that is why the assignment fails with a Duplicate IP address, because the module tries to create a new IP instead of modifying the existing one:

fatal: [any_vm.local -> localhost]: FAILED! => {"changed": false, "msg": "{\"address\":[\"Duplicate IP address found in VRF my_company-RFC1918: 10.1.60.232/24\"]}"}
Zwordi commented 11 months ago

Hello,

My 2cents here as I’m looking of kind of the same topic. The "Enforce unique space" is the good way. As by having an unique address you’re being certain that this one will not be treated differently. The need here is about being able to update an ip record with the "state:present" attribute.

d-zalewski commented 7 months ago

I'm seeing exact same behaviour. My scenario is that sometimes I need to reserve bunch of IPs ahead of time to get network teams to update firewall rules (which can take days). Once firewall rules are in place I can then build VMs and I would like them to use reserved IPs.

Based on below I would expect to be able to assign an existing IP to a VM if the state is set to present.

https://github.com/netbox-community/ansible_modules/blob/devel/plugins/modules/netbox_ip_address.py#L51

With state C(present), if an interface is given, it will ensure
            that an IP inside this prefix (and vrf, if given) is attached
            to this interface. Otherwise, it will get the next available IP
            of this prefix and attach it.
            With state C(new), it will force to get the next available IP in
            this prefix. If an interface is given, it will also force to attach
            it.
Ido-Don commented 1 month ago

Issue Summary

The current behavior of the Ansible netbox_ip_address module does not align with the NetBox API's expectations for assigning IP addresses. Specifically, the API uses assigned_object_type and assigned_object_id to identify the target of the IP assignment, whereas the Ansible module accepts assigned_object and interface, which are more descriptive names.

Proposed Solution

To align the Ansible module with the NetBox API, we need to convert the human-readable names provided by the user into the appropriate assigned_object_type and assigned_object_id values. This can be achieved by modifying the NetboxIpamModule.run() function in the netbox_ipam.py file.

A psudo-code exemple for the fix to the issue:


def run(self):
    ...
    assigned_object = data.get("assigned_object")
    if assigned_object:
        # Determine the type and ID of the assigned object
        interface_name = assigned_object.get("name")
        if assigned_object.get("device"):
            device_name = assigned_object.get("device")
            device_type = DCIM_INTERFACE
        elif assigned_object.get("virtual_machine"):
            device_name = assigned_object.get("virtual_machine")
            device_type = VIRTUALIZATION_VIRTUAL_MACHINE
        else:
            raise Exception("Invalid assigned_object parameters. Device or virtual machine must be specified.")

        # Fetch the interface object based on the type and name
        interface = netbox_object.get(device_type, device_name, interface_name)
        if not interface:
            raise Exception(f"No interface found with device '{device_name}' and name '{interface_name}'")

        # Update the data dictionary with the required fields for the API
        data.update({
            "assigned_object_type": device_type,
            "assigned_object_id": interface.id
        })

    # Process the 'interface' argument similarly
    interface = data.get("interface")
    if interface:
        interface_name = interface.get("name")
        device_name = data.get("device")
        device_type = DCIM_INTERFACE

        # Fetch the interface object based on the type and name
        interface = netbox_object.get(device_type, device_name, interface_name)
        if not interface:
            raise Exception(f"No interface found with device '{device_name}' and name '{interface_name}'")

        # Update the data dictionary
        data.update({
            "assigned_object_type": device_type,
            "assigned_object_id": interface.id
        })

    ...
    # Update the NetBox instance with the modified data
    update_netbox(data)

Explanation

Footnotes

Ido-Don commented 2 weeks ago

BTW If anyone want's to take these changes and make a pull request, she/he is welcome to do so. I'm not doing it myself since it is a lot of work to create the development environment for proper testing of the fix. Might come around some weekend to do so. If you beat me to the punch then you have my blessing 😄