Skip to content

Commit 71e2e37

Browse files
authored
Merge branch 'master' into issues/1049-send-generic-message
2 parents 1682a67 + fa94f90 commit 71e2e37

File tree

8 files changed

+396
-21
lines changed

8 files changed

+396
-21
lines changed

docs/user/settings.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,23 @@ object.
273273
``OPENWISP_CONTROLLER_CONTEXT`` can be used to define system-wide
274274
configuration variables.
275275

276+
.. important::
277+
278+
After modifying the system-defined variables in
279+
``OPENWISP_CONTROLLER_CONTEXT``, clear the cache to ensure that
280+
devices, templates, and VPN receive the updated configuration values:
281+
282+
.. code-block:: bash
283+
284+
cd /opt/openwisp2
285+
python manage.py clear_cache
286+
287+
System-defined variables can be referenced in VPN, Template, and
288+
Config objects. When these variables are updated, existing cached
289+
configurations that depend on them do not automatically reflect the
290+
new values. Consequently, devices may continue using outdated
291+
configurations unless the cache is cleared.
292+
276293
For more information regarding how to use configuration variables in
277294
OpenWISP, refer to :doc:`variables`.
278295

openwisp_controller/config/apps.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,18 @@ def connect_signals(self):
7878
sender=self.config_model.templates.through,
7979
dispatch_uid="config.clean_templates",
8080
)
81+
# VPN clients must be created or removed **before**
82+
# self.config_model.templates_changed is evaluated, because
83+
# the VpnClient context can influence the configuration checksum.
8184
m2m_changed.connect(
82-
self.config_model.templates_changed,
85+
self.config_model.manage_vpn_clients,
8386
sender=self.config_model.templates.through,
84-
dispatch_uid="config.templates_changed",
87+
dispatch_uid="config.manage_vpn_clients",
8588
)
8689
m2m_changed.connect(
87-
self.config_model.manage_vpn_clients,
90+
self.config_model.templates_changed,
8891
sender=self.config_model.templates.through,
89-
dispatch_uid="config.manage_vpn_clients",
92+
dispatch_uid="config.templates_changed",
9093
)
9194
# the order of the following connect() call must be maintained
9295
m2m_changed.connect(

openwisp_controller/config/base/config.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from collections import defaultdict
55

66
from cache_memoize import cache_memoize
7+
from django.core.cache import cache
78
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
89
from django.db import models, transaction
910
from django.utils.translation import gettext_lazy as _
@@ -110,6 +111,7 @@ class AbstractConfig(ChecksumCacheMixin, BaseConfig):
110111
)
111112

112113
_CHECKSUM_CACHE_TIMEOUT = ChecksumCacheMixin._CHECKSUM_CACHE_TIMEOUT
114+
_CONFIG_MODIFIED_TIMEOUT = 3
113115
_config_context_functions = list()
114116
_old_backend = None
115117

@@ -124,9 +126,11 @@ def __init__(self, *args, **kwargs):
124126
self._just_created = False
125127
self._initial_status = self.status
126128
self._send_config_modified_after_save = False
129+
self._config_modified_action = "config_changed"
127130
self._send_config_deactivated = False
128131
self._send_config_deactivating = False
129132
self._send_config_status_changed = False
133+
self._is_enforcing_required_templates = False
130134

131135
def __str__(self):
132136
if self._has_device():
@@ -301,17 +305,26 @@ def templates_changed(cls, action, instance, **kwargs):
301305
# execute only after a config has been saved or deleted
302306
if action not in ["post_add", "post_remove"] or instance._state.adding:
303307
return
308+
if instance._is_enforcing_required_templates:
309+
# The required templates are enforced on "post_clear" action and
310+
# they are added back using Config.templates.add(). This sends a
311+
# m2m_changed signal with the "post_add" action.
312+
# At this stage, all templates have not yet been re-added,
313+
# so the checksum cannot be accurately evaluated.
314+
# Defer checksum validation until a subsequent post_add or
315+
# post_remove signal is received.
316+
instance._is_enforcing_required_templates = False
317+
return
304318
# use atomic to ensure any code bound to
305319
# be executed via transaction.on_commit
306320
# is executed after the whole block
307321
with transaction.atomic():
308322
# do not send config modified signal if
309323
# config instance has just been created
310324
if not instance._just_created:
311-
# sends only config modified signal
312-
instance._send_config_modified_signal(action="m2m_templates_changed")
325+
instance._config_modified_action = "m2m_templates_changed"
313326
instance.update_status_if_checksum_changed(
314-
send_config_modified_signal=False
327+
send_config_modified_signal=not instance._just_created
315328
)
316329

