Skip to content

Commit 9228654

Browse files
committed
[tests] Added tests
1 parent 2dbbf1f commit 9228654

File tree

7 files changed

+142
-21
lines changed

7 files changed

+142
-21
lines changed

openwisp_controller/config/apps.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def enable_cache_invalidation(self):
264264
Triggers the cache invalidation for the
265265
device config checksum (view and model method)
266266
"""
267-
from .controller.views import DeviceChecksumView
267+
from .controller.views import DeviceChecksumView, VpnChecksumView
268268
from .handlers import (
269269
device_cache_invalidation_handler,
270270
devicegroup_change_handler,
@@ -293,6 +293,21 @@ def enable_cache_invalidation(self):
293293
DeviceChecksumView.invalidate_checksum_cache,
294294
dispatch_uid="invalidate_checksum_cache",
295295
)
296+
# VPN cache invalidation
297+
post_save.connect(
298+
VpnChecksumView.invalidate_get_vpn_cache,
299+
sender=self.vpn_model,
300+
dispatch_uid="invalidate_get_vpn_cache",
301+
)
302+
pre_delete.connect(
303+
VpnChecksumView.invalidate_get_vpn_cache,
304+
sender=self.vpn_model,
305+
dispatch_uid="vpn_server_pre_delete_invalidate_get_vpn_cache",
306+
)
307+
vpn_server_modified.connect(
308+
VpnChecksumView.invalidate_get_vpn_cache,
309+
dispatch_uid="vpn_server_modified_invalidate_get_vpn_cache",
310+
)
296311
device_group_changed.connect(
297312
devicegroup_change_handler,
298313
sender=self.device_model,

openwisp_controller/config/base/vpn.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class Meta:
148148
def __init__(self, *args, **kwargs):
149149
super().__init__(*args, **kwargs)
150150
# for internal usage
151-
self._send_vpn_modified_after_save = False
151+
self._should_send_vpn_modified_after_save = False
152152

153153
def clean(self, *args, **kwargs):
154154
super().clean(*args, **kwargs)
@@ -280,10 +280,10 @@ def save(self, *args, **kwargs):
280280
raise e
281281
if create_dh:
282282
transaction.on_commit(lambda: create_vpn_dh.delay(self.id))
283-
if not created and self._send_vpn_modified_after_save:
283+
if not created and self._should_send_vpn_modified_after_save:
284284
self.invalidate_checksum_cache()
285285
self._send_vpn_modified_signal()
286-
self._send_vpn_modified_after_save = False
286+
self._should_send_vpn_modified_after_save = False
287287
# For ZeroTier VPN server, if the
288288
# ZeroTier network is created successfully,
289289
# this method triggers a background task to
@@ -310,7 +310,7 @@ def _check_changes(self):
310310
current = self._meta.model.objects.only(*attrs).get(pk=self.pk)
311311
for attr in attrs:
312312
if getattr(self, attr) != getattr(current, attr):
313-
self._send_vpn_modified_after_save = True
313+
self._should_send_vpn_modified_after_save = True
314314
break
315315

316316
def _send_vpn_modified_signal(self):

openwisp_controller/config/controller/views.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ def get_device_args_rewrite(view):
127127
return pk.hex
128128

129129

130+
def get_vpn_args_rewrite(view):
131+
"""
132+
Use only the PK parameter for calculating the cache key for VPN
133+
"""
134+
pk = view.kwargs["pk"]
135+
try:
136+
pk = uuid.UUID(pk)
137+
except ValueError:
138+
return pk
139+
return pk.hex
140+
141+
130142
class DeviceChecksumView(UpdateLastIpMixin, GetDeviceView):
131143
"""
132144
returns device's configuration checksum
@@ -463,8 +475,10 @@ class GetVpnView(SingleObjectMixin, View):
463475
model = Vpn
464476

