Merge ~ltrager/maas:2.3_1759091 into maas:2.3

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 4e69f4442de033490b074e3a1fa5187ded0386de
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:2.3_1759091
Merge into: maas:2.3
Prerequisite: ~ltrager/maas:2.3_1752754
Diff against target: 992 lines (+325/-152)
16 files modified
src/maasserver/third_party_drivers.py (+3/-2)
src/maasserver/websockets/base.py (+8/-4)
src/maasserver/websockets/handlers/controller.py (+17/-8)
src/maasserver/websockets/handlers/device.py (+15/-7)
src/maasserver/websockets/handlers/event.py (+1/-1)
src/maasserver/websockets/handlers/machine.py (+36/-30)
src/maasserver/websockets/handlers/node.py (+69/-50)
src/maasserver/websockets/handlers/notification.py (+2/-2)
src/maasserver/websockets/handlers/sshkey.py (+1/-1)
src/maasserver/websockets/handlers/switch.py (+7/-3)
src/maasserver/websockets/handlers/tests/test_controller.py (+69/-1)
src/maasserver/websockets/handlers/tests/test_device.py (+19/-4)
src/maasserver/websockets/handlers/tests/test_machine.py (+54/-15)
src/maasserver/websockets/handlers/tests/test_switch.py (+1/-20)
src/maasserver/websockets/handlers/user.py (+3/-3)
src/maasserver/websockets/tests/test_base.py (+20/-1)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+368115@code.launchpad.net

Commit message

LP: #1759091 - Limit the amount of queries used during a list operation.

Handlers can now specify a list_queryset which is used when the client calls
list(). The machine, device, and controller handlers have been updated to
only output data needed for the listing page for that node type. All node
types now have their query count tested for list and get.

Backport of 3a0114c for LP: #1830365

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :
~ltrager/maas:2.3_1759091 updated
4e69f44... by Lee Trager

