Skip to content

Commit eaa0194

Browse files
committed
[enhancement] Added management command for clearing last IP
Signed-off-by: DragnEmperor <[email protected]>
1 parent 5a0ae7e commit eaa0194

File tree

5 files changed

+112
-0
lines changed

5 files changed

+112
-0
lines changed

docs/user/whois.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ met:
4545
- There is **no existing WHOIS record** for that IP.
4646
- WHOIS lookup is **enabled** for the device's organization.
4747

48+
Handling Existing Devices
49+
-------------------------
50+
51+
For creating WHOIS records for existing devices, run the ``clear_last_ip``
52+
management command, to clear the last IP address of **all active devices
53+
across organizations**. Active devices will update their last IP address,
54+
triggering the WHOIS lookup automatically.
55+
56+
It accepts an optional flag ``--whois-related`` to exclude devices with
57+
WHOIS records.
58+
4859
Managing WHOIS Records
4960
----------------------
5061

openwisp_controller/config/management/__init__.py

Whitespace-only changes.

openwisp_controller/config/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from django.core.management.base import BaseCommand, CommandError
2+
from django.db.models import OuterRef, Subquery
3+
from swapper import load_model
4+
5+
from openwisp_controller.config import settings as app_settings
6+
7+
8+
class Command(BaseCommand):
9+
help = "Clear the last IP address, if set, of active devices of all organizations."
10+
11+
def add_arguments(self, parser):
12+
parser.add_argument(
13+
"--noinput",
14+
"--no-input",
15+
action="store_false",
16+
dest="interactive",
17+
help="Do NOT prompt the user for input of any kind.",
18+
)
19+
parser.add_argument(
20+
"--whois-related",
21+
action="store_true",
22+
help="Clear only those IPs having no WHOIS information.",
23+
)
24+
return super().add_arguments(parser)
25+
26+
def handle(self, *args, **options):
27+
Device = load_model("config", "Device")
28+
WHOISInfo = load_model("config", "WHOISInfo")
29+
30+
if options["interactive"]:
31+
message = ["\n"]
32+
message.append(
33+
"This will clear last IP of all active devices across organizations!\n"
34+
)
35+
message.append(
36+
"Are you sure you want to do this?\n\n"
37+
"Type 'yes' to continue, or 'no' to cancel: "
38+
)
39+
if input("".join(message)) != "yes":
40+
raise CommandError("Operation cancelled by user.")
41+
42+
devices = Device.objects.filter(_is_deactivated=False).exclude(last_ip=None)
43+
devices = devices.only("last_ip")
44+
if options["whois_related"]:
45+
if not app_settings.WHOIS_CONFIGURED:
46+
self.stdout.write("WHOIS must be configured to use this option.")
47+
return
48+
# Filter devices that have no WHOIS information for their last IP
49+
devices = devices.exclude(
50+
last_ip__in=Subquery(
51+
WHOISInfo.objects.filter(ip_address=OuterRef("last_ip")).values(
52+
"ip_address"
53+
)
54+
)
55+
)
56+
57+
updated_devices = devices.update(last_ip=None)
58+
if updated_devices:
59+
self.stdout.write(
60+
f"Cleared last IP addresses for {updated_devices} active device(s)."
61+
)
62+
else:
63+
self.stdout.write("No active devices with last IP to clear.")

openwisp_controller/config/whois/test_whois.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import importlib
2+
from io import StringIO
23
from unittest import mock
34

45
from django.contrib.gis.geos import Point
56
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
67
from django.core.exceptions import ImproperlyConfigured, ValidationError
8+
from django.core.management import call_command
79
from django.db.models.signals import post_delete, post_save
810
from django.test import TestCase, TransactionTestCase, override_settings, tag
911
from django.urls import reverse
@@ -265,6 +267,42 @@ def test_whois_details_device_api(self):
265267
self.assertEqual(response.status_code, 200)
266268
self.assertNotIn("whois_info", response.data)
267269

270+
@mock.patch.object(app_settings, "WHOIS_CONFIGURED", True)
271+
def test_last_ip_management_command(self):
272+
out = StringIO()
273+
device = self._create_device(last_ip="172.217.22.11")
274+
args = ["--noinput"]
275+
call_command("clear_last_ip", *args, stdout=out, stderr=StringIO())
276+
self.assertIn(
277+
"Cleared last IP addresses for 1 active device(s).", out.getvalue()
278+
)
279+
device.refresh_from_db()
280+
self.assertIsNone(device.last_ip)
281+
282+
device.last_ip = "172.217.22.11"
283+
device.save()
284+
self._create_whois_info(ip_address=device.last_ip)
285+
device2 = self._create_device(
286+
name="11:22:33:44:55:66",
287+
mac_address="11:22:33:44:55:66",
288+
last_ip="172.217.22.12",
289+
)
290+
args.append("--whois-related")
291+
call_command("clear_last_ip", *args, stdout=out, stderr=StringIO())
292+
self.assertIn(
293+
"Cleared last IP addresses for 1 active device(s).", out.getvalue()
294+
)
295+
device.refresh_from_db()
296+
device2.refresh_from_db()
297+
self.assertIsNotNone(device.last_ip)
298+
self.assertIsNone(device2.last_ip)
299+
300+
device.last_ip = None
301+
WHOISInfo.objects.all().delete()
302+
device.save()
303+
call_command("clear_last_ip", *args, stdout=out, stderr=StringIO())
304+
self.assertIn("No active devices with last IP to clear.", out.getvalue())
305+
268306

269307
class TestWHOISInfoModel(CreateWHOISMixin, TestCase):
270308
def test_whois_model_fields_validation(self):

0 commit comments

Comments
 (0)