Repository: netbox-community/netbox
Pull request: Fixes #10058: Allow searching device by primary IP address
<!--
    Thank you for your interest in contributing to NetBox! Please note that
    our contribution policy requires that a feature request or bug report be
    approved and assigned prior to opening a pull request. This helps avoid
    waste time and effort on a proposed change that we might not be able to
    accept.

    IF YOUR PULL REQUEST DOES NOT REFERENCE AN ISSUE WHICH HAS BEEN ASSIGNED
    TO YOU, IT WILL BE CLOSED AUTOMATICALLY.

    Please specify your assigned issue number on the line below.
-->
Fixes #10058: Allow searching device by primary IP address

<!--
    Please include a summary of the proposed changes below.
-->


Problem statement:
Allow searching on device page by IP address
### NetBox version

v3.3.0

### Feature type

Change to existing functionality

### Proposed functionality

We often use the devices page. It has a great layout and shows the info we need most of the time. We also frequently search for a machine by IP address. In order to do this we have to navigate to the IP Addresses page and then search there, get the machine name, then go back to the devices page.

It would be great if the 'search' on the devices pages can also search IP addresses on the same page (or perhaps other info as well).

### Use case

Use case described in the functionality text.

### Database changes

Don't believe db changes are needed.

### External dependencies

None.

Hints from issue discussion:
This would likely be implemented first : https://github.com/netbox-community/netbox/issues/7016
This doesn't necessarily rely on #7016, however it would be very difficult to implement efficiently without some change to the data model. Searching for devices by IP address requires us to traverse two relations, from device to interface and from interface to IP address.
Is the ask to filter using the Device model using the  primary_ip4 or primary_ip6 fields?  I think I have a working example in the Django shell, something like this:

`Device.objects.filter(primary_ip4__net_host="x.x.x.x")`

I found where the search is being done in netbox/dcim/filtersets at DeviceFilterSet.search().  Another Q query could be appended for primary_ip4__net_host or primary_ip6__net_host, there would just need to be some validation to make sure it is a valid IPv4/IPv6 address then conditionally add the filter.

Thoughts?  I'd like to take a stab at creating a PR with the update and some tests for it if this is a valid solution.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
I've added the primary_ip4 and primary_ip6 filters here (https://github.com/netbox-community/netbox/pull/11382), so this could be implemented with those filters?

This would allow to search for a specific device with the mentioned primary IP address from the device filter page, but since the primary_ip4/primary_ip6 is unique, there will only be maximum one result though.

If this is okay, I could take a look at adding these fields. It looks fairly straightforward, by just adding some fields in `netbox/dcim/forms/filtersets.py`, right?
It is actually on the `netbox/dcim/filtersets.py`

I added this, and it works pretty well : 
```
    def search(self, queryset, name, value):
        if not value.strip():
            return queryset
        return queryset.filter(
            Q(name__icontains=value) |
            Q(serial__icontains=value.strip()) |
            Q(inventoryitems__serial__icontains=value.strip()) |
            Q(asset_tag__icontains=value.strip()) |
            Q(comments__icontains=value) | 
            Q(primary_ip4__address__startswith=value) |
            Q(primary_ip6__address__startswith=value)
        ).distinct()
```

I also added the fields to the search index: 
```class DeviceIndex(SearchIndex):
    model = models.Device
    fields = (
        ('asset_tag', 50),
        ('serial', 60),
        ('name', 100),
        ('primary_ip4', 110),
        ('primary_ip6', 110),
        ('description', 500),
        ('comments', 5000),
    )
```

Commit message: Search device by primary IP address

Patch to review:
diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py
index c10ef44c3b8..1ea56b3ef27 100644
--- a/netbox/dcim/filtersets.py
+++ b/netbox/dcim/filtersets.py
@@ -981,7 +981,9 @@ def search(self, queryset, name, value):
             Q(serial__icontains=value.strip()) |
             Q(inventoryitems__serial__icontains=value.strip()) |
             Q(asset_tag__icontains=value.strip()) |
-            Q(comments__icontains=value)
+            Q(comments__icontains=value) |
+            Q(primary_ip4__address__startswith=value) |
+            Q(primary_ip6__address__startswith=value)
         ).distinct()
 
     def _has_primary_ip(self, queryset, name, value):
diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py
index bae4f030f67..7cb2c6205f6 100644
--- a/netbox/dcim/search.py
+++ b/netbox/dcim/search.py
@@ -41,6 +41,8 @@ class DeviceIndex(SearchIndex):
         ('asset_tag', 50),
         ('serial', 60),
         ('name', 100),
+        ('primary_ip4', 110),
+        ('primary_ip6', 110),
         ('description', 500),
         ('comments', 5000),
     )


Code review comments:
diff_hunk: @@ -41,6 +41,8 @@ class DeviceIndex(SearchIndex):
         ('asset_tag', 50),
         ('serial', 60),
         ('name', 100),
+        ('primary_ip4', 110),
path: netbox/dcim/search.py
text: @user1:
This will not work, primary_ip4 is a foreignkey to the IPAddress model and as such cannot be indexed. Furthermore changing indexes would probably require dumping the existing index in a migration.

@author:
Works fine. When adding an IP to a device and checking the 'make primary' it seems to be indexed.
I can search that IP immediately and find the ip, device, prefix, aggregate
Isn't a the reindex is done automatically during the housekeeping ? A note can be added to run the reindex manually if needed.

@user1:
I see why it works, the device is for some reason saved when the primary ip is modified in the UI (empty changelog though). In general we can't index related objects, because it's complex to update the index when related objects change.

In this case, try to modify the primary ip via. the API and change the address. You will end up with an inconsistent cache. Here I change the IPAddress object assigned as primary ip from 199.199.199.3/25 to 199.199.199.3/26. The index for the ip address was updated, but not for the device as none of the fields on the device model changed.

![image](https://user-images.githubusercontent.com/400797/221126161-ded06e09-40ff-4d92-bcb8-cede497634f4.png)

So no, it doesn't work.

@user1:
> Isn't a the reindex is done automatically during the housekeeping ?

No, a full reindex can take 10+ minutes on large instances and during the reindex the search index would be incomplete.

> A note can be added to run the reindex manually if needed.

Currently, as I said, the way we do it is to drop the index for the specific model in a migration and then we have added a reindex --lazy command which is run during ./upgrade.sh. This avoids having to refresh the entire index every upgrade.

@author:
That's a bummer.. I really liked the device in those results. I hope this might make it in a different FR, as there is a request to add related objects to the results

I'll remove this from the PR