Skip to content

Commit bd5670d

Browse files
committed
[feature] Cache VPN server checksum #1064
Closes #1064
1 parent 4ea68be commit bd5670d

File tree

6 files changed

+68
-44
lines changed

6 files changed

+68
-44
lines changed

openwisp_controller/config/base/base.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import collections
22
import hashlib
33
import json
4+
import logging
45
from copy import deepcopy
56

7+
from cache_memoize import cache_memoize
68
from django.core.exceptions import ValidationError
79
from django.db import models
810
from django.utils.functional import cached_property
@@ -15,6 +17,53 @@
1517

1618
from .. import settings as app_settings
1719

20+
logger = logging.getLogger(__name__)
21+
22+
23+
def get_cached_checksum_args_rewrite(instance):
24+
"""
25+
Use only the PK parameter for calculating the cache key
26+
"""
27+
return instance.pk.hex
28+
29+
30+
class ChecksumCacheMixin:
31+
"""
32+
Mixin that provides checksum caching functionality
33+
"""
34+
35+
_CHECKSUM_CACHE_TIMEOUT = 60 * 60 * 24 * 30 # 30 days
36+
37+
@cache_memoize(
38+
timeout=_CHECKSUM_CACHE_TIMEOUT,
39+
args_rewrite=get_cached_checksum_args_rewrite
40+
)
41+
def get_cached_checksum(self):
42+
"""
43+
Handles caching,
44+
timeout=None means value is cached indefinitely
45+
(invalidation handled on post_save/post_delete signal)
46+
"""
47+
logger.debug(f"calculating checksum for {self.__class__.__name__} ID {self.pk}")
48+
return self.checksum
49+
50+
@classmethod
51+
def bulk_invalidate_get_cached_checksum(cls, query_params):
52+
"""
53+
Bulk invalidate checksum cache for multiple instances
54+
"""
55+
for instance in cls.objects.only("id").filter(**query_params).iterator():
56+
instance.get_cached_checksum.invalidate(instance)
57+
58+
def invalidate_checksum_cache(self):
59+
"""
60+
Invalidate the checksum cache for this instance
61+
"""
62+
self.get_cached_checksum.invalidate(self)
63+
logger.debug(
64+
f"invalidated checksum cache for {self.__class__.__name__} ID {self.pk}"
65+
)
66+
1867

