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