Fix failing tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/third_party_drivers.py b/src/maasserver/third_party_drivers.py
2index f3480cf..91818a7 100644
3--- a/src/maasserver/third_party_drivers.py
4+++ b/src/maasserver/third_party_drivers.py
5@@ -155,13 +155,14 @@ def populate_kernel_opts(driver):
6 return driver
7
8
9-def get_third_party_driver(node):
10+def get_third_party_driver(node, detected_aliases=None):
11 """Determine which, if any, third party driver is required.
12
13 Use the node's modaliases strings to determine if a third party
14 driver is required.
15 """
16- detected_aliases = node_modaliases(node)
17+ if detected_aliases is None:
18+ detected_aliases = node_modaliases(node)
19
20 third_party_drivers_config = DriversConfig.load_from_cache()
21 third_party_drivers = third_party_drivers_config['drivers']
22diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py
23index 53fda92..064f1e0 100644
24--- a/src/maasserver/websockets/base.py
25+++ b/src/maasserver/websockets/base.py
26@@ -80,6 +80,7 @@ class HandlerOptions(object):
27 handler_name = None
28 object_class = None
29 queryset = None
30+ list_queryset = None
31 pk = 'id'
32 pk_type = int
33 fields = None
34@@ -280,19 +281,22 @@ class Handler(metaclass=HandlerMetaclass):
35 })
36 pk = params[self._meta.pk]
37 try:
38- obj = self.get_queryset().get(**{
39+ obj = self.get_queryset(for_list=False).get(**{
40 self._meta.pk: pk,
41 })
42 except self._meta.object_class.DoesNotExist:
43 raise HandlerDoesNotExistError(pk)
44 return obj
45
46- def get_queryset(self):
47+ def get_queryset(self, for_list=False):
48 """Return `QuerySet` used by this handler.
49
50 Override if you need to modify the queryset based on the current user.
51 """
52- return self._meta.queryset
53+ if for_list and self._meta.list_queryset is not None:
54+ return self._meta.list_queryset
55+ else:
56+ return self._meta.queryset
57
58 def get_form_class(self, action):
59 """Return the form class used for `action`.
60@@ -349,7 +353,7 @@ class Handler(metaclass=HandlerMetaclass):
61 :param offset: Offset into the queryset to return.
62 :param limit: Maximum number of objects to return.
63 """
64- queryset = self.get_queryset()
65+ queryset = self.get_queryset(for_list=True)
66 queryset = queryset.order_by(self._meta.batch_key)
67 if "start" in params:
68 queryset = queryset.filter(**{
69diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py
70index 84ccdd8..ce54365 100644
71--- a/src/maasserver/websockets/handlers/controller.py
72+++ b/src/maasserver/websockets/handlers/controller.py
73@@ -24,8 +24,12 @@ class ControllerHandler(MachineHandler):
74 class Meta(MachineHandler.Meta):
75 abstract = False
76 queryset = node_prefetch(
77- Controller.controllers.all().prefetch_related("interface_set"),
78- 'controllerinfo'
79+ Controller.controllers.all().prefetch_related(
80+ "service_set"), "controllerinfo")
81+ list_queryset = (
82+ Controller.controllers.all()
83+ .select_related("controllerinfo", "domain")
84+ .prefetch_related("service_set")
85 )
86 allowed_methods = [
87 'list',
88@@ -87,10 +91,14 @@ class ControllerHandler(MachineHandler):
89 else:
90 raise HandlerError("Unknown action: %s" % action)
91
92- def get_queryset(self):
93+ def get_queryset(self, for_list=False):
94 """Return `QuerySet` for controllers only viewable by `user`."""
95+ if for_list:
96+ qs = self._meta.list_queryset
97+ else:
98+ qs = self._meta.queryset
99 return Controller.controllers.get_nodes(
100- self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)
101+ self.user, NODE_PERMISSION.VIEW, from_nodes=qs)
102
103 def dehydrate(self, obj, data, for_list=False):
104 obj = obj.as_self()
105@@ -105,14 +113,15 @@ class ControllerHandler(MachineHandler):
106 if version.is_snap:
107 long_version += " (snap)"
108 data["version__long"] = long_version
109- data["vlan_ids"] = [
110- interface.vlan_id
111- for interface in obj.interface_set.all()
112- ]
113 data["service_ids"] = [
114 service.id
115 for service in obj.service_set.all()
116 ]
117+ if not for_list:
118+ data["vlan_ids"] = [
119+ interface.vlan_id
120+ for interface in obj.interface_set.all()
121+ ]
122 return data
123
124 def check_images(self, params):
125diff --git a/src/maasserver/websockets/handlers/device.py b/src/maasserver/websockets/handlers/device.py
126index 7469821..6904193 100644
127--- a/src/maasserver/websockets/handlers/device.py
128+++ b/src/maasserver/websockets/handlers/device.py
129@@ -37,10 +37,7 @@ from maasserver.websockets.base import (
130 HandlerPermissionError,
131 HandlerValidationError,
132 )
133-from maasserver.websockets.handlers.node import (
134- node_prefetch,
135- NodeHandler,
136-)
137+from maasserver.websockets.handlers.node import NodeHandler
138 from netaddr import EUI
139 from provisioningserver.logger import get_maas_logger
140
141@@ -65,7 +62,16 @@ class DeviceHandler(NodeHandler):
142
143 class Meta(NodeHandler.Meta):
144 abstract = False
145- queryset = node_prefetch(Device.objects.filter(parent=None))
146+ queryset = (
147+ Device.objects.filter(parent=None).select_related(
148+ 'boot_interface', 'owner', 'zone', 'domain')
149+ .prefetch_related(
150+ 'interface_set__ip_addresses__subnet__vlan__space')
151+ .prefetch_related(
152+ 'interface_set__ip_addresses__subnet__vlan__fabric')
153+ .prefetch_related('interface_set__vlan__fabric')
154+ .prefetch_related('tags')
155+ )
156 allowed_methods = [
157 'list',
158 'get',
159@@ -80,6 +86,7 @@ class DeviceHandler(NodeHandler):
160 'update',
161 'action']
162 exclude = [
163+ "bmc",
164 "creation_type",
165 "type",
166 "boot_interface",
167@@ -144,10 +151,11 @@ class DeviceHandler(NodeHandler):
168 getpk = attrgetter(self._meta.pk)
169 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)
170
171- def get_queryset(self):
172+ def get_queryset(self, for_list=False):
173 """Return `QuerySet` for devices only viewable by `user`."""
174 return Device.objects.get_nodes(
175- self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)
176+ self.user, NODE_PERMISSION.VIEW, from_nodes=super().get_queryset(
177+ for_list=for_list))
178
179 def dehydrate_parent(self, parent):
180 if parent is None:
181diff --git a/src/maasserver/websockets/handlers/event.py b/src/maasserver/websockets/handlers/event.py
182index b7e4a80..0845b5f 100644
183--- a/src/maasserver/websockets/handlers/event.py
184+++ b/src/maasserver/websockets/handlers/event.py
185@@ -75,7 +75,7 @@ class EventHandler(TimestampedModelHandler):
186 """
187 node = self.get_node(params)
188 self.cache['node_ids'].append(node.id)
189- queryset = self.get_queryset()
190+ queryset = self.get_queryset(for_list=True)
191 queryset = queryset.filter(node=node)
192 queryset = queryset.order_by('-id')
193
194diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
195index cd0e6db..ca505d9 100644
196--- a/src/maasserver/websockets/handlers/machine.py
197+++ b/src/maasserver/websockets/handlers/machine.py
198@@ -54,7 +54,6 @@ from maasserver.forms.interface import (
199 from maasserver.forms.interface_link import InterfaceLinkForm
200 from maasserver.models.blockdevice import BlockDevice
201 from maasserver.models.cacheset import CacheSet
202-from maasserver.models.config import Config
203 from maasserver.models.filesystem import Filesystem
204 from maasserver.models.filesystemgroup import VolumeGroup
205 from maasserver.models.interface import Interface
206@@ -96,7 +95,20 @@ class MachineHandler(NodeHandler):
207
208 class Meta(NodeHandler.Meta):
209 abstract = False
210- queryset = node_prefetch(Machine.objects.all()).select_related('bmc')
211+ queryset = node_prefetch(Machine.objects.all())
212+ list_queryset = (
213+ Machine.objects.select_related(
214+ 'boot_interface', 'owner', 'zone', 'domain')
215+ .prefetch_related('blockdevice_set__iscsiblockdevice')
216+ .prefetch_related('blockdevice_set__physicalblockdevice')
217+ .prefetch_related('blockdevice_set__virtualblockdevice')
218+ .prefetch_related(
219+ 'interface_set__ip_addresses__subnet__vlan__space')
220+ .prefetch_related(
221+ 'interface_set__ip_addresses__subnet__vlan__fabric')
222+ .prefetch_related('interface_set__vlan__fabric')
223+ .prefetch_related('tags')
224+ )
225 allowed_methods = [
226 'list',
227 'get',
228@@ -174,42 +186,24 @@ class MachineHandler(NodeHandler):
229 "machine",
230 ]
231
232- def get_queryset(self):
233+ def get_queryset(self, for_list=False):
234 """Return `QuerySet` for devices only viewable by `user`."""
235 return Machine.objects.get_nodes(
236- self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)
237-
238- def list(self, params):
239- """List objects.
240-
241- Caches default_osystem and default_distro_series so only 2 queries are
242- made for the whole list of nodes.
243-
244- Caches the hardware status so only one additional query is needed for
245- all nodes.
246- """
247- self.default_osystem = Config.objects.get_config('default_osystem')
248- self.default_distro_series = Config.objects.get_config(
249- 'default_distro_series')
250- return super(MachineHandler, self).list(params)
251+ self.user, NODE_PERMISSION.VIEW,
252+ from_nodes=super().get_queryset(for_list=for_list))
253
254 def dehydrate(self, obj, data, for_list=False):
255 """Add extra fields to `data`."""
256 data = super(MachineHandler, self).dehydrate(
257 obj, data, for_list=for_list)
258- bmc = obj.bmc
259- if bmc is not None and bmc.bmc_type == BMC_TYPE.POD:
260- data['pod'] = bmc.id
261
262- if not for_list:
263- # Add info specific to a machine.
264- data["show_os_info"] = self.dehydrate_show_os_info(obj)
265- devices = [
266- self.dehydrate_device(device)
267- for device in obj.children.all()
268- ]
269- data["devices"] = sorted(
270- devices, key=itemgetter("fqdn"))
271+ if obj.is_machine or not for_list:
272+ boot_interface = obj.get_boot_interface()
273+ if boot_interface is not None:
274+ data["pxe_mac"] = "%s" % boot_interface.mac_address
275+ data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor()
276+ else:
277+ data["pxe_mac"] = data["pxe_mac_vendor"] = ""
278
279 cpu_script_results = [
280 script_result for script_result in
281@@ -264,6 +258,18 @@ class MachineHandler(NodeHandler):
282 else:
283 data["status_tooltip"] = ""
284
285+ if not for_list:
286+ if obj.bmc is not None and obj.bmc.bmc_type == BMC_TYPE.POD:
287+ data['pod'] = self.dehydrate_pod(obj.bmc)
288+ # Add info specific to a machine.
289+ data["show_os_info"] = self.dehydrate_show_os_info(obj)
290+ devices = [
291+ self.dehydrate_device(device)
292+ for device in obj.children.all()
293+ ]
294+ data["devices"] = sorted(
295+ devices, key=itemgetter("fqdn"))
296+
297 return data
298
299 def dehydrate_show_os_info(self, obj):
300diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
301index fda47a7..c9c76c7 100644
302--- a/src/maasserver/websockets/handlers/node.py
303+++ b/src/maasserver/websockets/handlers/node.py
304@@ -53,6 +53,9 @@ from metadataserver.enum import (
305 )
306 from metadataserver.models.scriptresult import ScriptResult
307 from metadataserver.models.scriptset import get_status_from_qs
308+from provisioningserver.refresh.node_info_scripts import (
309+ LIST_MODALIASES_OUTPUT_NAME,
310+)
311 from provisioningserver.tags import merge_details_cleanly
312
313
314@@ -68,7 +71,8 @@ NODE_TYPE_TO_LINK_TYPE = {
315 def node_prefetch(queryset, *args):
316 return (
317 queryset
318- .select_related('boot_interface', 'owner', 'zone', 'domain', *args)
319+ .select_related(
320+ 'boot_interface', 'owner', 'zone', 'domain', 'bmc', *args)
321 .prefetch_related('blockdevice_set__iscsiblockdevice')
322 .prefetch_related('blockdevice_set__physicalblockdevice')
323 .prefetch_related('blockdevice_set__virtualblockdevice')
324@@ -83,15 +87,15 @@ def node_prefetch(queryset, *args):
325
326 class NodeHandler(TimestampedModelHandler):
327
328- default_osystem = None
329- default_distro_series = None
330- _script_results = {}
331-
332 class Meta:
333 abstract = True
334 pk = 'system_id'
335 pk_type = str
336
337+ def __init__(self, user, cache):
338+ super().__init__(user, cache)
339+ self._script_results = {}
340+
341 def dehydrate_owner(self, user):
342 """Return owners username."""
343 if user is None:
344@@ -167,35 +171,10 @@ class NodeHandler(TimestampedModelHandler):
345 data["node_type_display"] = obj.get_node_type_display()
346 data["link_type"] = NODE_TYPE_TO_LINK_TYPE[obj.node_type]
347
348- data["extra_macs"] = [
349- "%s" % mac_address
350- for mac_address in obj.get_extra_macs()
351- ]
352- subnets = self.get_all_subnets(obj)
353- data["subnets"] = [subnet.cidr for subnet in subnets]
354- data["fabrics"] = self.get_all_fabric_names(obj, subnets)
355- data["spaces"] = self.get_all_space_names(subnets)
356-
357- data["tags"] = [
358- tag.name
359- for tag in obj.tags.all()
360- ]
361- data["metadata"] = {
362- metadata.key: metadata.value
363- for metadata in obj.nodemetadata_set.all()
364- }
365- if obj.node_type != NODE_TYPE.DEVICE:
366- data["architecture"] = obj.architecture
367- data["memory"] = obj.display_memory()
368- data["status"] = obj.display_status()
369- data["status_code"] = obj.status
370- boot_interface = obj.get_boot_interface()
371- if boot_interface is not None:
372- data["pxe_mac"] = "%s" % boot_interface.mac_address
373- data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor()
374- else:
375- data["pxe_mac"] = data["pxe_mac_vendor"] = ""
376-
377+ if obj.node_type == NODE_TYPE.MACHINE or (
378+ obj.is_controller and not for_list):
379+ # Disk count and storage amount is shown on the machine listing
380+ # page and the machine and controllers details page.
381 blockdevices = self.get_blockdevices_for(obj)
382 physical_blockdevices = [
383 blockdevice for blockdevice in blockdevices
384@@ -208,16 +187,6 @@ class NodeHandler(TimestampedModelHandler):
385 for blockdevice in physical_blockdevices
386 ) / (1000 ** 3))
387 data["storage_tags"] = self.get_all_storage_tags(blockdevices)
388- data["grouped_storages"] = self.get_grouped_storages(
389- physical_blockdevices)
390-
391- data["osystem"] = obj.get_osystem(
392- default=self.default_osystem)
393- data["distro_series"] = obj.get_distro_series(
394- default=self.default_distro_series)
395- data["dhcp_on"] = self.get_providing_dhcp(obj)
396-
397- if obj.node_type != NODE_TYPE.DEVICE:
398 commissioning_script_results = []
399 testing_script_results = []
400 log_results = set()
401@@ -235,12 +204,12 @@ class NodeHandler(TimestampedModelHandler):
402 RESULT_TYPE.COMMISSIONING):
403 commissioning_script_results.append(script_result)
404 if (script_result.name in script_output_nsmap and
405- script_result.status == SCRIPT_STATUS.PASSED):
406+ script_result.status ==
407+ SCRIPT_STATUS.PASSED):
408 log_results.add(script_result.name)
409 elif (script_result.script_set.result_type ==
410 RESULT_TYPE.TESTING):
411 testing_script_results.append(script_result)
412-
413 data["commissioning_script_count"] = len(
414 commissioning_script_results)
415 data["commissioning_status"] = get_status_from_qs(
416@@ -250,11 +219,41 @@ class NodeHandler(TimestampedModelHandler):
417 commissioning_script_results).replace(
418 'test', 'commissioning script'))
419 data["testing_script_count"] = len(testing_script_results)
420- data["testing_status"] = get_status_from_qs(testing_script_results)
421+ data["testing_status"] = get_status_from_qs(
422+ testing_script_results)
423 data["testing_status_tooltip"] = (
424- self.dehydrate_hardware_status_tooltip(testing_script_results))
425+ self.dehydrate_hardware_status_tooltip(
426+ testing_script_results))
427 data["has_logs"] = (
428- log_results.difference(script_output_nsmap.keys()) == set())
429+ log_results.difference(script_output_nsmap.keys()) ==
430+ set())
431+ else:
432+ blockdevices = []
433+
434+ if obj.node_type != NODE_TYPE.DEVICE:
435+ # These values are not defined on a device.
436+ data["architecture"] = obj.architecture
437+ data["osystem"] = obj.osystem
438+ data["distro_series"] = obj.distro_series
439+ data["memory"] = obj.display_memory()
440+ data["status"] = obj.display_status()
441+ data["status_code"] = obj.status
442+
443+ # Filters are only available on machines and devices.
444+ if not obj.is_controller:
445+ # For filters
446+ subnets = self.get_all_subnets(obj)
447+ data["subnets"] = [subnet.cidr for subnet in subnets]
448+ data["fabrics"] = self.get_all_fabric_names(obj, subnets)
449+ data["spaces"] = self.get_all_space_names(subnets)
450+ data["tags"] = [
451+ tag.name
452+ for tag in obj.tags.all()
453+ ]
454+ data["extra_macs"] = [
455+ "%s" % mac_address
456+ for mac_address in obj.get_extra_macs()
457+ ]
458
459 if not for_list:
460 data["on_network"] = obj.on_network()
461@@ -262,11 +261,18 @@ class NodeHandler(TimestampedModelHandler):
462 # XXX lamont 2017-02-15 Much of this should be split out into
463 # individual methods, rather than having this huge block of
464 # dense code here.
465+ # Status of the commissioning, testing, and logs tabs
466+ data["metadata"] = {
467+ metadata.key: metadata.value
468+ for metadata in obj.nodemetadata_set.all()
469+ }
470+
471 # Network
472 data["interfaces"] = [
473 self.dehydrate_interface(interface, obj)
474 for interface in obj.interface_set.all().order_by('name')
475 ]
476+ data["dhcp_on"] = self.get_providing_dhcp(obj)
477
478 data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel)
479
480@@ -294,6 +300,8 @@ class NodeHandler(TimestampedModelHandler):
481 self.dehydrate_filesystem(filesystem)
482 for filesystem in obj.special_filesystems.order_by("id")
483 ]
484+ data["grouped_storages"] = self.get_grouped_storages(
485+ physical_blockdevices)
486
487 # Events
488 data["events"] = self.dehydrate_events(obj)
489@@ -304,7 +312,18 @@ class NodeHandler(TimestampedModelHandler):
490
491 # Third party drivers
492 if Config.objects.get_config('enable_third_party_drivers'):
493- driver = get_third_party_driver(obj)
494+ # Pull modaliases from the cache
495+ modaliases = []
496+ for script_result in commissioning_script_results:
497+ if script_result.name == LIST_MODALIASES_OUTPUT_NAME:
498+ if script_result.status == SCRIPT_STATUS.PASSED:
499+ # STDOUT is deferred in the cache so load it.
500+ script_result = ScriptResult.objects.filter(
501+ id=script_result.id).only(
502+ 'id', 'stdout').first()
503+ modaliases = script_result.stdout.decode(
504+ 'utf-8').splitlines()
505+ driver = get_third_party_driver(obj, modaliases)
506 if "module" in driver and "comment" in driver:
507 data["third_party_driver"] = {
508 "module": driver["module"],
509diff --git a/src/maasserver/websockets/handlers/notification.py b/src/maasserver/websockets/handlers/notification.py
510index 7292e68..d189e75 100644
511--- a/src/maasserver/websockets/handlers/notification.py
512+++ b/src/maasserver/websockets/handlers/notification.py
513@@ -1,4 +1,4 @@
514-# Copyright 2017 Canonical Ltd. This software is licensed under the
515+# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
516 # GNU Affero General Public License version 3 (see the file LICENSE).
517
518 """The notification handler for the WebSocket connection."""
519@@ -22,7 +22,7 @@ class NotificationHandler(TimestampedModelHandler):
520 exclude = list_exclude = {"context"}
521 listen_channels = {'notification', 'notificationdismissal'}
522
523- def get_queryset(self):
524+ def get_queryset(self, for_list=False):
525 """Return `Notifications` for the current user."""
526 return Notification.objects.find_for_user(self.user)
527
528diff --git a/src/maasserver/websockets/handlers/sshkey.py b/src/maasserver/websockets/handlers/sshkey.py
529index 0012efd..04956f7 100644
530--- a/src/maasserver/websockets/handlers/sshkey.py
531+++ b/src/maasserver/websockets/handlers/sshkey.py
532@@ -37,7 +37,7 @@ class SSHKeyHandler(TimestampedModelHandler):
533 "sshkey",
534 ]
535
536- def get_queryset(self):
537+ def get_queryset(self, for_list=False):
538 """Return `QuerySet` for SSH keys owned by `user`."""
539 return self._meta.queryset.filter(user=self.user)
540
541diff --git a/src/maasserver/websockets/handlers/switch.py b/src/maasserver/websockets/handlers/switch.py
542index e40f779..72accf5 100644
543--- a/src/maasserver/websockets/handlers/switch.py
544+++ b/src/maasserver/websockets/handlers/switch.py
545@@ -1,4 +1,4 @@
546-# Copyright 2017 Canonical Ltd. This software is licensed under the
547+# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
548 # GNU Affero General Public License version 3 (see the file LICENSE).
549
550 """The switch handler for the WebSocket connection."""
551@@ -45,10 +45,14 @@ class SwitchHandler(NodeHandler):
552 'switch',
553 ]
554
555- def get_queryset(self):
556+ def get_queryset(self, for_list=False):
557 """Return `QuerySet` for devices only viewable by `user`."""
558+ # FIXME - Return a different query set when for_list is true. This
559+ # should contain only the items needed to display a switch when listing
560+ # in the UI.
561 return Node.objects.get_nodes(
562- self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)
563+ self.user, NODE_PERMISSION.VIEW,
564+ from_nodes=self._meta.queryset)
565
566 def get_object(self, params):
567 """Get object by using the `pk` in `params`."""
568diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py
569index 18d2433..e86c19a 100644
570--- a/src/maasserver/websockets/handlers/tests/test_controller.py
571+++ b/src/maasserver/websockets/handlers/tests/test_controller.py
572@@ -1,4 +1,4 @@
573-# Copyright 2016-2017 Canonical Ltd. This software is licensed under the
574+# Copyright 2016-2018 Canonical Ltd. This software is licensed under the
575 # GNU Affero General Public License version 3 (see the file LICENSE).
576
577 """Tests for `maasserver.websockets.handlers.controller`"""
578@@ -12,6 +12,11 @@ from maasserver.testing.factory import factory
579 from maasserver.testing.testcase import MAASServerTestCase
580 from maasserver.websockets.base import dehydrate_datetime
581 from maasserver.websockets.handlers.controller import ControllerHandler
582+from maastesting.djangotestcase import count_queries
583+from metadataserver.enum import (
584+ RESULT_TYPE,
585+ SCRIPT_STATUS,
586+)
587 from testscenarios import multiply_scenarios
588 from testtools.matchers import (
589 ContainsDict,
590@@ -65,6 +70,69 @@ class TestControllerHandler(MAASServerTestCase):
591 self.assertEqual(1, len(result))
592 self.assertEqual(NODE_TYPE.RACK_CONTROLLER, result[0].get('node_type'))
593
594+ def test_list_num_queries_is_the_expected_number(self):
595+ owner = factory.make_admin()
596+ for _ in range(10):
597+ node = factory.make_RegionRackController(owner=owner)
598+ commissioning_script_set = factory.make_ScriptSet(
599+ node=node, result_type=RESULT_TYPE.COMMISSIONING)
600+ testing_script_set = factory.make_ScriptSet(
601+ node=node, result_type=RESULT_TYPE.TESTING)
602+ node.current_commissioning_script_set = commissioning_script_set
603+ node.current_testing_script_set = testing_script_set
604+ node.save()
605+ for __ in range(10):
606+ factory.make_ScriptResult(
607+ status=SCRIPT_STATUS.PASSED,
608+ script_set=commissioning_script_set)
609+ factory.make_ScriptResult(
610+ status=SCRIPT_STATUS.PASSED,
611+ script_set=testing_script_set)
612+
613+ handler = ControllerHandler(owner, {})
614+ queries_one, _ = count_queries(handler.list, {'limit': 1})
615+ queries_total, _ = count_queries(handler.list, {})
616+ # This check is to notify the developer that a change was made that
617+ # affects the number of queries performed when doing a node listing.
618+ # It is important to keep this number as low as possible. A larger
619+ # number means regiond has to do more work slowing down its process
620+ # and slowing down the client waiting for the response.
621+ self.assertEqual(
622+ queries_one, 3,
623+ "Number of queries has changed; make sure this is expected.")
624+ self.assertEqual(
625+ queries_total, 3,
626+ "Number of queries has changed; make sure this is expected.")
627+
628+ def test_get_num_queries_is_the_expected_number(self):
629+ owner = factory.make_admin()
630+ node = factory.make_RegionRackController(owner=owner)
631+ commissioning_script_set = factory.make_ScriptSet(
632+ node=node, result_type=RESULT_TYPE.COMMISSIONING)
633+ testing_script_set = factory.make_ScriptSet(
634+ node=node, result_type=RESULT_TYPE.TESTING)
635+ node.current_commissioning_script_set = commissioning_script_set
636+ node.current_testing_script_set = testing_script_set
637+ node.save()
638+ for __ in range(10):
639+ factory.make_ScriptResult(
640+ status=SCRIPT_STATUS.PASSED,
641+ script_set=commissioning_script_set)
642+ factory.make_ScriptResult(
643+ status=SCRIPT_STATUS.PASSED,
644+ script_set=testing_script_set)
645+
646+ handler = ControllerHandler(owner, {})
647+ queries, _ = count_queries(handler.get, {'system_id': node.system_id})
648+ # This check is to notify the developer that a change was made that
649+ # affects the number of queries performed when doing a node get.
650+ # It is important to keep this number as low as possible. A larger
651+ # number means regiond has to do more work slowing down its process
652+ # and slowing down the client waiting for the response.
653+ self.assertEqual(
654+ queries, 28,
655+ "Number of queries has changed; make sure this is expected.")
656+
657 def test_get_form_class_for_create(self):
658 user = factory.make_admin()
659 handler = ControllerHandler(user, {})
660diff --git a/src/maasserver/websockets/handlers/tests/test_device.py b/src/maasserver/websockets/handlers/tests/test_device.py
661index c26e303..ef0b543 100644
662--- a/src/maasserver/websockets/handlers/tests/test_device.py
663+++ b/src/maasserver/websockets/handlers/tests/test_device.py
664@@ -137,7 +137,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
665 boot_interface = node.get_boot_interface()
666 data = {
667 "actions": list(compile_node_actions(node, user).keys()),
668- "bmc": node.bmc_id,
669 "created": dehydrate_datetime(node.created),
670 "domain": {
671 "id": node.domain.id,
672@@ -149,7 +148,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
673 ],
674 "fqdn": node.fqdn,
675 "hostname": node.hostname,
676- "metadata": {},
677 "node_type_display": node.get_node_type_display(),
678 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],
679 "id": node.id,
680@@ -193,7 +191,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
681 "ip_address",
682 "ip_assignment",
683 "link_type",
684- "metadata",
685 "node_type_display",
686 "primary_mac",
687 "spaces",
688@@ -251,6 +248,24 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
689 handler.get({"system_id": device.system_id}))
690
691 @transactional
692+ def test_get_num_queries_is_the_expected_number(self):
693+ owner = factory.make_User()
694+ handler = DeviceHandler(owner, {})
695+ device = self.make_device_with_ip_address(
696+ owner=owner, ip_assignment=DEVICE_IP_ASSIGNMENT_TYPE.STATIC)
697+ queries, _ = count_queries(
698+ handler.get, {"system_id": device.system_id})
699+
700+ # This check is to notify the developer that a change was made that
701+ # affects the number of queries performed when doing a node get.
702+ # It is important to keep this number as low as possible. A larger
703+ # number means regiond has to do more work slowing down its process
704+ # and slowing down the client waiting for the response.
705+ self.assertEqual(
706+ queries, 19,
707+ "Number of queries has changed; make sure this is expected.")
708+
709+ @transactional
710 def test_list(self):
711 owner = factory.make_User()
712 handler = DeviceHandler(owner, {})
713@@ -299,7 +314,7 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
714 # number means regiond has to do more work slowing down its process
715 # and slowing down the client waiting for the response.
716 self.assertEqual(
717- query_10_count, 13,
718+ query_10_count, 10,
719 "Number of queries has changed; make sure this is expected.")
720
721 @transactional
722diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
723index 007bf55..0617b68 100644
724--- a/src/maasserver/websockets/handlers/tests/test_machine.py
725+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
726@@ -150,11 +150,12 @@ class TestMachineHandler(MAASServerTestCase):
727
728 boot_interface = node.get_boot_interface()
729 pxe_mac_vendor = node.get_pxe_mac_vendor()
730+ subnets = handler.get_all_subnets(node)
731+
732 blockdevices = [
733 blockdevice.actual_instance
734 for blockdevice in node.blockdevice_set.all()
735 ]
736- driver = get_third_party_driver(node)
737 disks = [
738 handler.dehydrate_blockdevice(blockdevice, node)
739 for blockdevice in blockdevices
740@@ -167,8 +168,10 @@ class TestMachineHandler(MAASServerTestCase):
741 for cache_set in CacheSet.objects.get_cache_sets_for_node(node)
742 ]
743 disks = sorted(disks, key=itemgetter("name"))
744- subnets = handler.get_all_subnets(node)
745- commissioning_scripts = node.get_latest_commissioning_script_results
746+ driver = get_third_party_driver(node)
747+
748+ commissioning_scripts = (
749+ node.get_latest_commissioning_script_results)
750 commissioning_scripts = commissioning_scripts.exclude(
751 status=SCRIPT_STATUS.ABORTED)
752 testing_scripts = node.get_latest_testing_script_results
753@@ -179,6 +182,7 @@ class TestMachineHandler(MAASServerTestCase):
754 if (script_result.name in script_output_nsmap and
755 script_result.status == SCRIPT_STATUS.PASSED):
756 log_results.add(script_result.name)
757+
758 data = {
759 "actions": list(compile_node_actions(node, handler.user).keys()),
760 "architecture": node.architecture,
761@@ -186,18 +190,20 @@ class TestMachineHandler(MAASServerTestCase):
762 "boot_disk": node.boot_disk,
763 "bios_boot_method": node.bios_boot_method,
764 "commissioning_script_count": commissioning_scripts.count(),
765- "commissioning_status": get_status_from_qs(commissioning_scripts),
766+ "commissioning_status": get_status_from_qs(
767+ commissioning_scripts),
768 "commissioning_status_tooltip": (
769 handler.dehydrate_hardware_status_tooltip(
770 commissioning_scripts).replace(
771 'test', 'commissioning script')),
772 "current_commissioning_script_set": (
773 node.current_commissioning_script_set_id),
774+ "current_testing_script_set": node.current_testing_script_set_id,
775 "testing_script_count": testing_scripts.count(),
776 "testing_status": get_status_from_qs(testing_scripts),
777 "testing_status_tooltip": (
778- handler.dehydrate_hardware_status_tooltip(testing_scripts)),
779- "current_testing_script_set": node.current_testing_script_set_id,
780+ handler.dehydrate_hardware_status_tooltip(
781+ testing_scripts)),
782 "current_installation_script_set": (
783 node.current_installation_script_set_id),
784 "installation_status": (
785@@ -230,7 +236,7 @@ class TestMachineHandler(MAASServerTestCase):
786 "supported_filesystems": [
787 {'key': key, 'ui': ui}
788 for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES],
789- "distro_series": node.get_distro_series(),
790+ "distro_series": node.distro_series,
791 "error": node.error,
792 "error_description": node.error_description,
793 "events": handler.dehydrate_events(node),
794@@ -250,10 +256,9 @@ class TestMachineHandler(MAASServerTestCase):
795 "license_key": node.license_key,
796 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],
797 "memory": node.display_memory(),
798- "metadata": {},
799 "node_type_display": node.get_node_type_display(),
800 "min_hwe_kernel": node.min_hwe_kernel,
801- "osystem": node.get_osystem(),
802+ "osystem": node.osystem,
803 "owner": handler.dehydrate_owner(node.owner),
804 "power_parameters": handler.dehydrate_power_parameters(
805 node.power_parameters),
806@@ -290,7 +295,6 @@ class TestMachineHandler(MAASServerTestCase):
807 "updated": dehydrate_datetime(node.updated),
808 "zone": handler.dehydrate_zone(node.zone),
809 "default_user": node.default_user,
810- "dhcp_on": node.interface_set.filter(vlan__dhcp_on=True).exists(),
811 }
812 bmc = node.bmc
813 if bmc is not None and bmc.bmc_type == BMC_TYPE.POD:
814@@ -330,6 +334,13 @@ class TestMachineHandler(MAASServerTestCase):
815 for key in list(data):
816 if key not in allowed_fields:
817 del data[key]
818+ else:
819+ data.update({
820+ "dhcp_on": node.interface_set.filter(
821+ vlan__dhcp_on=True).exists(),
822+ "grouped_storages": handler.get_grouped_storages(blockdevices),
823+ "metadata": {},
824+ })
825
826 cpu_script_results = [
827 script_result for script_result in
828@@ -392,8 +403,6 @@ class TestMachineHandler(MAASServerTestCase):
829 else:
830 data["status_tooltip"] = ""
831
832- data["grouped_storages"] = handler.get_grouped_storages(blockdevices)
833-
834 return data
835
836 def make_nodes(self, number):
837@@ -492,10 +501,39 @@ class TestMachineHandler(MAASServerTestCase):
838 # number means regiond has to do more work slowing down its process
839 # and slowing down the client waiting for the response.
840 self.assertEqual(
841- queries_one, 12,
842+ queries_one, 8,
843+ "Number of queries has changed; make sure this is expected.")
844+ self.assertEqual(
845+ queries_total, 8,
846 "Number of queries has changed; make sure this is expected.")
847+
848+ def test_get_num_queries_is_the_expected_number(self):
849+ owner = factory.make_User()
850+ node = factory.make_Node(owner=owner)
851+ commissioning_script_set = factory.make_ScriptSet(
852+ node=node, result_type=RESULT_TYPE.COMMISSIONING)
853+ testing_script_set = factory.make_ScriptSet(
854+ node=node, result_type=RESULT_TYPE.TESTING)
855+ node.current_commissioning_script_set = commissioning_script_set
856+ node.current_testing_script_set = testing_script_set
857+ node.save()
858+ for __ in range(10):
859+ factory.make_ScriptResult(
860+ status=SCRIPT_STATUS.PASSED,
861+ script_set=commissioning_script_set)
862+ factory.make_ScriptResult(
863+ status=SCRIPT_STATUS.PASSED,
864+ script_set=testing_script_set)
865+
866+ handler = MachineHandler(owner, {})
867+ queries, _ = count_queries(handler.get, {'system_id': node.system_id})
868+ # This check is to notify the developer that a change was made that
869+ # affects the number of queries performed when doing a node get.
870+ # It is important to keep this number as low as possible. A larger
871+ # number means regiond has to do more work slowing down its process
872+ # and slowing down the client waiting for the response.
873 self.assertEqual(
874- queries_total, 12,
875+ queries, 47,
876 "Number of queries has changed; make sure this is expected.")
877
878 def test_trigger_update_updates_script_result_cache(self):
879@@ -518,6 +556,7 @@ class TestMachineHandler(MAASServerTestCase):
880
881 handler = MachineHandler(owner, {})
882 # Simulate a trigger pushing an update to the UI
883+ handler.cache = {'active_pk': node.system_id}
884 _, _, ret = handler.on_listen_for_active_pk(
885 'update', node.system_id, node)
886 self.assertEquals(ret['commissioning_script_count'], 10)
887@@ -1627,7 +1666,7 @@ class TestMachineHandler(MAASServerTestCase):
888 # number means regiond has to do more work slowing down its process
889 # and slowing down the client waiting for the response.
890 self.assertEqual(
891- query_10_count, 15,
892+ query_10_count, 11,
893 "Number of queries has changed; make sure this is expected.")
894 self.assertEqual(
895 query_10_count, query_20_count,
896diff --git a/src/maasserver/websockets/handlers/tests/test_switch.py b/src/maasserver/websockets/handlers/tests/test_switch.py
897index 8565db0..4a62082 100644
898--- a/src/maasserver/websockets/handlers/tests/test_switch.py
899+++ b/src/maasserver/websockets/handlers/tests/test_switch.py
900@@ -5,10 +5,7 @@
901
902 __all__ = []
903
904-from maasserver.enum import (
905- NODE_METADATA,
906- NODE_TYPE,
907-)
908+from maasserver.enum import NODE_TYPE
909 from maasserver.exceptions import NodeActionError
910 from maasserver.testing.factory import factory
911 from maasserver.testing.testcase import MAASTransactionServerTestCase
912@@ -67,22 +64,6 @@ class TestSwitchHandler(MAASTransactionServerTestCase):
913 [result['system_id'] for result in handler.list({})])
914
915 @transactional
916- def test_list_switches_includes_metadata(self):
917- owner = factory.make_User()
918- handler = SwitchHandler(owner, {})
919- machine = factory.make_Machine(owner=owner)
920- metadata = {
921- NODE_METADATA.VENDOR_NAME: "Canonical",
922- NODE_METADATA.PHYSICAL_MODEL_NAME: "Cloud-in-a-box"
923- }
924- for key, value in metadata.items():
925- machine.set_metadata(key, value)
926- factory.make_Switch(node=machine)
927- self.assertItemsEqual(
928- [metadata],
929- [result['metadata'] for result in handler.list({})])
930-
931- @transactional
932 def test_list_ignores_nodes_that_arent_switches(self):
933 owner = factory.make_User()
934 handler = SwitchHandler(owner, {})
935diff --git a/src/maasserver/websockets/handlers/user.py b/src/maasserver/websockets/handlers/user.py
936index 8c10046..cc8759a 100644
937--- a/src/maasserver/websockets/handlers/user.py
938+++ b/src/maasserver/websockets/handlers/user.py
939@@ -1,4 +1,4 @@
940-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
941+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
942 # GNU Affero General Public License version 3 (see the file LICENSE).
943
944 """The user handler for the WebSocket connection."""
945@@ -35,9 +35,9 @@ class UserHandler(Handler):
946 "user",
947 ]
948
949- def get_queryset(self):
950+ def get_queryset(self, for_list=False):
951 """Return `QuerySet` for users only viewable by `user`."""
952- users = super(UserHandler, self).get_queryset()
953+ users = super(UserHandler, self).get_queryset(for_list=for_list)
954 if reload_object(self.user).is_superuser:
955 # Super users can view all users, except for the built-in users
956 return users.exclude(username__in=SYSTEM_USERS)
957diff --git a/src/maasserver/websockets/tests/test_base.py b/src/maasserver/websockets/tests/test_base.py
958index 9a3001f..ee71feb 100644
959--- a/src/maasserver/websockets/tests/test_base.py
960+++ b/src/maasserver/websockets/tests/test_base.py
961@@ -1,4 +1,4 @@
962-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
963+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
964 # GNU Affero General Public License version 3 (see the file LICENSE).
965
966 """Tests for `maasserver.websockets.base`"""
967@@ -337,6 +337,25 @@ class TestHandler(MAASServerTestCase):
968 HandlerDoesNotExistError,
969 handler.get_object, {"system_id": machine.system_id})
970
971+ def test_get_queryset(self):
972+ queryset = MagicMock()
973+ list_queryset = MagicMock()
974+ handler = make_handler(
975+ "TestHandler", queryset=queryset, list_queryset=list_queryset)
976+ self.assertEqual(queryset, handler.get_queryset())
977+
978+ def test_get_queryset_list(self):
979+ queryset = MagicMock()
980+ list_queryset = MagicMock()
981+ handler = make_handler(
982+ "TestHandler", queryset=queryset, list_queryset=list_queryset)
983+ self.assertEqual(list_queryset, handler.get_queryset(for_list=True))
984+
985+ def test_get_queryset_list_only_if_avail(self):
986+ queryset = MagicMock()
987+ handler = make_handler("TestHandler", queryset=queryset)
988+ self.assertEqual(queryset, handler.get_queryset(for_list=True))
989+
990 def test_execute_only_allows_meta_allowed_methods(self):
991 handler = self.make_nodes_handler(allowed_methods=['list'])
992 with ExpectedException(HandlerNoSuchMethodError):

Subscribers

People subscribed via source and target branches