Merge ~mpontillo/maas:version-info-updates-and-triggers into maas:master

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: b040b7ccab7cffe27fbce2ac149ac6d012fc2d5a
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:version-info-updates-and-triggers
Merge into: maas:master
Diff against target: 908 lines (+312/-207)
11 files modified
src/maasserver/models/node.py (+18/-8)
src/maasserver/static/partials/node-details.html (+7/-0)
src/maasserver/triggers/__init__.py (+102/-10)
src/maasserver/triggers/tests/test_init.py (+3/-0)
src/maasserver/triggers/tests/test_websocket_listener.py (+105/-0)
src/maasserver/triggers/websocket.py (+54/-186)
src/maasserver/websockets/handlers/controller.py (+4/-1)
src/maasserver/websockets/handlers/node.py (+2/-2)
src/maasserver/websockets/handlers/tests/test_controller.py (+10/-0)
src/provisioningserver/refresh/__init__.py (+3/-0)
src/provisioningserver/refresh/tests/test_refresh.py (+4/-0)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Review via email: mp+329864@code.launchpad.net

Commit message

Trigger and websocket work for controller verison

 * Add triggers for ControllerInfo model. (Only fire triggers
   if the version was updated.)
 * Add version information to Controller handler.
 * Add version reporting to controller refresh.
 * Add version display on controller details page.
 * Refactor trigger addition code to add register_triggers() method.
 * Drive-by fix to process_sys_info() to prevent updating fields
   that did not change.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

quick comment :)

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Quick reply; thanks.

Revision history for this message
Lee Trager (ltrager) wrote :

Looks good just one question below.

review: Needs Information
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Thanks for taking a look; I've replied to your question inline below.

Revision history for this message
Lee Trager (ltrager) wrote :

Thanks for the explanation. LGTM!

review: Approve

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
2index 63e689b..1ec195c 100644
3--- a/src/maasserver/models/node.py
4+++ b/src/maasserver/models/node.py
5@@ -4822,23 +4822,33 @@ class Controller(Node):
6 @transactional
7 def _process_sys_info(self, response):
8 update_fields = []
9- if response['hostname'] != '':
10- self.hostname = response['hostname']
11+ hostname = response.get('hostname')
12+ if hostname and self.hostname != hostname:
13+ self.hostname = hostname
14 update_fields.append('hostname')
15- if response['architecture'] != '':
16- self.architecture = response['architecture']
17+ architecture = response.get('architecture')
18+ if architecture and self.architecture != architecture:
19+ self.architecture = architecture
20 update_fields.append('architecture')
21- if response['osystem'] != '':
22- self.osystem = response['osystem']
23+ osystem = response.get('osystem')
24+ if osystem and self.osystem != osystem:
25+ self.osystem = osystem
26 update_fields.append('osystem')
27- if response['distro_series'] != '':
28+ distro_series = response.get('distro_series')
29+ if distro_series and self.distro_series != distro_series:
30 self.distro_series = response['distro_series']
31 update_fields.append('distro_series')
32+ maas_version = response.get('maas_version')
33+ if maas_version and self.version != maas_version:
34+ # Circular imports.
35+ from maasserver.models import ControllerInfo
36+ ControllerInfo.objects.set_version(self, maas_version)
37 # MAAS 2.3+ will send an empty dictionary on purpose, but older
38 # versions of the MAAS rack will send real data (and it might arrive
39 # in a more timely manner than the UpdateInterfaces call from the
40 # NetworksMonitoringService).
41- if response['interfaces'] != {}:
42+ interfaces = response.get('interfaces', {})
43+ if len(interfaces) > 0:
44 self.update_interfaces(response['interfaces'])
45 if len(update_fields) > 0:
46 self.save(update_fields=update_fields)
47diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html
48index ccaffa0..66619a1 100755
49--- a/src/maasserver/static/partials/node-details.html
50+++ b/src/maasserver/static/partials/node-details.html
51@@ -321,6 +321,13 @@
52 aria-label="A region controller controls MAAS services like DNS and runs the database.

