Merge ~cgrabowski/maas:reverse_unsubscribe_logic into maas:master

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: 7cd89f4e08129cb5519836200e7d325a6f6cc7cf
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:reverse_unsubscribe_logic
Merge into: maas:master
Diff against target: 274 lines (+95/-92)
5 files modified
src/maasserver/websockets/base.py (+11/-21)
src/maasserver/websockets/handlers/machine.py (+12/-0)
src/maasserver/websockets/handlers/tests/test_machine.py (+71/-0)
src/maasserver/websockets/tests/test_base.py (+0/-50)
src/maasserver/websockets/tests/test_protocol.py (+1/-21)
Reviewer Review Type Date Requested Status
Alexsander de Souza Approve
MAAS Lander Approve
Review via email: mp+426761@code.launchpad.net

Commit message

add back a set of unsubscribed pks to know when to ignore an update

update units to reflect allow-list behavior

reverse unsubscribe endpoint logic to receive an allow-list

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b reverse_unsubscribe_logic lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas-tester/34/consoleText
COMMIT: 93893ed47c2552b86d8032e92909657784f22bd4

review: Needs Fixing
c36b1b1... by Christian Grabowski

remove no longer relevant test

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b reverse_unsubscribe_logic lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas-tester/36/consoleText
COMMIT: 27d6b8cf2b0a3787969123b5972af1019883986c

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b reverse_unsubscribe_logic lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: c36b1b185951f71429cbc34ccc1be904f99f6d2c

review: Approve
02a30d6... by Christian Grabowski

override machine's listen function to only listen for machines in loaded_pks

7cd89f4... by Christian Grabowski

safely access active_pk

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b reverse_unsubscribe_logic lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: a704ef6445d66fdf0cd6a7511a637a7cd0c17f8c

review: Approve
Revision history for this message
Alexsander de Souza (alexsander-souza) :
Revision history for this message
Christian Grabowski (cgrabowski) :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b reverse_unsubscribe_logic lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 7cd89f4e08129cb5519836200e7d325a6f6cc7cf

