Skip to content

Commit 9882ec4

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 c13e6e4 commit 9882ec4

File tree

1 file changed

+94
-6
lines changed

1 file changed

+94
-6
lines changed

openwisp_controller/config/whois/service.py

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import requests
55
from celery import shared_task
6+
from django.contrib.gis.geos import Point
67
from django.core.cache import cache
78
from django.db import transaction
89
from django.utils.translation import gettext as _
@@ -161,18 +162,23 @@ def _need_whois_lookup(self, new_ip):
161162
162163
The lookup is not triggered if:
163164
- The new IP address is None or it is a private IP address.
164-
- The WHOIS information of new ip is already present.
165-
- WHOIS is disabled in the organization settings. (query from db)
165+
- The WhoIs information of new ip is already present.
166+
- WhoIs is disabled in the organization settings. (query from db)
167+
168+
Two boolean values are returned:
169+
- First boolean indicates if WhoIs lookup is needed.
170+
- Second boolean indicates if WhoIs info already exists in the db,
171+
which is used for managing fuzzy locations.
166172
"""
167173

168174
# Check cheap conditions first before hitting the database
169175
if not self.is_valid_public_ip_address(new_ip):
170-
return False
176+
return False, False
171177

172178
if self._get_whois_info_from_db(new_ip).exists():
173-
return False
179+
return False, True
174180

175-
return self.is_whois_enabled
181+
return self.is_whois_enabled, False
176182

177183
def get_device_whois_info(self):
178184
"""
@@ -190,14 +196,20 @@ def trigger_whois_lookup(self):
190196
Trigger WhoIs lookup based on the conditions of `_need_whois_lookup`.
191197
Task is triggered on commit to ensure redundant data is not created.
192198
"""
193-
if self._need_whois_lookup(self.device.last_ip):
199+
200+
fetch_whois, whois_info_exists = self._need_whois_lookup(self.device.last_ip)
201+
if fetch_whois:
194202
transaction.on_commit(
195203
lambda: self.fetch_whois_details.delay(
196204
device_pk=self.device.pk,
197205
initial_ip_address=self.device._initial_last_ip,
198206
new_ip_address=self.device.last_ip,
199207
)
200208
)
209+
elif whois_info_exists and self.is_whois_enabled:
210+
self.manage_fuzzy_locations.delay(
211+
self.device.pk, self.device.last_ip, add_existing=True
212+
)
201213

202214
# device_pk is used when task fails to report for which device failure occurred
203215
@shared_task(
@@ -259,6 +271,14 @@ def fetch_whois_details(self, device_pk, initial_ip_address, new_ip_address):
259271
whois_obj.full_clean()
260272
whois_obj.save()
261273
logger.info(f"Successfully fetched WHOIS details for {new_ip_address}.")
274+
location_address = whois_obj.formatted_address
275+
WhoIsService.manage_fuzzy_locations.delay(
276+
device_pk,
277+
new_ip_address,
278+
data.location.latitude,
279+
data.location.longitude,
280+
location_address,
281+
)
262282

263283
# the following check ensures that for a case when device last_ip
264284
# is not changed and there is no related WHOIS record, we do not
@@ -281,3 +301,71 @@ def delete_whois_record(ip_address):
281301
queryset = WhoIsInfo.objects.filter(ip_address=ip_address)
282302
if queryset.exists():
283303
queryset.delete()
304+
305+
@shared_task
306+
def manage_fuzzy_locations(
307+
device_pk,
308+
ip_address,
309+
latitude=None,
310+
longitude=None,
311+
address=None,
312+
add_existing=False,
313+
):
314+
"""
315+
Creates/updates fuzzy location for a device based on the latitude and longitude
316+
or attaches an existing location if `add_existing` is True.
317+
Existing location here means a location of a device whose last_ip matches
318+
the given ip_address.
319+
"""
320+
Device = load_model("config", "Device")
321+
Location = load_model("geo", "Location")
322+
DeviceLocation = load_model("geo", "DeviceLocation")
323+
324+
device_location = (
325+
DeviceLocation.objects.filter(content_object_id=device_pk)
326+
.select_related("location")
327+
.first()
328+
)
329+
330+
if not device_location:
331+
device_location = DeviceLocation(content_object_id=device_pk)
332+
333+
# if attaching an existing location, the current device should not have
334+
# a location already set.
335+
#TODO: Do we do this if device location exists but is marked fuzzy?
336+
if add_existing and not device_location.location:
337+
existing_device_with_location = (
338+
Device.objects.select_related("device_location")
339+
.filter(last_ip=ip_address, device_location__location__isnull=False)
340+
.first()
341+
)
342+
if existing_device_with_location:
343+
location = existing_device_with_location.device_location.location
344+
device_location.location = location
345+
device_location.full_clean()
346+
device_location.save()
347+
elif latitude and longitude:
348+
device = Device.objects.get(pk=device_pk)
349+
coords = Point(longitude, latitude, srid=4326)
350+
# Create/update the device location mapping, updating existing location
351+
# if exists else create a new location
352+
location_defaults = {
353+
"name": f"{device.name} Location",
354+
"type": "outdoor",
355+
"organization_id": device.organization_id,
356+
"is_mobile": False,
357+
"geometry": coords,
358+
"address": address,
359+
}
360+
if device_location.location and device_location.location.fuzzy:
361+
for attr, value in location_defaults.items():
362+
setattr(device_location.location, attr, value)
363+
device_location.location.full_clean()
364+
device_location.location.save()
365+
elif not device_location.location:
366+
location = Location(**location_defaults, fuzzy=True)
367+
location.full_clean()
368+
location.save()
369+
device_location.location = location
370+
device_location.full_clean()
371+
device_location.save()

0 commit comments

Comments
 (0)