11import logging
22
3- import requests
43from celery import shared_task
5- from django .contrib .gis .geos import Point
64from django .db import transaction
7- from django .utils .translation import gettext as _
85from geoip2 import errors
9- from geoip2 import webservice as geoip2_webservice
106from swapper import load_model
117
128from openwisp_controller .geo .estimated_location .tasks import manage_estimated_locations
1713
1814logger = logging .getLogger (__name__ )
1915
20- EXCEPTION_MESSAGES = {
21- errors .AddressNotFoundError : _ (
22- "No WHOIS information found for IP address {ip_address}"
23- ),
24- errors .AuthenticationError : _ (
25- "Authentication failed for GeoIP2 service. "
26- "Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and "
27- "OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings."
28- ),
29- errors .OutOfQueriesError : _ (
30- "Your account has run out of queries for the GeoIP2 service."
31- ),
32- errors .PermissionRequiredError : _ (
33- "Your account does not have permission to access this service."
34- ),
35- }
36-
3716
3817class WHOISCeleryRetryTask (OpenwispCeleryTask ):
3918 """
@@ -54,6 +33,28 @@ def on_failure(self, exc, task_id, args, kwargs, einfo):
5433 return super ().on_failure (exc , task_id , args , kwargs , einfo )
5534
5635
36+ def _manage_whois_record (whois_details , whois_instance = None ):
37+ """
38+ Used to update an existing WHOIS instance; else, creates a new one.
39+ Returns the updated or created WHOIS instance along with update fields.
40+ """
41+ WHOISInfo = load_model ("config" , "WHOISInfo" )
42+
43+ update_fields = []
44+ if whois_instance :
45+ for attr , value in whois_details .items ():
46+ if getattr (whois_instance , attr ) != value :
47+ update_fields .append (attr )
48+ setattr (whois_instance , attr , value )
49+ if update_fields :
50+ whois_instance .save (update_fields = update_fields )
51+ else :
52+ whois_instance = WHOISInfo (** whois_details )
53+ whois_instance .full_clean ()
54+ whois_instance .save ()
55+ return whois_instance , update_fields
56+
57+
5758# device_pk is used when task fails to report for which device failure occurred
5859@shared_task (
5960 bind = True ,
@@ -71,93 +72,35 @@ def fetch_whois_details(self, device_pk, initial_ip_address):
7172 with transaction .atomic ():
7273 device = Device .objects .get (pk = device_pk )
7374 new_ip_address = device .last_ip
75+ WHOISService = device .whois_service
76+
7477 # If there is existing WHOIS older record then it needs to be updated
7578 whois_obj = WHOISInfo .objects .filter (ip_address = new_ip_address ).first ()
76- if whois_obj and not device . whois_service .is_older (whois_obj .modified ):
79+ if whois_obj and not WHOISService .is_older (whois_obj .modified ):
7780 return
7881
79- # Host is based on the db that is used to fetch the details.
80- # As we are using GeoLite2, 'geolite.info' host is used.
81- # Refer: https://geoip2.readthedocs.io/en/latest/#sync-web-service-example
82- ip_client = geoip2_webservice .Client (
83- account_id = app_settings .WHOIS_GEOIP_ACCOUNT ,
84- license_key = app_settings .WHOIS_GEOIP_KEY ,
85- host = "geolite.info" ,
86- )
82+ fetched_details = WHOISService .process_whois_details (new_ip_address )
83+ whois_obj , update_fields = _manage_whois_record (fetched_details , whois_obj )
84+ logger .info (f"Successfully fetched WHOIS details for { new_ip_address } ." )
8785
88- try :
89- data = ip_client .city (ip_address = new_ip_address )
90-
91- # Catching all possible exceptions raised by the geoip2 client
92- # and raising them with appropriate messages to be handled by the task
93- # retry mechanism.
94- except (
95- errors .AddressNotFoundError ,
96- errors .AuthenticationError ,
97- errors .OutOfQueriesError ,
98- errors .PermissionRequiredError ,
99- ) as e :
100- exc_type = type (e )
101- message = EXCEPTION_MESSAGES .get (exc_type )
102- if exc_type is errors .AddressNotFoundError :
103- message = message .format (ip_address = new_ip_address )
104- raise exc_type (message )
105- except requests .RequestException as e :
106- raise e
107-
108- else :
109- # The attributes are always present in the response,
110- # but they can be None, so added fallbacks.
111- address = {
112- "city" : data .city .name or "" ,
113- "country" : data .country .name or "" ,
114- "continent" : data .continent .name or "" ,
115- "postal" : str (data .postal .code or "" ),
116- }
117- coordinates = Point (
118- data .location .longitude , data .location .latitude , srid = 4326
119- )
120- details = {
121- "isp" : data .traits .autonomous_system_organization ,
122- "asn" : data .traits .autonomous_system_number ,
123- "timezone" : data .location .time_zone ,
124- "address" : address ,
125- "coordinates" : coordinates ,
126- "cidr" : data .traits .network ,
127- "ip_address" : new_ip_address ,
128- }
129- update_fields = []
130- if whois_obj :
131- for attr , value in details .items ():
132- if getattr (whois_obj , attr ) != value :
133- update_fields .append (attr )
134- setattr (whois_obj , attr , value )
135- if update_fields :
136- whois_obj .save (update_fields = update_fields )
137- else :
138- whois_obj = WHOISInfo (** details )
139- whois_obj .full_clean ()
140- whois_obj .save ()
141- logger .info (f"Successfully fetched WHOIS details for { new_ip_address } ." )
142-
143- if device ._get_organization__config_settings ().estimated_location_enabled :
144- # the estimated location task should not run if old record is updated
145- # and location related fields are not updated
146- if update_fields and not any (
147- i in update_fields for i in ["address" , "coordinates" ]
148- ):
149- return
150- manage_estimated_locations .delay (
151- device_pk = device_pk , ip_address = new_ip_address
152- )
153-
154- # delete WHOIS record for initial IP if no devices are linked to it
155- if (
156- not Device .objects .filter (_is_deactivated = False )
157- .filter (last_ip = initial_ip_address )
158- .exists ()
86+ if device ._get_organization__config_settings ().estimated_location_enabled :
87+ # the estimated location task should not run if old record is updated
88+ # and location related fields are not updated
89+ if update_fields and not any (
90+ i in update_fields for i in ["address" , "coordinates" ]
15991 ):
160- delete_whois_record (ip_address = initial_ip_address )
92+ return
93+ manage_estimated_locations .delay (
94+ device_pk = device_pk , ip_address = new_ip_address
95+ )
96+
97+ # delete WHOIS record for initial IP if no devices are linked to it
98+ if (
99+ not Device .objects .filter (_is_deactivated = False )
100+ .filter (last_ip = initial_ip_address )
101+ .exists ()
102+ ):
103+ delete_whois_record (ip_address = initial_ip_address )
161104
162105
163106@shared_task
0 commit comments