Merge ~cgrabowski/maas:add_machine.unsubscribe_websocket_endpoint into maas:master

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: c5c877056548bbf19c5f7396eeca5aba759afb3a
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:add_machine.unsubscribe_websocket_endpoint
Merge into: maas:master
Diff against target: 208 lines (+105/-2)
5 files modified
src/maasserver/websockets/base.py (+31/-1)
src/maasserver/websockets/handlers/machine.py (+1/-0)
src/maasserver/websockets/handlers/node.py (+1/-0)
src/maasserver/websockets/tests/test_base.py (+51/-0)
src/maasserver/websockets/tests/test_protocol.py (+21/-1)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Alexsander de Souza Approve
Adam Collard (community) Needs Fixing
Review via email: mp+424597@code.launchpad.net

Commit message

add 'unsubscribe' to base WebSocket Handler

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/12932/console
COMMIT: 54344b905972fd3db412178fdd80e8aa32c30248

review: Needs Fixing
Revision history for this message
Adam Collard (adam-collard) :
review: Needs Fixing
Revision history for this message
Christian Grabowski (cgrabowski) :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/12941/console
COMMIT: aaf7e5934669af2479123696954dde257cab7b72

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/12945/console
COMMIT: 3501c22ca277043ddd91d8df3ab81eeea26fe2c1

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 5f990f6d6bea87212185d405cbc44118970435ab

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: d35818db4fc8ee0c600b7e20904884a0f946ddf5

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: f2b9749326b1f54f3de2de4ac24b5ff8452afc8e

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

The object should continue to be visible after unsubscribe()

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/13024/console
COMMIT: 117c14626846b52d721c3902effa224051a28da1

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/13026/console
COMMIT: 44b0b138ceb23d65d37a75a68bc759b93d3f3ef6

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

+1

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

UNIT TESTS
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: c5c877056548bbf19c5f7396eeca5aba759afb3a

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

