-
-
Notifications
You must be signed in to change notification settings - Fork 300
[feature] Added generic solution for cache invalidation in config models #1095 #1418
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
Changes from all commits
567626f
d7b0eb5
f5afdf1
a53d513
0d73d5d
c927e60
201237c
2550520
23bd1b5
eeaae35
768ae15
90bb8c2
2796657
2b7f337
8f0ed7a
64f454d
699c63d
8f9b87a
01326df
c5b6012
0fadcfb
aea229e
9c9a4a5
a503e01
75c59a7
d30946d
8e44395
f2a566f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,7 @@ | |
| from django.conf import settings | ||
| from django.core.exceptions import ImproperlyConfigured | ||
| from django.db.models import Case, Count, When | ||
| from django.db.models.signals import ( | ||
| m2m_changed, | ||
| post_delete, | ||
| post_save, | ||
| pre_delete, | ||
| pre_save, | ||
| ) | ||
| from django.db.models.signals import m2m_changed, post_delete, post_save, pre_save | ||
| from django.urls import register_converter | ||
| from django.utils.translation import gettext_lazy as _ | ||
| from openwisp_notifications.types import ( | ||
|
|
@@ -53,12 +47,114 @@ def ready(self, *args, **kwargs): | |
| self.connect_signals() | ||
| self.register_notification_types() | ||
| self.add_ignore_notification_widget() | ||
| self.enable_cache_invalidation() | ||
| self.connect_related_changes_handlers() | ||
| self.connect_cache_dependencies() | ||
| self.register_dashboard_charts() | ||
| self.register_menu_groups() | ||
| self.notification_cache_update() | ||
| connect_whois_handlers() | ||
|
|
||
| def connect_cache_dependencies(self): | ||
| """ | ||
| Wires the declarative cache-invalidation dependencies. | ||
|
|
||
| Models that own a cached value declare their related-change | ||
| dependencies in ``get_cache_dependencies`` (see | ||
| ``CacheInvalidationMixin``). | ||
| Caches that are not owned by a model (controller view caches and device | ||
| group caches) are declared here. Connecting all of them in one place | ||
| replaces the cache-invalidation ``signal.connect()`` calls that were | ||
| previously scattered across the codebase. | ||
| """ | ||
| from .base.cache import CacheDependency, _resolve_pk_snapshot | ||
| from .controller.views import DeviceChecksumView | ||
| from .handlers import ( | ||
| devicegroup_delete_handler, | ||
| invalidate_devicegroup_cache_change_handler, | ||
| organization_disabled_handler, | ||
| ) | ||
|
|
||
| # Model-owned checksum caches (declared on the models themselves). | ||
| self.config_model.register_cache_dependencies() | ||
| self.vpn_model.register_cache_dependencies() | ||
|
|
||
| dependencies = [ | ||
| # DeviceChecksumView caches are invalidated when a device is created, | ||
| # updated, deleted or when its config is deactivated. | ||
| CacheDependency( | ||
| source=self.device_model, | ||
| signal="post_save", | ||
| on_create=True, | ||
| target=DeviceChecksumView.invalidate_get_device_cache, | ||
| ), | ||
| # Deferred to commit so a concurrent request cannot repopulate the | ||
| # cache with a device that is about to be (or was just) deleted. | ||
| # ``post_delete`` + ``_resolve_pk_snapshot`` because Django clears | ||
| # ``instance.pk`` on deleted instances before the deferred | ||
| # on_commit callback runs (see ``_resolve_pk_snapshot``). | ||
| CacheDependency( | ||
| source=self.device_model, | ||
| signal="post_delete", | ||
| resolve=_resolve_pk_snapshot, | ||
| target=DeviceChecksumView.invalidate_get_device_cache, | ||
| ), | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| CacheDependency( | ||
| signal_obj=config_deactivated, | ||
| name="config_deactivated", | ||
| target=( | ||
| DeviceChecksumView.invalidate_get_device_cache_on_config_deactivated | ||
| ), | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ), | ||
| # When an organization is disabled, all its devices are deactivated, | ||
| # so we need to invalidate the controller view caches for all objects. | ||
| CacheDependency( | ||
| source=self.org_model, | ||
| signal="pre_save", | ||
| on_commit=False, | ||
| target=organization_disabled_handler, | ||
| ), | ||
| # Invalidate the DeviceGroupCommonName cache when a device's group, | ||
| # a device group, or a certificate changes. | ||
| CacheDependency( | ||
| signal_obj=device_group_changed, | ||
| name="device_group_changed", | ||
| source=self.device_model, | ||
| target=invalidate_devicegroup_cache_change_handler, | ||
| ), | ||
| CacheDependency( | ||
| source=self.devicegroup_model, | ||
| signal="post_save", | ||
| target=invalidate_devicegroup_cache_change_handler, | ||
| ), | ||
| CacheDependency( | ||
| source=self.cert_model, | ||
| signal="post_save", | ||
| target=invalidate_devicegroup_cache_change_handler, | ||
| ), | ||
| # Kept synchronous (on_commit=False) so devicegroup_delete_handler | ||
| # still receives the live instance and can read organization_id | ||
| # before Django clears instance.pk post-delete. The handler | ||
| # itself defers the actual task enqueue via transaction.on_commit(). | ||
| CacheDependency( | ||
| source=self.devicegroup_model, | ||
| signal="post_delete", | ||
| on_commit=False, | ||
| target=devicegroup_delete_handler, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not keep this delete invalidation immediate. The handler already has the data it needs on the deleted instance here, so we can snapshot the primitive values and enqueue the actual cache invalidation with |
||
| ), | ||
| # Same as above: kept synchronous so the handler can also read | ||
| # common_name before Django clears instance.pk post-delete. | ||
| CacheDependency( | ||
| source=self.cert_model, | ||
| signal="post_delete", | ||
| on_commit=False, | ||
| target=devicegroup_delete_handler, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ), | ||
| ] | ||
| for dependency in dependencies: | ||
| dependency.connect( | ||
| dispatch_uid=dependency.build_dispatch_uid("cache_invalidation.app") | ||
| ) | ||
|
|
||
| def __setmodels__(self): | ||
| self.device_model = load_model("config", "Device") | ||
| self.template_model = load_model("config", "Template") | ||
|
|
@@ -125,11 +221,6 @@ def connect_signals(self): | |
| sender=self.vpn_model, | ||
| dispatch_uid="vpn.post_delete", | ||
| ) | ||
| post_save.connect( | ||
| self.config_model.certificate_updated, | ||
| sender=self.cert_model, | ||
| dispatch_uid="cert_update_invalidate_checksum_cache", | ||
| ) | ||
| group_templates_changed.connect( | ||
| handlers.devicegroup_templates_change_handler, | ||
| sender=self.devicegroup_model, | ||
|
|
@@ -150,11 +241,6 @@ def connect_signals(self): | |
| sender=self.template_model, | ||
| dispatch_uid="template_pre_save_handler", | ||
| ) | ||
| pre_save.connect( | ||
| handlers.organization_disabled_handler, | ||
| sender=self.org_model, | ||
| dispatch_uid="organization_disabled_pre_save_clear_device_checksum_cache", | ||
| ) | ||
| post_save.connect( | ||
| self.template_model.post_save_handler, | ||
| sender=self.template_model, | ||
|
|
@@ -268,76 +354,32 @@ def add_ignore_notification_widget(self): | |
| obj_notification_widget, | ||
| ) | ||
|
|
||
| def enable_cache_invalidation(self): | ||
| def connect_related_changes_handlers(self): | ||
| """ | ||
| Triggers the cache invalidation for the | ||
| device config checksum (view and model method) | ||
| Connects signal handlers that react to a change in one object by | ||
| propagating side effects to related objects. These are intentionally | ||
| kept out of the declarative cache-invalidation engine (see | ||
| ``connect_cache_dependencies``) because they do more than invalidate a | ||
| cached value: | ||
|
|
||
| * clearing a device's management IP when its config is deactivated; | ||
| * re-applying group templates when a device's group changes | ||
| (``devicegroup_change_handler``); | ||
| * refreshing the configs of a VPN server's clients when the server | ||
| changes. ``vpn_server_change_handler`` recomputes each client's | ||
| checksum and emits ``config_modified`` for it, but only when that | ||
| checksum actually changed. | ||
| """ | ||
| from .controller.views import DeviceChecksumView, GetVpnView | ||
| from .handlers import ( | ||
| device_cache_invalidation_handler, | ||
| devicegroup_change_handler, | ||
| devicegroup_delete_handler, | ||
| vpn_server_change_handler, | ||
| ) | ||
| from .handlers import devicegroup_change_handler, vpn_server_change_handler | ||
|
|
||
| post_save.connect( | ||
| DeviceChecksumView.invalidate_get_device_cache, | ||
| sender=self.device_model, | ||
| dispatch_uid="invalidate_get_device_cache", | ||
| ) | ||
| config_deactivated.connect( | ||
| self.device_model.config_deactivated_clear_management_ip, | ||
| dispatch_uid="config_deactivated_clear_management_ip", | ||
| ) | ||
| config_deactivated.connect( | ||
| DeviceChecksumView.invalidate_get_device_cache_on_config_deactivated, | ||
| dispatch_uid="config_deactivated_invalidate_get_device_cache", | ||
| ) | ||
| # VPN cache invalidation | ||
| post_save.connect( | ||
| GetVpnView.invalidate_get_vpn_cache, | ||
| sender=self.vpn_model, | ||
| dispatch_uid="invalidate_get_vpn_cache", | ||
| ) | ||
| pre_delete.connect( | ||
| GetVpnView.invalidate_get_vpn_cache, | ||
| sender=self.vpn_model, | ||
| dispatch_uid="vpn_server_pre_delete_invalidate_get_vpn_cache", | ||
| ) | ||
| vpn_server_modified.connect( | ||
| GetVpnView.invalidate_get_vpn_cache, | ||
| dispatch_uid="vpn_server_modified_invalidate_get_vpn_cache", | ||
| ) | ||
| device_group_changed.connect( | ||
| devicegroup_change_handler, | ||
| sender=self.device_model, | ||
| dispatch_uid="invalidate_devicegroup_cache_on_device_change", | ||
| ) | ||
| post_save.connect( | ||
| devicegroup_change_handler, | ||
| sender=self.devicegroup_model, | ||
| dispatch_uid="invalidate_devicegroup_cache_on_devicegroup_change", | ||
| ) | ||
| post_save.connect( | ||
| devicegroup_change_handler, | ||
| sender=self.cert_model, | ||
| dispatch_uid="invalidate_devicegroup_cache_on_certificate_change", | ||
| ) | ||
| post_delete.connect( | ||
| devicegroup_delete_handler, | ||
| sender=self.devicegroup_model, | ||
| dispatch_uid="invalidate_devicegroup_cache_on_devicegroup_delete", | ||
| ) | ||
| post_delete.connect( | ||
| devicegroup_delete_handler, | ||
| sender=self.cert_model, | ||
| dispatch_uid="invalidate_devicegroup_cache_on_certificate_delete", | ||
| ) | ||
| pre_delete.connect( | ||
| device_cache_invalidation_handler, | ||
| sender=self.device_model, | ||
| dispatch_uid="device.invalidate_cache", | ||
| dispatch_uid="manage_devicegroup_templates_on_device_change", | ||
| ) | ||
| vpn_server_modified.connect( | ||
| vpn_server_change_handler, | ||
|
|
||
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.
WARNING: Broken import path in the documentation example.
CacheDependencyandCacheInvalidationMixinare defined inopenwisp_controller/config/base/base.py, and there is noopenwisp_controller/config/base/cache.pymodule in this branch. Copying this example would raiseModuleNotFoundError: No module named 'openwisp_controller.config.base.cache'. The import should keep referencing...config.base.base.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.