-
-
Notifications
You must be signed in to change notification settings - Fork 229
[feature] Updating stale WHOIS records #1116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gsoc25-whois
Are you sure you want to change the base?
Conversation
0a1c1cc
to
2c1dda6
Compare
docs/user/estimated-location.rst
Outdated
Overview | ||
-------- | ||
|
||
The Estimated Location feature automatically creates or updates a device’s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we need not to repeat the topic name. Same for other cases as well
The Estimated Location feature automatically creates or updates a device’s | |
This feature automatically creates or updates a device’s |
docs/user/whois.rst
Outdated
This will be triggered for the same scenarios defined in `trigger | ||
conditions <whois_trigger_conditions_>`_ but among the conditions **only | ||
WHOIS enablement is required**. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This statement is not very clear
) | ||
return | ||
|
||
def _handle_attach_existing_location( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this function has a lot of responsibilities, should we refactor this?
|
||
|
||
class Command(BaseCommand): | ||
help = "Clear the last IP address, if set, of active devices of all organizations." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
help = "Clear the last IP address, if set, of active devices of all organizations." | |
help = "Clears the last IP address (if set) for every active devices of all organizations." |
"Are you sure you want to do this?\n\n" | ||
"Type 'yes' to continue, or 'no' to cancel: " | ||
) | ||
if input("".join(message)) != "yes": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make it case-insensitive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can but since we have explicitly provided the choices, it shouldn't be confusing
raise CommandError("Operation cancelled by user.") | ||
|
||
devices = Device.objects.filter(_is_deactivated=False).only("last_ip") | ||
# Filter devices that have no WHOIS information for their last IP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Filter devices that have no WHOIS information for their last IP | |
# Filter out devices that have no WHOIS information for their last IP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It excludes the devices "having" WHOIS information. Will rephrase this for clarity
if not self.is_valid_public_ip_address(new_ip): | ||
return False | ||
|
||
if not self.is_whois_enabled: | ||
return False | ||
|
||
return self.is_estimated_location_enabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not self.is_valid_public_ip_address(new_ip): | |
return False | |
if not self.is_whois_enabled: | |
return False | |
return self.is_estimated_location_enabled | |
return ( | |
self.is_valid_public_ip_address(new_ip) and | |
self.is_whois_enabled and | |
self.is_estimated_location_enabled | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am following the same structure that we have done for other functions dealing with conditions within this class.
I think we decided to split the conditions in other functions to make them more readable.
# Host is based on the db that is used to fetch the details. | ||
# As we are using GeoLite2, 'geolite.info' host is used. | ||
# Refer: https://geoip2.readthedocs.io/en/latest/#sync-web-service-example | ||
ip_client = geoip2_webservice.Client( | ||
account_id=app_settings.WHOIS_GEOIP_ACCOUNT, | ||
license_key=app_settings.WHOIS_GEOIP_KEY, | ||
host="geolite.info", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: This should be abstracted out for extensibility
**app_settings.API_TASK_RETRY_OPTIONS, | ||
) | ||
def fetch_whois_details(self, device_pk, initial_ip_address, new_ip_address): | ||
def fetch_whois_details(self, device_pk, initial_ip_address): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is quite big to follow, we should look to break it in smaller functions, if possible
ddcf823
to
55a77c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice there are quite a few else conditions now just to handle updates. If the task is only about refreshing stale information, why not run it as a scheduled task in Celery? Would that cause any issues?
This way, we can avoid calling the update method in the WhoIs service for every checksum call or other device update operation.
docs/user/whois.rst
Outdated
A WHOIS lookup is triggered automatically when: | ||
|
||
- A new device is registered. | ||
- A new device is registered or its last IP address is changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can add it as a new pointer below
- The WHOIS information of new ip is present and is not older than | ||
14 days. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use the configured variable name
Hi Sankalp, initially the plan was to create a task but as discussed on the weekly call, it was decided that it should run only when required to avoid too many requests. |
7c909e4
to
f934ae3
Compare
df469a6
to
155ea0b
Compare
docs/user/estimated-location.rst
Outdated
Managing Older Estimated Locations | ||
---------------------------------- | ||
|
||
Whenever location related fields in WHOIS record are updated as per |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever location related fields in WHOIS record are updated as per | |
Whenever location related fields in WHOIS records are updated as per |
self.device = device | ||
|
||
@staticmethod | ||
def geoip_client(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about?
def geoip_client(): | |
def get_geoip_client(): |
For consistency.
raise exc_type(message) | ||
except requests.RequestException as e: | ||
raise e | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this blank line
|
||
try: | ||
data = ip_client.city(ip_address=ip_address) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this blank line
the relevant information. | ||
""" | ||
ip_client = self.geoip_client() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this blank line
ip_address = self.device.last_ip | ||
if not self.is_valid_public_ip_address(ip_address): | ||
return | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this blank line
Existing location here means a location of another device whose last_ip matches | ||
the given ip_address. | ||
Does not alters the existing location if it is not estimated. | ||
def _create_estimated_location(device_location, location_defaults, ip_address): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should make this function create or update the location, this would make the code more readable.
We have similar constructs in our code base, please see:
https://github.com/search?q=org%3Aopenwisp+get_or_create&type=code
Moreover, another pattern that we're striving to follow is to have most logic concentrated in either models or classes like the one you did for the whois service, and then have admin views, API views and celery tasks use the methods provided by in models.
device_pk=device.pk, | ||
notify_type="estimated_location_updated", | ||
actor=current_location, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally this logic would go in the create or update function/method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it and it is working as expected.
# If there are multiple devices with same last_ip then we need to inform | ||
# the user to resolve the conflict manually. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this comment? logger error message seems to be clear.
if existing_device_location: | ||
existing_location = existing_device_location.location | ||
# We need to remove existing estimated location of the device | ||
# if it is not shared | ||
if attached_devices_exists is False: | ||
current_location.delete() | ||
device_location.location = existing_location | ||
device_location.full_clean() | ||
device_location.save() | ||
logger.info( | ||
f"Estimated location saved successfully for {device.pk}" | ||
f" for IP: {ip_address}" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test asserting the number of queries?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have added this in the latest commit for creation/updating related tests for estimate location
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding assertNumQueries
, it's always good to keep the query count in check because new contributors can inadvertently change ORM calls and cause query count to go banana.
I have a few comments below.
whois_instance = WHOISInfo(**whois_details) | ||
whois_instance.full_clean() | ||
whois_instance.save() | ||
whois_instance.save(force_insert=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one curiosity, why is this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Django hits an UPDATE
query using the primary key ip_address
to update any existing records, if any. Since we have checks to ensure we update existing record, if it exists, I think we can proceed with force_insert
.
# We need to remove existing estimated location of the device | ||
# if it is not shared | ||
if attached_devices_exists is False: | ||
current_location.delete() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the comment above needs to be moved too.
dbb7885
to
141a1b1
Compare
cf35f57
to
5eca0e0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DragnEmperor the test failing due to email verification not being sent can be fixed with this: 260289a.
141a1b1
to
3f6ec02
Compare
Added required checks for ensuring WHOIS records older than a certain Threshold (configurable) will be updated along with the coordinates. Formatted existing code to be consistent with current coding standards. Extend django-loci's location change form template Closes #1058 Signed-off-by: DragnEmperor <[email protected]>
005500f
to
3346bfc
Compare
Checklist
Reference to Existing Issue
Closes #1058
Description of Changes
Added functionality to update WHOIS records which are older than a threshold which is a configurable setting.
Since the main conditions are different from existing conditions of whois lookup, have defined new method for handling update.
Updated documentation and test cases.