1968
class BaseModel(TimeStampedEditableModel):
2069
"""

openwisp_controller/config/base/config.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import re
44
from collections import defaultdict
55

6-
from cache_memoize import cache_memoize
76
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
87
from django.db import models, transaction
98
from django.utils.translation import gettext_lazy as _
@@ -24,7 +23,7 @@
2423
)
2524
from ..sortedm2m.fields import SortedManyToManyField
2625
from ..utils import get_default_templates_queryset
27-
from .base import BaseConfig
26+
from .base import BaseConfig, ChecksumCacheMixin
2827

2928
logger = logging.getLogger(__name__)
3029

@@ -38,14 +37,7 @@ def __str__(self):
3837
return _("Relationship with {0}").format(self.template.name)
3938

4039

41-
def get_cached_checksum_args_rewrite(config):
42-
"""
43-
Use only the PK parameter for calculating the cache key
44-
"""
45-
return config.pk.hex
46-
47-
48-
class AbstractConfig(BaseConfig):
40+
class AbstractConfig(ChecksumCacheMixin, BaseConfig):
4941
"""
5042
Abstract model implementing the
5143
NetJSON DeviceConfiguration object
@@ -150,23 +142,6 @@ def key(self):
150142
"""
151143
return self.device.key
152144

153-
@cache_memoize(
154-
timeout=_CHECKSUM_CACHE_TIMEOUT, args_rewrite=get_cached_checksum_args_rewrite
155-
)
156-
def get_cached_checksum(self):
157-
"""
158-
Handles caching,
159-
timeout=None means value is cached indefinitely
160-
(invalidation handled on post_save/post_delete signal)
161-
"""
162-
logger.debug(f"calculating checksum for config ID {self.pk}")
163-
return self.checksum
164-
165-
@classmethod
166-
def bulk_invalidate_get_cached_checksum(cls, query_params):
167-
for config in cls.objects.only("id").filter(**query_params).iterator():
168-
config.get_cached_checksum.invalidate(config)
169-
170145
@classmethod
171146
def get_template_model(cls):
172147
return cls.templates.rel.model

openwisp_controller/config/base/vpn.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
trigger_zerotier_server_update,
3434
trigger_zerotier_server_update_member,
3535
)
36-
from .base import BaseConfig
36+
from .base import BaseConfig, ChecksumCacheMixin
3737

3838
logger = logging.getLogger(__name__)
3939

@@ -43,7 +43,7 @@ def _peer_cache_key(vpn):
4343
return str(vpn.pk)
4444

4545

46-
class AbstractVpn(ShareableOrgMixinUniqueName, BaseConfig):
46+
class AbstractVpn(ChecksumCacheMixin, ShareableOrgMixinUniqueName, BaseConfig):
4747
"""
4848
Abstract VPN model
4949
"""
@@ -281,6 +281,7 @@ def save(self, *args, **kwargs):
281281
if create_dh:
282282
transaction.on_commit(lambda: create_vpn_dh.delay(self.id))
283283
if not created and self._send_vpn_modified_after_save:
284+
self.invalidate_checksum_cache()
284285
self._send_vpn_modified_signal()
285286
self._send_vpn_modified_after_save = False
286287
# For ZeroTier VPN server, if the
@@ -308,10 +309,9 @@ def _check_changes(self):
308309
]
309310
current = self._meta.model.objects.only(*attrs).get(pk=self.pk)
310311
for attr in attrs:
311-
if getattr(self, attr) == getattr(current, attr):
312-
continue
313-
self._send_vpn_modified_after_save = True
314-
break
312+
if getattr(self, attr) != getattr(current, attr):
313+
self._send_vpn_modified_after_save = True
314+
break
315315

316316
def _send_vpn_modified_signal(self):
317317
vpn_server_modified.send(sender=self.__class__, instance=self)

openwisp_controller/config/controller/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ def get(self, request, *args, **kwargs):
480480
if bad_request:
481481
return bad_request
482482
checksum_requested.send(sender=vpn.__class__, instance=vpn, request=request)
483-
return ControllerResponse(vpn.checksum, content_type="text/plain")
483+
return ControllerResponse(vpn.get_cached_checksum(), content_type="text/plain")
484484

485485

486486
class VpnDownloadConfigView(GetVpnView):

openwisp_controller/config/tests/test_config.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from openwisp_utils.tests import catch_signal
1212

1313
from .. import settings as app_settings
14-
from ..base.config import logger as config_model_logger
14+
from ..base.base import logger as base_config_logger
1515
from ..signals import config_backend_changed, config_modified, config_status_changed
1616
from .utils import CreateConfigTemplateMixin, CreateDeviceGroupMixin, TestVpnX509Mixin
1717

@@ -229,20 +229,20 @@ def test_get_cached_checksum(self):
229229
mocked_get.assert_called_once()
230230

231231
with self.subTest("ensure fresh checksum is calculated when cache is clear"):
232-
with patch.object(config_model_logger, "debug") as mocked_debug:
232+
with patch.object(base_config_logger, "debug") as mocked_debug:
233233
c.get_cached_checksum.invalidate(c)
234234
self.assertEqual(len(c.get_cached_checksum()), 32)
235235
mocked_debug.assert_called_once()
236236

237237
with self.subTest(
238238
"ensure fresh checksum is NOT calculated when cache is present"
239239
):
240-
with patch.object(config_model_logger, "debug") as mocked_debug:
240+
with patch.object(base_config_logger, "debug") as mocked_debug:
241241
self.assertEqual(len(c.get_cached_checksum()), 32)
242242
mocked_debug.assert_not_called()
243243

244244
with self.subTest("ensure cache invalidation works"):
245-
with patch.object(config_model_logger, "debug") as mocked_debug:
245+
with patch.object(base_config_logger, "debug") as mocked_debug:
246246
old_checksum = c.checksum
247247
c.config["general"]["timezone"] = "Europe/Rome"
248248
c.full_clean()
@@ -253,7 +253,7 @@ def test_get_cached_checksum(self):
253253
mocked_debug.assert_called_once()
254254

255255
with self.subTest("test cache invalidation when config templates are changed"):
256-
with patch.object(config_model_logger, "debug") as mocked_debug:
256+
with patch.object(base_config_logger, "debug") as mocked_debug:
257257
old_checksum = c.checksum
258258
template = self._create_template()
259259
c.templates.add(template)

openwisp_controller/config/tests/test_controller.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from openwisp_utils.tests import capture_any_output, catch_signal
1212

1313
from .. import settings as app_settings
14-
from ..base.config import logger as config_model_logger
14+
from ..base.base import logger as base_config_logger
1515
from ..controller.views import DeviceChecksumView
1616
from ..controller.views import logger as controller_views_logger
1717
from ..signals import (
@@ -186,7 +186,7 @@ def test_get_cached_checksum(self):
186186
controller_views_logger, "debug"
187187
) as mocked_view_debug:
188188
with patch.object(
189-
config_model_logger, "debug"
189+
base_config_logger, "debug"
190190
) as mocked_config_debug:
191191
self.client.get(
192192
url, {"key": d.key, "management_ip": "10.0.0.2"}
@@ -200,7 +200,7 @@ def test_get_cached_checksum(self):
200200
controller_views_logger, "debug"
201201
) as mocked_view_debug:
202202
with patch.object(
203-
config_model_logger, "debug"
203+
base_config_logger, "debug"
204204
) as mocked_config_debug:
205205
self.client.get(
206206
url, {"key": d.key, "management_ip": "10.0.0.3"}
@@ -223,7 +223,7 @@ def test_get_cached_checksum(self):
223223
controller_views_logger, "debug"
224224
) as mocked_view_debug:
225225
with patch.object(
226-
config_model_logger, "debug"
226+
base_config_logger, "debug"
227227
) as mocked_config_debug:
228228
self.client.get(
229229
url, {"key": d.key, "management_ip": "10.0.0.3"}
@@ -233,7 +233,7 @@ def test_get_cached_checksum(self):
233233

234234
with self.subTest("ensure cache invalidation works"):
235235
old_checksum = d.config.checksum
236-
with patch.object(config_model_logger, "debug") as mocked_debug:
236+
with patch.object(base_config_logger, "debug") as mocked_debug:
237237
d.config.config["general"]["timezone"] = "Europe/Rome"
238238
d.config.full_clean()
239239
d.config.save()

0 commit comments

Comments
 (0)