Skip to content

Commit 1d4cc08

Browse files
committed
[enhancement] Added task and logic for fuzzy location creation #1034
Fuzzy location task should be executed when creating WhoIs for a device. It should also be executed when we have an existing WhoIs and attach the device to the same location as the similar ip's devices are attached. Closes #1034 Signed-off-by: DragnEmperor <[email protected]>
1 parent 3ffae10 commit 1d4cc08

File tree

2 files changed

+90
-7
lines changed

2 files changed

+90
-7
lines changed

openwisp_controller/config/whois/service.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from openwisp_controller.config import settings as app_settings
88

9-
from .tasks import fetch_whois_details
9+
from .tasks import fetch_whois_details, manage_fuzzy_locations
1010

1111

1212
class WHOISService:
@@ -79,18 +79,23 @@ def _need_whois_lookup(self, new_ip):
7979
8080
The lookup is not triggered if:
8181
- The new IP address is None or it is a private IP address.
82-
- The WHOIS information of new ip is already present.
83-
- WHOIS is disabled in the organization settings. (query from db)
82+
- The WhoIs information of new ip is already present.
83+
- WhoIs is disabled in the organization settings. (query from db)
84+
85+
Two boolean values are returned:
86+
- First boolean indicates if WhoIs lookup is needed.
87+
- Second boolean indicates if WhoIs info already exists in the db,
88+
which is used for managing fuzzy locations.
8489
"""
8590

8691
# Check cheap conditions first before hitting the database
8792
if not self.is_valid_public_ip_address(new_ip):
88-
return False
93+
return False, False
8994

9095
if self._get_whois_info_from_db(new_ip).exists():
91-
return False
96+
return False, True
9297

93-
return self.is_whois_enabled
98+
return self.is_whois_enabled, False
9499

95100
def get_device_whois_info(self):
96101
"""
@@ -108,11 +113,19 @@ def trigger_whois_lookup(self):
108113
Trigger WHOIS lookup based on the conditions of `_need_whois_lookup`.
109114
Task is triggered on commit to ensure redundant data is not created.
110115
"""
111-
if self._need_whois_lookup(self.device.last_ip):
116+
117+
fetch_whois, whois_info_exists = self._need_whois_lookup(self.device.last_ip)
118+
if fetch_whois:
112119
transaction.on_commit(
113120
lambda: fetch_whois_details.delay(
114121
device_pk=self.device.pk,
115122
initial_ip_address=self.device._initial_last_ip,
116123
new_ip_address=self.device.last_ip,
117124
)
118125
)
126+
elif whois_info_exists and self.is_whois_enabled:
127+
transaction.on_commit(
128+
manage_fuzzy_locations.delay(
129+
self.device.pk, self.device.last_ip, add_existing=True
130+
)
131+
)

openwisp_controller/config/whois/tasks.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import requests
44
from celery import shared_task
5+
from django.contrib.gis.geos import Point
56
from django.utils.translation import gettext as _
67
from geoip2 import errors
78
from geoip2 import webservice as geoip2_webservice
@@ -158,3 +159,72 @@ def delete_whois_record(ip_address):
158159
queryset = WHOISInfo.objects.filter(ip_address=ip_address)
159160
if queryset.exists():
160161
queryset.delete()
162+
163+
164+
@shared_task
165+
def manage_fuzzy_locations(
166+
device_pk,
167+
ip_address,
168+
latitude=None,
169+
longitude=None,
170+
address=None,
171+
add_existing=False,
172+
):
173+
"""
174+
Creates/updates fuzzy location for a device based on the latitude and longitude
175+
or attaches an existing location if `add_existing` is True.
176+
Existing location here means a location of a device whose last_ip matches
177+
the given ip_address.
178+
"""
179+
Device = load_model("config", "Device")
180+
Location = load_model("geo", "Location")
181+
DeviceLocation = load_model("geo", "DeviceLocation")
182+
183+
device_location = (
184+
DeviceLocation.objects.filter(content_object_id=device_pk)
185+
.select_related("location")
186+
.first()
187+
)
188+
189+
if not device_location:
190+
device_location = DeviceLocation(content_object_id=device_pk)
191+
192+
# if attaching an existing location, the current device should not have
193+
# a location already set.
194+
# TODO: Do we do this if device location exists but is marked fuzzy?
195+
if add_existing and not device_location.location:
196+
existing_device_with_location = (
197+
Device.objects.select_related("device_location")
198+
.filter(last_ip=ip_address, device_location__location__isnull=False)
199+
.first()
200+
)
201+
if existing_device_with_location:
202+
location = existing_device_with_location.device_location.location
203+
device_location.location = location
204+
device_location.full_clean()
205+
device_location.save()
206+
elif latitude and longitude:
207+
device = Device.objects.get(pk=device_pk)
208+
coords = Point(longitude, latitude, srid=4326)
209+
# Create/update the device location mapping, updating existing location
210+
# if exists else create a new location
211+
location_defaults = {
212+
"name": f"{device.name} Location",
213+
"type": "outdoor",
214+
"organization_id": device.organization_id,
215+
"is_mobile": False,
216+
"geometry": coords,
217+
"address": address,
218+
}
219+
if device_location.location and device_location.location.fuzzy:
220+
for attr, value in location_defaults.items():
221+
setattr(device_location.location, attr, value)
222+
device_location.location.full_clean()
223+
device_location.location.save()
224+
elif not device_location.location:
225+
location = Location(**location_defaults, fuzzy=True)
226+
location.full_clean()
227+
location.save()
228+
device_location.location = location
229+
device_location.full_clean()
230+
device_location.save()

0 commit comments

Comments
 (0)