LANDING
-b add_machine.unsubscribe_websocket_endpoint lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED BUILD
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/13030/consoleText

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py
2index 9d4ed77..f93ae0b 100644
3--- a/src/maasserver/websockets/base.py
4+++ b/src/maasserver/websockets/base.py
5@@ -94,13 +94,16 @@ class HandlerOptions:
6 "update",
7 "delete",
8 "set_active",
9+ "unsubscribe",
10 ]
11 handler_name = None
12 object_class = None
13 queryset = None
14 list_queryset = None
15 pk = "id"
16+ bulk_pk = "ids"
17 pk_type = int
18+ unsubscribed_pks = set()
19 fields = None
20 exclude = None
21 list_fields = None
22@@ -337,6 +340,7 @@ class Handler(metaclass=HandlerMetaclass):
23 permission = self._meta.view_permission
24 if not self.user.has_perm(permission, obj):
25 raise HandlerPermissionError()
26+ self._meta.unsubscribed_pks.discard(pk)
27 return obj
28
29 def get_queryset(self, for_list=False):
30@@ -461,6 +465,7 @@ class Handler(metaclass=HandlerMetaclass):
31 """Cache all loaded object pks."""
32 getpk = attrgetter(self._meta.pk)
33 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)
34+ [self._meta.unsubscribed_pks.discard(getpk(obj)) for obj in objs]
35
36 def _filter(self, qs, action, params):
37 """Return a filtered queryset
38@@ -740,7 +745,11 @@ class Handler(metaclass=HandlerMetaclass):
39 :param action: Action that caused this event.
40 :param pk: Id of the object.
41 """
42- return self.get_object({self._meta.pk: pk})
43+ return (
44+ self.get_object({self._meta.pk: pk})
45+ if pk not in self._meta.unsubscribed_pks
46+ else None
47+ )
48
49 def refetch(self, obj):
50 """Refetch an object using the handler queryset.
51@@ -750,6 +759,27 @@ class Handler(metaclass=HandlerMetaclass):
52 """
53 return self.get_object({self._meta.pk: getattr(obj, self._meta.pk)})
54
55+ def _unsubscribe(self, pk):
56+ if pk == self.cache.get("active_pk"):
57+ del self.cache["active_pk"]
58+
59+ if pk in self.cache.get("loaded_pks", []):
60+ self.cache["loaded_pks"].remove(pk)
61+
62+ self._meta.unsubscribed_pks.add(pk)
63+
64+ return pk
65+
66+ def unsubscribe(self, params):
67+ if self._meta.pk in params:
68+ return self._unsubscribe(params[self._meta.pk])
69+ elif self._meta.bulk_pk in params:
70+ return [self._unsubscribe(pk) for pk in params[self._meta.bulk_pk]]
71+ else:
72+ raise HandlerValidationError(
73+ f"'{self._meta.pk}' or '{self._meta.bulk_pk}' must be provided in params for unsubscribe"
74+ )
75+
76
77 class AdminOnlyMixin(Handler):
78 class Meta:
79diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
80index 8c27240..78d9efd 100644
81--- a/src/maasserver/websockets/handlers/machine.py
82+++ b/src/maasserver/websockets/handlers/machine.py
83@@ -161,6 +161,7 @@ class MachineHandler(NodeHandler):
84 "update",
85 "action",
86 "set_active",
87+ "unsubscribe",
88 "check_power",
89 "create_physical",
90 "create_vlan",
91diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
92index 7fd590b..bc3ced5 100644
93--- a/src/maasserver/websockets/handlers/node.py
94+++ b/src/maasserver/websockets/handlers/node.py
95@@ -128,6 +128,7 @@ class NodeHandler(TimestampedModelHandler):
96 class Meta:
97 abstract = True
98 pk = "system_id"
99+ bulk_pk = "system_ids"
100 pk_type = str
101
102 def __init__(self, user, cache, request):
103diff --git a/src/maasserver/websockets/tests/test_base.py b/src/maasserver/websockets/tests/test_base.py
104index 9d5ea16..f13a6fc 100644
105--- a/src/maasserver/websockets/tests/test_base.py
106+++ b/src/maasserver/websockets/tests/test_base.py
107@@ -59,6 +59,7 @@ class TestHandlerMeta(MAASTestCase):
108 "update",
109 "delete",
110 "set_active",
111+ "unsubscribe",
112 ]
113 ),
114 handler_name=Equals(""),
115@@ -981,6 +982,56 @@ class TestHandler(MAASServerTestCase, FakeNodesHandlerMixin):
116 for idx in range(3):
117 self.assertEqual(f"host-{2-idx}", result[idx]["hostname"])
118
119+ def test_unsubscribe_prevents_further_updates_for_pk(self):
120+ handler = self.make_nodes_handler()
121+ node = factory.make_Node()
122+ handler._meta.queryset = Node.objects.all()
123+ handler._meta.listen_channels = ["node"]
124+ handler._meta.bulk_pk = "system_ids"
125+ handler._meta.pk = "system_id"
126+ listen_result = handler.listen("node", "update", node.system_id)
127+ self.assertIsNotNone(listen_result)
128+ handler.unsubscribe({"system_ids": [node.system_id]})
129+ self.assertIsNone(handler.listen("node", "update", node.system_id))
130+ list_result = handler.list({})
131+ self.assertEqual(len(list_result), 1)
132+
133+ def test_unsubscribe_raises_validation_error_with_no_pk(self):
134+ handler = self.make_nodes_handler()
135+ handler._meta.queryset = Node.objects.all()
136+ handler._meta.listen_channels = ["node"]
137+ handler._meta.bulk_pk = "system_ids"
138+ handler._meta.pk = "system_id"
139+ self.assertRaises(HandlerValidationError, handler.unsubscribe, {})
140+
141+ def test_read_an_unsubscribed_object_subscribes(self):
142+ handler = self.make_nodes_handler()
143+ node = factory.make_Node()
144+ handler._meta.queryset = Node.objects.all()
145+ handler._meta.listen_channels = ["node"]
146+ handler._meta.bulk_pk = "system_ids"
147+ handler._meta.pk = "system_id"
148+ self.assertIsNotNone(handler.listen("node", "update", node.system_id))
149+ handler.unsubscribe({"system_ids": [node.system_id]})
150+ self.assertIsNone(handler.listen("node", "update", node.system_id))
151+ self.assertIsNotNone(handler.get({"system_id": node.system_id}))
152+ self.assertIsNotNone(handler.listen("node", "update", node.system_id))
153+
154+ def test_list_an_unsubscribed_object_subscribes(self):
155+ handler = self.make_nodes_handler()
156+ node = factory.make_Node()
157+ handler._meta.queryset = Node.objects.all()
158+ handler._meta.listen_channels = ["node"]
159+ handler._meta.bulk_pk = "system_ids"
160+ handler._meta.pk = "system_id"
161+ listen_result = handler.listen("node", "update", node.system_id)
162+ self.assertIsNotNone(listen_result)
163+ handler.unsubscribe({"system_ids": [node.system_id]})
164+ self.assertIsNone(handler.listen("node", "update", node.system_id))
165+ list_result = handler.list({})
166+ self.assertEqual(len(list_result), 1)
167+ self.assertIsNotNone(handler.listen("node", "update", node.system_id))
168+
169
170 class TestHandlerTransaction(
171 MAASTransactionServerTestCase, FakeNodesHandlerMixin
172diff --git a/src/maasserver/websockets/tests/test_protocol.py b/src/maasserver/websockets/tests/test_protocol.py
173index 5181adb..b5cbdc7 100644
174--- a/src/maasserver/websockets/tests/test_protocol.py
175+++ b/src/maasserver/websockets/tests/test_protocol.py
176@@ -20,7 +20,10 @@ from apiclient.utils import ascii_url
177 from maasserver.eventloop import services
178 from maasserver.testing.factory import factory as maas_factory
179 from maasserver.testing.listener import FakePostgresListenerService
180-from maasserver.testing.testcase import MAASTransactionServerTestCase
181+from maasserver.testing.testcase import (
182+ MAASServerTestCase,
183+ MAASTransactionServerTestCase,
184+)
185 from maasserver.utils.orm import transactional
186 from maasserver.utils.threads import deferToDatabase
187 from maasserver.websockets import protocol as protocol_module
188@@ -1000,3 +1003,20 @@ class TestWebSocketFactoryTransactional(
189 controller.system_id,
190 ),
191 )
192+
193+
194+class TestWebSocketFactoryServer(MAASServerTestCase, MakeProtocolFactoryMixin):
195+ def test_processNotify_unsubscribed_object(self):
196+ factory = self.make_factory()
197+ machine = maas_factory.make_Machine()
198+ handler = MachineHandler(maas_factory.make_User(), {}, None)
199+ factory.handlers["machine"] = handler
200+ result = factory.processNotify(
201+ handler, "machine", "update", machine.system_id
202+ )
203+ self.assertIsNotNone(result)
204+ handler.unsubscribe({"system_ids": [machine.system_id]})
205+ result = factory.processNotify(
206+ handler, "machine", "update", machine.system_id
207+ )
208+ self.assertIsNone(result)

Subscribers

People subscribed via source and target branches