A rack controller controls hosts and images and runs network services
like DHCP for connected VLANs."
53 data-ng-show="node.node_type == 4"></i>
54 </dd>
55+ <dt class="two-col">MAAS Version</dt>
56+ <dd class="four-col last-col" data-ng-show="node.version">
57+ {$ node.version $}
58+ </dd>
59+ <dd class="four-col last-col u-text--subtle" data-ng-show="!node.version">
60+ Unknown (&lt; 2.3.0)
61+ </dd>
62 <dt class="two-col" data-ng-show="node.node_type == 2 || node.node_type == 4">Last image sync</dt>
63 <dd class="four-col last-col" data-ng-show="node.node_type == 2 || node.node_type == 4">
64 {$ node.last_image_sync || 'never' $}
65diff --git a/src/maasserver/triggers/__init__.py b/src/maasserver/triggers/__init__.py
66index 88323f2..f84a1db 100644
67--- a/src/maasserver/triggers/__init__.py
68+++ b/src/maasserver/triggers/__init__.py
69@@ -29,21 +29,113 @@ def register_procedure(procedure):
70 cursor.execute(procedure)
71
72
73-def register_trigger(table, procedure, event, params=None, when="after"):
74+# Mappings for (postgres_event_type, maas_notification_type, pg_obj) for
75+# trigger notification events. Three conventions are currently in-use in MAAS.
76+
77+# Event names: create/update/delete.
78+# The majority of triggers use this convention; this is the default.
79+EVENTS_CUD = (
80+ ('insert', 'create', 'NEW'),
81+ ('update', 'update', 'NEW'),
82+ ('delete', 'delete', 'OLD'),
83+)
84+
85+# Event names: insert/update/delete.
86+EVENTS_IUD = (
87+ ('insert', 'insert', 'NEW'),
88+ ('update', 'update', 'NEW'),
89+ ('delete', 'delete', 'OLD'),
90+)
91+
92+# Event names: link/update/unlink.
93+EVENTS_LUU = (
94+ ('insert', 'link', 'NEW'),
95+ ('update', 'update', 'NEW'),
96+ ('delete', 'unlink', 'OLD'),
97+)
98+
99+
100+def register_triggers(
101+ table, event_prefix, params=None, fields=None, events=None,
102+ when="after"):
103+ """Registers a set of triggers for insert, update, and delete.
104+
105+ Event names will be determined based on MAAS convention, unless the
106+ convention is passed in via the `events` parameter. Predefined conventions
107+ in-use in MAAS are provided via the EVENTS_* constants.
108+
109+ :param table: The table name to create the trigger on.
110+ :param event_prefix: The event prefix for the trigger. For example, if
111+ the table is maasserver_subnet, 'subnet' might be an appropriate event
112+ prefix.
113+ :param params: A dictionary of parameters that should be ANDed together to
114+ form the initial WHEN clause.
115+ :param fields: A list of fields whose values will be checked for changes
116+ before the trigger fires. If None is specified, all fields in the row
117+ will be checked.
118+ :param events: A tuple in the format of the EVENTS_* constants indicating
119+ which convention to use for notification names.
120+ :param when: When the trigger should fire relative to the row update. The
121+ default is AFTER, but postgresql also supports BEFORE and INSTEAD OF.
122+ """
123+ if events is None:
124+ events = EVENTS_CUD
125+ for pg_event, maas_event_type, pg_obj in events:
126+ event_params = None
127+ if params is not None:
128+ event_params = {}
129+ for key, value in params.items():
130+ event_params['%s.%s' % (pg_obj, key)] = value
131+ register_trigger(
132+ table, '%s_%s_notify' % (event_prefix, maas_event_type),
133+ pg_event, event_params, fields, when)
134+
135+
136+def _make_when_clause(is_update, params, fields):
137+ """Generates a WHEN clause for the trigger.
138+
139+ :param is_update: If true, this trigger is for update. (not insert/delete)
140+ :param params: A dictionary of parameters that should be ANDed together to
141+ form the initial WHEN clause.
142+ :param fields: A list of fields whose values will be checked for changes
143+ before the trigger fires. If None is specified, all fields in the row
144+ will be checked.
145+ :return: the WHEN clause to use in the trigger.
146+ """
147+ when_clause = ''
148+ if params is not None or (fields is not None and is_update):
149+ if params is None:
150+ params = {}
151+ if fields is None:
152+ fields = []
153+ when_clause = 'WHEN ('
154+ when_clause += ' AND '.join([
155+ "%s = '%s'" % (key, value)
156+ for key, value in params.items()
157+ ])
158+ if is_update and len(fields) > 0:
159+ if len(params) > 0:
160+ when_clause += " AND ("
161+ when_clause += ' OR '.join([
162+ "NEW.%s IS DISTINCT FROM OLD.%s" % (field, field)
163+ for field in fields
164+ ])
165+ if len(params) > 0:
166+ when_clause += ")"
167+ when_clause += ")"
168+ return when_clause
169+
170+
171+def register_trigger(
172+ table, procedure, event, params=None, fields=None, when="after"):
173 """Register `trigger` on `table` if it doesn't exist."""
174 # Strip the "maasserver_" off the front of the table name.
175 table_name = table
176 if table.startswith("maasserver_"):
177 table_name = table_name[11:]
178 trigger_name = "%s_%s" % (table_name, procedure)
179- if params is not None:
180- filter = 'WHEN (' + ''.join(
181- [
182- "%s = '%s'" % (key, value)
183- for key, value in params.items()
184- ]) + ')'
185- else:
186- filter = ''
187+ is_update = (event == "update")
188+ when_clause = _make_when_clause(is_update, params, fields)
189 trigger_sql = dedent("""\
190 DROP TRIGGER IF EXISTS %s ON %s;
191 CREATE TRIGGER %s
192@@ -58,7 +150,7 @@ def register_trigger(table, procedure, event, params=None, when="after"):
193 when.upper(),
194 event.upper(),
195 table,
196- filter,
197+ when_clause,
198 procedure,
199 )
200 with closing(connection.cursor()) as cursor:
201diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py
202index a0256c4..3f291cb 100644
203--- a/src/maasserver/triggers/tests/test_init.py
204+++ b/src/maasserver/triggers/tests/test_init.py
205@@ -125,6 +125,9 @@ class TestTriggersUsed(MAASServerTestCase):
206 "config_config_update_notify",
207 "config_sys_proxy_config_use_peer_proxy_insert",
208 "config_sys_proxy_config_use_peer_proxy_update",
209+ 'controllerinfo_controllerinfo_link_notify',
210+ 'controllerinfo_controllerinfo_unlink_notify',
211+ 'controllerinfo_controllerinfo_update_notify',
212 "dhcpsnippet_dhcpsnippet_create_notify",
213 "dhcpsnippet_dhcpsnippet_delete_notify",
214 "dhcpsnippet_dhcpsnippet_update_notify",
215diff --git a/src/maasserver/triggers/tests/test_websocket_listener.py b/src/maasserver/triggers/tests/test_websocket_listener.py
216index 8877fcc..c40a3c6 100644
217--- a/src/maasserver/triggers/tests/test_websocket_listener.py
218+++ b/src/maasserver/triggers/tests/test_websocket_listener.py
219@@ -18,6 +18,7 @@ from maasserver.enum import (
220 NODE_TYPE,
221 )
222 from maasserver.listener import PostgresListenerService
223+from maasserver.models import ControllerInfo
224 from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
225 from maasserver.models.config import Config
226 from maasserver.models.node import Node
227@@ -36,7 +37,9 @@ from provisioningserver.utils.twisted import (
228 FOREVER,
229 synchronous,
230 )
231+from testtools import ExpectedException
232 from twisted.internet.defer import (
233+ CancelledError,
234 DeferredList,
235 DeferredQueue,
236 inlineCallbacks,
237@@ -219,6 +222,108 @@ class TestNodeListener(
238 yield listener.stopService()
239
240
241+class TestControllerListener(
242+ MAASTransactionServerTestCase, TransactionalHelpersMixin):
243+ """End-to-end test of both the listeners code and the triggers code."""
244+
245+ scenarios = (
246+ ('rack', {
247+ 'params': {'node_type': NODE_TYPE.RACK_CONTROLLER},
248+ 'listener': 'controller',
249+ }),
250+ ('region_and_rack', {
251+ 'params': {'node_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER},
252+ 'listener': 'controller',
253+ }),
254+ ('region', {
255+ 'params': {'node_type': NODE_TYPE.REGION_CONTROLLER},
256+ 'listener': 'controller',
257+ }),
258+ )
259+
260+ def set_version(self, controller, version):
261+ ControllerInfo.objects.set_version(controller, version)
262+
263+ def set_interface_update_info(self, controller, interfaces, hints):
264+ ControllerInfo.objects.set_interface_update_info(
265+ controller, interfaces, hints)
266+
267+ def delete_controllerinfo(self, controller):
268+ ControllerInfo.objects.filter(node=controller).delete()
269+
270+ @wait_for_reactor
271+ @inlineCallbacks
272+ def test__calls_handler_on_controllerinfo_insert(self):
273+ yield deferToDatabase(register_websocket_triggers)
274+ listener = self.make_listener_without_delay()
275+ dv = DeferredValue()
276+ params = self.params.copy()
277+ controller = yield deferToDatabase(self.create_node, params)
278+ listener.register(self.listener, lambda *args: dv.set(args))
279+ yield listener.startService()
280+ try:
281+ yield deferToDatabase(
282+ self.set_version, controller, factory.make_string())
283+ yield dv.get(timeout=2)
284+ finally:
285+ yield listener.stopService()
286+
287+ @wait_for_reactor
288+ @inlineCallbacks
289+ def test__calls_handler_on_controllerinfo_version_update(self):
290+ yield deferToDatabase(register_websocket_triggers)
291+ listener = self.make_listener_without_delay()
292+ dv = DeferredValue()
293+ params = self.params.copy()
294+ controller = yield deferToDatabase(self.create_node, params)
295+ yield deferToDatabase(self.set_version, controller, '')
296+ listener.register(self.listener, lambda *args: dv.set(args))
297+ yield listener.startService()
298+ try:
299+ yield deferToDatabase(
300+ self.set_version, controller, factory.make_string())
301+ yield dv.get(timeout=2)
302+ finally:
303+ yield listener.stopService()
304+
305+ @wait_for_reactor
306+ @inlineCallbacks
307+ def test__skips_notify_on_controllerinfo_interface_update(self):
308+ yield deferToDatabase(register_websocket_triggers)
309+ listener = self.make_listener_without_delay()
310+ dv = DeferredValue()
311+ params = self.params.copy()
312+ controller = yield deferToDatabase(self.create_node, params)
313+ yield deferToDatabase(self.set_version, controller, '')
314+ listener.register(self.listener, lambda *args: dv.set(args))
315+ yield listener.startService()
316+ try:
317+ yield deferToDatabase(
318+ self.set_interface_update_info, controller, '{]', '{}')
319+ with ExpectedException(CancelledError):
320+ yield dv.get(timeout=0.2)
321+ finally:
322+ yield listener.stopService()
323+
324+ @wait_for_reactor
325+ @inlineCallbacks
326+ def test__calls_handler_on_controllerinfo_delete(self):
327+ yield deferToDatabase(register_websocket_triggers)
328+ listener = self.make_listener_without_delay()
329+ dv = DeferredValue()
330+ params = self.params.copy()
331+ controller = yield deferToDatabase(self.create_node, params)
332+ yield deferToDatabase(self.set_version, controller, '')
333+ listener.register(self.listener, lambda *args: dv.set(args))
334+ yield listener.startService()
335+ try:
336+ yield deferToDatabase(
337+ self.delete_controllerinfo, controller)
338+ yield dv.get(timeout=2)
339+ finally:
340+ yield listener.stopService()
341+
342+
343 class TestDeviceWithParentListener(
344 MAASTransactionServerTestCase, TransactionalHelpersMixin):
345 """End-to-end test of both the listeners code and the triggers code."""
346diff --git a/src/maasserver/triggers/websocket.py b/src/maasserver/triggers/websocket.py
347index 0e9df27..0ec50e4 100644
348--- a/src/maasserver/triggers/websocket.py
349+++ b/src/maasserver/triggers/websocket.py
350@@ -22,8 +22,11 @@ from maasserver.enum import (
351 NODE_TYPE,
352 )
353 from maasserver.triggers import (
354+ EVENTS_IUD,
355+ EVENTS_LUU,
356 register_procedure,
357 register_trigger,
358+ register_triggers,
359 )
360 from maasserver.utils.orm import transactional
361
362@@ -1175,21 +1178,27 @@ def register_websocket_triggers():
363 '%s_delete_notify' % proc_name_prefix,
364 '%s_delete' % event_name_prefix,
365 'OLD.system_id'))
366- register_trigger(
367- "maasserver_node",
368- "%s_create_notify" % proc_name_prefix,
369- "insert",
370- {'NEW.node_type': node_type})
371- register_trigger(
372- "maasserver_node",
373- "%s_update_notify" % proc_name_prefix,
374- "update",
375- {'NEW.node_type': node_type})
376- register_trigger(
377- "maasserver_node",
378- "%s_delete_notify" % proc_name_prefix,
379- "delete",
380- {'OLD.node_type': node_type})
381+ register_triggers(
382+ "maasserver_node", proc_name_prefix, {'node_type': node_type})
383+
384+ # ControllerInfo notifications
385+ register_procedure(
386+ render_node_related_notification_procedure(
387+ 'controllerinfo_link_notify', 'NEW.node_id'))
388+ register_procedure(
389+ render_node_related_notification_procedure(
390+ 'controllerinfo_update_notify', 'NEW.node_id'))
391+ register_procedure(
392+ render_node_related_notification_procedure(
393+ 'controllerinfo_unlink_notify', 'OLD.node_id'))
394+ register_triggers(
395+ "maasserver_controllerinfo",
396+ "controllerinfo",
397+ # Trigger only fires for version information on update; it doesn't
398+ # care when the other metadata is updated.
399+ fields=('version',),
400+ events=EVENTS_LUU
401+ )
402
403 # Config table
404 register_procedure(
405@@ -1201,12 +1210,7 @@ def register_websocket_triggers():
406 register_procedure(
407 render_notification_procedure(
408 'config_delete_notify', 'config_delete', 'OLD.id'))
409- register_trigger(
410- "maasserver_config", "config_create_notify", "insert")
411- register_trigger(
412- "maasserver_config", "config_update_notify", "update")
413- register_trigger(
414- "maasserver_config", "config_delete_notify", "delete")
415+ register_triggers("maasserver_config", "config")
416
417 # Device Node types
418 register_procedure(
419@@ -1218,15 +1222,8 @@ def register_websocket_triggers():
420 register_procedure(
421 render_device_notification_procedure(
422 'device_delete_notify', 'device_delete', 'OLD'))
423- register_trigger(
424- "maasserver_node", "device_create_notify", "insert",
425- {'NEW.node_type': NODE_TYPE.DEVICE})
426- register_trigger(
427- "maasserver_node", "device_update_notify", "update",
428- {'NEW.node_type': NODE_TYPE.DEVICE})
429- register_trigger(
430- "maasserver_node", "device_delete_notify", "delete",
431- {'OLD.node_type': NODE_TYPE.DEVICE})
432+ register_triggers(
433+ "maasserver_node", "device", {'node_type': NODE_TYPE.DEVICE})
434
435 # VLAN table
436 register_procedure(
437@@ -1238,12 +1235,7 @@ def register_websocket_triggers():
438 register_procedure(
439 render_notification_procedure(
440 'vlan_delete_notify', 'vlan_delete', 'OLD.id'))
441- register_trigger(
442- "maasserver_vlan", "vlan_create_notify", "insert")
443- register_trigger(
444- "maasserver_vlan", "vlan_update_notify", "update")
445- register_trigger(
446- "maasserver_vlan", "vlan_delete_notify", "delete")
447+ register_triggers("maasserver_vlan", "vlan")
448
449 # IPRange table
450 register_procedure(
451@@ -1255,12 +1247,7 @@ def register_websocket_triggers():
452 register_procedure(
453 render_notification_procedure(
454 'iprange_delete_notify', 'iprange_delete', 'OLD.id'))
455- register_trigger(
456- "maasserver_iprange", "iprange_create_notify", "insert")
457- register_trigger(
458- "maasserver_iprange", "iprange_update_notify", "update")
459- register_trigger(
460- "maasserver_iprange", "iprange_delete_notify", "delete")
461+ register_triggers("maasserver_iprange", "iprange")
462
463 # Neighbour table
464 register_procedure(
465@@ -1272,12 +1259,7 @@ def register_websocket_triggers():
466 register_procedure(
467 render_notification_procedure(
468 'neighbour_delete_notify', 'neighbour_delete', 'OLD.ip'))
469- register_trigger(
470- "maasserver_neighbour", "neighbour_create_notify", "insert")
471- register_trigger(
472- "maasserver_neighbour", "neighbour_update_notify", "update")
473- register_trigger(
474- "maasserver_neighbour", "neighbour_delete_notify", "delete")
475+ register_triggers("maasserver_neighbour", "neighbour")
476
477 # StaticRoute table
478 register_procedure(
479@@ -1289,12 +1271,7 @@ def register_websocket_triggers():
480 register_procedure(
481 render_notification_procedure(
482 'staticroute_delete_notify', 'staticroute_delete', 'OLD.id'))
483- register_trigger(
484- "maasserver_staticroute", "staticroute_create_notify", "insert")
485- register_trigger(
486- "maasserver_staticroute", "staticroute_update_notify", "update")
487- register_trigger(
488- "maasserver_staticroute", "staticroute_delete_notify", "delete")
489+ register_triggers("maasserver_staticroute", "staticroute")
490
491 # Fabric table
492 register_procedure(
493@@ -1306,12 +1283,7 @@ def register_websocket_triggers():
494 register_procedure(
495 render_notification_procedure(
496 'fabric_delete_notify', 'fabric_delete', 'OLD.id'))
497- register_trigger(
498- "maasserver_fabric", "fabric_create_notify", "insert")
499- register_trigger(
500- "maasserver_fabric", "fabric_update_notify", "update")
501- register_trigger(
502- "maasserver_fabric", "fabric_delete_notify", "delete")
503+ register_triggers("maasserver_fabric", "fabric")
504
505 # Space table
506 register_procedure(
507@@ -1323,12 +1295,7 @@ def register_websocket_triggers():
508 register_procedure(
509 render_notification_procedure(
510 'space_delete_notify', 'space_delete', 'OLD.id'))
511- register_trigger(
512- "maasserver_space", "space_create_notify", "insert")
513- register_trigger(
514- "maasserver_space", "space_update_notify", "update")
515- register_trigger(
516- "maasserver_space", "space_delete_notify", "delete")
517+ register_triggers("maasserver_space", "space")
518
519 # Subnet table
520 register_procedure(
521@@ -1340,12 +1307,7 @@ def register_websocket_triggers():
522 register_procedure(
523 render_notification_procedure(
524 'subnet_delete_notify', 'subnet_delete', 'OLD.id'))
525- register_trigger(
526- "maasserver_subnet", "subnet_create_notify", "insert")
527- register_trigger(
528- "maasserver_subnet", "subnet_update_notify", "update")
529- register_trigger(
530- "maasserver_subnet", "subnet_delete_notify", "delete")
531+ register_triggers("maasserver_subnet", "subnet")
532
533 # Subnet node notifications
534 register_procedure(
535@@ -1421,15 +1383,8 @@ def register_websocket_triggers():
536 register_procedure(
537 STATIC_IP_ADDRESS_DOMAIN_NOTIFY % (
538 'ipaddress_domain_delete_notify', 'OLD.id'))
539- register_trigger(
540- "maasserver_staticipaddress",
541- "ipaddress_domain_insert_notify", "insert")
542- register_trigger(
543- "maasserver_staticipaddress",
544- "ipaddress_domain_update_notify", "update")
545- register_trigger(
546- "maasserver_staticipaddress",
547- "ipaddress_domain_delete_notify", "delete")
548+ register_triggers(
549+ "maasserver_staticipaddress", "ipaddress_domain", events=EVENTS_IUD)
550
551 # IP range subnet notifications
552 register_procedure(
553@@ -1438,15 +1393,8 @@ def register_websocket_triggers():
554 IP_RANGE_SUBNET_UPDATE_NOTIFY % 'iprange_subnet_update_notify')
555 register_procedure(
556 IP_RANGE_SUBNET_DELETE_NOTIFY % 'iprange_subnet_delete_notify')
557- register_trigger(
558- "maasserver_iprange",
559- "iprange_subnet_insert_notify", "insert")
560- register_trigger(
561- "maasserver_iprange",
562- "iprange_subnet_update_notify", "update")
563- register_trigger(
564- "maasserver_iprange",
565- "iprange_subnet_delete_notify", "delete")
566+ register_triggers(
567+ "maasserver_iprange", "iprange_subnet", events=EVENTS_IUD)
568
569 # Pod notifications
570 register_procedure(
571@@ -1457,15 +1405,7 @@ def register_websocket_triggers():
572 BMC_TYPE.POD, BMC_TYPE.POD, BMC_TYPE.BMC))
573 register_procedure(
574 POD_DELETE_NOTIFY % ('pod_delete_notify', BMC_TYPE.POD))
575- register_trigger(
576- "maasserver_bmc",
577- "pod_insert_notify", "insert")
578- register_trigger(
579- "maasserver_bmc",
580- "pod_update_notify", "update")
581- register_trigger(
582- "maasserver_bmc",
583- "pod_delete_notify", "delete")
584+ register_triggers("maasserver_bmc", "pod", events=EVENTS_IUD)
585
586 # Node pod notifications
587 register_procedure(
588@@ -1475,15 +1415,7 @@ def register_websocket_triggers():
589 'node_pod_update_notify', BMC_TYPE.POD, BMC_TYPE.POD))
590 register_procedure(
591 NODE_POD_DELETE_NOTIFY % ('node_pod_delete_notify', BMC_TYPE.POD))
592- register_trigger(
593- "maasserver_node",
594- "node_pod_insert_notify", "insert")
595- register_trigger(
596- "maasserver_node",
597- "node_pod_update_notify", "update")
598- register_trigger(
599- "maasserver_node",
600- "node_pod_delete_notify", "delete")
601+ register_triggers("maasserver_node", "node_pod", events=EVENTS_IUD)
602
603 # DNSData table
604 register_procedure(
605@@ -1496,15 +1428,8 @@ def register_websocket_triggers():
606 register_procedure(
607 DNSDATA_DOMAIN_NOTIFY % (
608 'dnsdata_domain_delete_notify', 'OLD.dnsresource_id'))
609- register_trigger(
610- "maasserver_dnsdata",
611- "dnsdata_domain_insert_notify", "insert")
612- register_trigger(
613- "maasserver_dnsdata",
614- "dnsdata_domain_update_notify", "update")
615- register_trigger(
616- "maasserver_dnsdata",
617- "dnsdata_domain_delete_notify", "delete")
618+ register_triggers(
619+ "maasserver_dnsdata", "dnsdata_domain", events=EVENTS_IUD)
620
621 # DNSResource table
622 register_procedure(
623@@ -1515,15 +1440,8 @@ def register_websocket_triggers():
624 register_procedure(
625 DNSRESOURCE_DOMAIN_NOTIFY % (
626 'dnsresource_domain_delete_notify', 'OLD'))
627- register_trigger(
628- "maasserver_dnsresource",
629- "dnsresource_domain_insert_notify", "insert")
630- register_trigger(
631- "maasserver_dnsresource",
632- "dnsresource_domain_update_notify", "update")
633- register_trigger(
634- "maasserver_dnsresource",
635- "dnsresource_domain_delete_notify", "delete")
636+ register_triggers(
637+ "maasserver_dnsresource", "dnsresource_domain", events=EVENTS_IUD)
638
639 # Domain table
640 register_procedure(
641@@ -1542,12 +1460,7 @@ def register_websocket_triggers():
642 'domain_delete_notify', 'domain_delete', 'OLD.id'))
643 register_trigger(
644 "maasserver_domain", "domain_node_update_notify", "update")
645- register_trigger(
646- "maasserver_domain", "domain_create_notify", "insert")
647- register_trigger(
648- "maasserver_domain", "domain_update_notify", "update")
649- register_trigger(
650- "maasserver_domain", "domain_delete_notify", "delete")
651+ register_triggers("maasserver_domain", "domain")
652
653 # MAC static ip address table, update to linked domain via dnsresource
654 register_procedure(
655@@ -1573,12 +1486,7 @@ def register_websocket_triggers():
656 register_procedure(
657 render_notification_procedure(
658 'zone_delete_notify', 'zone_delete', 'OLD.id'))
659- register_trigger(
660- "maasserver_zone", "zone_create_notify", "insert")
661- register_trigger(
662- "maasserver_zone", "zone_update_notify", "update")
663- register_trigger(
664- "maasserver_zone", "zone_delete_notify", "delete")
665+ register_triggers("maasserver_zone", "zone")
666
667 # Service table
668 register_procedure(
669@@ -1590,12 +1498,7 @@ def register_websocket_triggers():
670 register_procedure(
671 render_notification_procedure(
672 'service_delete_notify', 'service_delete', 'OLD.id'))
673- register_trigger(
674- "maasserver_service", "service_create_notify", "insert")
675- register_trigger(
676- "maasserver_service", "service_update_notify", "update")
677- register_trigger(
678- "maasserver_service", "service_delete_notify", "delete")
679+ register_triggers("maasserver_service", "service")
680
681 # Tag table
682 register_procedure(
683@@ -1607,12 +1510,7 @@ def register_websocket_triggers():
684 register_procedure(
685 render_notification_procedure(
686 'tag_delete_notify', 'tag_delete', 'OLD.id'))
687- register_trigger(
688- "maasserver_tag", "tag_create_notify", "insert")
689- register_trigger(
690- "maasserver_tag", "tag_update_notify", "update")
691- register_trigger(
692- "maasserver_tag", "tag_delete_notify", "delete")
693+ register_triggers("maasserver_tag", "tag")
694
695 # Node tag link table
696 register_procedure(
697@@ -1647,12 +1545,7 @@ def register_websocket_triggers():
698 register_procedure(
699 render_notification_procedure(
700 'user_delete_notify', 'user_delete', 'OLD.id'))
701- register_trigger(
702- "auth_user", "user_create_notify", "insert")
703- register_trigger(
704- "auth_user", "user_update_notify", "update")
705- register_trigger(
706- "auth_user", "user_delete_notify", "delete")
707+ register_triggers("auth_user", "user")
708
709 # Events table
710 register_procedure(
711@@ -1928,12 +1821,7 @@ def register_websocket_triggers():
712 register_procedure(
713 render_notification_procedure(
714 'sshkey_delete_notify', 'sshkey_delete', 'OLD.id'))
715- register_trigger(
716- "maasserver_sshkey", "sshkey_create_notify", "insert")
717- register_trigger(
718- "maasserver_sshkey", "sshkey_update_notify", "update")
719- register_trigger(
720- "maasserver_sshkey", "sshkey_delete_notify", "delete")
721+ register_triggers("maasserver_sshkey", "sshkey")
722
723 # SSL key table, update to linked user.
724 register_procedure(
725@@ -1957,12 +1845,7 @@ def register_websocket_triggers():
726 register_procedure(
727 render_notification_procedure(
728 'dhcpsnippet_delete_notify', 'dhcpsnippet_delete', 'OLD.id'))
729- register_trigger(
730- "maasserver_dhcpsnippet", "dhcpsnippet_create_notify", "insert")
731- register_trigger(
732- "maasserver_dhcpsnippet", "dhcpsnippet_update_notify", "update")
733- register_trigger(
734- "maasserver_dhcpsnippet", "dhcpsnippet_delete_notify", "delete")
735+ register_triggers("maasserver_dhcpsnippet", "dhcpsnippet")
736
737 # PackageRepository table
738 register_procedure(
739@@ -1977,15 +1860,7 @@ def register_websocket_triggers():
740 render_notification_procedure(
741 'packagerepository_delete_notify', 'packagerepository_delete',
742 'OLD.id'))
743- register_trigger(
744- "maasserver_packagerepository", "packagerepository_create_notify",
745- "insert")
746- register_trigger(
747- "maasserver_packagerepository", "packagerepository_update_notify",
748- "update")
749- register_trigger(
750- "maasserver_packagerepository", "packagerepository_delete_notify",
751- "delete")
752+ register_triggers("maasserver_packagerepository", "packagerepository")
753
754 # Node type change.
755 register_procedure(node_type_change())
756@@ -2002,12 +1877,7 @@ def register_websocket_triggers():
757 register_procedure(
758 render_notification_procedure(
759 'notification_delete_notify', 'notification_delete', 'OLD.id'))
760- register_trigger(
761- "maasserver_notification", "notification_create_notify", "insert")
762- register_trigger(
763- "maasserver_notification", "notification_update_notify", "update")
764- register_trigger(
765- "maasserver_notification", "notification_delete_notify", "delete")
766+ register_triggers("maasserver_notification", "notification")
767
768 # NotificationDismissal table.
769 register_procedure(
770@@ -2028,6 +1898,4 @@ def register_websocket_triggers():
771 register_procedure(
772 render_notification_procedure(
773 'script_delete_notify', 'script_delete', 'OLD.id'))
774- register_trigger('metadataserver_script', 'script_create_notify', 'insert')
775- register_trigger('metadataserver_script', 'script_update_notify', 'update')
776- register_trigger('metadataserver_script', 'script_delete_notify', 'delete')
777+ register_triggers('metadataserver_script', 'script')
778diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py
779index ca85333..6edc6f0 100644
780--- a/src/maasserver/websockets/handlers/controller.py
781+++ b/src/maasserver/websockets/handlers/controller.py
782@@ -23,7 +23,9 @@ class ControllerHandler(MachineHandler):
783 class Meta(MachineHandler.Meta):
784 abstract = False
785 queryset = node_prefetch(
786- Controller.controllers.all().prefetch_related("interface_set"))
787+ Controller.controllers.all().prefetch_related("interface_set"),
788+ 'controllerinfo'
789+ )
790 allowed_methods = [
791 'list',
792 'get',
793@@ -89,6 +91,7 @@ class ControllerHandler(MachineHandler):
794
795 def dehydrate(self, obj, data, for_list=False):
796 data = super().dehydrate(obj, data, for_list=for_list)
797+ data["version"] = obj.version
798 data["vlan_ids"] = [
799 interface.vlan_id
800 for interface in obj.interface_set.all()
801diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
802index 12bb3c3..5014fa8 100644
803--- a/src/maasserver/websockets/handlers/node.py
804+++ b/src/maasserver/websockets/handlers/node.py
805@@ -45,10 +45,10 @@ from metadataserver.enum import RESULT_TYPE
806 from provisioningserver.tags import merge_details_cleanly
807
808
809-def node_prefetch(queryset):
810+def node_prefetch(queryset, *args):
811 return (
812 queryset
813- .select_related('boot_interface', 'owner', 'zone', 'domain')
814+ .select_related('boot_interface', 'owner', 'zone', 'domain', *args)
815 .prefetch_related('blockdevice_set__iscsiblockdevice')
816 .prefetch_related('blockdevice_set__physicalblockdevice')
817 .prefetch_related('blockdevice_set__virtualblockdevice')
818diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py
819index 111ed57..ad628e9 100644
820--- a/src/maasserver/websockets/handlers/tests/test_controller.py
821+++ b/src/maasserver/websockets/handlers/tests/test_controller.py
822@@ -7,6 +7,7 @@ __all__ = []
823
824 from maasserver.enum import NODE_TYPE
825 from maasserver.forms import ControllerForm
826+from maasserver.models import ControllerInfo
827 from maasserver.testing.factory import factory
828 from maasserver.testing.testcase import MAASServerTestCase
829 from maasserver.websockets.base import dehydrate_datetime
830@@ -96,6 +97,15 @@ class TestControllerHandler(MAASServerTestCase):
831 handler = ControllerHandler(owner, {})
832 self.assertTrue(handler.dehydrate_show_os_info(rack))
833
834+ def test_dehydrate_includes_version(self):
835+ owner = factory.make_admin()
836+ handler = ControllerHandler(owner, {})
837+ rack = factory.make_RackController()
838+ version = factory.make_string()
839+ ControllerInfo.objects.set_version(rack, version)
840+ result = handler.list({})
841+ self.assertEqual(version, result[0].get('version'))
842+
843
844 class TestControllerHandlerScenarios(MAASServerTestCase):
845
846diff --git a/src/provisioningserver/refresh/__init__.py b/src/provisioningserver/refresh/__init__.py
847index a377038..bf05f10 100644
848--- a/src/provisioningserver/refresh/__init__.py
849+++ b/src/provisioningserver/refresh/__init__.py
850@@ -2,6 +2,7 @@
851 # GNU Affero General Public License version 3 (see the file LICENSE).
852
853 """Functionality to refresh rack controller hardware and networking details."""
854+
855 import os
856 import socket
857 import stat
858@@ -25,6 +26,7 @@ from provisioningserver.utils.shell import (
859 ExternalProcessError,
860 )
861 from provisioningserver.utils.twisted import synchronous
862+from provisioningserver.utils.version import get_maas_version
863
864
865 maaslog = get_maas_logger("refresh")
866@@ -77,6 +79,7 @@ def get_sys_info():
867 'architecture': get_architecture(),
868 'osystem': osystem,
869 'distro_series': distro_series,
870+ 'maas_version': get_maas_version(),
871 # In MAAS 2.3+, the NetworksMonitoringService is solely responsible for
872 # interface update, because interface updates need to include beaconing
873 # data. The key and empty dictionary are necessary for backward
874diff --git a/src/provisioningserver/refresh/tests/test_refresh.py b/src/provisioningserver/refresh/tests/test_refresh.py
875index ff6b759..d03cac1 100644
876--- a/src/provisioningserver/refresh/tests/test_refresh.py
877+++ b/src/provisioningserver/refresh/tests/test_refresh.py
878@@ -25,6 +25,7 @@ from provisioningserver.refresh.maas_api_helper import (
879 MD_VERSION,
880 SignalException,
881 )
882+from provisioningserver.utils.version import get_maas_version
883 from testtools.matchers import (
884 Contains,
885 DirExists,
886@@ -82,6 +83,7 @@ class TestHelpers(MAASTestCase):
887 'architecture': architecture,
888 'osystem': osystem,
889 'distro_series': distro_series,
890+ 'maas_version': get_maas_version(),
891 'interfaces': {},
892 }, Equals(refresh.get_sys_info()))
893
894@@ -105,6 +107,7 @@ class TestHelpers(MAASTestCase):
895 'architecture': architecture,
896 'osystem': osystem,
897 'distro_series': distro_series,
898+ 'maas_version': get_maas_version(),
899 'interfaces': {},
900 }, Equals(refresh.get_sys_info()))
901
902@@ -120,6 +123,7 @@ class TestHelpers(MAASTestCase):
903 'architecture': architecture,
904 'osystem': '',
905 'distro_series': '',
906+ 'maas_version': get_maas_version(),
907 'interfaces': {},
908 }, Equals(refresh.get_sys_info()))
909

Subscribers

People subscribed via source and target branches