review: Approve
Revision history for this message
Alexsander de Souza (alexsander-souza) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py
index 3063777..8787174 100644
--- a/src/maasserver/websockets/base.py
+++ b/src/maasserver/websockets/base.py
@@ -103,7 +103,6 @@ class HandlerOptions:
103 pk = "id"103 pk = "id"
104 bulk_pk = "ids"104 bulk_pk = "ids"
105 pk_type = int105 pk_type = int
106 unsubscribed_pks = set()
107 fields = None106 fields = None
108 exclude = None107 exclude = None
109 list_fields = None108 list_fields = None
@@ -340,7 +339,6 @@ class Handler(metaclass=HandlerMetaclass):
340 permission = self._meta.view_permission339 permission = self._meta.view_permission
341 if not self.user.has_perm(permission, obj):340 if not self.user.has_perm(permission, obj):
342 raise HandlerPermissionError()341 raise HandlerPermissionError()
343 self._meta.unsubscribed_pks.discard(pk)
344 return obj342 return obj
345343
346 def get_queryset(self, for_list=False):344 def get_queryset(self, for_list=False):
@@ -465,7 +463,6 @@ class Handler(metaclass=HandlerMetaclass):
465 """Cache all loaded object pks."""463 """Cache all loaded object pks."""
466 getpk = attrgetter(self._meta.pk)464 getpk = attrgetter(self._meta.pk)
467 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)465 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)
468 [self._meta.unsubscribed_pks.discard(getpk(obj)) for obj in objs]
469466
470 def _filter(self, qs, action, params):467 def _filter(self, qs, action, params):
471 """Return a filtered queryset468 """Return a filtered queryset
@@ -812,11 +809,7 @@ class Handler(metaclass=HandlerMetaclass):
812 :param action: Action that caused this event.809 :param action: Action that caused this event.
813 :param pk: Id of the object.810 :param pk: Id of the object.
814 """811 """
815 return (812 return self.get_object({self._meta.pk: pk})
816 self.get_object({self._meta.pk: pk})
817 if pk not in self._meta.unsubscribed_pks
818 else None
819 )
820813
821 def refetch(self, obj):814 def refetch(self, obj):
822 """Refetch an object using the handler queryset.815 """Refetch an object using the handler queryset.
@@ -826,22 +819,19 @@ class Handler(metaclass=HandlerMetaclass):
826 """819 """
827 return self.get_object({self._meta.pk: getattr(obj, self._meta.pk)})820 return self.get_object({self._meta.pk: getattr(obj, self._meta.pk)})
828821
829 def _unsubscribe(self, pk):
830 if pk == self.cache.get("active_pk"):
831 del self.cache["active_pk"]
832
833 if pk in self.cache.get("loaded_pks", []):
834 self.cache["loaded_pks"].remove(pk)
835
836 self._meta.unsubscribed_pks.add(pk)
837
838 return pk
839
840 def unsubscribe(self, params):822 def unsubscribe(self, params):
841 if self._meta.pk in params:823 if self._meta.pk in params:
842 return self._unsubscribe(params[self._meta.pk])824 pk = params[self._meta.pk]
825 if pk == self.cache.get("active_pk"):
826 del self.cache["active_pk"]
827 self.cache["loaded_pks"] = self.cache["loaded_pks"] - set(pk)
828 return [pk]
843 elif self._meta.bulk_pk in params:829 elif self._meta.bulk_pk in params:
844 return [self._unsubscribe(pk) for pk in params[self._meta.bulk_pk]]830 pks = set(params[self._meta.bulk_pk])
831 if self.cache.get("active_pk") in pks:
832 del self.cache["active_pk"]
833 self.cache["loaded_pks"] = self.cache["loaded_pks"] - pks
834 return pks
845 else:835 else:
846 raise HandlerValidationError(836 raise HandlerValidationError(
847 f"'{self._meta.pk}' or '{self._meta.bulk_pk}' must be provided in params for unsubscribe"837 f"'{self._meta.pk}' or '{self._meta.bulk_pk}' must be provided in params for unsubscribe"
diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
index 93cd2eb..955c350 100644
--- a/src/maasserver/websockets/handlers/machine.py
+++ b/src/maasserver/websockets/handlers/machine.py
@@ -521,6 +521,18 @@ class MachineHandler(NodeHandler):
521 % (op, " or ".join(status_names))521 % (op, " or ".join(status_names))
522 )522 )
523523
524 def listen(self, channel, action, pk):
525 """Called when the handler listens for events on channels with
526 `Meta.listen_channels`.
527
528 :param channel: Channel event occured on.
529 :param action: Action that caused this event.
530 :param pk: Id of the object.
531 """
532 # if loaded / not unsubscrived, allow listen events
533 if pk in self.cache["loaded_pks"] or pk == self.cache.get("active_pk"):
534 return self.get_object({self._meta.pk: pk})
535
524 def update_filesystem(self, params):536 def update_filesystem(self, params):
525 node = self._get_node_or_permission_error(537 node = self._get_node_or_permission_error(
526 params, permission=NodePermission.edit538 params, permission=NodePermission.edit
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index 352d4ea..c4d3c6f 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -6271,3 +6271,74 @@ class TestMachineHandlerFilter(MAASServerTestCase):
6271 _assert_value_in(machine.bmc.power_type, "not_pod_type")6271 _assert_value_in(machine.bmc.power_type, "not_pod_type")
6272 _assert_value_in(machine.bmc.id, "pod")6272 _assert_value_in(machine.bmc.id, "pod")
6273 _assert_value_in(machine.bmc.id, "not_pod")6273 _assert_value_in(machine.bmc.id, "not_pod")
6274
6275 def test_unsubscribe_prevents_further_updates_for_pk(self):
6276 admin = factory.make_admin()
6277 handler = MachineHandler(admin, {}, None)
6278 node = factory.make_Node()
6279 handler.list({})
6280 listen_result = handler.on_listen("machine", "update", node.system_id)
6281 self.assertIsNotNone(listen_result)
6282 handler.unsubscribe({"system_ids": [node.system_id]})
6283 self.assertIsNone(
6284 handler.on_listen("machine", "update", node.system_id)
6285 )
6286 list_result = handler.list({})
6287 self.assertEqual(len(list_result), 1)
6288
6289 def test_unsubscribe_raises_validation_error_with_no_pk(self):
6290 admin = factory.make_admin()
6291 handler = MachineHandler(admin, {}, None)
6292 self.assertRaises(HandlerValidationError, handler.unsubscribe, {})
6293
6294 def test_read_an_unsubscribed_object_subscribes(self):
6295 admin = factory.make_admin()
6296 handler = MachineHandler(admin, {}, None)
6297 node1 = factory.make_Node()
6298 node2 = factory.make_Node()
6299 handler.list({})
6300 self.assertIsNotNone(
6301 handler.on_listen("machine", "update", node1.system_id)
6302 )
6303 self.assertIsNotNone(
6304 handler.on_listen("machine", "update", node2.system_id)
6305 )
6306 handler.unsubscribe({"system_ids": [node2.system_id]})
6307 self.assertIsNotNone(
6308 handler.on_listen("machine", "update", node1.system_id)
6309 )
6310 self.assertIsNone(
6311 handler.on_listen("machine", "update", node2.system_id)
6312 )
6313 self.assertIsNotNone(handler.get({"system_id": node2.system_id}))
6314 self.assertIsNotNone(
6315 handler.on_listen("machine", "update", node2.system_id)
6316 )
6317
6318 def test_list_an_unsubscribed_object_subscribes(self):
6319 admin = factory.make_admin()
6320 handler = MachineHandler(admin, {}, None)
6321 node1 = factory.make_Node()
6322 node2 = factory.make_Node()
6323 handler.list({})
6324 self.assertIsNotNone(
6325 handler.on_listen("machine", "update", node1.system_id)
6326 )
6327 self.assertIsNotNone(
6328 handler.on_listen("machine", "update", node2.system_id)
6329 )
6330 handler.unsubscribe({"system_ids": [node1.system_id]})
6331 self.assertIsNotNone(
6332 handler.on_listen("machine", "update", node2.system_id)
6333 )
6334 self.assertIsNone(
6335 handler.on_listen("machine", "update", node1.system_id)
6336 )
6337 list_result = handler.list({})
6338 self.assertEqual(len(list_result), 2)
6339 self.assertIsNotNone(
6340 handler.on_listen("machine", "update", node1.system_id)
6341 )
6342 self.assertIsNotNone(
6343 handler.on_listen("machine", "update", node2.system_id)
6344 )
diff --git a/src/maasserver/websockets/tests/test_base.py b/src/maasserver/websockets/tests/test_base.py
index 77f3fd3..e7632b1 100644
--- a/src/maasserver/websockets/tests/test_base.py
+++ b/src/maasserver/websockets/tests/test_base.py
@@ -986,56 +986,6 @@ class TestHandler(MAASServerTestCase, FakeNodesHandlerMixin):
986 for idx in range(3):986 for idx in range(3):
987 self.assertEqual(f"host-{2-idx}", result[idx]["hostname"])987 self.assertEqual(f"host-{2-idx}", result[idx]["hostname"])
988988
989 def test_unsubscribe_prevents_further_updates_for_pk(self):
990 handler = self.make_nodes_handler()
991 node = factory.make_Node()
992 handler._meta.queryset = Node.objects.all()
993 handler._meta.listen_channels = ["node"]
994 handler._meta.bulk_pk = "system_ids"
995 handler._meta.pk = "system_id"
996 listen_result = handler.listen("node", "update", node.system_id)
997 self.assertIsNotNone(listen_result)
998 handler.unsubscribe({"system_ids": [node.system_id]})
999 self.assertIsNone(handler.listen("node", "update", node.system_id))
1000 list_result = handler.list({})
1001 self.assertEqual(len(list_result), 1)
1002
1003 def test_unsubscribe_raises_validation_error_with_no_pk(self):
1004 handler = self.make_nodes_handler()
1005 handler._meta.queryset = Node.objects.all()
1006 handler._meta.listen_channels = ["node"]
1007 handler._meta.bulk_pk = "system_ids"
1008 handler._meta.pk = "system_id"
1009 self.assertRaises(HandlerValidationError, handler.unsubscribe, {})
1010
1011 def test_read_an_unsubscribed_object_subscribes(self):
1012 handler = self.make_nodes_handler()
1013 node = factory.make_Node()
1014 handler._meta.queryset = Node.objects.all()
1015 handler._meta.listen_channels = ["node"]
1016 handler._meta.bulk_pk = "system_ids"
1017 handler._meta.pk = "system_id"
1018 self.assertIsNotNone(handler.listen("node", "update", node.system_id))
1019 handler.unsubscribe({"system_ids": [node.system_id]})
1020 self.assertIsNone(handler.listen("node", "update", node.system_id))
1021 self.assertIsNotNone(handler.get({"system_id": node.system_id}))
1022 self.assertIsNotNone(handler.listen("node", "update", node.system_id))
1023
1024 def test_list_an_unsubscribed_object_subscribes(self):
1025 handler = self.make_nodes_handler()
1026 node = factory.make_Node()
1027 handler._meta.queryset = Node.objects.all()
1028 handler._meta.listen_channels = ["node"]
1029 handler._meta.bulk_pk = "system_ids"
1030 handler._meta.pk = "system_id"
1031 listen_result = handler.listen("node", "update", node.system_id)
1032 self.assertIsNotNone(listen_result)
1033 handler.unsubscribe({"system_ids": [node.system_id]})
1034 self.assertIsNone(handler.listen("node", "update", node.system_id))
1035 list_result = handler.list({})
1036 self.assertEqual(len(list_result), 1)
1037 self.assertIsNotNone(handler.listen("node", "update", node.system_id))
1038
1039989
1040class TestHandlerGrouping(MAASServerTestCase, FakeNodesHandlerMixin):990class TestHandlerGrouping(MAASServerTestCase, FakeNodesHandlerMixin):
1041 def test_group_simple(self):991 def test_group_simple(self):
diff --git a/src/maasserver/websockets/tests/test_protocol.py b/src/maasserver/websockets/tests/test_protocol.py
index b5cbdc7..5181adb 100644
--- a/src/maasserver/websockets/tests/test_protocol.py
+++ b/src/maasserver/websockets/tests/test_protocol.py
@@ -20,10 +20,7 @@ from apiclient.utils import ascii_url
20from maasserver.eventloop import services20from maasserver.eventloop import services
21from maasserver.testing.factory import factory as maas_factory21from maasserver.testing.factory import factory as maas_factory
22from maasserver.testing.listener import FakePostgresListenerService22from maasserver.testing.listener import FakePostgresListenerService
23from maasserver.testing.testcase import (23from maasserver.testing.testcase import MAASTransactionServerTestCase
24 MAASServerTestCase,
25 MAASTransactionServerTestCase,
26)
27from maasserver.utils.orm import transactional24from maasserver.utils.orm import transactional
28from maasserver.utils.threads import deferToDatabase25from maasserver.utils.threads import deferToDatabase
29from maasserver.websockets import protocol as protocol_module26from maasserver.websockets import protocol as protocol_module
@@ -1003,20 +1000,3 @@ class TestWebSocketFactoryTransactional(
1003 controller.system_id,1000 controller.system_id,
1004 ),1001 ),
1005 )1002 )
1006
1007
1008class TestWebSocketFactoryServer(MAASServerTestCase, MakeProtocolFactoryMixin):
1009 def test_processNotify_unsubscribed_object(self):
1010 factory = self.make_factory()
1011 machine = maas_factory.make_Machine()
1012 handler = MachineHandler(maas_factory.make_User(), {}, None)
1013 factory.handlers["machine"] = handler
1014 result = factory.processNotify(
1015 handler, "machine", "update", machine.system_id
1016 )
1017 self.assertIsNotNone(result)
1018 handler.unsubscribe({"system_ids": [machine.system_id]})
1019 result = factory.processNotify(
1020 handler, "machine", "update", machine.system_id
1021 )
1022 self.assertIsNone(result)

Subscribers

People subscribed via source and target branches