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