Merge ~ltrager/maas:limit_listing_queries into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: c38c9fbf8d8b15380cd07a04bceb18ceee87219b
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:limit_listing_queries
Merge into: maas:master
Diff against target: 995 lines (+323/-165)
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 (+37/-43)
src/maasserver/websockets/handlers/node.py (+65/-49)
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 (+55/-16)
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
MAAS Lander Needs Fixing
Review via email: mp+342384@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.

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

UNIT TESTS
-b limit_listing_queries lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2198/console
COMMIT: 9c3d32c4d91bc6339b0c191d370d9083f37ce9d0

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) :
review: Needs Fixing
~ltrager/maas:limit_listing_queries updated
37b692f... by Lee Trager

Merge branch 'master' into limit_listing_queries

c38c9fb... by Lee Trager

Add get_queryset tests

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

Thanks for the review. We never had query_count tests for get(), only list(). The reason get() takes so many more queries then list() is due to storage and network interface dehydration. Get queries have gone down with this branch. get() on the MachineHandler went from 61 queries to 48.

I was relying on the query_count tests to test get_queryset, I've added explicit tests for get_queryset.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Thanks for the extra test and the clarification.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/third_party_drivers.py b/src/maasserver/third_party_drivers.py
index ff2039a..6c7311b 100644
--- a/src/maasserver/third_party_drivers.py
+++ b/src/maasserver/third_party_drivers.py
@@ -134,13 +134,14 @@ def populate_kernel_opts(driver):
134 return driver134 return driver
135135
136136
137def get_third_party_driver(node):137def get_third_party_driver(node, detected_aliases=None):
138 """Determine which, if any, third party driver is required.138 """Determine which, if any, third party driver is required.
139139
140 Use the node's modaliases strings to determine if a third party140 Use the node's modaliases strings to determine if a third party
141 driver is required.141 driver is required.
142 """142 """
143 detected_aliases = node.modaliases143 if detected_aliases is None:
144 detected_aliases = node.modaliases
144145
145 third_party_drivers_config = DriversConfig.load_from_cache()146 third_party_drivers_config = DriversConfig.load_from_cache()
146 third_party_drivers = third_party_drivers_config['drivers']147 third_party_drivers = third_party_drivers_config['drivers']
diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py
index 636d50a..d7327e4 100644
--- a/src/maasserver/websockets/base.py
+++ b/src/maasserver/websockets/base.py
@@ -81,6 +81,7 @@ class HandlerOptions(object):
81 handler_name = None81 handler_name = None
82 object_class = None82 object_class = None
83 queryset = None83 queryset = None
84 list_queryset = None
84 pk = 'id'85 pk = 'id'
85 pk_type = int86 pk_type = int
86 fields = None87 fields = None
@@ -281,19 +282,22 @@ class Handler(metaclass=HandlerMetaclass):
281 })282 })
282 pk = params[self._meta.pk]283 pk = params[self._meta.pk]
283 try:284 try:
284 obj = self.get_queryset().get(**{285 obj = self.get_queryset(for_list=False).get(**{
285 self._meta.pk: pk,286 self._meta.pk: pk,
286 })287 })
287 except self._meta.object_class.DoesNotExist:288 except self._meta.object_class.DoesNotExist:
288 raise HandlerDoesNotExistError(pk)289 raise HandlerDoesNotExistError(pk)
289 return obj290 return obj
290291
291 def get_queryset(self):292 def get_queryset(self, for_list=False):
292 """Return `QuerySet` used by this handler.293 """Return `QuerySet` used by this handler.
293294
294 Override if you need to modify the queryset based on the current user.295 Override if you need to modify the queryset based on the current user.
295 """296 """
296 return self._meta.queryset297 if for_list and self._meta.list_queryset is not None:
298 return self._meta.list_queryset
299 else:
300 return self._meta.queryset
297301
298 def get_form_class(self, action):302 def get_form_class(self, action):
299 """Return the form class used for `action`.303 """Return the form class used for `action`.
@@ -350,7 +354,7 @@ class Handler(metaclass=HandlerMetaclass):
350 :param offset: Offset into the queryset to return.354 :param offset: Offset into the queryset to return.
351 :param limit: Maximum number of objects to return.355 :param limit: Maximum number of objects to return.
352 """356 """
353 queryset = self.get_queryset()357 queryset = self.get_queryset(for_list=True)
354 queryset = queryset.order_by(self._meta.batch_key)358 queryset = queryset.order_by(self._meta.batch_key)
355 if "start" in params:359 if "start" in params:
356 queryset = queryset.filter(**{360 queryset = queryset.filter(**{
diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py
index 9fbe5c8..636c4d9 100644
--- a/src/maasserver/websockets/handlers/controller.py
+++ b/src/maasserver/websockets/handlers/controller.py
@@ -24,8 +24,12 @@ class ControllerHandler(MachineHandler):
24 class Meta(MachineHandler.Meta):24 class Meta(MachineHandler.Meta):
25 abstract = False25 abstract = False
26 queryset = node_prefetch(26 queryset = node_prefetch(
27 Controller.controllers.all().prefetch_related("interface_set"),27 Controller.controllers.all().prefetch_related(
28 'controllerinfo'28 "service_set"), "controllerinfo")
29 list_queryset = (
30 Controller.controllers.all()
31 .select_related("controllerinfo", "domain")
32 .prefetch_related("service_set")
29 )33 )
30 allowed_methods = [34 allowed_methods = [
31 'list',35 'list',
@@ -90,10 +94,14 @@ class ControllerHandler(MachineHandler):
90 else:94 else:
91 raise HandlerError("Unknown action: %s" % action)95 raise HandlerError("Unknown action: %s" % action)
9296
93 def get_queryset(self):97 def get_queryset(self, for_list=False):
94 """Return `QuerySet` for controllers only viewable by `user`."""98 """Return `QuerySet` for controllers only viewable by `user`."""
99 if for_list:
100 qs = self._meta.list_queryset
101 else:
102 qs = self._meta.queryset
95 return Controller.controllers.get_nodes(103 return Controller.controllers.get_nodes(
96 self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)104 self.user, NODE_PERMISSION.VIEW, from_nodes=qs)
97105
98 def dehydrate(self, obj, data, for_list=False):106 def dehydrate(self, obj, data, for_list=False):
99 obj = obj.as_self()107 obj = obj.as_self()
@@ -108,14 +116,15 @@ class ControllerHandler(MachineHandler):
108 if version.is_snap:116 if version.is_snap:
109 long_version += " (snap)"117 long_version += " (snap)"
110 data["version__long"] = long_version118 data["version__long"] = long_version
111 data["vlan_ids"] = [
112 interface.vlan_id
113 for interface in obj.interface_set.all()
114 ]
115 data["service_ids"] = [119 data["service_ids"] = [
116 service.id120 service.id
117 for service in obj.service_set.all()121 for service in obj.service_set.all()
118 ]122 ]
123 if not for_list:
124 data["vlan_ids"] = [
125 interface.vlan_id
126 for interface in obj.interface_set.all()
127 ]
119 return data128 return data
120129
121 def check_images(self, params):130 def check_images(self, params):
diff --git a/src/maasserver/websockets/handlers/device.py b/src/maasserver/websockets/handlers/device.py
index dc5374f..0407b0c 100644
--- a/src/maasserver/websockets/handlers/device.py
+++ b/src/maasserver/websockets/handlers/device.py
@@ -37,10 +37,7 @@ from maasserver.websockets.base import (
37 HandlerPermissionError,37 HandlerPermissionError,
38 HandlerValidationError,38 HandlerValidationError,
39)39)
40from maasserver.websockets.handlers.node import (40from maasserver.websockets.handlers.node import NodeHandler
41 node_prefetch,
42 NodeHandler,
43)
44from netaddr import EUI41from netaddr import EUI
45from provisioningserver.logger import get_maas_logger42from provisioningserver.logger import get_maas_logger
4643
@@ -65,7 +62,16 @@ class DeviceHandler(NodeHandler):
6562
66 class Meta(NodeHandler.Meta):63 class Meta(NodeHandler.Meta):
67 abstract = False64 abstract = False
68 queryset = node_prefetch(Device.objects.filter(parent=None))65 queryset = (
66 Device.objects.filter(parent=None).select_related(
67 'boot_interface', 'owner', 'zone', 'domain')
68 .prefetch_related(
69 'interface_set__ip_addresses__subnet__vlan__space')
70 .prefetch_related(
71 'interface_set__ip_addresses__subnet__vlan__fabric')
72 .prefetch_related('interface_set__vlan__fabric')
73 .prefetch_related('tags')
74 )
69 allowed_methods = [75 allowed_methods = [
70 'list',76 'list',
71 'get',77 'get',
@@ -80,6 +86,7 @@ class DeviceHandler(NodeHandler):
80 'update',86 'update',
81 'action']87 'action']
82 exclude = [88 exclude = [
89 "bmc",
83 "creation_type",90 "creation_type",
84 "type",91 "type",
85 "boot_interface",92 "boot_interface",
@@ -145,10 +152,11 @@ class DeviceHandler(NodeHandler):
145 getpk = attrgetter(self._meta.pk)152 getpk = attrgetter(self._meta.pk)
146 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)153 self.cache["loaded_pks"].update(getpk(obj) for obj in objs)
147154
148 def get_queryset(self):155 def get_queryset(self, for_list=False):
149 """Return `QuerySet` for devices only viewable by `user`."""156 """Return `QuerySet` for devices only viewable by `user`."""
150 return Device.objects.get_nodes(157 return Device.objects.get_nodes(
151 self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)158 self.user, NODE_PERMISSION.VIEW, from_nodes=super().get_queryset(
159 for_list=for_list))
152160
153 def dehydrate_parent(self, parent):161 def dehydrate_parent(self, parent):
154 if parent is None:162 if parent is None:
diff --git a/src/maasserver/websockets/handlers/event.py b/src/maasserver/websockets/handlers/event.py
index b7e4a80..0845b5f 100644
--- a/src/maasserver/websockets/handlers/event.py
+++ b/src/maasserver/websockets/handlers/event.py
@@ -75,7 +75,7 @@ class EventHandler(TimestampedModelHandler):
75 """75 """
76 node = self.get_node(params)76 node = self.get_node(params)
77 self.cache['node_ids'].append(node.id)77 self.cache['node_ids'].append(node.id)
78 queryset = self.get_queryset()78 queryset = self.get_queryset(for_list=True)
79 queryset = queryset.filter(node=node)79 queryset = queryset.filter(node=node)
80 queryset = queryset.order_by('-id')80 queryset = queryset.order_by('-id')
8181
diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
index 375b0fb..ca5cbeb 100644
--- a/src/maasserver/websockets/handlers/machine.py
+++ b/src/maasserver/websockets/handlers/machine.py
@@ -54,7 +54,6 @@ from maasserver.forms.interface import (
54from maasserver.forms.interface_link import InterfaceLinkForm54from maasserver.forms.interface_link import InterfaceLinkForm
55from maasserver.models.blockdevice import BlockDevice55from maasserver.models.blockdevice import BlockDevice
56from maasserver.models.cacheset import CacheSet56from maasserver.models.cacheset import CacheSet
57from maasserver.models.config import Config
58from maasserver.models.filesystem import Filesystem57from maasserver.models.filesystem import Filesystem
59from maasserver.models.filesystemgroup import VolumeGroup58from maasserver.models.filesystemgroup import VolumeGroup
60from maasserver.models.interface import Interface59from maasserver.models.interface import Interface
@@ -96,7 +95,20 @@ class MachineHandler(NodeHandler):
9695
97 class Meta(NodeHandler.Meta):96 class Meta(NodeHandler.Meta):
98 abstract = False97 abstract = False
99 queryset = node_prefetch(Machine.objects.all()).select_related('bmc')98 queryset = node_prefetch(Machine.objects.all())
99 list_queryset = (
100 Machine.objects.select_related(
101 'boot_interface', 'owner', 'zone', 'domain')
102 .prefetch_related('blockdevice_set__iscsiblockdevice')
103 .prefetch_related('blockdevice_set__physicalblockdevice')
104 .prefetch_related('blockdevice_set__virtualblockdevice')
105 .prefetch_related(
106 'interface_set__ip_addresses__subnet__vlan__space')
107 .prefetch_related(
108 'interface_set__ip_addresses__subnet__vlan__fabric')
109 .prefetch_related('interface_set__vlan__fabric')
110 .prefetch_related('tags')
111 )
100 allowed_methods = [112 allowed_methods = [
101 'list',113 'list',
102 'get',114 'get',
@@ -177,54 +189,24 @@ class MachineHandler(NodeHandler):
177 "machine",189 "machine",
178 ]190 ]
179191
180 def get_queryset(self):192 def get_queryset(self, for_list=False):
181 """Return `QuerySet` for devices only viewable by `user`."""193 """Return `QuerySet` for devices only viewable by `user`."""
182 return Machine.objects.get_nodes(194 return Machine.objects.get_nodes(
183 self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)195 self.user, NODE_PERMISSION.VIEW,
184196 from_nodes=super().get_queryset(for_list=for_list))
185 def list(self, params):
186 """List objects.
187
188 Caches default_osystem and default_distro_series so only 1 queries are
189 made for the whole list of nodes.
190
191 Caches the hardware status so only one additional query is needed for
192 all nodes.
193 """
194 configs = (
195 Config.objects.get_configs(
196 ['default_osystem', 'default_distro_series']))
197 self.default_osystem = configs['default_osystem']
198 self.default_distro_series = configs['default_distro_series']
199 return super(MachineHandler, self).list(params)
200197
201 def dehydrate(self, obj, data, for_list=False):198 def dehydrate(self, obj, data, for_list=False):
202 """Add extra fields to `data`."""199 """Add extra fields to `data`."""
203 data = super(MachineHandler, self).dehydrate(200 data = super(MachineHandler, self).dehydrate(
204 obj, data, for_list=for_list)201 obj, data, for_list=for_list)
205 data["locked"] = obj.locked202
206 bmc = obj.bmc203 if obj.is_machine or not for_list:
207 if bmc is not None and bmc.bmc_type == BMC_TYPE.POD:204 boot_interface = obj.get_boot_interface()
208 data['pod'] = self.dehydrate_pod(bmc)205 if boot_interface is not None:
209206 data["pxe_mac"] = "%s" % boot_interface.mac_address
210 if (getattr(self, 'default_osystem', None) is not None and207 data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor()
211 getattr(self, 'default_distro_series', None) is not None):208 else:
212 data["osystem"] = obj.get_osystem(209 data["pxe_mac"] = data["pxe_mac_vendor"] = ""
213 default=self.default_osystem)
214 data["distro_series"] = obj.get_distro_series(
215 default=self.default_distro_series)
216 else:
217 data["osystem"] = obj.get_osystem()
218 data["distro_series"] = obj.get_distro_series()
219 if not for_list:
220 # Add info specific to a machine.
221 data["show_os_info"] = self.dehydrate_show_os_info(obj)
222 devices = [
223 self.dehydrate_device(device)
224 for device in obj.children.all()
225 ]
226 data["devices"] = sorted(
227 devices, key=itemgetter("fqdn"))
228210
229 cpu_script_results = [211 cpu_script_results = [
230 script_result for script_result in212 script_result for script_result in
@@ -279,6 +261,18 @@ class MachineHandler(NodeHandler):
279 else:261 else:
280 data["status_tooltip"] = ""262 data["status_tooltip"] = ""
281263
264 if not for_list:
265 if obj.bmc is not None and obj.bmc.bmc_type == BMC_TYPE.POD:
266 data['pod'] = self.dehydrate_pod(obj.bmc)
267 # Add info specific to a machine.
268 data["show_os_info"] = self.dehydrate_show_os_info(obj)
269 devices = [
270 self.dehydrate_device(device)
271 for device in obj.children.all()
272 ]
273 data["devices"] = sorted(
274 devices, key=itemgetter("fqdn"))
275
282 return data276 return data
283277
284 def dehydrate_show_os_info(self, obj):278 def dehydrate_show_os_info(self, obj):
diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
index 92fc9b5..8f33bdc 100644
--- a/src/maasserver/websockets/handlers/node.py
+++ b/src/maasserver/websockets/handlers/node.py
@@ -54,6 +54,9 @@ from metadataserver.enum import (
54)54)
55from metadataserver.models.scriptresult import ScriptResult55from metadataserver.models.scriptresult import ScriptResult
56from metadataserver.models.scriptset import get_status_from_qs56from metadataserver.models.scriptset import get_status_from_qs
57from provisioningserver.refresh.node_info_scripts import (
58 LIST_MODALIASES_OUTPUT_NAME,
59)
57from provisioningserver.tags import merge_details_cleanly60from provisioningserver.tags import merge_details_cleanly
5861
5962
@@ -69,7 +72,8 @@ NODE_TYPE_TO_LINK_TYPE = {
69def node_prefetch(queryset, *args):72def node_prefetch(queryset, *args):
70 return (73 return (
71 queryset74 queryset
72 .select_related('boot_interface', 'owner', 'zone', 'domain', *args)75 .select_related(
76 'boot_interface', 'owner', 'zone', 'domain', 'bmc', *args)
73 .prefetch_related('blockdevice_set__iscsiblockdevice')77 .prefetch_related('blockdevice_set__iscsiblockdevice')
74 .prefetch_related('blockdevice_set__physicalblockdevice')78 .prefetch_related('blockdevice_set__physicalblockdevice')
75 .prefetch_related('blockdevice_set__virtualblockdevice')79 .prefetch_related('blockdevice_set__virtualblockdevice')
@@ -84,9 +88,6 @@ def node_prefetch(queryset, *args):
8488
85class NodeHandler(TimestampedModelHandler):89class NodeHandler(TimestampedModelHandler):
8690
87 default_osystem = None
88 default_distro_series = None
89
90 class Meta:91 class Meta:
91 abstract = True92 abstract = True
92 pk = 'system_id'93 pk = 'system_id'
@@ -186,35 +187,10 @@ class NodeHandler(TimestampedModelHandler):
186 data["node_type_display"] = obj.get_node_type_display()187 data["node_type_display"] = obj.get_node_type_display()
187 data["link_type"] = NODE_TYPE_TO_LINK_TYPE[obj.node_type]188 data["link_type"] = NODE_TYPE_TO_LINK_TYPE[obj.node_type]
188189
189 data["extra_macs"] = [190 if obj.node_type == NODE_TYPE.MACHINE or (
190 "%s" % mac_address191 obj.is_controller and not for_list):
191 for mac_address in obj.get_extra_macs()192 # Disk count and storage amount is shown on the machine listing
192 ]193 # page and the machine and controllers details page.
193 subnets = self.get_all_subnets(obj)
194 data["subnets"] = [subnet.cidr for subnet in subnets]
195 data["fabrics"] = self.get_all_fabric_names(obj, subnets)
196 data["spaces"] = self.get_all_space_names(subnets)
197
198 data["tags"] = [
199 tag.name
200 for tag in obj.tags.all()
201 ]
202 data["metadata"] = {
203 metadata.key: metadata.value
204 for metadata in obj.nodemetadata_set.all()
205 }
206 if obj.node_type != NODE_TYPE.DEVICE:
207 data["architecture"] = obj.architecture
208 data["memory"] = obj.display_memory()
209 data["status"] = obj.display_status()
210 data["status_code"] = obj.status
211 boot_interface = obj.get_boot_interface()
212 if boot_interface is not None:
213 data["pxe_mac"] = "%s" % boot_interface.mac_address
214 data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor()
215 else:
216 data["pxe_mac"] = data["pxe_mac_vendor"] = ""
217
218 blockdevices = self.get_blockdevices_for(obj)194 blockdevices = self.get_blockdevices_for(obj)
219 physical_blockdevices = [195 physical_blockdevices = [
220 blockdevice for blockdevice in blockdevices196 blockdevice for blockdevice in blockdevices
@@ -227,16 +203,6 @@ class NodeHandler(TimestampedModelHandler):
227 for blockdevice in physical_blockdevices203 for blockdevice in physical_blockdevices
228 ) / (1000 ** 3))204 ) / (1000 ** 3))
229 data["storage_tags"] = self.get_all_storage_tags(blockdevices)205 data["storage_tags"] = self.get_all_storage_tags(blockdevices)
230 data["grouped_storages"] = self.get_grouped_storages(
231 physical_blockdevices)
232
233 data["osystem"] = obj.get_osystem(
234 default=self.default_osystem)
235 data["distro_series"] = obj.get_distro_series(
236 default=self.default_distro_series)
237 data["dhcp_on"] = self.get_providing_dhcp(obj)
238
239 if obj.node_type != NODE_TYPE.DEVICE:
240 commissioning_script_results = []206 commissioning_script_results = []
241 testing_script_results = []207 testing_script_results = []
242 log_results = set()208 log_results = set()
@@ -254,12 +220,12 @@ class NodeHandler(TimestampedModelHandler):
254 RESULT_TYPE.COMMISSIONING):220 RESULT_TYPE.COMMISSIONING):
255 commissioning_script_results.append(script_result)221 commissioning_script_results.append(script_result)
256 if (script_result.name in script_output_nsmap and222 if (script_result.name in script_output_nsmap and
257 script_result.status == SCRIPT_STATUS.PASSED):223 script_result.status ==
224 SCRIPT_STATUS.PASSED):
258 log_results.add(script_result.name)225 log_results.add(script_result.name)
259 elif (script_result.script_set.result_type ==226 elif (script_result.script_set.result_type ==
260 RESULT_TYPE.TESTING):227 RESULT_TYPE.TESTING):
261 testing_script_results.append(script_result)228 testing_script_results.append(script_result)
262
263 data["commissioning_script_count"] = len(229 data["commissioning_script_count"] = len(
264 commissioning_script_results)230 commissioning_script_results)
265 data["commissioning_status"] = get_status_from_qs(231 data["commissioning_status"] = get_status_from_qs(
@@ -269,11 +235,41 @@ class NodeHandler(TimestampedModelHandler):
269 commissioning_script_results).replace(235 commissioning_script_results).replace(
270 'test', 'commissioning script'))236 'test', 'commissioning script'))
271 data["testing_script_count"] = len(testing_script_results)237 data["testing_script_count"] = len(testing_script_results)
272 data["testing_status"] = get_status_from_qs(testing_script_results)238 data["testing_status"] = get_status_from_qs(
239 testing_script_results)
273 data["testing_status_tooltip"] = (240 data["testing_status_tooltip"] = (
274 self.dehydrate_hardware_status_tooltip(testing_script_results))241 self.dehydrate_hardware_status_tooltip(
242 testing_script_results))
275 data["has_logs"] = (243 data["has_logs"] = (
276 log_results.difference(script_output_nsmap.keys()) == set())244 log_results.difference(script_output_nsmap.keys()) ==
245 set())
246 else:
247 blockdevices = []
248
249 if obj.node_type != NODE_TYPE.DEVICE:
250 # These values are not defined on a device.
251 data["architecture"] = obj.architecture
252 data["osystem"] = obj.osystem
253 data["distro_series"] = obj.distro_series
254 data["memory"] = obj.display_memory()
255 data["status"] = obj.display_status()
256 data["status_code"] = obj.status
257
258 # Filters are only available on machines and devices.
259 if not obj.is_controller:
260 # For filters
261 subnets = self.get_all_subnets(obj)
262 data["subnets"] = [subnet.cidr for subnet in subnets]
263 data["fabrics"] = self.get_all_fabric_names(obj, subnets)
264 data["spaces"] = self.get_all_space_names(subnets)
265 data["tags"] = [
266 tag.name
267 for tag in obj.tags.all()
268 ]
269 data["extra_macs"] = [
270 "%s" % mac_address
271 for mac_address in obj.get_extra_macs()
272 ]
277273
278 if not for_list:274 if not for_list:
279 data["on_network"] = obj.on_network()275 data["on_network"] = obj.on_network()
@@ -281,11 +277,18 @@ class NodeHandler(TimestampedModelHandler):
281 # XXX lamont 2017-02-15 Much of this should be split out into277 # XXX lamont 2017-02-15 Much of this should be split out into
282 # individual methods, rather than having this huge block of278 # individual methods, rather than having this huge block of
283 # dense code here.279 # dense code here.
280 # Status of the commissioning, testing, and logs tabs
281 data["metadata"] = {
282 metadata.key: metadata.value
283 for metadata in obj.nodemetadata_set.all()
284 }
285
284 # Network286 # Network
285 data["interfaces"] = [287 data["interfaces"] = [
286 self.dehydrate_interface(interface, obj)288 self.dehydrate_interface(interface, obj)
287 for interface in obj.interface_set.all().order_by('name')289 for interface in obj.interface_set.all().order_by('name')
288 ]290 ]
291 data["dhcp_on"] = self.get_providing_dhcp(obj)
289292
290 data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel)293 data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel)
291294
@@ -313,6 +316,8 @@ class NodeHandler(TimestampedModelHandler):
313 self.dehydrate_filesystem(filesystem)316 self.dehydrate_filesystem(filesystem)
314 for filesystem in obj.special_filesystems.order_by("id")317 for filesystem in obj.special_filesystems.order_by("id")
315 ]318 ]
319 data["grouped_storages"] = self.get_grouped_storages(
320 physical_blockdevices)
316321
317 # Events322 # Events
318 data["events"] = self.dehydrate_events(obj)323 data["events"] = self.dehydrate_events(obj)
@@ -323,7 +328,18 @@ class NodeHandler(TimestampedModelHandler):
323328
324 # Third party drivers329 # Third party drivers
325 if Config.objects.get_config('enable_third_party_drivers'):330 if Config.objects.get_config('enable_third_party_drivers'):
326 driver = get_third_party_driver(obj)331 # Pull modaliases from the cache
332 modaliases = []
333 for script_result in commissioning_script_results:
334 if script_result.name == LIST_MODALIASES_OUTPUT_NAME:
335 if script_result.status == SCRIPT_STATUS.PASSED:
336 # STDOUT is deferred in the cache so load it.
337 script_result = ScriptResult.objects.filter(
338 id=script_result.id).only(
339 'id', 'stdout').first()
340 modaliases = script_result.stdout.decode(
341 'utf-8').splitlines()
342 driver = get_third_party_driver(obj, modaliases)
327 if "module" in driver and "comment" in driver:343 if "module" in driver and "comment" in driver:
328 data["third_party_driver"] = {344 data["third_party_driver"] = {
329 "module": driver["module"],345 "module": driver["module"],
diff --git a/src/maasserver/websockets/handlers/notification.py b/src/maasserver/websockets/handlers/notification.py
index 7292e68..d189e75 100644
--- a/src/maasserver/websockets/handlers/notification.py
+++ b/src/maasserver/websockets/handlers/notification.py
@@ -1,4 +1,4 @@
1# Copyright 2017 Canonical Ltd. This software is licensed under the1# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
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"""The notification handler for the WebSocket connection."""4"""The notification handler for the WebSocket connection."""
@@ -22,7 +22,7 @@ class NotificationHandler(TimestampedModelHandler):
22 exclude = list_exclude = {"context"}22 exclude = list_exclude = {"context"}
23 listen_channels = {'notification', 'notificationdismissal'}23 listen_channels = {'notification', 'notificationdismissal'}
2424
25 def get_queryset(self):25 def get_queryset(self, for_list=False):
26 """Return `Notifications` for the current user."""26 """Return `Notifications` for the current user."""
27 return Notification.objects.find_for_user(self.user)27 return Notification.objects.find_for_user(self.user)
2828
diff --git a/src/maasserver/websockets/handlers/sshkey.py b/src/maasserver/websockets/handlers/sshkey.py
index efac2e6..37503dc 100644
--- a/src/maasserver/websockets/handlers/sshkey.py
+++ b/src/maasserver/websockets/handlers/sshkey.py
@@ -41,7 +41,7 @@ class SSHKeyHandler(TimestampedModelHandler):
41 "sshkey",41 "sshkey",
42 ]42 ]
4343
44 def get_queryset(self):44 def get_queryset(self, for_list=False):
45 """Return `QuerySet` for SSH keys owned by `user`."""45 """Return `QuerySet` for SSH keys owned by `user`."""
46 return self._meta.queryset.filter(user=self.user)46 return self._meta.queryset.filter(user=self.user)
4747
diff --git a/src/maasserver/websockets/handlers/switch.py b/src/maasserver/websockets/handlers/switch.py
index e40f779..72accf5 100644
--- a/src/maasserver/websockets/handlers/switch.py
+++ b/src/maasserver/websockets/handlers/switch.py
@@ -1,4 +1,4 @@
1# Copyright 2017 Canonical Ltd. This software is licensed under the1# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
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"""The switch handler for the WebSocket connection."""4"""The switch handler for the WebSocket connection."""
@@ -45,10 +45,14 @@ class SwitchHandler(NodeHandler):
45 'switch',45 'switch',
46 ]46 ]
4747
48 def get_queryset(self):48 def get_queryset(self, for_list=False):
49 """Return `QuerySet` for devices only viewable by `user`."""49 """Return `QuerySet` for devices only viewable by `user`."""
50 # FIXME - Return a different query set when for_list is true. This
51 # should contain only the items needed to display a switch when listing
52 # in the UI.
50 return Node.objects.get_nodes(53 return Node.objects.get_nodes(
51 self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset)54 self.user, NODE_PERMISSION.VIEW,
55 from_nodes=self._meta.queryset)
5256
53 def get_object(self, params):57 def get_object(self, params):
54 """Get object by using the `pk` in `params`."""58 """Get object by using the `pk` in `params`."""
diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py
index 18d2433..e86c19a 100644
--- a/src/maasserver/websockets/handlers/tests/test_controller.py
+++ b/src/maasserver/websockets/handlers/tests/test_controller.py
@@ -1,4 +1,4 @@
1# Copyright 2016-2017 Canonical Ltd. This software is licensed under the1# Copyright 2016-2018 Canonical Ltd. This software is licensed under the
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"""Tests for `maasserver.websockets.handlers.controller`"""4"""Tests for `maasserver.websockets.handlers.controller`"""
@@ -12,6 +12,11 @@ from maasserver.testing.factory import factory
12from maasserver.testing.testcase import MAASServerTestCase12from maasserver.testing.testcase import MAASServerTestCase
13from maasserver.websockets.base import dehydrate_datetime13from maasserver.websockets.base import dehydrate_datetime
14from maasserver.websockets.handlers.controller import ControllerHandler14from maasserver.websockets.handlers.controller import ControllerHandler
15from maastesting.djangotestcase import count_queries
16from metadataserver.enum import (
17 RESULT_TYPE,
18 SCRIPT_STATUS,
19)
15from testscenarios import multiply_scenarios20from testscenarios import multiply_scenarios
16from testtools.matchers import (21from testtools.matchers import (
17 ContainsDict,22 ContainsDict,
@@ -65,6 +70,69 @@ class TestControllerHandler(MAASServerTestCase):
65 self.assertEqual(1, len(result))70 self.assertEqual(1, len(result))
66 self.assertEqual(NODE_TYPE.RACK_CONTROLLER, result[0].get('node_type'))71 self.assertEqual(NODE_TYPE.RACK_CONTROLLER, result[0].get('node_type'))
6772
73 def test_list_num_queries_is_the_expected_number(self):
74 owner = factory.make_admin()
75 for _ in range(10):
76 node = factory.make_RegionRackController(owner=owner)
77 commissioning_script_set = factory.make_ScriptSet(
78 node=node, result_type=RESULT_TYPE.COMMISSIONING)
79 testing_script_set = factory.make_ScriptSet(
80 node=node, result_type=RESULT_TYPE.TESTING)
81 node.current_commissioning_script_set = commissioning_script_set
82 node.current_testing_script_set = testing_script_set
83 node.save()
84 for __ in range(10):
85 factory.make_ScriptResult(
86 status=SCRIPT_STATUS.PASSED,
87 script_set=commissioning_script_set)
88 factory.make_ScriptResult(
89 status=SCRIPT_STATUS.PASSED,
90 script_set=testing_script_set)
91
92 handler = ControllerHandler(owner, {})
93 queries_one, _ = count_queries(handler.list, {'limit': 1})
94 queries_total, _ = count_queries(handler.list, {})
95 # This check is to notify the developer that a change was made that
96 # affects the number of queries performed when doing a node listing.
97 # It is important to keep this number as low as possible. A larger
98 # number means regiond has to do more work slowing down its process
99 # and slowing down the client waiting for the response.
100 self.assertEqual(
101 queries_one, 3,
102 "Number of queries has changed; make sure this is expected.")
103 self.assertEqual(
104 queries_total, 3,
105 "Number of queries has changed; make sure this is expected.")
106
107 def test_get_num_queries_is_the_expected_number(self):
108 owner = factory.make_admin()
109 node = factory.make_RegionRackController(owner=owner)
110 commissioning_script_set = factory.make_ScriptSet(
111 node=node, result_type=RESULT_TYPE.COMMISSIONING)
112 testing_script_set = factory.make_ScriptSet(
113 node=node, result_type=RESULT_TYPE.TESTING)
114 node.current_commissioning_script_set = commissioning_script_set
115 node.current_testing_script_set = testing_script_set
116 node.save()
117 for __ in range(10):
118 factory.make_ScriptResult(
119 status=SCRIPT_STATUS.PASSED,
120 script_set=commissioning_script_set)
121 factory.make_ScriptResult(
122 status=SCRIPT_STATUS.PASSED,
123 script_set=testing_script_set)
124
125 handler = ControllerHandler(owner, {})
126 queries, _ = count_queries(handler.get, {'system_id': node.system_id})
127 # This check is to notify the developer that a change was made that
128 # affects the number of queries performed when doing a node get.
129 # It is important to keep this number as low as possible. A larger
130 # number means regiond has to do more work slowing down its process
131 # and slowing down the client waiting for the response.
132 self.assertEqual(
133 queries, 28,
134 "Number of queries has changed; make sure this is expected.")
135
68 def test_get_form_class_for_create(self):136 def test_get_form_class_for_create(self):
69 user = factory.make_admin()137 user = factory.make_admin()
70 handler = ControllerHandler(user, {})138 handler = ControllerHandler(user, {})
diff --git a/src/maasserver/websockets/handlers/tests/test_device.py b/src/maasserver/websockets/handlers/tests/test_device.py
index ab26620..d6af973 100644
--- a/src/maasserver/websockets/handlers/tests/test_device.py
+++ b/src/maasserver/websockets/handlers/tests/test_device.py
@@ -137,7 +137,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
137 boot_interface = node.get_boot_interface()137 boot_interface = node.get_boot_interface()
138 data = {138 data = {
139 "actions": list(compile_node_actions(node, user).keys()),139 "actions": list(compile_node_actions(node, user).keys()),
140 "bmc": node.bmc_id,
141 "created": dehydrate_datetime(node.created),140 "created": dehydrate_datetime(node.created),
142 "domain": {141 "domain": {
143 "id": node.domain.id,142 "id": node.domain.id,
@@ -149,7 +148,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
149 ],148 ],
150 "fqdn": node.fqdn,149 "fqdn": node.fqdn,
151 "hostname": node.hostname,150 "hostname": node.hostname,
152 "metadata": {},
153 "node_type_display": node.get_node_type_display(),151 "node_type_display": node.get_node_type_display(),
154 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],152 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],
155 "id": node.id,153 "id": node.id,
@@ -195,7 +193,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
195 "ip_address",193 "ip_address",
196 "ip_assignment",194 "ip_assignment",
197 "link_type",195 "link_type",
198 "metadata",
199 "node_type_display",196 "node_type_display",
200 "primary_mac",197 "primary_mac",
201 "spaces",198 "spaces",
@@ -253,6 +250,24 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
253 handler.get({"system_id": device.system_id}))250 handler.get({"system_id": device.system_id}))
254251
255 @transactional252 @transactional
253 def test_get_num_queries_is_the_expected_number(self):
254 owner = factory.make_User()
255 handler = DeviceHandler(owner, {})
256 device = self.make_device_with_ip_address(
257 owner=owner, ip_assignment=DEVICE_IP_ASSIGNMENT_TYPE.STATIC)
258 queries, _ = count_queries(
259 handler.get, {"system_id": device.system_id})
260
261 # This check is to notify the developer that a change was made that
262 # affects the number of queries performed when doing a node get.
263 # It is important to keep this number as low as possible. A larger
264 # number means regiond has to do more work slowing down its process
265 # and slowing down the client waiting for the response.
266 self.assertEqual(
267 queries, 19,
268 "Number of queries has changed; make sure this is expected.")
269
270 @transactional
256 def test_list(self):271 def test_list(self):
257 owner = factory.make_User()272 owner = factory.make_User()
258 handler = DeviceHandler(owner, {})273 handler = DeviceHandler(owner, {})
@@ -301,7 +316,7 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
301 # number means regiond has to do more work slowing down its process316 # number means regiond has to do more work slowing down its process
302 # and slowing down the client waiting for the response.317 # and slowing down the client waiting for the response.
303 self.assertEqual(318 self.assertEqual(
304 query_10_count, 13,319 query_10_count, 10,
305 "Number of queries has changed; make sure this is expected.")320 "Number of queries has changed; make sure this is expected.")
306321
307 @transactional322 @transactional
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index 27bc195..3b333ed 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -150,11 +150,12 @@ class TestMachineHandler(MAASServerTestCase):
150150
151 boot_interface = node.get_boot_interface()151 boot_interface = node.get_boot_interface()
152 pxe_mac_vendor = node.get_pxe_mac_vendor()152 pxe_mac_vendor = node.get_pxe_mac_vendor()
153 subnets = handler.get_all_subnets(node)
154
153 blockdevices = [155 blockdevices = [
154 blockdevice.actual_instance156 blockdevice.actual_instance
155 for blockdevice in node.blockdevice_set.all()157 for blockdevice in node.blockdevice_set.all()
156 ]158 ]
157 driver = get_third_party_driver(node)
158 disks = [159 disks = [
159 handler.dehydrate_blockdevice(blockdevice, node)160 handler.dehydrate_blockdevice(blockdevice, node)
160 for blockdevice in blockdevices161 for blockdevice in blockdevices
@@ -167,8 +168,10 @@ class TestMachineHandler(MAASServerTestCase):
167 for cache_set in CacheSet.objects.get_cache_sets_for_node(node)168 for cache_set in CacheSet.objects.get_cache_sets_for_node(node)
168 ]169 ]
169 disks = sorted(disks, key=itemgetter("name"))170 disks = sorted(disks, key=itemgetter("name"))
170 subnets = handler.get_all_subnets(node)171 driver = get_third_party_driver(node)
171 commissioning_scripts = node.get_latest_commissioning_script_results172
173 commissioning_scripts = (
174 node.get_latest_commissioning_script_results)
172 commissioning_scripts = commissioning_scripts.exclude(175 commissioning_scripts = commissioning_scripts.exclude(
173 status=SCRIPT_STATUS.ABORTED)176 status=SCRIPT_STATUS.ABORTED)
174 testing_scripts = node.get_latest_testing_script_results177 testing_scripts = node.get_latest_testing_script_results
@@ -179,6 +182,7 @@ class TestMachineHandler(MAASServerTestCase):
179 if (script_result.name in script_output_nsmap and182 if (script_result.name in script_output_nsmap and
180 script_result.status == SCRIPT_STATUS.PASSED):183 script_result.status == SCRIPT_STATUS.PASSED):
181 log_results.add(script_result.name)184 log_results.add(script_result.name)
185
182 data = {186 data = {
183 "actions": list(compile_node_actions(node, handler.user).keys()),187 "actions": list(compile_node_actions(node, handler.user).keys()),
184 "architecture": node.architecture,188 "architecture": node.architecture,
@@ -186,25 +190,27 @@ class TestMachineHandler(MAASServerTestCase):
186 "boot_disk": node.boot_disk,190 "boot_disk": node.boot_disk,
187 "bios_boot_method": node.bios_boot_method,191 "bios_boot_method": node.bios_boot_method,
188 "commissioning_script_count": commissioning_scripts.count(),192 "commissioning_script_count": commissioning_scripts.count(),
189 "commissioning_status": get_status_from_qs(commissioning_scripts),193 "commissioning_status": get_status_from_qs(
194 commissioning_scripts),
190 "commissioning_status_tooltip": (195 "commissioning_status_tooltip": (
191 handler.dehydrate_hardware_status_tooltip(196 handler.dehydrate_hardware_status_tooltip(
192 commissioning_scripts).replace(197 commissioning_scripts).replace(
193 'test', 'commissioning script')),198 'test', 'commissioning script')),
194 "current_commissioning_script_set": (199 "current_commissioning_script_set": (
195 node.current_commissioning_script_set_id),200 node.current_commissioning_script_set_id),
201 "current_testing_script_set": node.current_testing_script_set_id,
196 "testing_script_count": testing_scripts.count(),202 "testing_script_count": testing_scripts.count(),
197 "testing_status": get_status_from_qs(testing_scripts),203 "testing_status": get_status_from_qs(testing_scripts),
198 "testing_status_tooltip": (204 "testing_status_tooltip": (
199 handler.dehydrate_hardware_status_tooltip(testing_scripts)),205 handler.dehydrate_hardware_status_tooltip(
200 "current_testing_script_set": node.current_testing_script_set_id,206 testing_scripts)),
201 "current_installation_script_set": (207 "current_installation_script_set": (
202 node.current_installation_script_set_id),208 node.current_installation_script_set_id),
203 "installation_status": (209 "installation_status": (
204 handler.dehydrate_script_set_status(210 handler.dehydrate_script_set_status(
205 node.current_installation_script_set)),211 node.current_installation_script_set)),
206 "has_logs": (212 "has_logs": (log_results.difference(
207 log_results.difference(script_output_nsmap.keys()) == set()),213 script_output_nsmap.keys()) == set()),
208 "locked": node.locked,214 "locked": node.locked,
209 "cpu_count": node.cpu_count,215 "cpu_count": node.cpu_count,
210 "cpu_speed": node.cpu_speed,216 "cpu_speed": node.cpu_speed,
@@ -231,7 +237,7 @@ class TestMachineHandler(MAASServerTestCase):
231 "supported_filesystems": [237 "supported_filesystems": [
232 {'key': key, 'ui': ui}238 {'key': key, 'ui': ui}
233 for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES],239 for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES],
234 "distro_series": node.get_distro_series(),240 "distro_series": node.distro_series,
235 "error": node.error,241 "error": node.error,
236 "error_description": node.error_description,242 "error_description": node.error_description,
237 "events": handler.dehydrate_events(node),243 "events": handler.dehydrate_events(node),
@@ -251,10 +257,9 @@ class TestMachineHandler(MAASServerTestCase):
251 "license_key": node.license_key,257 "license_key": node.license_key,
252 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],258 "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type],
253 "memory": node.display_memory(),259 "memory": node.display_memory(),
254 "metadata": {},
255 "node_type_display": node.get_node_type_display(),260 "node_type_display": node.get_node_type_display(),
256 "min_hwe_kernel": node.min_hwe_kernel,261 "min_hwe_kernel": node.min_hwe_kernel,
257 "osystem": node.get_osystem(),262 "osystem": node.osystem,
258 "owner": handler.dehydrate_owner(node.owner),263 "owner": handler.dehydrate_owner(node.owner),
259 "power_parameters": handler.dehydrate_power_parameters(264 "power_parameters": handler.dehydrate_power_parameters(
260 node.power_parameters),265 node.power_parameters),
@@ -292,7 +297,6 @@ class TestMachineHandler(MAASServerTestCase):
292 "zone": handler.dehydrate_zone(node.zone),297 "zone": handler.dehydrate_zone(node.zone),
293 "pool": handler.dehydrate_pool(node.pool),298 "pool": handler.dehydrate_pool(node.pool),
294 "default_user": node.default_user,299 "default_user": node.default_user,
295 "dhcp_on": node.interface_set.filter(vlan__dhcp_on=True).exists(),
296 }300 }
297 bmc = node.bmc301 bmc = node.bmc
298 if bmc is not None and bmc.bmc_type == BMC_TYPE.POD:302 if bmc is not None and bmc.bmc_type == BMC_TYPE.POD:
@@ -333,6 +337,13 @@ class TestMachineHandler(MAASServerTestCase):
333 for key in list(data):337 for key in list(data):
334 if key not in allowed_fields:338 if key not in allowed_fields:
335 del data[key]339 del data[key]
340 else:
341 data.update({
342 "dhcp_on": node.interface_set.filter(
343 vlan__dhcp_on=True).exists(),
344 "grouped_storages": handler.get_grouped_storages(blockdevices),
345 "metadata": {},
346 })
336347
337 cpu_script_results = [348 cpu_script_results = [
338 script_result for script_result in349 script_result for script_result in
@@ -395,8 +406,6 @@ class TestMachineHandler(MAASServerTestCase):
395 else:406 else:
396 data["status_tooltip"] = ""407 data["status_tooltip"] = ""
397408
398 data["grouped_storages"] = handler.get_grouped_storages(blockdevices)
399
400 return data409 return data
401410
402 def make_nodes(self, number):411 def make_nodes(self, number):
@@ -495,10 +504,39 @@ class TestMachineHandler(MAASServerTestCase):
495 # number means regiond has to do more work slowing down its process504 # number means regiond has to do more work slowing down its process
496 # and slowing down the client waiting for the response.505 # and slowing down the client waiting for the response.
497 self.assertEqual(506 self.assertEqual(
498 queries_one, 11,507 queries_one, 8,
508 "Number of queries has changed; make sure this is expected.")
509 self.assertEqual(
510 queries_total, 8,
499 "Number of queries has changed; make sure this is expected.")511 "Number of queries has changed; make sure this is expected.")
512
513 def test_get_num_queries_is_the_expected_number(self):
514 owner = factory.make_User()
515 node = factory.make_Node(owner=owner)
516 commissioning_script_set = factory.make_ScriptSet(
517 node=node, result_type=RESULT_TYPE.COMMISSIONING)
518 testing_script_set = factory.make_ScriptSet(
519 node=node, result_type=RESULT_TYPE.TESTING)
520 node.current_commissioning_script_set = commissioning_script_set
521 node.current_testing_script_set = testing_script_set
522 node.save()
523 for __ in range(10):
524 factory.make_ScriptResult(
525 status=SCRIPT_STATUS.PASSED,
526 script_set=commissioning_script_set)
527 factory.make_ScriptResult(
528 status=SCRIPT_STATUS.PASSED,
529 script_set=testing_script_set)
530
531 handler = MachineHandler(owner, {})
532 queries, _ = count_queries(handler.get, {'system_id': node.system_id})
533 # This check is to notify the developer that a change was made that
534 # affects the number of queries performed when doing a node get.
535 # It is important to keep this number as low as possible. A larger
536 # number means regiond has to do more work slowing down its process
537 # and slowing down the client waiting for the response.
500 self.assertEqual(538 self.assertEqual(
501 queries_total, 11,539 queries, 48,
502 "Number of queries has changed; make sure this is expected.")540 "Number of queries has changed; make sure this is expected.")
503541
504 def test_trigger_update_updates_script_result_cache(self):542 def test_trigger_update_updates_script_result_cache(self):
@@ -521,6 +559,7 @@ class TestMachineHandler(MAASServerTestCase):
521559
522 handler = MachineHandler(owner, {})560 handler = MachineHandler(owner, {})
523 # Simulate a trigger pushing an update to the UI561 # Simulate a trigger pushing an update to the UI
562 handler.cache = {'active_pk': node.system_id}
524 _, _, ret = handler.on_listen_for_active_pk(563 _, _, ret = handler.on_listen_for_active_pk(
525 'update', node.system_id, node)564 'update', node.system_id, node)
526 self.assertEquals(ret['commissioning_script_count'], 10)565 self.assertEquals(ret['commissioning_script_count'], 10)
diff --git a/src/maasserver/websockets/handlers/tests/test_switch.py b/src/maasserver/websockets/handlers/tests/test_switch.py
index 6313a5f..d3f4689 100644
--- a/src/maasserver/websockets/handlers/tests/test_switch.py
+++ b/src/maasserver/websockets/handlers/tests/test_switch.py
@@ -5,10 +5,7 @@
55
6__all__ = []6__all__ = []
77
8from maasserver.enum import (8from maasserver.enum import NODE_TYPE
9 NODE_METADATA,
10 NODE_TYPE,
11)
12from maasserver.exceptions import NodeActionError9from maasserver.exceptions import NodeActionError
13from maasserver.testing.factory import factory10from maasserver.testing.factory import factory
14from maasserver.testing.testcase import MAASTransactionServerTestCase11from maasserver.testing.testcase import MAASTransactionServerTestCase
@@ -67,22 +64,6 @@ class TestSwitchHandler(MAASTransactionServerTestCase):
67 [result['system_id'] for result in handler.list({})])64 [result['system_id'] for result in handler.list({})])
6865
69 @transactional66 @transactional
70 def test_list_switches_includes_metadata(self):
71 owner = factory.make_User()
72 handler = SwitchHandler(owner, {})
73 machine = factory.make_Machine(owner=owner)
74 metadata = {
75 NODE_METADATA.VENDOR_NAME: "Canonical",
76 NODE_METADATA.PHYSICAL_MODEL_NAME: "Cloud-in-a-box"
77 }
78 for key, value in metadata.items():
79 factory.make_NodeMetadata(node=machine, key=key, value=value)
80 factory.make_Switch(node=machine)
81 self.assertItemsEqual(
82 [metadata],
83 [result['metadata'] for result in handler.list({})])
84
85 @transactional
86 def test_list_ignores_nodes_that_arent_switches(self):67 def test_list_ignores_nodes_that_arent_switches(self):
87 owner = factory.make_User()68 owner = factory.make_User()
88 handler = SwitchHandler(owner, {})69 handler = SwitchHandler(owner, {})
diff --git a/src/maasserver/websockets/handlers/user.py b/src/maasserver/websockets/handlers/user.py
index 8c10046..cc8759a 100644
--- a/src/maasserver/websockets/handlers/user.py
+++ b/src/maasserver/websockets/handlers/user.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
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"""The user handler for the WebSocket connection."""4"""The user handler for the WebSocket connection."""
@@ -35,9 +35,9 @@ class UserHandler(Handler):
35 "user",35 "user",
36 ]36 ]
3737
38 def get_queryset(self):38 def get_queryset(self, for_list=False):
39 """Return `QuerySet` for users only viewable by `user`."""39 """Return `QuerySet` for users only viewable by `user`."""
40 users = super(UserHandler, self).get_queryset()40 users = super(UserHandler, self).get_queryset(for_list=for_list)
41 if reload_object(self.user).is_superuser:41 if reload_object(self.user).is_superuser:
42 # Super users can view all users, except for the built-in users42 # Super users can view all users, except for the built-in users
43 return users.exclude(username__in=SYSTEM_USERS)43 return users.exclude(username__in=SYSTEM_USERS)
diff --git a/src/maasserver/websockets/tests/test_base.py b/src/maasserver/websockets/tests/test_base.py
index 9a3001f..ee71feb 100644
--- a/src/maasserver/websockets/tests/test_base.py
+++ b/src/maasserver/websockets/tests/test_base.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
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"""Tests for `maasserver.websockets.base`"""4"""Tests for `maasserver.websockets.base`"""
@@ -337,6 +337,25 @@ class TestHandler(MAASServerTestCase):
337 HandlerDoesNotExistError,337 HandlerDoesNotExistError,
338 handler.get_object, {"system_id": machine.system_id})338 handler.get_object, {"system_id": machine.system_id})
339339
340 def test_get_queryset(self):
341 queryset = MagicMock()
342 list_queryset = MagicMock()
343 handler = make_handler(
344 "TestHandler", queryset=queryset, list_queryset=list_queryset)
345 self.assertEqual(queryset, handler.get_queryset())
346
347 def test_get_queryset_list(self):
348 queryset = MagicMock()
349 list_queryset = MagicMock()
350 handler = make_handler(
351 "TestHandler", queryset=queryset, list_queryset=list_queryset)
352 self.assertEqual(list_queryset, handler.get_queryset(for_list=True))
353
354 def test_get_queryset_list_only_if_avail(self):
355 queryset = MagicMock()
356 handler = make_handler("TestHandler", queryset=queryset)
357 self.assertEqual(queryset, handler.get_queryset(for_list=True))
358
340 def test_execute_only_allows_meta_allowed_methods(self):359 def test_execute_only_allows_meta_allowed_methods(self):
341 handler = self.make_nodes_handler(allowed_methods=['list'])360 handler = self.make_nodes_handler(allowed_methods=['list'])
342 with ExpectedException(HandlerNoSuchMethodError):361 with ExpectedException(HandlerNoSuchMethodError):

Subscribers

People subscribed via source and target branches