Merge ~ltrager/maas:limit_listing_queries into maas:master
- Git
- lp:~ltrager/maas
- limit_listing_queries
- Merge into master
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) |
Related bugs: |
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.
Description of the change
Blake Rouse (blake-rouse) : | # |
- 37b692f... by Lee Trager
-
Merge branch 'master' into limit_listing_
queries - c38c9fb... by Lee Trager
-
Add get_queryset tests
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.
Blake Rouse (blake-rouse) wrote : | # |
Thanks for the extra test and the clarification.
Preview Diff
1 | diff --git a/src/maasserver/third_party_drivers.py b/src/maasserver/third_party_drivers.py |
2 | index ff2039a..6c7311b 100644 |
3 | --- a/src/maasserver/third_party_drivers.py |
4 | +++ b/src/maasserver/third_party_drivers.py |
5 | @@ -134,13 +134,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 |
17 | + if detected_aliases is None: |
18 | + detected_aliases = node.modaliases |
19 | |
20 | third_party_drivers_config = DriversConfig.load_from_cache() |
21 | third_party_drivers = third_party_drivers_config['drivers'] |
22 | diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py |
23 | index 636d50a..d7327e4 100644 |
24 | --- a/src/maasserver/websockets/base.py |
25 | +++ b/src/maasserver/websockets/base.py |
26 | @@ -81,6 +81,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 | @@ -281,19 +282,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 | @@ -350,7 +354,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(**{ |
69 | diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py |
70 | index 9fbe5c8..636c4d9 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 | @@ -90,10 +94,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 | @@ -108,14 +116,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): |
125 | diff --git a/src/maasserver/websockets/handlers/device.py b/src/maasserver/websockets/handlers/device.py |
126 | index dc5374f..0407b0c 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 | @@ -145,10 +152,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: |
181 | diff --git a/src/maasserver/websockets/handlers/event.py b/src/maasserver/websockets/handlers/event.py |
182 | index 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 | |
194 | diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py |
195 | index 375b0fb..ca5cbeb 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 | @@ -177,54 +189,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 1 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 | - configs = ( |
248 | - Config.objects.get_configs( |
249 | - ['default_osystem', 'default_distro_series'])) |
250 | - self.default_osystem = configs['default_osystem'] |
251 | - self.default_distro_series = configs['default_distro_series'] |
252 | - return super(MachineHandler, self).list(params) |
253 | + self.user, NODE_PERMISSION.VIEW, |
254 | + from_nodes=super().get_queryset(for_list=for_list)) |
255 | |
256 | def dehydrate(self, obj, data, for_list=False): |
257 | """Add extra fields to `data`.""" |
258 | data = super(MachineHandler, self).dehydrate( |
259 | obj, data, for_list=for_list) |
260 | - data["locked"] = obj.locked |
261 | - bmc = obj.bmc |
262 | - if bmc is not None and bmc.bmc_type == BMC_TYPE.POD: |
263 | - data['pod'] = self.dehydrate_pod(bmc) |
264 | - |
265 | - if (getattr(self, 'default_osystem', None) is not None and |
266 | - getattr(self, 'default_distro_series', None) is not None): |
267 | - data["osystem"] = obj.get_osystem( |
268 | - default=self.default_osystem) |
269 | - data["distro_series"] = obj.get_distro_series( |
270 | - default=self.default_distro_series) |
271 | - else: |
272 | - data["osystem"] = obj.get_osystem() |
273 | - data["distro_series"] = obj.get_distro_series() |
274 | - if not for_list: |
275 | - # Add info specific to a machine. |
276 | - data["show_os_info"] = self.dehydrate_show_os_info(obj) |
277 | - devices = [ |
278 | - self.dehydrate_device(device) |
279 | - for device in obj.children.all() |
280 | - ] |
281 | - data["devices"] = sorted( |
282 | - devices, key=itemgetter("fqdn")) |
283 | + |
284 | + if obj.is_machine or not for_list: |
285 | + boot_interface = obj.get_boot_interface() |
286 | + if boot_interface is not None: |
287 | + data["pxe_mac"] = "%s" % boot_interface.mac_address |
288 | + data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor() |
289 | + else: |
290 | + data["pxe_mac"] = data["pxe_mac_vendor"] = "" |
291 | |
292 | cpu_script_results = [ |
293 | script_result for script_result in |
294 | @@ -279,6 +261,18 @@ class MachineHandler(NodeHandler): |
295 | else: |
296 | data["status_tooltip"] = "" |
297 | |
298 | + if not for_list: |
299 | + if obj.bmc is not None and obj.bmc.bmc_type == BMC_TYPE.POD: |
300 | + data['pod'] = self.dehydrate_pod(obj.bmc) |
301 | + # Add info specific to a machine. |
302 | + data["show_os_info"] = self.dehydrate_show_os_info(obj) |
303 | + devices = [ |
304 | + self.dehydrate_device(device) |
305 | + for device in obj.children.all() |
306 | + ] |
307 | + data["devices"] = sorted( |
308 | + devices, key=itemgetter("fqdn")) |
309 | + |
310 | return data |
311 | |
312 | def dehydrate_show_os_info(self, obj): |
313 | diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py |
314 | index 92fc9b5..8f33bdc 100644 |
315 | --- a/src/maasserver/websockets/handlers/node.py |
316 | +++ b/src/maasserver/websockets/handlers/node.py |
317 | @@ -54,6 +54,9 @@ from metadataserver.enum import ( |
318 | ) |
319 | from metadataserver.models.scriptresult import ScriptResult |
320 | from metadataserver.models.scriptset import get_status_from_qs |
321 | +from provisioningserver.refresh.node_info_scripts import ( |
322 | + LIST_MODALIASES_OUTPUT_NAME, |
323 | +) |
324 | from provisioningserver.tags import merge_details_cleanly |
325 | |
326 | |
327 | @@ -69,7 +72,8 @@ NODE_TYPE_TO_LINK_TYPE = { |
328 | def node_prefetch(queryset, *args): |
329 | return ( |
330 | queryset |
331 | - .select_related('boot_interface', 'owner', 'zone', 'domain', *args) |
332 | + .select_related( |
333 | + 'boot_interface', 'owner', 'zone', 'domain', 'bmc', *args) |
334 | .prefetch_related('blockdevice_set__iscsiblockdevice') |
335 | .prefetch_related('blockdevice_set__physicalblockdevice') |
336 | .prefetch_related('blockdevice_set__virtualblockdevice') |
337 | @@ -84,9 +88,6 @@ def node_prefetch(queryset, *args): |
338 | |
339 | class NodeHandler(TimestampedModelHandler): |
340 | |
341 | - default_osystem = None |
342 | - default_distro_series = None |
343 | - |
344 | class Meta: |
345 | abstract = True |
346 | pk = 'system_id' |
347 | @@ -186,35 +187,10 @@ class NodeHandler(TimestampedModelHandler): |
348 | data["node_type_display"] = obj.get_node_type_display() |
349 | data["link_type"] = NODE_TYPE_TO_LINK_TYPE[obj.node_type] |
350 | |
351 | - data["extra_macs"] = [ |
352 | - "%s" % mac_address |
353 | - for mac_address in obj.get_extra_macs() |
354 | - ] |
355 | - subnets = self.get_all_subnets(obj) |
356 | - data["subnets"] = [subnet.cidr for subnet in subnets] |
357 | - data["fabrics"] = self.get_all_fabric_names(obj, subnets) |
358 | - data["spaces"] = self.get_all_space_names(subnets) |
359 | - |
360 | - data["tags"] = [ |
361 | - tag.name |
362 | - for tag in obj.tags.all() |
363 | - ] |
364 | - data["metadata"] = { |
365 | - metadata.key: metadata.value |
366 | - for metadata in obj.nodemetadata_set.all() |
367 | - } |
368 | - if obj.node_type != NODE_TYPE.DEVICE: |
369 | - data["architecture"] = obj.architecture |
370 | - data["memory"] = obj.display_memory() |
371 | - data["status"] = obj.display_status() |
372 | - data["status_code"] = obj.status |
373 | - boot_interface = obj.get_boot_interface() |
374 | - if boot_interface is not None: |
375 | - data["pxe_mac"] = "%s" % boot_interface.mac_address |
376 | - data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor() |
377 | - else: |
378 | - data["pxe_mac"] = data["pxe_mac_vendor"] = "" |
379 | - |
380 | + if obj.node_type == NODE_TYPE.MACHINE or ( |
381 | + obj.is_controller and not for_list): |
382 | + # Disk count and storage amount is shown on the machine listing |
383 | + # page and the machine and controllers details page. |
384 | blockdevices = self.get_blockdevices_for(obj) |
385 | physical_blockdevices = [ |
386 | blockdevice for blockdevice in blockdevices |
387 | @@ -227,16 +203,6 @@ class NodeHandler(TimestampedModelHandler): |
388 | for blockdevice in physical_blockdevices |
389 | ) / (1000 ** 3)) |
390 | data["storage_tags"] = self.get_all_storage_tags(blockdevices) |
391 | - data["grouped_storages"] = self.get_grouped_storages( |
392 | - physical_blockdevices) |
393 | - |
394 | - data["osystem"] = obj.get_osystem( |
395 | - default=self.default_osystem) |
396 | - data["distro_series"] = obj.get_distro_series( |
397 | - default=self.default_distro_series) |
398 | - data["dhcp_on"] = self.get_providing_dhcp(obj) |
399 | - |
400 | - if obj.node_type != NODE_TYPE.DEVICE: |
401 | commissioning_script_results = [] |
402 | testing_script_results = [] |
403 | log_results = set() |
404 | @@ -254,12 +220,12 @@ class NodeHandler(TimestampedModelHandler): |
405 | RESULT_TYPE.COMMISSIONING): |
406 | commissioning_script_results.append(script_result) |
407 | if (script_result.name in script_output_nsmap and |
408 | - script_result.status == SCRIPT_STATUS.PASSED): |
409 | + script_result.status == |
410 | + SCRIPT_STATUS.PASSED): |
411 | log_results.add(script_result.name) |
412 | elif (script_result.script_set.result_type == |
413 | RESULT_TYPE.TESTING): |
414 | testing_script_results.append(script_result) |
415 | - |
416 | data["commissioning_script_count"] = len( |
417 | commissioning_script_results) |
418 | data["commissioning_status"] = get_status_from_qs( |
419 | @@ -269,11 +235,41 @@ class NodeHandler(TimestampedModelHandler): |
420 | commissioning_script_results).replace( |
421 | 'test', 'commissioning script')) |
422 | data["testing_script_count"] = len(testing_script_results) |
423 | - data["testing_status"] = get_status_from_qs(testing_script_results) |
424 | + data["testing_status"] = get_status_from_qs( |
425 | + testing_script_results) |
426 | data["testing_status_tooltip"] = ( |
427 | - self.dehydrate_hardware_status_tooltip(testing_script_results)) |
428 | + self.dehydrate_hardware_status_tooltip( |
429 | + testing_script_results)) |
430 | data["has_logs"] = ( |
431 | - log_results.difference(script_output_nsmap.keys()) == set()) |
432 | + log_results.difference(script_output_nsmap.keys()) == |
433 | + set()) |
434 | + else: |
435 | + blockdevices = [] |
436 | + |
437 | + if obj.node_type != NODE_TYPE.DEVICE: |
438 | + # These values are not defined on a device. |
439 | + data["architecture"] = obj.architecture |
440 | + data["osystem"] = obj.osystem |
441 | + data["distro_series"] = obj.distro_series |
442 | + data["memory"] = obj.display_memory() |
443 | + data["status"] = obj.display_status() |
444 | + data["status_code"] = obj.status |
445 | + |
446 | + # Filters are only available on machines and devices. |
447 | + if not obj.is_controller: |
448 | + # For filters |
449 | + subnets = self.get_all_subnets(obj) |
450 | + data["subnets"] = [subnet.cidr for subnet in subnets] |
451 | + data["fabrics"] = self.get_all_fabric_names(obj, subnets) |
452 | + data["spaces"] = self.get_all_space_names(subnets) |
453 | + data["tags"] = [ |
454 | + tag.name |
455 | + for tag in obj.tags.all() |
456 | + ] |
457 | + data["extra_macs"] = [ |
458 | + "%s" % mac_address |
459 | + for mac_address in obj.get_extra_macs() |
460 | + ] |
461 | |
462 | if not for_list: |
463 | data["on_network"] = obj.on_network() |
464 | @@ -281,11 +277,18 @@ class NodeHandler(TimestampedModelHandler): |
465 | # XXX lamont 2017-02-15 Much of this should be split out into |
466 | # individual methods, rather than having this huge block of |
467 | # dense code here. |
468 | + # Status of the commissioning, testing, and logs tabs |
469 | + data["metadata"] = { |
470 | + metadata.key: metadata.value |
471 | + for metadata in obj.nodemetadata_set.all() |
472 | + } |
473 | + |
474 | # Network |
475 | data["interfaces"] = [ |
476 | self.dehydrate_interface(interface, obj) |
477 | for interface in obj.interface_set.all().order_by('name') |
478 | ] |
479 | + data["dhcp_on"] = self.get_providing_dhcp(obj) |
480 | |
481 | data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel) |
482 | |
483 | @@ -313,6 +316,8 @@ class NodeHandler(TimestampedModelHandler): |
484 | self.dehydrate_filesystem(filesystem) |
485 | for filesystem in obj.special_filesystems.order_by("id") |
486 | ] |
487 | + data["grouped_storages"] = self.get_grouped_storages( |
488 | + physical_blockdevices) |
489 | |
490 | # Events |
491 | data["events"] = self.dehydrate_events(obj) |
492 | @@ -323,7 +328,18 @@ class NodeHandler(TimestampedModelHandler): |
493 | |
494 | # Third party drivers |
495 | if Config.objects.get_config('enable_third_party_drivers'): |
496 | - driver = get_third_party_driver(obj) |
497 | + # Pull modaliases from the cache |
498 | + modaliases = [] |
499 | + for script_result in commissioning_script_results: |
500 | + if script_result.name == LIST_MODALIASES_OUTPUT_NAME: |
501 | + if script_result.status == SCRIPT_STATUS.PASSED: |
502 | + # STDOUT is deferred in the cache so load it. |
503 | + script_result = ScriptResult.objects.filter( |
504 | + id=script_result.id).only( |
505 | + 'id', 'stdout').first() |
506 | + modaliases = script_result.stdout.decode( |
507 | + 'utf-8').splitlines() |
508 | + driver = get_third_party_driver(obj, modaliases) |
509 | if "module" in driver and "comment" in driver: |
510 | data["third_party_driver"] = { |
511 | "module": driver["module"], |
512 | diff --git a/src/maasserver/websockets/handlers/notification.py b/src/maasserver/websockets/handlers/notification.py |
513 | index 7292e68..d189e75 100644 |
514 | --- a/src/maasserver/websockets/handlers/notification.py |
515 | +++ b/src/maasserver/websockets/handlers/notification.py |
516 | @@ -1,4 +1,4 @@ |
517 | -# Copyright 2017 Canonical Ltd. This software is licensed under the |
518 | +# Copyright 2017-2018 Canonical Ltd. This software is licensed under the |
519 | # GNU Affero General Public License version 3 (see the file LICENSE). |
520 | |
521 | """The notification handler for the WebSocket connection.""" |
522 | @@ -22,7 +22,7 @@ class NotificationHandler(TimestampedModelHandler): |
523 | exclude = list_exclude = {"context"} |
524 | listen_channels = {'notification', 'notificationdismissal'} |
525 | |
526 | - def get_queryset(self): |
527 | + def get_queryset(self, for_list=False): |
528 | """Return `Notifications` for the current user.""" |
529 | return Notification.objects.find_for_user(self.user) |
530 | |
531 | diff --git a/src/maasserver/websockets/handlers/sshkey.py b/src/maasserver/websockets/handlers/sshkey.py |
532 | index efac2e6..37503dc 100644 |
533 | --- a/src/maasserver/websockets/handlers/sshkey.py |
534 | +++ b/src/maasserver/websockets/handlers/sshkey.py |
535 | @@ -41,7 +41,7 @@ class SSHKeyHandler(TimestampedModelHandler): |
536 | "sshkey", |
537 | ] |
538 | |
539 | - def get_queryset(self): |
540 | + def get_queryset(self, for_list=False): |
541 | """Return `QuerySet` for SSH keys owned by `user`.""" |
542 | return self._meta.queryset.filter(user=self.user) |
543 | |
544 | diff --git a/src/maasserver/websockets/handlers/switch.py b/src/maasserver/websockets/handlers/switch.py |
545 | index e40f779..72accf5 100644 |
546 | --- a/src/maasserver/websockets/handlers/switch.py |
547 | +++ b/src/maasserver/websockets/handlers/switch.py |
548 | @@ -1,4 +1,4 @@ |
549 | -# Copyright 2017 Canonical Ltd. This software is licensed under the |
550 | +# Copyright 2017-2018 Canonical Ltd. This software is licensed under the |
551 | # GNU Affero General Public License version 3 (see the file LICENSE). |
552 | |
553 | """The switch handler for the WebSocket connection.""" |
554 | @@ -45,10 +45,14 @@ class SwitchHandler(NodeHandler): |
555 | 'switch', |
556 | ] |
557 | |
558 | - def get_queryset(self): |
559 | + def get_queryset(self, for_list=False): |
560 | """Return `QuerySet` for devices only viewable by `user`.""" |
561 | + # FIXME - Return a different query set when for_list is true. This |
562 | + # should contain only the items needed to display a switch when listing |
563 | + # in the UI. |
564 | return Node.objects.get_nodes( |
565 | - self.user, NODE_PERMISSION.VIEW, from_nodes=self._meta.queryset) |
566 | + self.user, NODE_PERMISSION.VIEW, |
567 | + from_nodes=self._meta.queryset) |
568 | |
569 | def get_object(self, params): |
570 | """Get object by using the `pk` in `params`.""" |
571 | diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py |
572 | index 18d2433..e86c19a 100644 |
573 | --- a/src/maasserver/websockets/handlers/tests/test_controller.py |
574 | +++ b/src/maasserver/websockets/handlers/tests/test_controller.py |
575 | @@ -1,4 +1,4 @@ |
576 | -# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
577 | +# Copyright 2016-2018 Canonical Ltd. This software is licensed under the |
578 | # GNU Affero General Public License version 3 (see the file LICENSE). |
579 | |
580 | """Tests for `maasserver.websockets.handlers.controller`""" |
581 | @@ -12,6 +12,11 @@ from maasserver.testing.factory import factory |
582 | from maasserver.testing.testcase import MAASServerTestCase |
583 | from maasserver.websockets.base import dehydrate_datetime |
584 | from maasserver.websockets.handlers.controller import ControllerHandler |
585 | +from maastesting.djangotestcase import count_queries |
586 | +from metadataserver.enum import ( |
587 | + RESULT_TYPE, |
588 | + SCRIPT_STATUS, |
589 | +) |
590 | from testscenarios import multiply_scenarios |
591 | from testtools.matchers import ( |
592 | ContainsDict, |
593 | @@ -65,6 +70,69 @@ class TestControllerHandler(MAASServerTestCase): |
594 | self.assertEqual(1, len(result)) |
595 | self.assertEqual(NODE_TYPE.RACK_CONTROLLER, result[0].get('node_type')) |
596 | |
597 | + def test_list_num_queries_is_the_expected_number(self): |
598 | + owner = factory.make_admin() |
599 | + for _ in range(10): |
600 | + node = factory.make_RegionRackController(owner=owner) |
601 | + commissioning_script_set = factory.make_ScriptSet( |
602 | + node=node, result_type=RESULT_TYPE.COMMISSIONING) |
603 | + testing_script_set = factory.make_ScriptSet( |
604 | + node=node, result_type=RESULT_TYPE.TESTING) |
605 | + node.current_commissioning_script_set = commissioning_script_set |
606 | + node.current_testing_script_set = testing_script_set |
607 | + node.save() |
608 | + for __ in range(10): |
609 | + factory.make_ScriptResult( |
610 | + status=SCRIPT_STATUS.PASSED, |
611 | + script_set=commissioning_script_set) |
612 | + factory.make_ScriptResult( |
613 | + status=SCRIPT_STATUS.PASSED, |
614 | + script_set=testing_script_set) |
615 | + |
616 | + handler = ControllerHandler(owner, {}) |
617 | + queries_one, _ = count_queries(handler.list, {'limit': 1}) |
618 | + queries_total, _ = count_queries(handler.list, {}) |
619 | + # This check is to notify the developer that a change was made that |
620 | + # affects the number of queries performed when doing a node listing. |
621 | + # It is important to keep this number as low as possible. A larger |
622 | + # number means regiond has to do more work slowing down its process |
623 | + # and slowing down the client waiting for the response. |
624 | + self.assertEqual( |
625 | + queries_one, 3, |
626 | + "Number of queries has changed; make sure this is expected.") |
627 | + self.assertEqual( |
628 | + queries_total, 3, |
629 | + "Number of queries has changed; make sure this is expected.") |
630 | + |
631 | + def test_get_num_queries_is_the_expected_number(self): |
632 | + owner = factory.make_admin() |
633 | + node = factory.make_RegionRackController(owner=owner) |
634 | + commissioning_script_set = factory.make_ScriptSet( |
635 | + node=node, result_type=RESULT_TYPE.COMMISSIONING) |
636 | + testing_script_set = factory.make_ScriptSet( |
637 | + node=node, result_type=RESULT_TYPE.TESTING) |
638 | + node.current_commissioning_script_set = commissioning_script_set |
639 | + node.current_testing_script_set = testing_script_set |
640 | + node.save() |
641 | + for __ in range(10): |
642 | + factory.make_ScriptResult( |
643 | + status=SCRIPT_STATUS.PASSED, |
644 | + script_set=commissioning_script_set) |
645 | + factory.make_ScriptResult( |
646 | + status=SCRIPT_STATUS.PASSED, |
647 | + script_set=testing_script_set) |
648 | + |
649 | + handler = ControllerHandler(owner, {}) |
650 | + queries, _ = count_queries(handler.get, {'system_id': node.system_id}) |
651 | + # This check is to notify the developer that a change was made that |
652 | + # affects the number of queries performed when doing a node get. |
653 | + # It is important to keep this number as low as possible. A larger |
654 | + # number means regiond has to do more work slowing down its process |
655 | + # and slowing down the client waiting for the response. |
656 | + self.assertEqual( |
657 | + queries, 28, |
658 | + "Number of queries has changed; make sure this is expected.") |
659 | + |
660 | def test_get_form_class_for_create(self): |
661 | user = factory.make_admin() |
662 | handler = ControllerHandler(user, {}) |
663 | diff --git a/src/maasserver/websockets/handlers/tests/test_device.py b/src/maasserver/websockets/handlers/tests/test_device.py |
664 | index ab26620..d6af973 100644 |
665 | --- a/src/maasserver/websockets/handlers/tests/test_device.py |
666 | +++ b/src/maasserver/websockets/handlers/tests/test_device.py |
667 | @@ -137,7 +137,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase): |
668 | boot_interface = node.get_boot_interface() |
669 | data = { |
670 | "actions": list(compile_node_actions(node, user).keys()), |
671 | - "bmc": node.bmc_id, |
672 | "created": dehydrate_datetime(node.created), |
673 | "domain": { |
674 | "id": node.domain.id, |
675 | @@ -149,7 +148,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase): |
676 | ], |
677 | "fqdn": node.fqdn, |
678 | "hostname": node.hostname, |
679 | - "metadata": {}, |
680 | "node_type_display": node.get_node_type_display(), |
681 | "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type], |
682 | "id": node.id, |
683 | @@ -195,7 +193,6 @@ class TestDeviceHandler(MAASTransactionServerTestCase): |
684 | "ip_address", |
685 | "ip_assignment", |
686 | "link_type", |
687 | - "metadata", |
688 | "node_type_display", |
689 | "primary_mac", |
690 | "spaces", |
691 | @@ -253,6 +250,24 @@ class TestDeviceHandler(MAASTransactionServerTestCase): |
692 | handler.get({"system_id": device.system_id})) |
693 | |
694 | @transactional |
695 | + def test_get_num_queries_is_the_expected_number(self): |
696 | + owner = factory.make_User() |
697 | + handler = DeviceHandler(owner, {}) |
698 | + device = self.make_device_with_ip_address( |
699 | + owner=owner, ip_assignment=DEVICE_IP_ASSIGNMENT_TYPE.STATIC) |
700 | + queries, _ = count_queries( |
701 | + handler.get, {"system_id": device.system_id}) |
702 | + |
703 | + # This check is to notify the developer that a change was made that |
704 | + # affects the number of queries performed when doing a node get. |
705 | + # It is important to keep this number as low as possible. A larger |
706 | + # number means regiond has to do more work slowing down its process |
707 | + # and slowing down the client waiting for the response. |
708 | + self.assertEqual( |
709 | + queries, 19, |
710 | + "Number of queries has changed; make sure this is expected.") |
711 | + |
712 | + @transactional |
713 | def test_list(self): |
714 | owner = factory.make_User() |
715 | handler = DeviceHandler(owner, {}) |
716 | @@ -301,7 +316,7 @@ class TestDeviceHandler(MAASTransactionServerTestCase): |
717 | # number means regiond has to do more work slowing down its process |
718 | # and slowing down the client waiting for the response. |
719 | self.assertEqual( |
720 | - query_10_count, 13, |
721 | + query_10_count, 10, |
722 | "Number of queries has changed; make sure this is expected.") |
723 | |
724 | @transactional |
725 | diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py |
726 | index 27bc195..3b333ed 100644 |
727 | --- a/src/maasserver/websockets/handlers/tests/test_machine.py |
728 | +++ b/src/maasserver/websockets/handlers/tests/test_machine.py |
729 | @@ -150,11 +150,12 @@ class TestMachineHandler(MAASServerTestCase): |
730 | |
731 | boot_interface = node.get_boot_interface() |
732 | pxe_mac_vendor = node.get_pxe_mac_vendor() |
733 | + subnets = handler.get_all_subnets(node) |
734 | + |
735 | blockdevices = [ |
736 | blockdevice.actual_instance |
737 | for blockdevice in node.blockdevice_set.all() |
738 | ] |
739 | - driver = get_third_party_driver(node) |
740 | disks = [ |
741 | handler.dehydrate_blockdevice(blockdevice, node) |
742 | for blockdevice in blockdevices |
743 | @@ -167,8 +168,10 @@ class TestMachineHandler(MAASServerTestCase): |
744 | for cache_set in CacheSet.objects.get_cache_sets_for_node(node) |
745 | ] |
746 | disks = sorted(disks, key=itemgetter("name")) |
747 | - subnets = handler.get_all_subnets(node) |
748 | - commissioning_scripts = node.get_latest_commissioning_script_results |
749 | + driver = get_third_party_driver(node) |
750 | + |
751 | + commissioning_scripts = ( |
752 | + node.get_latest_commissioning_script_results) |
753 | commissioning_scripts = commissioning_scripts.exclude( |
754 | status=SCRIPT_STATUS.ABORTED) |
755 | testing_scripts = node.get_latest_testing_script_results |
756 | @@ -179,6 +182,7 @@ class TestMachineHandler(MAASServerTestCase): |
757 | if (script_result.name in script_output_nsmap and |
758 | script_result.status == SCRIPT_STATUS.PASSED): |
759 | log_results.add(script_result.name) |
760 | + |
761 | data = { |
762 | "actions": list(compile_node_actions(node, handler.user).keys()), |
763 | "architecture": node.architecture, |
764 | @@ -186,25 +190,27 @@ class TestMachineHandler(MAASServerTestCase): |
765 | "boot_disk": node.boot_disk, |
766 | "bios_boot_method": node.bios_boot_method, |
767 | "commissioning_script_count": commissioning_scripts.count(), |
768 | - "commissioning_status": get_status_from_qs(commissioning_scripts), |
769 | + "commissioning_status": get_status_from_qs( |
770 | + commissioning_scripts), |
771 | "commissioning_status_tooltip": ( |
772 | handler.dehydrate_hardware_status_tooltip( |
773 | commissioning_scripts).replace( |
774 | 'test', 'commissioning script')), |
775 | "current_commissioning_script_set": ( |
776 | node.current_commissioning_script_set_id), |
777 | + "current_testing_script_set": node.current_testing_script_set_id, |
778 | "testing_script_count": testing_scripts.count(), |
779 | "testing_status": get_status_from_qs(testing_scripts), |
780 | "testing_status_tooltip": ( |
781 | - handler.dehydrate_hardware_status_tooltip(testing_scripts)), |
782 | - "current_testing_script_set": node.current_testing_script_set_id, |
783 | + handler.dehydrate_hardware_status_tooltip( |
784 | + testing_scripts)), |
785 | "current_installation_script_set": ( |
786 | node.current_installation_script_set_id), |
787 | "installation_status": ( |
788 | handler.dehydrate_script_set_status( |
789 | node.current_installation_script_set)), |
790 | - "has_logs": ( |
791 | - log_results.difference(script_output_nsmap.keys()) == set()), |
792 | + "has_logs": (log_results.difference( |
793 | + script_output_nsmap.keys()) == set()), |
794 | "locked": node.locked, |
795 | "cpu_count": node.cpu_count, |
796 | "cpu_speed": node.cpu_speed, |
797 | @@ -231,7 +237,7 @@ class TestMachineHandler(MAASServerTestCase): |
798 | "supported_filesystems": [ |
799 | {'key': key, 'ui': ui} |
800 | for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES], |
801 | - "distro_series": node.get_distro_series(), |
802 | + "distro_series": node.distro_series, |
803 | "error": node.error, |
804 | "error_description": node.error_description, |
805 | "events": handler.dehydrate_events(node), |
806 | @@ -251,10 +257,9 @@ class TestMachineHandler(MAASServerTestCase): |
807 | "license_key": node.license_key, |
808 | "link_type": NODE_TYPE_TO_LINK_TYPE[node.node_type], |
809 | "memory": node.display_memory(), |
810 | - "metadata": {}, |
811 | "node_type_display": node.get_node_type_display(), |
812 | "min_hwe_kernel": node.min_hwe_kernel, |
813 | - "osystem": node.get_osystem(), |
814 | + "osystem": node.osystem, |
815 | "owner": handler.dehydrate_owner(node.owner), |
816 | "power_parameters": handler.dehydrate_power_parameters( |
817 | node.power_parameters), |
818 | @@ -292,7 +297,6 @@ class TestMachineHandler(MAASServerTestCase): |
819 | "zone": handler.dehydrate_zone(node.zone), |
820 | "pool": handler.dehydrate_pool(node.pool), |
821 | "default_user": node.default_user, |
822 | - "dhcp_on": node.interface_set.filter(vlan__dhcp_on=True).exists(), |
823 | } |
824 | bmc = node.bmc |
825 | if bmc is not None and bmc.bmc_type == BMC_TYPE.POD: |
826 | @@ -333,6 +337,13 @@ class TestMachineHandler(MAASServerTestCase): |
827 | for key in list(data): |
828 | if key not in allowed_fields: |
829 | del data[key] |
830 | + else: |
831 | + data.update({ |
832 | + "dhcp_on": node.interface_set.filter( |
833 | + vlan__dhcp_on=True).exists(), |
834 | + "grouped_storages": handler.get_grouped_storages(blockdevices), |
835 | + "metadata": {}, |
836 | + }) |
837 | |
838 | cpu_script_results = [ |
839 | script_result for script_result in |
840 | @@ -395,8 +406,6 @@ class TestMachineHandler(MAASServerTestCase): |
841 | else: |
842 | data["status_tooltip"] = "" |
843 | |
844 | - data["grouped_storages"] = handler.get_grouped_storages(blockdevices) |
845 | - |
846 | return data |
847 | |
848 | def make_nodes(self, number): |
849 | @@ -495,10 +504,39 @@ class TestMachineHandler(MAASServerTestCase): |
850 | # number means regiond has to do more work slowing down its process |
851 | # and slowing down the client waiting for the response. |
852 | self.assertEqual( |
853 | - queries_one, 11, |
854 | + queries_one, 8, |
855 | + "Number of queries has changed; make sure this is expected.") |
856 | + self.assertEqual( |
857 | + queries_total, 8, |
858 | "Number of queries has changed; make sure this is expected.") |
859 | + |
860 | + def test_get_num_queries_is_the_expected_number(self): |
861 | + owner = factory.make_User() |
862 | + node = factory.make_Node(owner=owner) |
863 | + commissioning_script_set = factory.make_ScriptSet( |
864 | + node=node, result_type=RESULT_TYPE.COMMISSIONING) |
865 | + testing_script_set = factory.make_ScriptSet( |
866 | + node=node, result_type=RESULT_TYPE.TESTING) |
867 | + node.current_commissioning_script_set = commissioning_script_set |
868 | + node.current_testing_script_set = testing_script_set |
869 | + node.save() |
870 | + for __ in range(10): |
871 | + factory.make_ScriptResult( |
872 | + status=SCRIPT_STATUS.PASSED, |
873 | + script_set=commissioning_script_set) |
874 | + factory.make_ScriptResult( |
875 | + status=SCRIPT_STATUS.PASSED, |
876 | + script_set=testing_script_set) |
877 | + |
878 | + handler = MachineHandler(owner, {}) |
879 | + queries, _ = count_queries(handler.get, {'system_id': node.system_id}) |
880 | + # This check is to notify the developer that a change was made that |
881 | + # affects the number of queries performed when doing a node get. |
882 | + # It is important to keep this number as low as possible. A larger |
883 | + # number means regiond has to do more work slowing down its process |
884 | + # and slowing down the client waiting for the response. |
885 | self.assertEqual( |
886 | - queries_total, 11, |
887 | + queries, 48, |
888 | "Number of queries has changed; make sure this is expected.") |
889 | |
890 | def test_trigger_update_updates_script_result_cache(self): |
891 | @@ -521,6 +559,7 @@ class TestMachineHandler(MAASServerTestCase): |
892 | |
893 | handler = MachineHandler(owner, {}) |
894 | # Simulate a trigger pushing an update to the UI |
895 | + handler.cache = {'active_pk': node.system_id} |
896 | _, _, ret = handler.on_listen_for_active_pk( |
897 | 'update', node.system_id, node) |
898 | self.assertEquals(ret['commissioning_script_count'], 10) |
899 | diff --git a/src/maasserver/websockets/handlers/tests/test_switch.py b/src/maasserver/websockets/handlers/tests/test_switch.py |
900 | index 6313a5f..d3f4689 100644 |
901 | --- a/src/maasserver/websockets/handlers/tests/test_switch.py |
902 | +++ b/src/maasserver/websockets/handlers/tests/test_switch.py |
903 | @@ -5,10 +5,7 @@ |
904 | |
905 | __all__ = [] |
906 | |
907 | -from maasserver.enum import ( |
908 | - NODE_METADATA, |
909 | - NODE_TYPE, |
910 | -) |
911 | +from maasserver.enum import NODE_TYPE |
912 | from maasserver.exceptions import NodeActionError |
913 | from maasserver.testing.factory import factory |
914 | from maasserver.testing.testcase import MAASTransactionServerTestCase |
915 | @@ -67,22 +64,6 @@ class TestSwitchHandler(MAASTransactionServerTestCase): |
916 | [result['system_id'] for result in handler.list({})]) |
917 | |
918 | @transactional |
919 | - def test_list_switches_includes_metadata(self): |
920 | - owner = factory.make_User() |
921 | - handler = SwitchHandler(owner, {}) |
922 | - machine = factory.make_Machine(owner=owner) |
923 | - metadata = { |
924 | - NODE_METADATA.VENDOR_NAME: "Canonical", |
925 | - NODE_METADATA.PHYSICAL_MODEL_NAME: "Cloud-in-a-box" |
926 | - } |
927 | - for key, value in metadata.items(): |
928 | - factory.make_NodeMetadata(node=machine, key=key, value=value) |
929 | - factory.make_Switch(node=machine) |
930 | - self.assertItemsEqual( |
931 | - [metadata], |
932 | - [result['metadata'] for result in handler.list({})]) |
933 | - |
934 | - @transactional |
935 | def test_list_ignores_nodes_that_arent_switches(self): |
936 | owner = factory.make_User() |
937 | handler = SwitchHandler(owner, {}) |
938 | diff --git a/src/maasserver/websockets/handlers/user.py b/src/maasserver/websockets/handlers/user.py |
939 | index 8c10046..cc8759a 100644 |
940 | --- a/src/maasserver/websockets/handlers/user.py |
941 | +++ b/src/maasserver/websockets/handlers/user.py |
942 | @@ -1,4 +1,4 @@ |
943 | -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
944 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
945 | # GNU Affero General Public License version 3 (see the file LICENSE). |
946 | |
947 | """The user handler for the WebSocket connection.""" |
948 | @@ -35,9 +35,9 @@ class UserHandler(Handler): |
949 | "user", |
950 | ] |
951 | |
952 | - def get_queryset(self): |
953 | + def get_queryset(self, for_list=False): |
954 | """Return `QuerySet` for users only viewable by `user`.""" |
955 | - users = super(UserHandler, self).get_queryset() |
956 | + users = super(UserHandler, self).get_queryset(for_list=for_list) |
957 | if reload_object(self.user).is_superuser: |
958 | # Super users can view all users, except for the built-in users |
959 | return users.exclude(username__in=SYSTEM_USERS) |
960 | diff --git a/src/maasserver/websockets/tests/test_base.py b/src/maasserver/websockets/tests/test_base.py |
961 | index 9a3001f..ee71feb 100644 |
962 | --- a/src/maasserver/websockets/tests/test_base.py |
963 | +++ b/src/maasserver/websockets/tests/test_base.py |
964 | @@ -1,4 +1,4 @@ |
965 | -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
966 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
967 | # GNU Affero General Public License version 3 (see the file LICENSE). |
968 | |
969 | """Tests for `maasserver.websockets.base`""" |
970 | @@ -337,6 +337,25 @@ class TestHandler(MAASServerTestCase): |
971 | HandlerDoesNotExistError, |
972 | handler.get_object, {"system_id": machine.system_id}) |
973 | |
974 | + def test_get_queryset(self): |
975 | + queryset = MagicMock() |
976 | + list_queryset = MagicMock() |
977 | + handler = make_handler( |
978 | + "TestHandler", queryset=queryset, list_queryset=list_queryset) |
979 | + self.assertEqual(queryset, handler.get_queryset()) |
980 | + |
981 | + def test_get_queryset_list(self): |
982 | + queryset = MagicMock() |
983 | + list_queryset = MagicMock() |
984 | + handler = make_handler( |
985 | + "TestHandler", queryset=queryset, list_queryset=list_queryset) |
986 | + self.assertEqual(list_queryset, handler.get_queryset(for_list=True)) |
987 | + |
988 | + def test_get_queryset_list_only_if_avail(self): |
989 | + queryset = MagicMock() |
990 | + handler = make_handler("TestHandler", queryset=queryset) |
991 | + self.assertEqual(queryset, handler.get_queryset(for_list=True)) |
992 | + |
993 | def test_execute_only_allows_meta_allowed_methods(self): |
994 | handler = self.make_nodes_handler(allowed_methods=['list']) |
995 | with ExpectedException(HandlerNoSuchMethodError): |
UNIT TESTS queries lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
-b limit_listing_
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 2198/console 39b0c191d370d90 83f37ce9d0
LOG: http://
COMMIT: 9c3d32c4d91bc63