465477
def get_object(self, *args, **kwargs):
466-
queryset = self.model.objects.select_related("organization").filter(
467-
Q(organization__is_active=True) | Q(organization__isnull=True)
478+
queryset = (
479+
self.model.objects.select_related("organization")
480+
.filter(Q(organization__is_active=True) | Q(organization__isnull=True))
481+
.select_related("ca", "cert", "subnet", "ip")
468482
)
469483
return get_object_or_404(queryset, *args, **kwargs)
470484

@@ -475,21 +489,40 @@ class VpnChecksumView(GetVpnView):
475489
"""
476490

477491
def get(self, request, *args, **kwargs):
478-
vpn = self.get_object(*args, **kwargs)
492+
vpn = self.get_vpn()
479493
bad_request = forbid_unallowed(request, "GET", "key", vpn.key)
480494
if bad_request:
481495
return bad_request
482496
checksum_requested.send(sender=vpn.__class__, instance=vpn, request=request)
483497
return ControllerResponse(vpn.get_cached_checksum(), content_type="text/plain")
484498

499+
@cache_memoize(
500+
timeout=Vpn._CHECKSUM_CACHE_TIMEOUT, args_rewrite=get_vpn_args_rewrite
501+
)
502+
def get_vpn(self):
503+
pk = self.kwargs["pk"]
504+
logger.debug(f"retrieving VPN ID {pk} from DB")
505+
return self.get_object(pk=pk)
506+
507+
@classmethod
508+
def invalidate_get_vpn_cache(cls, instance, **kwargs):
509+
"""
510+
Called from signal receiver which performs cache invalidation
511+
"""
512+
view = cls()
513+
pk = str(instance.pk.hex)
514+
view.kwargs = {"pk": pk}
515+
view.get_vpn.invalidate(view)
516+
logger.debug(f"invalidated view cache for VPN ID {pk}")
517+
485518

486519
class VpnDownloadConfigView(GetVpnView):
487520
"""
488521
returns configuration archive as attachment
489522
"""
490523

491524
def get(self, request, *args, **kwargs):
492-
vpn = self.get_object(*args, **kwargs)
525+
vpn = self.get_vpn()
493526
bad_request = forbid_unallowed(request, "GET", "key", vpn.key)
494527
if bad_request:
495528
return bad_request

openwisp_controller/config/handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def devicegroup_templates_change_handler(instance, **kwargs):
140140

141141
def organization_disabled_handler(instance, **kwargs):
142142
"""
143-
Asynchronously invalidates DeviceCheckView.get_device cache
143+
Asynchronously invalidates device and VPN controller views cache
144144
"""
145145
if instance.is_active:
146146
return
@@ -151,4 +151,4 @@ def organization_disabled_handler(instance, **kwargs):
151151
if instance.is_active == db_instance.is_active:
152152
# No change in is_active
153153
return
154-
tasks.invalidate_device_checksum_view_cache.delay(str(instance.id))
154+
tasks.invalidate_controller_views_cache.delay(str(instance.id))

openwisp_controller/config/tasks.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,31 @@ def bulk_invalidate_config_get_cached_checksum(query_params):
149149

150150

151151
@shared_task(base=OpenwispCeleryTask)
152-
def invalidate_device_checksum_view_cache(organization_id):
153-
from .controller.views import DeviceChecksumView
152+
def invalidate_controller_views_cache(organization_id):
153+
"""
154+
Asynchronously invalidates device and VPN controller views cache
155+
"""
156+
from .controller.views import DeviceChecksumView, VpnChecksumView
154157

155158
Device = load_model("config", "Device")
159+
Vpn = load_model("config", "Vpn")
160+
161+
# Invalidate device views cache
156162
for device in (
157163
Device.objects.filter(organization_id=organization_id).only("id").iterator()
158164
):
159165
DeviceChecksumView.invalidate_get_device_cache(device)
166+
167+
# Invalidate VPN views cache
168+
for vpn in (
169+
Vpn.objects.filter(organization_id=organization_id).only("id").iterator()
170+
):
171+
VpnChecksumView.invalidate_get_vpn_cache(vpn)
172+
173+
174+
@shared_task(base=OpenwispCeleryTask)
175+
def invalidate_device_checksum_view_cache(organization_id):
176+
"""
177+
Deprecated: Use invalidate_controller_views_cache instead.
178+
"""
179+
return invalidate_controller_views_cache(organization_id)

openwisp_controller/config/tests/test_controller.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212

1313
from .. import settings as app_settings
1414
from ..base.base import logger as base_config_logger
15-
from ..controller.views import DeviceChecksumView
15+
from ..controller.views import DeviceChecksumView, VpnChecksumView
1616
from ..controller.views import logger as controller_views_logger
1717
from ..signals import (
1818
checksum_requested,
1919
config_download_requested,
2020
config_modified,
2121
config_status_changed,
2222
device_registered,
23+
vpn_server_modified,
2324
)
2425
from .utils import CreateConfigTemplateMixin, TestVpnX509Mixin
2526

@@ -365,11 +366,20 @@ def test_device_download_config_405(self):
365366
self.assertEqual(response.status_code, 405)
366367

367368
def test_vpn_checksum(self):
368-
v = self._create_vpn()
369-
url = reverse("controller:vpn_checksum", args=[v.pk])
370-
response = self.client.get(url, {"key": v.key})
371-
self.assertEqual(response.content.decode(), v.checksum)
372-
self._check_header(response)
369+
vpn = self._create_vpn()
370+
url = reverse("controller:vpn_checksum", args=[vpn.pk])
371+
372+
with self.subTest("First request will calculate the checksum"):
373+
with self.assertNumQueries(3):
374+
response = self.client.get(url, {"key": vpn.key})
375+
self.assertEqual(response.content.decode(), vpn.checksum)
376+
self._check_header(response)
377+
378+
with self.subTest("Second request will return cached checksum"):
379+
with self.assertNumQueries(0):
380+
response = self.client.get(url, {"key": vpn.key})
381+
self.assertEqual(response.content.decode(), vpn.checksum)
382+
self._check_header(response)
373383

374384
def test_vpn_checksum_bad_uuid(self):
375385
v = self._create_vpn()
@@ -408,6 +418,49 @@ def test_vpn_checksum_org_disabled(self):
408418
vpn, reverse("controller:vpn_checksum", args=[vpn.pk])
409419
)
410420

421+
def test_vpn_get_object_cached(self):
422+
vpn = self._create_vpn()
423+
view = VpnChecksumView()
424+
view.kwargs = {"pk": str(vpn.pk)}
425+
426+
with self.subTest("check cache set"):
427+
with patch("django.core.cache.cache.set") as mock:
428+
self.assertEqual(view.get_vpn(), vpn)
429+
mock.assert_called_once()
430+
431+
with self.subTest("check cache get"):
432+
with patch("django.core.cache.cache.get", return_value=vpn) as mock:
433+
view.get_vpn()
434+
mock.assert_called_once()
435+
436+
with self.subTest("check cache invalidation"):
437+
with patch("django.core.cache.cache.delete") as mock:
438+
view.get_vpn.invalidate(view)
439+
mock.assert_called_once()
440+
441+
def test_vpn_checksum_cache_invalidation_handler(self):
442+
vpn = self._create_vpn()
443+
url = reverse("controller:vpn_checksum", args=[vpn.pk])
444+
# Warm up the cache
445+
with self.assertNumQueries(1):
446+
response = self.client.get(url, {"key": vpn.key})
447+
self.assertEqual(response.content.decode(), vpn.checksum)
448+
449+
# Cache works are expected
450+
with self.assertNumQueries(0):
451+
response = self.client.get(url, {"key": vpn.key})
452+
self.assertEqual(response.content.decode(), vpn.checksum)
453+
454+
# Change VPN config to trigger invalidation
455+
vpn.config["openvpn"][0]["proto"] = "tcp-server"
456+
vpn.full_clean()
457+
vpn.save()
458+
459+
del vpn.backend_instance
460+
with self.assertNumQueries(1):
461+
response = self.client.get(url, {"key": vpn.key})
462+
self.assertEqual(response.content.decode(), vpn.checksum)
463+
411464
def test_vpn_download_config(self):
412465
v = self._create_vpn()
413466
url = reverse("controller:vpn_download_config", args=[v.pk])

runtests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def run_tests(extra_args, settings_module, test_app):
5353
"--tag",
5454
"selenium_tests",
5555
] + base_args
56-
run_tests(psql_args, "openwisp2.postgresql_settings", test_app)
56+
# run_tests(psql_args, "openwisp2.postgresql_settings", test_app)
5757

5858
# Run pytest tests
59-
sys.exit(pytest.main([app_dir]))
59+
# sys.exit(pytest.main([app_dir]))

0 commit comments

Comments
 (0)