317330
@classmethod
@@ -464,6 +477,7 @@ def enforce_required_templates(
464477
)
465478
)
466479
if required_templates.exists():
480+
instance._is_enforcing_required_templates = True
467481
instance.templates.add(
468482
*required_templates.order_by("name").values_list("pk", flat=True)
469483
)
@@ -587,7 +601,7 @@ def save(self, *args, **kwargs):
587601
self._old_backend = None
588602
# emit signals if config is modified and/or if status is changing
589603
if not created and self._send_config_modified_after_save:
590-
self._send_config_modified_signal(action="config_changed")
604+
self._send_config_modified_signal()
591605
self._send_config_modified_after_save = False
592606
if self._send_config_status_changed:
593607
self._send_config_status_changed_signal()
@@ -648,6 +662,14 @@ def update_status_if_checksum_changed(
648662
self._update_checksum_db(new_checksum=self.checksum_db)
649663
if send_config_modified_signal:
650664
self._send_config_modified_after_save = True
665+
if save:
666+
# When this method is triggered by changes to Config.templates,
667+
# those changes are applied through the related manager rather
668+
# than via Config.save(). As a result, the model's save()
669+
# method (and thus the automatic "config modified" signal)
670+
# is never invoked. To ensure the signal is still emitted,
671+
# we send it explicitly here.
672+
self._send_config_modified_signal()
651673
self.invalidate_checksum_cache()
652674
return checksum_changed
653675

@@ -678,11 +700,38 @@ def _update_checksum_db(self, new_checksum=None):
678700
new_checksum = new_checksum or self.checksum
679701
self._meta.model.objects.filter(pk=self.pk).update(checksum_db=new_checksum)
680702

681-
def _send_config_modified_signal(self, action):
703+
@property
704+
def _config_modified_timeout_cache_key(self):
705+
return f"config_modified_timeout_{self.pk}"
706+
707+
def _set_config_modified_timeout_cache(self):
708+
cache.set(
709+
self._config_modified_timeout_cache_key,
710+
True,
711+
timeout=self._CONFIG_MODIFIED_TIMEOUT,
712+
)
713+
714+
def _delete_config_modified_timeout_cache(self):
715+
cache.delete(self._config_modified_timeout_cache_key)
716+
717+
def _send_config_modified_signal(self, action=None):
682718
"""
683719
Emits ``config_modified`` signal.
684-
Called also by Template when templates of a device are modified
720+
721+
A short-lived cache key prevents emitting duplicate signals inside the
722+
same change window; if that key exists the method returns early without
723+
emitting the signal again.
724+
725+
Side effects
726+
------------
727+
- Emits the ``config_modified`` Django signal with contextual data.
728+
- Resets ``_config_modified_action`` back to ``"config_changed"`` so
729+
subsequent calls without an explicit action revert to the default.
730+
- Sets the debouncing cache key to avoid duplicate emissions.
685731
"""
732+
if cache.get(self._config_modified_timeout_cache_key):
733+
return
734+
action = action or self._config_modified_action
686735
assert action in [
687736
"config_changed",
688737
"related_template_changed",
@@ -697,6 +746,12 @@ def _send_config_modified_signal(self, action):
697746
config=self,
698747
device=self.device,
699748
)
749+
cache.set(
750+
self._config_modified_timeout_cache_key,
751+
True,
752+
timeout=self._CONFIG_MODIFIED_TIMEOUT,
753+
)
754+
self._config_modified_action = "config_changed"
700755

701756
def _send_config_deactivating_signal(self):
702757
"""

0 commit comments

Comments
 (0)