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
diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
index 63e689b..1ec195c 100644
--- a/src/maasserver/models/node.py
+++ b/src/maasserver/models/node.py
@@ -4822,23 +4822,33 @@ class Controller(Node):
4822 @transactional4822 @transactional
4823 def _process_sys_info(self, response):4823 def _process_sys_info(self, response):
4824 update_fields = []4824 update_fields = []
4825 if response['hostname'] != '':4825 hostname = response.get('hostname')
4826 self.hostname = response['hostname']4826 if hostname and self.hostname != hostname:
4827 self.hostname = hostname
4827 update_fields.append('hostname')4828 update_fields.append('hostname')
4828 if response['architecture'] != '':4829 architecture = response.get('architecture')
4829 self.architecture = response['architecture']4830 if architecture and self.architecture != architecture:
4831 self.architecture = architecture
4830 update_fields.append('architecture')4832 update_fields.append('architecture')
4831 if response['osystem'] != '':4833 osystem = response.get('osystem')
4832 self.osystem = response['osystem']4834 if osystem and self.osystem != osystem:
4835 self.osystem = osystem
4833 update_fields.append('osystem')4836 update_fields.append('osystem')
4834 if response['distro_series'] != '':4837 distro_series = response.get('distro_series')
4838 if distro_series and self.distro_series != distro_series:
4835 self.distro_series = response['distro_series']4839 self.distro_series = response['distro_series']
4836 update_fields.append('distro_series')4840 update_fields.append('distro_series')
4841 maas_version = response.get('maas_version')
4842 if maas_version and self.version != maas_version:
4843 # Circular imports.
4844 from maasserver.models import ControllerInfo
4845 ControllerInfo.objects.set_version(self, maas_version)
4837 # MAAS 2.3+ will send an empty dictionary on purpose, but older4846 # MAAS 2.3+ will send an empty dictionary on purpose, but older
4838 # versions of the MAAS rack will send real data (and it might arrive4847 # versions of the MAAS rack will send real data (and it might arrive
4839 # in a more timely manner than the UpdateInterfaces call from the4848 # in a more timely manner than the UpdateInterfaces call from the
4840 # NetworksMonitoringService).4849 # NetworksMonitoringService).
4841 if response['interfaces'] != {}:4850 interfaces = response.get('interfaces', {})
4851 if len(interfaces) > 0:
4842 self.update_interfaces(response['interfaces'])4852 self.update_interfaces(response['interfaces'])
4843 if len(update_fields) > 0:4853 if len(update_fields) > 0:
4844 self.save(update_fields=update_fields)4854 self.save(update_fields=update_fields)
diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html
index ccaffa0..66619a1 100755
--- a/src/maasserver/static/partials/node-details.html
+++ b/src/maasserver/static/partials/node-details.html
@@ -321,6 +321,13 @@
321 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."321 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."
322 data-ng-show="node.node_type == 4"></i>322 data-ng-show="node.node_type == 4"></i>
323 </dd>323 </dd>
324 <dt class="two-col">MAAS Version</dt>
325 <dd class="four-col last-col" data-ng-show="node.version">
326 {$ node.version $}
327 </dd>
328 <dd class="four-col last-col u-text--subtle" data-ng-show="!node.version">
329 Unknown (&lt; 2.3.0)
330 </dd>
324 <dt class="two-col" data-ng-show="node.node_type == 2 || node.node_type == 4">Last image sync</dt>331 <dt class="two-col" data-ng-show="node.node_type == 2 || node.node_type == 4">Last image sync</dt>
325 <dd class="four-col last-col" data-ng-show="node.node_type == 2 || node.node_type == 4">332 <dd class="four-col last-col" data-ng-show="node.node_type == 2 || node.node_type == 4">
326 {$ node.last_image_sync || 'never' $}333 {$ node.last_image_sync || 'never' $}
diff --git a/src/maasserver/triggers/__init__.py b/src/maasserver/triggers/__init__.py
index 88323f2..f84a1db 100644
--- a/src/maasserver/triggers/__init__.py
+++ b/src/maasserver/triggers/__init__.py
@@ -29,21 +29,113 @@ def register_procedure(procedure):
29 cursor.execute(procedure)29 cursor.execute(procedure)
3030
3131
32def register_trigger(table, procedure, event, params=None, when="after"):32# Mappings for (postgres_event_type, maas_notification_type, pg_obj) for
33# trigger notification events. Three conventions are currently in-use in MAAS.
34
35# Event names: create/update/delete.
36# The majority of triggers use this convention; this is the default.
37EVENTS_CUD = (
38 ('insert', 'create', 'NEW'),
39 ('update', 'update', 'NEW'),
40 ('delete', 'delete', 'OLD'),
41)
42
43# Event names: insert/update/delete.
44EVENTS_IUD = (
45 ('insert', 'insert', 'NEW'),
46 ('update', 'update', 'NEW'),
47 ('delete', 'delete', 'OLD'),
48)
49
50# Event names: link/update/unlink.
51EVENTS_LUU = (
52 ('insert', 'link', 'NEW'),
53 ('update', 'update', 'NEW'),
54 ('delete', 'unlink', 'OLD'),
55)
56
57
58def register_triggers(
59 table, event_prefix, params=None, fields=None, events=None,
60 when="after"):
61 """Registers a set of triggers for insert, update, and delete.
62
63 Event names will be determined based on MAAS convention, unless the
64 convention is passed in via the `events` parameter. Predefined conventions
65 in-use in MAAS are provided via the EVENTS_* constants.
66
67 :param table: The table name to create the trigger on.
68 :param event_prefix: The event prefix for the trigger. For example, if
69 the table is maasserver_subnet, 'subnet' might be an appropriate event
70 prefix.
71 :param params: A dictionary of parameters that should be ANDed together to
72 form the initial WHEN clause.
73 :param fields: A list of fields whose values will be checked for changes
74 before the trigger fires. If None is specified, all fields in the row
75 will be checked.
76 :param events: A tuple in the format of the EVENTS_* constants indicating
77 which convention to use for notification names.
78 :param when: When the trigger should fire relative to the row update. The
79 default is AFTER, but postgresql also supports BEFORE and INSTEAD OF.
80 """
81 if events is None:
82 events = EVENTS_CUD
83 for pg_event, maas_event_type, pg_obj in events:
84 event_params = None
85 if params is not None:
86 event_params = {}
87 for key, value in params.items():
88 event_params['%s.%s' % (pg_obj, key)] = value
89 register_trigger(
90 table, '%s_%s_notify' % (event_prefix, maas_event_type),
91 pg_event, event_params, fields, when)
92
93
94def _make_when_clause(is_update, params, fields):
95 """Generates a WHEN clause for the trigger.
96
97 :param is_update: If true, this trigger is for update. (not insert/delete)
98 :param params: A dictionary of parameters that should be ANDed together to
99 form the initial WHEN clause.
100 :param fields: A list of fields whose values will be checked for changes
101 before the trigger fires. If None is specified, all fields in the row
102 will be checked.
103 :return: the WHEN clause to use in the trigger.
104 """
105 when_clause = ''
106 if params is not None or (fields is not None and is_update):
107 if params is None:
108 params = {}
109 if fields is None:
110 fields = []
111 when_clause = 'WHEN ('
112 when_clause += ' AND '.join([
113 "%s = '%s'" % (key, value)
114 for key, value in params.items()
115 ])
116 if is_update and len(fields) > 0:
117 if len(params) > 0:
118 when_clause += " AND ("
119 when_clause += ' OR '.join([
120 "NEW.%s IS DISTINCT FROM OLD.%s" % (field, field)
121 for field in fields
122 ])
123 if len(params) > 0:
124 when_clause += ")"
125 when_clause += ")"
126 return when_clause
127
128
129def register_trigger(
130 table, procedure, event, params=None, fields=None, when="after"):
33 """Register `trigger` on `table` if it doesn't exist."""131 """Register `trigger` on `table` if it doesn't exist."""
34 # Strip the "maasserver_" off the front of the table name.132 # Strip the "maasserver_" off the front of the table name.
35 table_name = table133 table_name = table
36 if table.startswith("maasserver_"):134 if table.startswith("maasserver_"):
37 table_name = table_name[11:]135 table_name = table_name[11:]
38 trigger_name = "%s_%s" % (table_name, procedure)136 trigger_name = "%s_%s" % (table_name, procedure)
39 if params is not None:137 is_update = (event == "update")
40 filter = 'WHEN (' + ''.join(138 when_clause = _make_when_clause(is_update, params, fields)
41 [
42 "%s = '%s'" % (key, value)
43 for key, value in params.items()
44 ]) + ')'
45 else:
46 filter = ''
47 trigger_sql = dedent("""\139 trigger_sql = dedent("""\
48 DROP TRIGGER IF EXISTS %s ON %s;140 DROP TRIGGER IF EXISTS %s ON %s;
49 CREATE TRIGGER %s141 CREATE TRIGGER %s
@@ -58,7 +150,7 @@ def register_trigger(table, procedure, event, params=None, when="after"):
58 when.upper(),150 when.upper(),
59 event.upper(),151 event.upper(),
60 table,152 table,
61 filter,153 when_clause,
62 procedure,154 procedure,
63 )155 )
64 with closing(connection.cursor()) as cursor:156 with closing(connection.cursor()) as cursor:
diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py
index a0256c4..3f291cb 100644
--- a/src/maasserver/triggers/tests/test_init.py
+++ b/src/maasserver/triggers/tests/test_init.py
@@ -125,6 +125,9 @@ class TestTriggersUsed(MAASServerTestCase):
125 "config_config_update_notify",125 "config_config_update_notify",
126 "config_sys_proxy_config_use_peer_proxy_insert",126 "config_sys_proxy_config_use_peer_proxy_insert",
127 "config_sys_proxy_config_use_peer_proxy_update",127 "config_sys_proxy_config_use_peer_proxy_update",
128 'controllerinfo_controllerinfo_link_notify',
129 'controllerinfo_controllerinfo_unlink_notify',
130 'controllerinfo_controllerinfo_update_notify',
128 "dhcpsnippet_dhcpsnippet_create_notify",131 "dhcpsnippet_dhcpsnippet_create_notify",
129 "dhcpsnippet_dhcpsnippet_delete_notify",132 "dhcpsnippet_dhcpsnippet_delete_notify",
130 "dhcpsnippet_dhcpsnippet_update_notify",133 "dhcpsnippet_dhcpsnippet_update_notify",
diff --git a/src/maasserver/triggers/tests/test_websocket_listener.py b/src/maasserver/triggers/tests/test_websocket_listener.py
index 8877fcc..c40a3c6 100644
--- a/src/maasserver/triggers/tests/test_websocket_listener.py
+++ b/src/maasserver/triggers/tests/test_websocket_listener.py
@@ -18,6 +18,7 @@ from maasserver.enum import (
18 NODE_TYPE,18 NODE_TYPE,
19)19)
20from maasserver.listener import PostgresListenerService20from maasserver.listener import PostgresListenerService
21from maasserver.models import ControllerInfo
21from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE22from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
22from maasserver.models.config import Config23from maasserver.models.config import Config
23from maasserver.models.node import Node24from maasserver.models.node import Node
@@ -36,7 +37,9 @@ from provisioningserver.utils.twisted import (
36 FOREVER,37 FOREVER,
37 synchronous,38 synchronous,
38)39)
40from testtools import ExpectedException
39from twisted.internet.defer import (41from twisted.internet.defer import (
42 CancelledError,
40 DeferredList,43 DeferredList,
41 DeferredQueue,44 DeferredQueue,
42 inlineCallbacks,45 inlineCallbacks,
@@ -219,6 +222,108 @@ class TestNodeListener(
219 yield listener.stopService()222 yield listener.stopService()
220223
221224
225class TestControllerListener(
226 MAASTransactionServerTestCase, TransactionalHelpersMixin):
227 """End-to-end test of both the listeners code and the triggers code."""
228
229 scenarios = (
230 ('rack', {
231 'params': {'node_type': NODE_TYPE.RACK_CONTROLLER},
232 'listener': 'controller',
233 }),
234 ('region_and_rack', {
235 'params': {'node_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER},
236 'listener': 'controller',
237 }),
238 ('region', {
239 'params': {'node_type': NODE_TYPE.REGION_CONTROLLER},
240 'listener': 'controller',
241 }),
242 )
243
244 def set_version(self, controller, version):
245 ControllerInfo.objects.set_version(controller, version)
246
247 def set_interface_update_info(self, controller, interfaces, hints):
248 ControllerInfo.objects.set_interface_update_info(
249 controller, interfaces, hints)
250
251 def delete_controllerinfo(self, controller):
252 ControllerInfo.objects.filter(node=controller).delete()
253
254 @wait_for_reactor
255 @inlineCallbacks
256 def test__calls_handler_on_controllerinfo_insert(self):
257 yield deferToDatabase(register_websocket_triggers)
258 listener = self.make_listener_without_delay()
259 dv = DeferredValue()
260 params = self.params.copy()
261 controller = yield deferToDatabase(self.create_node, params)
262 listener.register(self.listener, lambda *args: dv.set(args))
263 yield listener.startService()
264 try:
265 yield deferToDatabase(
266 self.set_version, controller, factory.make_string())
267 yield dv.get(timeout=2)
268 finally:
269 yield listener.stopService()
270
271 @wait_for_reactor
272 @inlineCallbacks
273 def test__calls_handler_on_controllerinfo_version_update(self):
274 yield deferToDatabase(register_websocket_triggers)
275 listener = self.make_listener_without_delay()
276 dv = DeferredValue()
277 params = self.params.copy()
278 controller = yield deferToDatabase(self.create_node, params)
279 yield deferToDatabase(self.set_version, controller, '')
280 listener.register(self.listener, lambda *args: dv.set(args))
281 yield listener.startService()
282 try:
283 yield deferToDatabase(
284 self.set_version, controller, factory.make_string())
285 yield dv.get(timeout=2)
286 finally:
287 yield listener.stopService()
288
289 @wait_for_reactor
290 @inlineCallbacks
291 def test__skips_notify_on_controllerinfo_interface_update(self):
292 yield deferToDatabase(register_websocket_triggers)
293 listener = self.make_listener_without_delay()
294 dv = DeferredValue()
295 params = self.params.copy()
296 controller = yield deferToDatabase(self.create_node, params)
297 yield deferToDatabase(self.set_version, controller, '')
298 listener.register(self.listener, lambda *args: dv.set(args))
299 yield listener.startService()
300 try:
301 yield deferToDatabase(
302 self.set_interface_update_info, controller, '{]', '{}')
303 with ExpectedException(CancelledError):
304 yield dv.get(timeout=0.2)
305 finally:
306 yield listener.stopService()
307
308 @wait_for_reactor
309 @inlineCallbacks
310 def test__calls_handler_on_controllerinfo_delete(self):
311 yield deferToDatabase(register_websocket_triggers)
312 listener = self.make_listener_without_delay()
313 dv = DeferredValue()
314 params = self.params.copy()
315 controller = yield deferToDatabase(self.create_node, params)
316 yield deferToDatabase(self.set_version, controller, '')
317 listener.register(self.listener, lambda *args: dv.set(args))
318 yield listener.startService()
319 try:
320 yield deferToDatabase(
321 self.delete_controllerinfo, controller)
322 yield dv.get(timeout=2)
323 finally:
324 yield listener.stopService()
325
326
222class TestDeviceWithParentListener(327class TestDeviceWithParentListener(
223 MAASTransactionServerTestCase, TransactionalHelpersMixin):328 MAASTransactionServerTestCase, TransactionalHelpersMixin):
224 """End-to-end test of both the listeners code and the triggers code."""329 """End-to-end test of both the listeners code and the triggers code."""
diff --git a/src/maasserver/triggers/websocket.py b/src/maasserver/triggers/websocket.py
index 0e9df27..0ec50e4 100644
--- a/src/maasserver/triggers/websocket.py
+++ b/src/maasserver/triggers/websocket.py
@@ -22,8 +22,11 @@ from maasserver.enum import (
22 NODE_TYPE,22 NODE_TYPE,
23)23)
24from maasserver.triggers import (24from maasserver.triggers import (
25 EVENTS_IUD,
26 EVENTS_LUU,
25 register_procedure,27 register_procedure,
26 register_trigger,28 register_trigger,
29 register_triggers,
27)30)
28from maasserver.utils.orm import transactional31from maasserver.utils.orm import transactional
2932
@@ -1175,21 +1178,27 @@ def register_websocket_triggers():
1175 '%s_delete_notify' % proc_name_prefix,1178 '%s_delete_notify' % proc_name_prefix,
1176 '%s_delete' % event_name_prefix,1179 '%s_delete' % event_name_prefix,
1177 'OLD.system_id'))1180 'OLD.system_id'))
1178 register_trigger(1181 register_triggers(
1179 "maasserver_node",1182 "maasserver_node", proc_name_prefix, {'node_type': node_type})
1180 "%s_create_notify" % proc_name_prefix,1183
1181 "insert",1184 # ControllerInfo notifications
1182 {'NEW.node_type': node_type})1185 register_procedure(
1183 register_trigger(1186 render_node_related_notification_procedure(
1184 "maasserver_node",1187 'controllerinfo_link_notify', 'NEW.node_id'))
1185 "%s_update_notify" % proc_name_prefix,1188 register_procedure(
1186 "update",1189 render_node_related_notification_procedure(
1187 {'NEW.node_type': node_type})1190 'controllerinfo_update_notify', 'NEW.node_id'))
1188 register_trigger(1191 register_procedure(
1189 "maasserver_node",1192 render_node_related_notification_procedure(
1190 "%s_delete_notify" % proc_name_prefix,1193 'controllerinfo_unlink_notify', 'OLD.node_id'))
1191 "delete",1194 register_triggers(
1192 {'OLD.node_type': node_type})1195 "maasserver_controllerinfo",
1196 "controllerinfo",
1197 # Trigger only fires for version information on update; it doesn't
1198 # care when the other metadata is updated.
1199 fields=('version',),
1200 events=EVENTS_LUU
1201 )
11931202
1194 # Config table1203 # Config table
1195 register_procedure(1204 register_procedure(
@@ -1201,12 +1210,7 @@ def register_websocket_triggers():
1201 register_procedure(1210 register_procedure(
1202 render_notification_procedure(1211 render_notification_procedure(
1203 'config_delete_notify', 'config_delete', 'OLD.id'))1212 'config_delete_notify', 'config_delete', 'OLD.id'))
1204 register_trigger(1213 register_triggers("maasserver_config", "config")
1205 "maasserver_config", "config_create_notify", "insert")
1206 register_trigger(
1207 "maasserver_config", "config_update_notify", "update")
1208 register_trigger(
1209 "maasserver_config", "config_delete_notify", "delete")
12101214
1211 # Device Node types1215 # Device Node types
1212 register_procedure(1216 register_procedure(
@@ -1218,15 +1222,8 @@ def register_websocket_triggers():
1218 register_procedure(1222 register_procedure(
1219 render_device_notification_procedure(1223 render_device_notification_procedure(
1220 'device_delete_notify', 'device_delete', 'OLD'))1224 'device_delete_notify', 'device_delete', 'OLD'))
1221 register_trigger(1225 register_triggers(
1222 "maasserver_node", "device_create_notify", "insert",1226 "maasserver_node", "device", {'node_type': NODE_TYPE.DEVICE})
1223 {'NEW.node_type': NODE_TYPE.DEVICE})
1224 register_trigger(
1225 "maasserver_node", "device_update_notify", "update",
1226 {'NEW.node_type': NODE_TYPE.DEVICE})
1227 register_trigger(
1228 "maasserver_node", "device_delete_notify", "delete",
1229 {'OLD.node_type': NODE_TYPE.DEVICE})
12301227
1231 # VLAN table1228 # VLAN table
1232 register_procedure(1229 register_procedure(
@@ -1238,12 +1235,7 @@ def register_websocket_triggers():
1238 register_procedure(1235 register_procedure(
1239 render_notification_procedure(1236 render_notification_procedure(
1240 'vlan_delete_notify', 'vlan_delete', 'OLD.id'))1237 'vlan_delete_notify', 'vlan_delete', 'OLD.id'))
1241 register_trigger(1238 register_triggers("maasserver_vlan", "vlan")
1242 "maasserver_vlan", "vlan_create_notify", "insert")
1243 register_trigger(
1244 "maasserver_vlan", "vlan_update_notify", "update")
1245 register_trigger(
1246 "maasserver_vlan", "vlan_delete_notify", "delete")
12471239
1248 # IPRange table1240 # IPRange table
1249 register_procedure(1241 register_procedure(
@@ -1255,12 +1247,7 @@ def register_websocket_triggers():
1255 register_procedure(1247 register_procedure(
1256 render_notification_procedure(1248 render_notification_procedure(
1257 'iprange_delete_notify', 'iprange_delete', 'OLD.id'))1249 'iprange_delete_notify', 'iprange_delete', 'OLD.id'))
1258 register_trigger(1250 register_triggers("maasserver_iprange", "iprange")
1259 "maasserver_iprange", "iprange_create_notify", "insert")
1260 register_trigger(
1261 "maasserver_iprange", "iprange_update_notify", "update")
1262 register_trigger(
1263 "maasserver_iprange", "iprange_delete_notify", "delete")
12641251
1265 # Neighbour table1252 # Neighbour table
1266 register_procedure(1253 register_procedure(
@@ -1272,12 +1259,7 @@ def register_websocket_triggers():
1272 register_procedure(1259 register_procedure(
1273 render_notification_procedure(1260 render_notification_procedure(
1274 'neighbour_delete_notify', 'neighbour_delete', 'OLD.ip'))1261 'neighbour_delete_notify', 'neighbour_delete', 'OLD.ip'))
1275 register_trigger(1262 register_triggers("maasserver_neighbour", "neighbour")
1276 "maasserver_neighbour", "neighbour_create_notify", "insert")
1277 register_trigger(
1278 "maasserver_neighbour", "neighbour_update_notify", "update")
1279 register_trigger(
1280 "maasserver_neighbour", "neighbour_delete_notify", "delete")
12811263
1282 # StaticRoute table1264 # StaticRoute table
1283 register_procedure(1265 register_procedure(
@@ -1289,12 +1271,7 @@ def register_websocket_triggers():
1289 register_procedure(1271 register_procedure(
1290 render_notification_procedure(1272 render_notification_procedure(
1291 'staticroute_delete_notify', 'staticroute_delete', 'OLD.id'))1273 'staticroute_delete_notify', 'staticroute_delete', 'OLD.id'))
1292 register_trigger(1274 register_triggers("maasserver_staticroute", "staticroute")
1293 "maasserver_staticroute", "staticroute_create_notify", "insert")
1294 register_trigger(
1295 "maasserver_staticroute", "staticroute_update_notify", "update")
1296 register_trigger(
1297 "maasserver_staticroute", "staticroute_delete_notify", "delete")
12981275
1299 # Fabric table1276 # Fabric table
1300 register_procedure(1277 register_procedure(
@@ -1306,12 +1283,7 @@ def register_websocket_triggers():
1306 register_procedure(1283 register_procedure(
1307 render_notification_procedure(1284 render_notification_procedure(
1308 'fabric_delete_notify', 'fabric_delete', 'OLD.id'))1285 'fabric_delete_notify', 'fabric_delete', 'OLD.id'))
1309 register_trigger(1286 register_triggers("maasserver_fabric", "fabric")
1310 "maasserver_fabric", "fabric_create_notify", "insert")
1311 register_trigger(
1312 "maasserver_fabric", "fabric_update_notify", "update")
1313 register_trigger(
1314 "maasserver_fabric", "fabric_delete_notify", "delete")
13151287
1316 # Space table1288 # Space table
1317 register_procedure(1289 register_procedure(
@@ -1323,12 +1295,7 @@ def register_websocket_triggers():
1323 register_procedure(1295 register_procedure(
1324 render_notification_procedure(1296 render_notification_procedure(
1325 'space_delete_notify', 'space_delete', 'OLD.id'))1297 'space_delete_notify', 'space_delete', 'OLD.id'))
1326 register_trigger(1298 register_triggers("maasserver_space", "space")
1327 "maasserver_space", "space_create_notify", "insert")
1328 register_trigger(
1329 "maasserver_space", "space_update_notify", "update")
1330 register_trigger(
1331 "maasserver_space", "space_delete_notify", "delete")
13321299
1333 # Subnet table1300 # Subnet table
1334 register_procedure(1301 register_procedure(
@@ -1340,12 +1307,7 @@ def register_websocket_triggers():
1340 register_procedure(1307 register_procedure(
1341 render_notification_procedure(1308 render_notification_procedure(
1342 'subnet_delete_notify', 'subnet_delete', 'OLD.id'))1309 'subnet_delete_notify', 'subnet_delete', 'OLD.id'))
1343 register_trigger(1310 register_triggers("maasserver_subnet", "subnet")
1344 "maasserver_subnet", "subnet_create_notify", "insert")
1345 register_trigger(
1346 "maasserver_subnet", "subnet_update_notify", "update")
1347 register_trigger(
1348 "maasserver_subnet", "subnet_delete_notify", "delete")
13491311
1350 # Subnet node notifications1312 # Subnet node notifications
1351 register_procedure(1313 register_procedure(
@@ -1421,15 +1383,8 @@ def register_websocket_triggers():
1421 register_procedure(1383 register_procedure(
1422 STATIC_IP_ADDRESS_DOMAIN_NOTIFY % (1384 STATIC_IP_ADDRESS_DOMAIN_NOTIFY % (
1423 'ipaddress_domain_delete_notify', 'OLD.id'))1385 'ipaddress_domain_delete_notify', 'OLD.id'))
1424 register_trigger(1386 register_triggers(
1425 "maasserver_staticipaddress",1387 "maasserver_staticipaddress", "ipaddress_domain", events=EVENTS_IUD)
1426 "ipaddress_domain_insert_notify", "insert")
1427 register_trigger(
1428 "maasserver_staticipaddress",
1429 "ipaddress_domain_update_notify", "update")
1430 register_trigger(
1431 "maasserver_staticipaddress",
1432 "ipaddress_domain_delete_notify", "delete")
14331388
1434 # IP range subnet notifications1389 # IP range subnet notifications
1435 register_procedure(1390 register_procedure(
@@ -1438,15 +1393,8 @@ def register_websocket_triggers():
1438 IP_RANGE_SUBNET_UPDATE_NOTIFY % 'iprange_subnet_update_notify')1393 IP_RANGE_SUBNET_UPDATE_NOTIFY % 'iprange_subnet_update_notify')
1439 register_procedure(1394 register_procedure(
1440 IP_RANGE_SUBNET_DELETE_NOTIFY % 'iprange_subnet_delete_notify')1395 IP_RANGE_SUBNET_DELETE_NOTIFY % 'iprange_subnet_delete_notify')
1441 register_trigger(1396 register_triggers(
1442 "maasserver_iprange",1397 "maasserver_iprange", "iprange_subnet", events=EVENTS_IUD)
1443 "iprange_subnet_insert_notify", "insert")
1444 register_trigger(
1445 "maasserver_iprange",
1446 "iprange_subnet_update_notify", "update")
1447 register_trigger(
1448 "maasserver_iprange",
1449 "iprange_subnet_delete_notify", "delete")
14501398
1451 # Pod notifications1399 # Pod notifications
1452 register_procedure(1400 register_procedure(
@@ -1457,15 +1405,7 @@ def register_websocket_triggers():
1457 BMC_TYPE.POD, BMC_TYPE.POD, BMC_TYPE.BMC))1405 BMC_TYPE.POD, BMC_TYPE.POD, BMC_TYPE.BMC))
1458 register_procedure(1406 register_procedure(
1459 POD_DELETE_NOTIFY % ('pod_delete_notify', BMC_TYPE.POD))1407 POD_DELETE_NOTIFY % ('pod_delete_notify', BMC_TYPE.POD))
1460 register_trigger(1408 register_triggers("maasserver_bmc", "pod", events=EVENTS_IUD)
1461 "maasserver_bmc",
1462 "pod_insert_notify", "insert")
1463 register_trigger(
1464 "maasserver_bmc",
1465 "pod_update_notify", "update")
1466 register_trigger(
1467 "maasserver_bmc",
1468 "pod_delete_notify", "delete")
14691409
1470 # Node pod notifications1410 # Node pod notifications
1471 register_procedure(1411 register_procedure(
@@ -1475,15 +1415,7 @@ def register_websocket_triggers():
1475 'node_pod_update_notify', BMC_TYPE.POD, BMC_TYPE.POD))1415 'node_pod_update_notify', BMC_TYPE.POD, BMC_TYPE.POD))
1476 register_procedure(1416 register_procedure(
1477 NODE_POD_DELETE_NOTIFY % ('node_pod_delete_notify', BMC_TYPE.POD))1417 NODE_POD_DELETE_NOTIFY % ('node_pod_delete_notify', BMC_TYPE.POD))
1478 register_trigger(1418 register_triggers("maasserver_node", "node_pod", events=EVENTS_IUD)
1479 "maasserver_node",
1480 "node_pod_insert_notify", "insert")
1481 register_trigger(
1482 "maasserver_node",
1483 "node_pod_update_notify", "update")
1484 register_trigger(
1485 "maasserver_node",
1486 "node_pod_delete_notify", "delete")
14871419
1488 # DNSData table1420 # DNSData table
1489 register_procedure(1421 register_procedure(
@@ -1496,15 +1428,8 @@ def register_websocket_triggers():
1496 register_procedure(1428 register_procedure(
1497 DNSDATA_DOMAIN_NOTIFY % (1429 DNSDATA_DOMAIN_NOTIFY % (
1498 'dnsdata_domain_delete_notify', 'OLD.dnsresource_id'))1430 'dnsdata_domain_delete_notify', 'OLD.dnsresource_id'))
1499 register_trigger(1431 register_triggers(
1500 "maasserver_dnsdata",1432 "maasserver_dnsdata", "dnsdata_domain", events=EVENTS_IUD)
1501 "dnsdata_domain_insert_notify", "insert")
1502 register_trigger(
1503 "maasserver_dnsdata",
1504 "dnsdata_domain_update_notify", "update")
1505 register_trigger(
1506 "maasserver_dnsdata",
1507 "dnsdata_domain_delete_notify", "delete")
15081433
1509 # DNSResource table1434 # DNSResource table
1510 register_procedure(1435 register_procedure(
@@ -1515,15 +1440,8 @@ def register_websocket_triggers():
1515 register_procedure(1440 register_procedure(
1516 DNSRESOURCE_DOMAIN_NOTIFY % (1441 DNSRESOURCE_DOMAIN_NOTIFY % (
1517 'dnsresource_domain_delete_notify', 'OLD'))1442 'dnsresource_domain_delete_notify', 'OLD'))
1518 register_trigger(1443 register_triggers(
1519 "maasserver_dnsresource",1444 "maasserver_dnsresource", "dnsresource_domain", events=EVENTS_IUD)
1520 "dnsresource_domain_insert_notify", "insert")
1521 register_trigger(
1522 "maasserver_dnsresource",
1523 "dnsresource_domain_update_notify", "update")
1524 register_trigger(
1525 "maasserver_dnsresource",
1526 "dnsresource_domain_delete_notify", "delete")
15271445
1528 # Domain table1446 # Domain table
1529 register_procedure(1447 register_procedure(
@@ -1542,12 +1460,7 @@ def register_websocket_triggers():
1542 'domain_delete_notify', 'domain_delete', 'OLD.id'))1460 'domain_delete_notify', 'domain_delete', 'OLD.id'))
1543 register_trigger(1461 register_trigger(
1544 "maasserver_domain", "domain_node_update_notify", "update")1462 "maasserver_domain", "domain_node_update_notify", "update")
1545 register_trigger(1463 register_triggers("maasserver_domain", "domain")
1546 "maasserver_domain", "domain_create_notify", "insert")
1547 register_trigger(
1548 "maasserver_domain", "domain_update_notify", "update")
1549 register_trigger(
1550 "maasserver_domain", "domain_delete_notify", "delete")
15511464
1552 # MAC static ip address table, update to linked domain via dnsresource1465 # MAC static ip address table, update to linked domain via dnsresource
1553 register_procedure(1466 register_procedure(
@@ -1573,12 +1486,7 @@ def register_websocket_triggers():
1573 register_procedure(1486 register_procedure(
1574 render_notification_procedure(1487 render_notification_procedure(
1575 'zone_delete_notify', 'zone_delete', 'OLD.id'))1488 'zone_delete_notify', 'zone_delete', 'OLD.id'))
1576 register_trigger(1489 register_triggers("maasserver_zone", "zone")
1577 "maasserver_zone", "zone_create_notify", "insert")
1578 register_trigger(
1579 "maasserver_zone", "zone_update_notify", "update")
1580 register_trigger(
1581 "maasserver_zone", "zone_delete_notify", "delete")
15821490
1583 # Service table1491 # Service table
1584 register_procedure(1492 register_procedure(
@@ -1590,12 +1498,7 @@ def register_websocket_triggers():
1590 register_procedure(1498 register_procedure(
1591 render_notification_procedure(1499 render_notification_procedure(
1592 'service_delete_notify', 'service_delete', 'OLD.id'))1500 'service_delete_notify', 'service_delete', 'OLD.id'))
1593 register_trigger(1501 register_triggers("maasserver_service", "service")
1594 "maasserver_service", "service_create_notify", "insert")
1595 register_trigger(
1596 "maasserver_service", "service_update_notify", "update")
1597 register_trigger(
1598 "maasserver_service", "service_delete_notify", "delete")
15991502
1600 # Tag table1503 # Tag table
1601 register_procedure(1504 register_procedure(
@@ -1607,12 +1510,7 @@ def register_websocket_triggers():
1607 register_procedure(1510 register_procedure(
1608 render_notification_procedure(1511 render_notification_procedure(
1609 'tag_delete_notify', 'tag_delete', 'OLD.id'))1512 'tag_delete_notify', 'tag_delete', 'OLD.id'))
1610 register_trigger(1513 register_triggers("maasserver_tag", "tag")
1611 "maasserver_tag", "tag_create_notify", "insert")
1612 register_trigger(
1613 "maasserver_tag", "tag_update_notify", "update")
1614 register_trigger(
1615 "maasserver_tag", "tag_delete_notify", "delete")
16161514
1617 # Node tag link table1515 # Node tag link table
1618 register_procedure(1516 register_procedure(
@@ -1647,12 +1545,7 @@ def register_websocket_triggers():
1647 register_procedure(1545 register_procedure(
1648 render_notification_procedure(1546 render_notification_procedure(
1649 'user_delete_notify', 'user_delete', 'OLD.id'))1547 'user_delete_notify', 'user_delete', 'OLD.id'))
1650 register_trigger(1548 register_triggers("auth_user", "user")
1651 "auth_user", "user_create_notify", "insert")
1652 register_trigger(
1653 "auth_user", "user_update_notify", "update")
1654 register_trigger(
1655 "auth_user", "user_delete_notify", "delete")
16561549
1657 # Events table1550 # Events table
1658 register_procedure(1551 register_procedure(
@@ -1928,12 +1821,7 @@ def register_websocket_triggers():
1928 register_procedure(1821 register_procedure(
1929 render_notification_procedure(1822 render_notification_procedure(
1930 'sshkey_delete_notify', 'sshkey_delete', 'OLD.id'))1823 'sshkey_delete_notify', 'sshkey_delete', 'OLD.id'))
1931 register_trigger(1824 register_triggers("maasserver_sshkey", "sshkey")
1932 "maasserver_sshkey", "sshkey_create_notify", "insert")
1933 register_trigger(
1934 "maasserver_sshkey", "sshkey_update_notify", "update")
1935 register_trigger(
1936 "maasserver_sshkey", "sshkey_delete_notify", "delete")
19371825
1938 # SSL key table, update to linked user.1826 # SSL key table, update to linked user.
1939 register_procedure(1827 register_procedure(
@@ -1957,12 +1845,7 @@ def register_websocket_triggers():
1957 register_procedure(1845 register_procedure(
1958 render_notification_procedure(1846 render_notification_procedure(
1959 'dhcpsnippet_delete_notify', 'dhcpsnippet_delete', 'OLD.id'))1847 'dhcpsnippet_delete_notify', 'dhcpsnippet_delete', 'OLD.id'))
1960 register_trigger(1848 register_triggers("maasserver_dhcpsnippet", "dhcpsnippet")
1961 "maasserver_dhcpsnippet", "dhcpsnippet_create_notify", "insert")
1962 register_trigger(
1963 "maasserver_dhcpsnippet", "dhcpsnippet_update_notify", "update")
1964 register_trigger(
1965 "maasserver_dhcpsnippet", "dhcpsnippet_delete_notify", "delete")
19661849
1967 # PackageRepository table1850 # PackageRepository table
1968 register_procedure(1851 register_procedure(
@@ -1977,15 +1860,7 @@ def register_websocket_triggers():
1977 render_notification_procedure(1860 render_notification_procedure(
1978 'packagerepository_delete_notify', 'packagerepository_delete',1861 'packagerepository_delete_notify', 'packagerepository_delete',
1979 'OLD.id'))1862 'OLD.id'))
1980 register_trigger(1863 register_triggers("maasserver_packagerepository", "packagerepository")
1981 "maasserver_packagerepository", "packagerepository_create_notify",
1982 "insert")
1983 register_trigger(
1984 "maasserver_packagerepository", "packagerepository_update_notify",
1985 "update")
1986 register_trigger(
1987 "maasserver_packagerepository", "packagerepository_delete_notify",
1988 "delete")
19891864
1990 # Node type change.1865 # Node type change.
1991 register_procedure(node_type_change())1866 register_procedure(node_type_change())
@@ -2002,12 +1877,7 @@ def register_websocket_triggers():
2002 register_procedure(1877 register_procedure(
2003 render_notification_procedure(1878 render_notification_procedure(
2004 'notification_delete_notify', 'notification_delete', 'OLD.id'))1879 'notification_delete_notify', 'notification_delete', 'OLD.id'))
2005 register_trigger(1880 register_triggers("maasserver_notification", "notification")
2006 "maasserver_notification", "notification_create_notify", "insert")
2007 register_trigger(
2008 "maasserver_notification", "notification_update_notify", "update")
2009 register_trigger(
2010 "maasserver_notification", "notification_delete_notify", "delete")
20111881
2012 # NotificationDismissal table.1882 # NotificationDismissal table.
2013 register_procedure(1883 register_procedure(
@@ -2028,6 +1898,4 @@ def register_websocket_triggers():
2028 register_procedure(1898 register_procedure(
2029 render_notification_procedure(1899 render_notification_procedure(
2030 'script_delete_notify', 'script_delete', 'OLD.id'))1900 'script_delete_notify', 'script_delete', 'OLD.id'))
2031 register_trigger('metadataserver_script', 'script_create_notify', 'insert')1901 register_triggers('metadataserver_script', 'script')
2032 register_trigger('metadataserver_script', 'script_update_notify', 'update')
2033 register_trigger('metadataserver_script', 'script_delete_notify', 'delete')
diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py
index ca85333..6edc6f0 100644
--- a/src/maasserver/websockets/handlers/controller.py
+++ b/src/maasserver/websockets/handlers/controller.py
@@ -23,7 +23,9 @@ class ControllerHandler(MachineHandler):
23 class Meta(MachineHandler.Meta):23 class Meta(MachineHandler.Meta):
24 abstract = False24 abstract = False
25 queryset = node_prefetch(25 queryset = node_prefetch(
26 Controller.controllers.all().prefetch_related("interface_set"))26 Controller.controllers.all().prefetch_related("interface_set"),
27 'controllerinfo'
28 )
27 allowed_methods = [29 allowed_methods = [
28 'list',30 'list',
29 'get',31 'get',
@@ -89,6 +91,7 @@ class ControllerHandler(MachineHandler):
8991
90 def dehydrate(self, obj, data, for_list=False):92 def dehydrate(self, obj, data, for_list=False):
91 data = super().dehydrate(obj, data, for_list=for_list)93 data = super().dehydrate(obj, data, for_list=for_list)
94 data["version"] = obj.version
92 data["vlan_ids"] = [95 data["vlan_ids"] = [
93 interface.vlan_id96 interface.vlan_id
94 for interface in obj.interface_set.all()97 for interface in obj.interface_set.all()
diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
index 12bb3c3..5014fa8 100644
--- a/src/maasserver/websockets/handlers/node.py
+++ b/src/maasserver/websockets/handlers/node.py
@@ -45,10 +45,10 @@ from metadataserver.enum import RESULT_TYPE
45from provisioningserver.tags import merge_details_cleanly45from provisioningserver.tags import merge_details_cleanly
4646
4747
48def node_prefetch(queryset):48def node_prefetch(queryset, *args):
49 return (49 return (
50 queryset50 queryset
51 .select_related('boot_interface', 'owner', 'zone', 'domain')51 .select_related('boot_interface', 'owner', 'zone', 'domain', *args)
52 .prefetch_related('blockdevice_set__iscsiblockdevice')52 .prefetch_related('blockdevice_set__iscsiblockdevice')
53 .prefetch_related('blockdevice_set__physicalblockdevice')53 .prefetch_related('blockdevice_set__physicalblockdevice')
54 .prefetch_related('blockdevice_set__virtualblockdevice')54 .prefetch_related('blockdevice_set__virtualblockdevice')
diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py
index 111ed57..ad628e9 100644
--- a/src/maasserver/websockets/handlers/tests/test_controller.py
+++ b/src/maasserver/websockets/handlers/tests/test_controller.py
@@ -7,6 +7,7 @@ __all__ = []
77
8from maasserver.enum import NODE_TYPE8from maasserver.enum import NODE_TYPE
9from maasserver.forms import ControllerForm9from maasserver.forms import ControllerForm
10from maasserver.models import ControllerInfo
10from maasserver.testing.factory import factory11from maasserver.testing.factory import factory
11from maasserver.testing.testcase import MAASServerTestCase12from maasserver.testing.testcase import MAASServerTestCase
12from maasserver.websockets.base import dehydrate_datetime13from maasserver.websockets.base import dehydrate_datetime
@@ -96,6 +97,15 @@ class TestControllerHandler(MAASServerTestCase):
96 handler = ControllerHandler(owner, {})97 handler = ControllerHandler(owner, {})
97 self.assertTrue(handler.dehydrate_show_os_info(rack))98 self.assertTrue(handler.dehydrate_show_os_info(rack))
9899
100 def test_dehydrate_includes_version(self):
101 owner = factory.make_admin()
102 handler = ControllerHandler(owner, {})
103 rack = factory.make_RackController()
104 version = factory.make_string()
105 ControllerInfo.objects.set_version(rack, version)
106 result = handler.list({})
107 self.assertEqual(version, result[0].get('version'))
108
99109
100class TestControllerHandlerScenarios(MAASServerTestCase):110class TestControllerHandlerScenarios(MAASServerTestCase):
101111
diff --git a/src/provisioningserver/refresh/__init__.py b/src/provisioningserver/refresh/__init__.py
index a377038..bf05f10 100644
--- a/src/provisioningserver/refresh/__init__.py
+++ b/src/provisioningserver/refresh/__init__.py
@@ -2,6 +2,7 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Functionality to refresh rack controller hardware and networking details."""4"""Functionality to refresh rack controller hardware and networking details."""
5
5import os6import os
6import socket7import socket
7import stat8import stat
@@ -25,6 +26,7 @@ from provisioningserver.utils.shell import (
25 ExternalProcessError,26 ExternalProcessError,
26)27)
27from provisioningserver.utils.twisted import synchronous28from provisioningserver.utils.twisted import synchronous
29from provisioningserver.utils.version import get_maas_version
2830
2931
30maaslog = get_maas_logger("refresh")32maaslog = get_maas_logger("refresh")
@@ -77,6 +79,7 @@ def get_sys_info():
77 'architecture': get_architecture(),79 'architecture': get_architecture(),
78 'osystem': osystem,80 'osystem': osystem,
79 'distro_series': distro_series,81 'distro_series': distro_series,
82 'maas_version': get_maas_version(),
80 # In MAAS 2.3+, the NetworksMonitoringService is solely responsible for83 # In MAAS 2.3+, the NetworksMonitoringService is solely responsible for
81 # interface update, because interface updates need to include beaconing84 # interface update, because interface updates need to include beaconing
82 # data. The key and empty dictionary are necessary for backward85 # data. The key and empty dictionary are necessary for backward
diff --git a/src/provisioningserver/refresh/tests/test_refresh.py b/src/provisioningserver/refresh/tests/test_refresh.py
index ff6b759..d03cac1 100644
--- a/src/provisioningserver/refresh/tests/test_refresh.py
+++ b/src/provisioningserver/refresh/tests/test_refresh.py
@@ -25,6 +25,7 @@ from provisioningserver.refresh.maas_api_helper import (
25 MD_VERSION,25 MD_VERSION,
26 SignalException,26 SignalException,
27)27)
28from provisioningserver.utils.version import get_maas_version
28from testtools.matchers import (29from testtools.matchers import (
29 Contains,30 Contains,
30 DirExists,31 DirExists,
@@ -82,6 +83,7 @@ class TestHelpers(MAASTestCase):
82 'architecture': architecture,83 'architecture': architecture,
83 'osystem': osystem,84 'osystem': osystem,
84 'distro_series': distro_series,85 'distro_series': distro_series,
86 'maas_version': get_maas_version(),
85 'interfaces': {},87 'interfaces': {},
86 }, Equals(refresh.get_sys_info()))88 }, Equals(refresh.get_sys_info()))
8789
@@ -105,6 +107,7 @@ class TestHelpers(MAASTestCase):
105 'architecture': architecture,107 'architecture': architecture,
106 'osystem': osystem,108 'osystem': osystem,
107 'distro_series': distro_series,109 'distro_series': distro_series,
110 'maas_version': get_maas_version(),
108 'interfaces': {},111 'interfaces': {},
109 }, Equals(refresh.get_sys_info()))112 }, Equals(refresh.get_sys_info()))
110113
@@ -120,6 +123,7 @@ class TestHelpers(MAASTestCase):
120 'architecture': architecture,123 'architecture': architecture,
121 'osystem': '',124 'osystem': '',
122 'distro_series': '',125 'distro_series': '',
126 'maas_version': get_maas_version(),
123 'interfaces': {},127 'interfaces': {},
124 }, Equals(refresh.get_sys_info()))128 }, Equals(refresh.get_sys_info()))
125129

Subscribers

People subscribed via source and target branches